/*  Prototype JavaScript framework, version 1.7_rc2
 *  (c) 2005-2010 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.7_rc2',

  Browser: (function(){
    var ua = navigator.userAgent;
    var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
    return {
      IE:             !!window.attachEvent && !isOpera,
      Opera:          isOpera,
      WebKit:         ua.indexOf('AppleWebKit/') > -1,
      Gecko:          ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1,
      MobileSafari:   /Apple.*Mobile/.test(ua)
    }
  })(),

  BrowserFeatures: {
    XPath: !!document.evaluate,

    SelectorsAPI: !!document.querySelector,

    ElementExtensions: (function() {
      var constructor = window.Element || window.HTMLElement;
      return !!(constructor && constructor.prototype);
    })(),
    SpecificElementExtensions: (function() {
      if (typeof window.HTMLDivElement !== 'undefined')
        return true;

      var div = document.createElement('div'),
          form = document.createElement('form'),
          isSupported = false;

      if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) {
        isSupported = true;
      }

      div = form = null;

      return isSupported;
    })()
  },

  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;


var Abstract = { };


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;
  }
};

/* Based on Alex Arnell's inheritance implementation. */

var Class = (function() {

  var IS_DONTENUM_BUGGY = (function(){
    for (var p in { toString: 1 }) {
      if (p === 'toString') return false;
    }
    return true;
  })();

  function subclass() {};
  function create() {
    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) {
      subclass.prototype = parent.prototype;
      klass.prototype = new subclass;
      parent.subclasses.push(klass);
    }

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

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

    klass.prototype.constructor = klass;
    return klass;
  }

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

    if (IS_DONTENUM_BUGGY) {
      if (source.toString != Object.prototype.toString)
        properties.push("toString");
      if (source.valueOf != Object.prototype.valueOf)
        properties.push("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()[0] == "$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;
  }

  return {
    create: create,
    Methods: {
      addMethods: addMethods
    }
  };
})();
(function() {

  var _toString = Object.prototype.toString,
      NULL_TYPE = 'Null',
      UNDEFINED_TYPE = 'Undefined',
      BOOLEAN_TYPE = 'Boolean',
      NUMBER_TYPE = 'Number',
      STRING_TYPE = 'String',
      OBJECT_TYPE = 'Object',
      BOOLEAN_CLASS = '[object Boolean]',
      NUMBER_CLASS = '[object Number]',
      STRING_CLASS = '[object String]',
      ARRAY_CLASS = '[object Array]',
      NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON &&
        typeof JSON.stringify === 'function' &&
        JSON.stringify(0) === '0' &&
        typeof JSON.stringify(Prototype.K) === 'undefined';

  function Type(o) {
    switch(o) {
      case null: return NULL_TYPE;
      case (void 0): return UNDEFINED_TYPE;
    }
    var type = typeof o;
    switch(type) {
      case 'boolean': return BOOLEAN_TYPE;
      case 'number':  return NUMBER_TYPE;
      case 'string':  return STRING_TYPE;
    }
    return OBJECT_TYPE;
  }

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

  function inspect(object) {
    try {
      if (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;
    }
  }

  function toJSON(value) {
    return Str('', { '': value }, []);
  }

  function Str(key, holder, stack) {
    var value = holder[key],
        type = typeof value;

    if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') {
      value = value.toJSON(key);
    }

    var _class = _toString.call(value);

    switch (_class) {
      case NUMBER_CLASS:
      case BOOLEAN_CLASS:
      case STRING_CLASS:
        value = value.valueOf();
    }

    switch (value) {
      case null: return 'null';
      case true: return 'true';
      case false: return 'false';
    }

    type = typeof value;
    switch (type) {
      case 'string':
        return value.inspect(true);
      case 'number':
        return isFinite(value) ? String(value) : 'null';
      case 'object':

        for (var i = 0, length = stack.length; i < length; i++) {
          if (stack[i] === value) { throw new TypeError(); }
        }
        stack.push(value);

        var partial = [];
        if (_class === ARRAY_CLASS) {
          for (var i = 0, length = value.length; i < length; i++) {
            var str = Str(i, value, stack);
            partial.push(typeof str === 'undefined' ? 'null' : str);
          }
          partial = '[' + partial.join(',') + ']';
        } else {
          var keys = Object.keys(value);
          for (var i = 0, length = keys.length; i < length; i++) {
            var key = keys[i], str = Str(key, value, stack);
            if (typeof str !== "undefined") {
               partial.push(key.inspect(true)+ ':' + str);
             }
          }
          partial = '{' + partial.join(',') + '}';
        }
        stack.pop();
        return partial;
    }
  }

  function stringify(object) {
    return JSON.stringify(object);
  }

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

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

  function keys(object) {
    if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); }
    var results = [];
    for (var property in object) {
      if (object.hasOwnProperty(property)) {
        results.push(property);
      }
    }
    return results;
  }

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

  function clone(object) {
    return extend({ }, object);
  }

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

  function isArray(object) {
    return _toString.call(object) === ARRAY_CLASS;
  }

  var hasNativeIsArray = (typeof Array.isArray == 'function')
    && Array.isArray([]) && !Array.isArray({});

  if (hasNativeIsArray) {
    isArray = Array.isArray;
  }

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

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

  function isString(object) {
    return _toString.call(object) === STRING_CLASS;
  }

  function isNumber(object) {
    return _toString.call(object) === NUMBER_CLASS;
  }

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

  extend(Object, {
    extend:        extend,
    inspect:       inspect,
    toJSON:        NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON,
    toQueryString: toQueryString,
    toHTML:        toHTML,
    keys:          Object.keys || keys,
    values:        values,
    clone:         clone,
    isElement:     isElement,
    isArray:       isArray,
    isHash:        isHash,
    isFunction:    isFunction,
    isString:      isString,
    isNumber:      isNumber,
    isUndefined:   isUndefined
  });
})();
Object.extend(Function.prototype, (function() {
  var slice = Array.prototype.slice;

  function update(array, args) {
    var arrayLength = array.length, length = args.length;
    while (length--) array[arrayLength + length] = args[length];
    return array;
  }

  function merge(array, args) {
    array = slice.call(array, 0);
    return update(array, args);
  }

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

  function bind(context) {
    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
    var __method = this, args = slice.call(arguments, 1);
    return function() {
      var a = merge(args, arguments);
      return __method.apply(context, a);
    }
  }

  function bindAsEventListener(context) {
    var __method = this, args = slice.call(arguments, 1);
    return function(event) {
      var a = update([event || window.event], args);
      return __method.apply(context, a);
    }
  }

  function curry() {
    if (!arguments.length) return this;
    var __method = this, args = slice.call(arguments, 0);
    return function() {
      var a = merge(args, arguments);
      return __method.apply(this, a);
    }
  }

  function delay(timeout) {
    var __method = this, args = slice.call(arguments, 1);
    timeout = timeout * 1000;
    return window.setTimeout(function() {
      return __method.apply(__method, args);
    }, timeout);
  }

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

  function wrap(wrapper) {
    var __method = this;
    return function() {
      var a = update([__method.bind(this)], arguments);
      return wrapper.apply(this, a);
    }
  }

  function methodize() {
    if (this._methodized) return this._methodized;
    var __method = this;
    return this._methodized = function() {
      var a = update([this], arguments);
      return __method.apply(null, a);
    };
  }

  return {
    argumentNames:       argumentNames,
    bind:                bind,
    bindAsEventListener: bindAsEventListener,
    curry:               curry,
    delay:               delay,
    defer:               defer,
    wrap:                wrap,
    methodize:           methodize
  }
})());



(function(proto) {


  function toISOString() {
    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';
  }


  function toJSON() {
    return this.toISOString();
  }

  if (!proto.toISOString) proto.toISOString = toISOString;
  if (!proto.toJSON) proto.toJSON = toJSON;

})(Date.prototype);


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();
        this.currentlyExecuting = false;
      } catch(e) {
        this.currentlyExecuting = false;
        throw e;
      }
    }
  }
});
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, (function() {
  var NATIVE_JSON_PARSE_SUPPORT = window.JSON &&
    typeof JSON.parse === 'function' &&
    JSON.parse('{"test": true}').test;

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

  function gsub(pattern, replacement) {
    var result = '', source = this, match;
    replacement = prepareReplacement(replacement);

    if (Object.isString(pattern))
      pattern = RegExp.escape(pattern);

    if (!(pattern.length || pattern.source)) {
      replacement = replacement('');
      return replacement + source.split('').join(replacement) + 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;
  }

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

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

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

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

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

  function stripTags() {
    return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, '');
  }

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

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

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

  function escapeHTML() {
    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  }

  function unescapeHTML() {
    return this.stripTags().replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');
  }


  function toQueryParams(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()),
            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;
    });
  }

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

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

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

  function camelize() {
    return this.replace(/-+(.)?/g, function(match, chr) {
      return chr ? chr.toUpperCase() : '';
    });
  }

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

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

  function dasherize() {
    return this.replace(/_/g, '-');
  }

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

  function unfilterJSON(filter) {
    return this.replace(filter || Prototype.JSONFilter, '$1');
  }

  function isJSON() {
    var str = this;
    if (str.blank()) return false;
    str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@');
    str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']');
    str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
    return (/^[\],:{}\s]*$/).test(str);
  }

  function evalJSON(sanitize) {
    var json = this.unfilterJSON(),
        cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
    if (cx.test(json)) {
      json = json.replace(cx, function (a) {
        return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
      });
    }
    try {
      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
    } catch (e) { }
    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
  }

  function parseJSON() {
    var json = this.unfilterJSON();
    return JSON.parse(json);
  }

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

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

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

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

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

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

  return {
    gsub:           gsub,
    sub:            sub,
    scan:           scan,
    truncate:       truncate,
    strip:          String.prototype.trim || strip,
    stripTags:      stripTags,
    stripScripts:   stripScripts,
    extractScripts: extractScripts,
    evalScripts:    evalScripts,
    escapeHTML:     escapeHTML,
    unescapeHTML:   unescapeHTML,
    toQueryParams:  toQueryParams,
    parseQuery:     toQueryParams,
    toArray:        toArray,
    succ:           succ,
    times:          times,
    camelize:       camelize,
    capitalize:     capitalize,
    underscore:     underscore,
    dasherize:      dasherize,
    inspect:        inspect,
    unfilterJSON:   unfilterJSON,
    isJSON:         isJSON,
    evalJSON:       NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON,
    include:        include,
    startsWith:     startsWith,
    endsWith:       endsWith,
    empty:          empty,
    blank:          blank,
    interpolate:    interpolate
  };
})());

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

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

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

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

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

      match = pattern.exec(expr);
      if (match == null) return before;

      while (match != null) {
        var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : 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 = (function() {
  function each(iterator, context) {
    var index = 0;
    try {
      this._each(function(value) {
        iterator.call(context, value, index++);
      });
    } catch (e) {
      if (e != $break) throw e;
    }
    return this;
  }

  function eachSlice(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);
  }

  function all(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;
  }

  function any(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;
  }

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

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

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

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

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

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

  function include(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;
  }

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

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

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

  function max(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;
  }

  function min(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;
  }

  function partition(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];
  }

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

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

  function sortBy(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');
  }

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

  function zip() {
    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));
    });
  }

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

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









  return {
    each:       each,
    eachSlice:  eachSlice,
    all:        all,
    every:      all,
    any:        any,
    some:       any,
    collect:    collect,
    map:        collect,
    detect:     detect,
    findAll:    findAll,
    select:     findAll,
    filter:     findAll,
    grep:       grep,
    include:    include,
    member:     include,
    inGroupsOf: inGroupsOf,
    inject:     inject,
    invoke:     invoke,
    max:        max,
    min:        min,
    partition:  partition,
    pluck:      pluck,
    reject:     reject,
    sortBy:     sortBy,
    toArray:    toArray,
    entries:    toArray,
    zip:        zip,
    size:       size,
    inspect:    inspect,
    find:       detect
  };
})();

function $A(iterable) {
  if (!iterable) return [];
  if ('toArray' in Object(iterable)) return iterable.toArray();
  var length = iterable.length || 0, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
}


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

Array.from = $A;


(function() {
  var arrayProto = Array.prototype,
      slice = arrayProto.slice,
      _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available

  function each(iterator) {
    for (var i = 0, length = this.length; i < length; i++)
      iterator(this[i]);
  }
  if (!_each) _each = each;

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

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

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

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

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

  function without() {
    var values = slice.call(arguments, 0);
    return this.select(function(value) {
      return !values.include(value);
    });
  }

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

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

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


  function clone() {
    return slice.call(this, 0);
  }

  function size() {
    return this.length;
  }

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

  function indexOf(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;
  }

  function lastIndexOf(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;
  }

  function concat() {
    var array = slice.call(this, 0), item;
    for (var i = 0, length = arguments.length; i < length; i++) {
      item = arguments[i];
      if (Object.isArray(item) && !('callee' in item)) {
        for (var j = 0, arrayLength = item.length; j < arrayLength; j++)
          array.push(item[j]);
      } else {
        array.push(item);
      }
    }
    return array;
  }

  Object.extend(arrayProto, Enumerable);

  if (!arrayProto._reverse)
    arrayProto._reverse = arrayProto.reverse;

  Object.extend(arrayProto, {
    _each:     _each,
    clear:     clear,
    first:     first,
    last:      last,
    compact:   compact,
    flatten:   flatten,
    without:   without,
    reverse:   reverse,
    uniq:      uniq,
    intersect: intersect,
    clone:     clone,
    toArray:   clone,
    size:      size,
    inspect:   inspect
  });

  var CONCAT_ARGUMENTS_BUGGY = (function() {
    return [].concat(arguments)[0][0] !== 1;
  })(1,2)

  if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat;

  if (!arrayProto.indexOf) arrayProto.indexOf = indexOf;
  if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf;
})();
function $H(object) {
  return new Hash(object);
};

var Hash = Class.create(Enumerable, (function() {
  function initialize(object) {
    this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
  }


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

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

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

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

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



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

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

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

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

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

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

  function toQueryString() {
    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('&');
  }

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

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

  return {
    initialize:             initialize,
    _each:                  _each,
    set:                    set,
    get:                    get,
    unset:                  unset,
    toObject:               toObject,
    toTemplateReplacements: toObject,
    keys:                   keys,
    values:                 values,
    index:                  index,
    merge:                  merge,
    update:                 update,
    toQueryString:          toQueryString,
    inspect:                inspect,
    toJSON:                 toObject,
    clone:                  clone
  };
})());

Hash.from = $H;
Object.extend(Number.prototype, (function() {
  function toColorPart() {
    return this.toPaddedString(2, 16);
  }

  function succ() {
    return this + 1;
  }

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

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

  function abs() {
    return Math.abs(this);
  }

  function round() {
    return Math.round(this);
  }

  function ceil() {
    return Math.ceil(this);
  }

  function floor() {
    return Math.floor(this);
  }

  return {
    toColorPart:    toColorPart,
    succ:           succ,
    times:          times,
    toPaddedString: toPaddedString,
    abs:            abs,
    round:          round,
    ceil:           ceil,
    floor:          floor
  };
})());

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

var ObjectRange = Class.create(Enumerable, (function() {
  function initialize(start, end, exclusive) {
    this.start = start;
    this.end = end;
    this.exclusive = exclusive;
  }

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

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

  return {
    initialize: initialize,
    _each:      _each,
    include:    include
  };
})());



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 (!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(global) {

  var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){
    try {
      var el = document.createElement('<input name="x">');
      return el.tagName.toLowerCase() === 'input' && el.name === 'x';
    }
    catch(err) {
      return false;
    }
  })();

  var element = global.Element;

  global.Element = function(tagName, attributes) {
    attributes = attributes || { };
    tagName = tagName.toLowerCase();
    var cache = Element.cache;
    if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && 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(global.Element, element || { });
  if (element) global.Element.prototype = element.prototype;

})(this);

Element.idCounter = 1;
Element.cache = { };

function purgeElement(element) {
  var uid = element._prototypeUID;
  if (uid) {
    Element.stopObserving(element);
    element._prototypeUID = void 0;
    delete Element.Storage[uid];
  }
}

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(){

    var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){
      var el = document.createElement("select"),
          isBuggy = true;
      el.innerHTML = "<option value=\"test\">test</option>";
      if (el.options && el.options[0]) {
        isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION";
      }
      el = null;
      return isBuggy;
    })();

    var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){
      try {
        var el = document.createElement("table");
        if (el && el.tBodies) {
          el.innerHTML = "<tbody><tr><td>test</td></tr></tbody>";
          var isBuggy = typeof el.tBodies[0] == "undefined";
          el = null;
          return isBuggy;
        }
      } catch (e) {
        return true;
      }
    })();

    var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () {
      var s = document.createElement("script"),
          isBuggy = false;
      try {
        s.appendChild(document.createTextNode(""));
        isBuggy = !s.firstChild ||
          s.firstChild && s.firstChild.nodeType !== 3;
      } catch (e) {
        isBuggy = true;
      }
      s = null;
      return isBuggy;
    })();

    function update(element, content) {
      element = $(element);

      var descendants = element.getElementsByTagName('*'),
       i = descendants.length;
      while (i--) purgeElement(descendants[i]);

      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 === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) {
        element.text = content;
        return element;
      }

      if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) {
        if (tagName in Element._insertionTranslations.tags) {
          while (element.firstChild) {
            element.removeChild(element.firstChild);
          }
          Element._getContentFromAnonymousElement(tagName, content.stripScripts())
            .each(function(node) {
              element.appendChild(node)
            });
        }
        else {
          element.innerHTML = content.stripScripts();
        }
      }
      else {
        element.innerHTML = content.stripScripts();
      }

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

    return update;
  })(),

  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(),
          value = (element[property] || '').toString();
      if (value) result += ' ' + attribute + '=' + value.inspect(true);
    });
    return result + '>';
  },

  recursivelyCollect: function(element, property, maximumLength) {
    element = $(element);
    maximumLength = maximumLength || -1;
    var elements = [];

    while (element = element[property]) {
      if (element.nodeType == 1)
        elements.push(Element.extend(element));
      if (elements.length == maximumLength)
        break;
    }

    return elements;
  },

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

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

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

  immediateDescendants: function(element) {
    var results = [], child = $(element).firstChild;
    while (child) {
      if (child.nodeType === 1) {
        results.push(Element.extend(child));
      }
      child = child.nextSibling;
    }
    return results;
  },

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

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

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

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

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

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

  previous: function(element, expression, index) {
    element = $(element);
    if (Object.isNumber(expression)) index = expression, expression = false;
    if (!Object.isNumber(index)) index = 0;

    if (expression) {
      return Prototype.Selector.find(element.previousSiblings(), expression, index);
    } else {
      return element.recursivelyCollect("previousSibling", index + 1)[index];
    }
  },

  next: function(element, expression, index) {
    element = $(element);
    if (Object.isNumber(expression)) index = expression, expression = false;
    if (!Object.isNumber(index)) index = 0;

    if (expression) {
      return Prototype.Selector.find(element.nextSiblings(), expression, index);
    } else {
      var maximumLength = Object.isNumber(index) ? index + 1 : 1;
      return element.recursivelyCollect("nextSibling", index + 1)[index];
    }
  },


  select: function(element) {
    element = $(element);
    var expressions = Array.prototype.slice.call(arguments, 1).join(', ');
    return Prototype.Selector.select(expressions, element);
  },

  adjacent: function(element) {
    element = $(element);
    var expressions = Array.prototype.slice.call(arguments, 1).join(', ');
    return Prototype.Selector.select(expressions, element.parentNode).without(element);
  },

  identify: function(element) {
    element = $(element);
    var id = Element.readAttribute(element, 'id');
    if (id) return id;
    do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id));
    Element.writeAttribute(element, '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(element).height;
  },

  getWidth: function(element) {
    return Element.getDimensions(element).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(element, 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(element, className) ?
      'removeClassName' : 'addClassName'](element, 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(element);
    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;
  },

  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;
    if (element.parentNode) {
      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(element, 'position') == 'absolute') return element;

    var offsets = Element.positionedOffset(element),
        top     = offsets[1],
        left    = offsets[0],
        width   = element.clientWidth,
        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(element, 'position') == 'relative') return element;

    element.style.position = 'relative';
    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0),
        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,
        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 = Element.viewportOffset(source), delta = [0, 0], parent = null;

    element = $(element);

    if (Element.getStyle(element, 'position') == 'absolute') {
      parent = Element.getOffsetParent(element);
      delta = Element.viewportOffset(parent);
    }

    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;
  }
};

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);
      if (!element.parentNode) 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);
        if (!element.parentNode) 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.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 = (function(){

    var classProp = 'className',
        forProp = 'for',
        el = document.createElement('div');

    el.setAttribute(classProp, 'x');

    if (el.className !== 'x') {
      el.setAttribute('class', 'x');
      if (el.className === 'x') {
        classProp = 'class';
      }
    }
    el = null;

    el = document.createElement('label');
    el.setAttribute(forProp, 'x');
    if (el.htmlFor !== 'x') {
      el.setAttribute('htmlFor', 'x');
      if (el.htmlFor === 'x') {
        forProp = 'htmlFor';
      }
    }
    el = null;

    return {
      read: {
        names: {
          'class':      classProp,
          'className':  classProp,
          'for':        forProp,
          'htmlFor':    forProp
        },
        values: {
          _getAttr: function(element, attribute) {
            return element.getAttribute(attribute);
          },
          _getAttr2: function(element, attribute) {
            return element.getAttribute(attribute, 2);
          },
          _getAttrNode: function(element, attribute) {
            var node = element.getAttributeNode(attribute);
            return node ? node.value : "";
          },
          _getEv: (function(){

            var el = document.createElement('div'), f;
            el.onclick = Prototype.emptyFunction;
            var value = el.getAttribute('onclick');

            if (String(value).indexOf('{') > -1) {
              f = function(element, attribute) {
                attribute = element.getAttribute(attribute);
                if (!attribute) return null;
                attribute = attribute.toString();
                attribute = attribute.split('{')[1];
                attribute = attribute.split('}')[0];
                return attribute.strip();
              };
            }
            else if (value === '') {
              f = function(element, attribute) {
                attribute = element.getAttribute(attribute);
                if (!attribute) return null;
                return attribute.strip();
              };
            }
            el = null;
            return f;
          })(),
          _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._getAttr2,
      src:         v._getAttr2,
      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);

  if (Prototype.BrowserFeatures.ElementExtensions) {
    (function() {
      function _descendants(element) {
        var nodes = element.getElementsByTagName('*'), results = [];
        for (var i = 0, node; node = nodes[i]; i++)
          if (node.tagName !== "!") // Filter out comment nodes.
            results.push(node);
        return results;
      }

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

}

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 ('outerHTML' in document.documentElement) {
  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(),
          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];
    for (var i = t[2]; i--; ) {
      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() {
  var tags = Element._insertionTranslations.tags;
  Object.extend(tags, {
    THEAD: tags.TBODY,
    TFOOT: tags.TBODY,
    TH:    tags.TD
  });
})();

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);

(function(div) {

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

  div = null;

})(document.createElement('div'));

Element.extend = (function() {

  function checkDeficiency(tagName) {
    if (typeof window.Element != 'undefined') {
      var proto = window.Element.prototype;
      if (proto) {
        var id = '_' + (Math.random()+'').slice(2),
            el = document.createElement(tagName);
        proto[id] = 'x';
        var isBuggy = (el[id] !== 'x');
        delete proto[id];
        el = null;
        return isBuggy;
      }
    }
    return false;
  }

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

  var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object');

  if (Prototype.BrowserFeatures.SpecificElementExtensions) {
    if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) {
      return function(element) {
        if (element && typeof element._extendedByPrototype == 'undefined') {
          var t = element.tagName;
          if (t && (/^(?:object|applet|embed)$/i.test(t))) {
            extendElementWith(element, Element.Methods);
            extendElementWith(element, Element.Methods.Simulated);
            extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]);
          }
        }
        return element;
      }
    }
    return Prototype.K;
  }

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

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

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

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

    extendElementWith(element, methods);

    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;
})();

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

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];

    var element = document.createElement(tagName),
        proto = element['__proto__'] || element.constructor.prototype;

    element = null;
    return proto;
  }

  var elementPrototype = window.HTMLElement ? HTMLElement.prototype :
   Element.prototype;

  if (F.ElementExtensions) {
    copy(Element.Methods, elementPrototype);
    copy(Element.Methods.Simulated, elementPrototype, 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() {
    return { width: this.getWidth(), height: this.getHeight() };
  },

  getScrollOffsets: function() {
    return Element._returnOffset(
      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
      window.pageYOffset || document.documentElement.scrollTop  || document.body.scrollTop);
  }
};

(function(viewport) {
  var B = Prototype.Browser, doc = document, element, property = {};

  function getRootElement() {
    if (B.WebKit && !doc.evaluate)
      return document;

    if (B.Opera && window.parseFloat(window.opera.version()) < 9.5)
      return document.body;

    return document.documentElement;
  }

  function define(D) {
    if (!element) element = getRootElement();

    property[D] = 'client' + D;

    viewport['get' + D] = function() { return element[property[D]] };
    return viewport['get' + D]();
  }

  viewport.getWidth  = define.curry('Width');

  viewport.getHeight = define.curry('Height');
})(document.viewport);


Element.Storage = {
  UID: 1
};

Element.addMethods({
  getStorage: function(element) {
    if (!(element = $(element))) return;

    var uid;
    if (element === window) {
      uid = 0;
    } else {
      if (typeof element._prototypeUID === "undefined")
        element._prototypeUID = Element.Storage.UID++;
      uid = element._prototypeUID;
    }

    if (!Element.Storage[uid])
      Element.Storage[uid] = $H();

    return Element.Storage[uid];
  },

  store: function(element, key, value) {
    if (!(element = $(element))) return;

    if (arguments.length === 2) {
      Element.getStorage(element).update(key);
    } else {
      Element.getStorage(element).set(key, value);
    }

    return element;
  },

  retrieve: function(element, key, defaultValue) {
    if (!(element = $(element))) return;
    var hash = Element.getStorage(element), value = hash.get(key);

    if (Object.isUndefined(value)) {
      hash.set(key, defaultValue);
      value = defaultValue;
    }

    return value;
  },

  clone: function(element, deep) {
    if (!(element = $(element))) return;
    var clone = element.cloneNode(deep);
    clone._prototypeUID = void 0;
    if (deep) {
      var descendants = Element.select(clone, '*'),
          i = descendants.length;
      while (i--) {
        descendants[i]._prototypeUID = void 0;
      }
    }
    return Element.extend(clone);
  },

  purge: function(element) {
    if (!(element = $(element))) return;
    purgeElement(element);

    var descendants = element.getElementsByTagName('*'),
     i = descendants.length;

    while (i--) purgeElement(descendants[i]);

    return null;
  }
});

(function() {

  function toDecimal(pctString) {
    var match = pctString.match(/^(\d+)%?$/i);
    if (!match) return null;
    return (Number(match[1]) / 100);
  }

  function getPixelValue(value, property) {
    if (Object.isElement(value)) {
      element = value;
      value = element.getStyle(property);
    }
    if (value === null) {
      return null;
    }

    if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) {
      return window.parseFloat(value);
    }

    if (/\d/.test(value) && element.runtimeStyle) {
      var style = element.style.left, rStyle = element.runtimeStyle.left;
      element.runtimeStyle.left = element.currentStyle.left;
      element.style.left = value || 0;
      value = element.style.pixelLeft;
      element.style.left = style;
      element.runtimeStyle.left = rStyle;

      return value;
    }

    if (value.include('%')) {
      var decimal = toDecimal(value);
      var whole;
      if (property.include('left') || property.include('right') ||
       property.include('width')) {
        whole = $(element.parentNode).measure('width');
      } else if (property.include('top') || property.include('bottom') ||
       property.include('height')) {
        whole = $(element.parentNode).measure('height');
      }

      return whole * decimal;
    }

    return 0;
  }

  function toCSSPixels(number) {
    if (Object.isString(number) && number.endsWith('px')) {
      return number;
    }
    return number + 'px';
  }

  function isDisplayed(element) {
    var originalElement = element;
    while (element && element.parentNode) {
      var display = element.getStyle('display');
      if (display === 'none') {
        return false;
      }
      element = $(element.parentNode);
    }
    return true;
  }

  var hasLayout = Prototype.K;
  if ('currentStyle' in document.documentElement) {
    hasLayout = function(element) {
      if (!element.currentStyle.hasLayout) {
        element.style.zoom = 1;
      }
      return element;
    };
  }

  function cssNameFor(key) {
    if (key.include('border')) key = key + '-width';
    return key.camelize();
  }

  Element.Layout = Class.create(Hash, {
    initialize: function($super, element, preCompute) {
      $super();
      this.element = $(element);

      Element.Layout.PROPERTIES.each( function(property) {
        this._set(property, null);
      }, this);

      if (preCompute) {
        this._preComputing = true;
        this._begin();
        Element.Layout.PROPERTIES.each( this._compute, this );
        this._end();
        this._preComputing = false;
      }
    },

    _set: function(property, value) {
      return Hash.prototype.set.call(this, property, value);
    },

    set: function(property, value) {
      throw "Properties of Element.Layout are read-only.";
    },

    get: function($super, property) {
      var value = $super(property);
      return value === null ? this._compute(property) : value;
    },

    _begin: function() {
      if (this._prepared) return;

      var element = this.element;
      if (isDisplayed(element)) {
        this._prepared = true;
        return;
      }

      var originalStyles = {
        position:   element.style.position   || '',
        width:      element.style.width      || '',
        visibility: element.style.visibility || '',
        display:    element.style.display    || ''
      };

      element.store('prototype_original_styles', originalStyles);

      var position = element.getStyle('position'),
       width = element.getStyle('width');

      element.setStyle({
        position:   'absolute',
        visibility: 'hidden',
        display:    'block'
      });

      var positionedWidth = element.getStyle('width');

      var newWidth;
      if (width && (positionedWidth === width)) {
        newWidth = getPixelValue(width);
      } else if (width && (position === 'absolute' || position === 'fixed')) {
        newWidth = getPixelValue(width);
      } else {
        var parent = element.parentNode, pLayout = $(parent).getLayout();

        newWidth = pLayout.get('width') -
         this.get('margin-left') -
         this.get('border-left') -
         this.get('padding-left') -
         this.get('padding-right') -
         this.get('border-right') -
         this.get('margin-right');
      }

      element.setStyle({ width: newWidth + 'px' });

      this._prepared = true;
    },

    _end: function() {
      var element = this.element;
      var originalStyles = element.retrieve('prototype_original_styles');
      element.store('prototype_original_styles', null);
      element.setStyle(originalStyles);
      this._prepared = false;
    },

    _compute: function(property) {
      var COMPUTATIONS = Element.Layout.COMPUTATIONS;
      if (!(property in COMPUTATIONS)) {
        throw "Property not found.";
      }
      return this._set(property, COMPUTATIONS[property].call(this, this.element));
    },

    toObject: function() {
      var args = $A(arguments);
      var keys = (args.length === 0) ? Element.Layout.PROPERTIES :
       args.join(' ').split(' ');
      var obj = {};
      keys.each( function(key) {
        if (!Element.Layout.PROPERTIES.include(key)) return;
        var value = this.get(key);
        if (value != null) obj[key] = value;
      }, this);
      return obj;
    },

    toHash: function() {
      var obj = this.toObject.apply(this, arguments);
      return new Hash(obj);
    },

    toCSS: function() {
      var args = $A(arguments);
      var keys = (args.length === 0) ? Element.Layout.PROPERTIES :
       args.join(' ').split(' ');
      var css = {};

      keys.each( function(key) {
        if (!Element.Layout.PROPERTIES.include(key)) return;
        if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return;

        var value = this.get(key);
        if (value != null) css[cssNameFor(key)] = value + 'px';
      }, this);
      return css;
    },

    inspect: function() {
      return "#<Element.Layout>";
    }
  });

  Object.extend(Element.Layout, {
    PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'),

    COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'),

    COMPUTATIONS: {
      'height': function(element) {
        if (!this._preComputing) this._begin();

        var bHeight = this.get('border-box-height');
        if (bHeight <= 0) return 0;

        var bTop = this.get('border-top'),
         bBottom = this.get('border-bottom');

        var pTop = this.get('padding-top'),
         pBottom = this.get('padding-bottom');

        if (!this._preComputing) this._end();

        return bHeight - bTop - bBottom - pTop - pBottom;
      },

      'width': function(element) {
        if (!this._preComputing) this._begin();

        var bWidth = this.get('border-box-width');
        if (bWidth <= 0) return 0;

        var bLeft = this.get('border-left'),
         bRight = this.get('border-right');

        var pLeft = this.get('padding-left'),
         pRight = this.get('padding-right');

        if (!this._preComputing) this._end();

        return bWidth - bLeft - bRight - pLeft - pRight;
      },

      'padding-box-height': function(element) {
        var height = this.get('height'),
         pTop = this.get('padding-top'),
         pBottom = this.get('padding-bottom');

        return height + pTop + pBottom;
      },

      'padding-box-width': function(element) {
        var width = this.get('width'),
         pLeft = this.get('padding-left'),
         pRight = this.get('padding-right');

        return width + pLeft + pRight;
      },

      'border-box-height': function(element) {
        return element.offsetHeight;
      },

      'border-box-width': function(element) {
        return element.offsetWidth;
      },

      'margin-box-height': function(element) {
        var bHeight = this.get('border-box-height'),
         mTop = this.get('margin-top'),
         mBottom = this.get('margin-bottom');

        if (bHeight <= 0) return 0;

        return bHeight + mTop + mBottom;
      },

      'margin-box-width': function(element) {
        var bWidth = this.get('border-box-width'),
         mLeft = this.get('margin-left'),
         mRight = this.get('margin-right');

        if (bWidth <= 0) return 0;

        return bWidth + mLeft + mRight;
      },

      'top': function(element) {
        var offset = element.positionedOffset();
        return offset.top;
      },

      'bottom': function(element) {
        var offset = element.positionedOffset(),
         parent = element.getOffsetParent(),
         pHeight = parent.measure('height');

        var mHeight = this.get('border-box-height');

        return pHeight - mHeight - offset.top;
      },

      'left': function(element) {
        var offset = element.positionedOffset();
        return offset.left;
      },

      'right': function(element) {
        var offset = element.positionedOffset(),
         parent = element.getOffsetParent(),
         pWidth = parent.measure('width');

        var mWidth = this.get('border-box-width');

        return pWidth - mWidth - offset.left;
      },

      'padding-top': function(element) {
        return getPixelValue(element, 'paddingTop');
      },

      'padding-bottom': function(element) {
        return getPixelValue(element, 'paddingBottom');
      },

      'padding-left': function(element) {
        return getPixelValue(element, 'paddingLeft');
      },

      'padding-right': function(element) {
        return getPixelValue(element, 'paddingRight');
      },

      'border-top': function(element) {
        return Object.isNumber(element.clientTop) ? element.clientTop :
         getPixelValue(element, 'borderTopWidth');
      },

      'border-bottom': function(element) {
        return Object.isNumber(element.clientBottom) ? element.clientBottom :
         getPixelValue(element, 'borderBottomWidth');
      },

      'border-left': function(element) {
        return Object.isNumber(element.clientLeft) ? element.clientLeft :
         getPixelValue(element, 'borderLeftWidth');
      },

      'border-right': function(element) {
        return Object.isNumber(element.clientRight) ? element.clientRight :
         getPixelValue(element, 'borderRightWidth');
      },

      'margin-top': function(element) {
        return getPixelValue(element, 'marginTop');
      },

      'margin-bottom': function(element) {
        return getPixelValue(element, 'marginBottom');
      },

      'margin-left': function(element) {
        return getPixelValue(element, 'marginLeft');
      },

      'margin-right': function(element) {
        return getPixelValue(element, 'marginRight');
      }
    }
  });

  if ('getBoundingClientRect' in document.documentElement) {
    Object.extend(Element.Layout.COMPUTATIONS, {
      'right': function(element) {
        var parent = hasLayout(element.getOffsetParent());
        var rect = element.getBoundingClientRect(),
         pRect = parent.getBoundingClientRect();

        return (pRect.right - rect.right).round();
      },

      'bottom': function(element) {
        var parent = hasLayout(element.getOffsetParent());
        var rect = element.getBoundingClientRect(),
         pRect = parent.getBoundingClientRect();

        return (pRect.bottom - rect.bottom).round();
      }
    });
  }

  Element.Offset = Class.create({
    initialize: function(left, top) {
      this.left = left.round();
      this.top  = top.round();

      this[0] = this.left;
      this[1] = this.top;
    },

    relativeTo: function(offset) {
      return new Element.Offset(
        this.left - offset.left,
        this.top  - offset.top
      );
    },

    inspect: function() {
      return "#<Element.Offset left: #{left} top: #{top}>".interpolate(this);
    },

    toString: function() {
      return "[#{left}, #{top}]".interpolate(this);
    },

    toArray: function() {
      return [this.left, this.top];
    }
  });

  function getLayout(element, preCompute) {
    return new Element.Layout(element, preCompute);
  }

  function measure(element, property) {
    return $(element).getLayout().get(property);
  }

  function getDimensions(element) {
    var layout = $(element).getLayout();
    return {
      width:  layout.get('width'),
      height: layout.get('height')
    };
  }

  function getOffsetParent(element) {
    if (isDetached(element)) return $(document.body);

    var isInline = (Element.getStyle(element, 'display') === 'inline');
    if (!isInline && 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.nodeName === 'HTML') ? $(document.body) : $(element);
      }
    }

    return $(document.body);
  }


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

  function positionedOffset(element) {
    var layout = element.getLayout();

    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
      if (element) {
        if (isBody(element)) break;
        var p = Element.getStyle(element, 'position');
        if (p !== 'static') break;
      }
    } while (element);

    valueL -= layout.get('margin-top');
    valueT -= layout.get('margin-left');

    return new Element.Offset(valueL, valueT);
  }

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

  function viewportOffset(forElement) {
    var valueT = 0, valueL = 0, docBody = document.body;

    var element = forElement;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      if (element.offsetParent == docBody &&
        Element.getStyle(element, 'position') == 'absolute') break;
    } while (element = element.offsetParent);

    element = forElement;
    do {
      if (element != docBody) {
        valueT -= element.scrollTop  || 0;
        valueL -= element.scrollLeft || 0;
      }
    } while (element = element.parentNode);
    return new Element.Offset(valueL, valueT);
  }

  function absolutize(element) {
    element = $(element);

    if (Element.getStyle(element, 'position') === 'absolute') {
      return element;
    }

    var offsetParent = getOffsetParent(element);
    var eOffset = element.viewportOffset(),
     pOffset = offsetParent.viewportOffset();

    var offset = eOffset.relativeTo(pOffset);
    var layout = element.getLayout();

    element.store('prototype_absolutize_original_styles', {
      left:   element.getStyle('left'),
      top:    element.getStyle('top'),
      width:  element.getStyle('width'),
      height: element.getStyle('height')
    });

    element.setStyle({
      position: 'absolute',
      top:    offset.top + 'px',
      left:   offset.left + 'px',
      width:  layout.get('width') + 'px',
      height: layout.get('height') + 'px'
    });

    return element;
  }

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

    var originalStyles =
     element.retrieve('prototype_absolutize_original_styles');

    if (originalStyles) element.setStyle(originalStyles);
    return element;
  }

  Element.addMethods({
    getLayout:              getLayout,
    measure:                measure,
    getDimensions:          getDimensions,
    getOffsetParent:        getOffsetParent,
    cumulativeOffset:       cumulativeOffset,
    positionedOffset:       positionedOffset,
    cumulativeScrollOffset: cumulativeScrollOffset,
    viewportOffset:         viewportOffset,
    absolutize:             absolutize,
    relativize:             relativize
  });

  function isBody(element) {
    return element.nodeName.toUpperCase() === 'BODY';
  }

  function isDetached(element) {
    return element !== document.body &&
     !Element.descendantOf(element, document.body);
  }

  if ('getBoundingClientRect' in document.documentElement) {
    Element.addMethods({
      viewportOffset: function(element) {
        element = $(element);
        if (isDetached(element)) return new Element.Offset(0, 0);

        var rect  = element.getBoundingClientRect(),
         docEl = document.documentElement;
        return new Element.Offset(rect.left - docEl.clientLeft,
         rect.top - docEl.clientTop);
      },

      positionedOffset: function(element) {
        element = $(element);
        var parent = element.getOffsetParent();
        if (isDetached(element)) return new Element.Offset(0, 0);

        if (element.offsetParent &&
         element.offsetParent.nodeName.toUpperCase() === 'HTML') {
          return positionedOffset(element);
        }

        var eOffset = element.viewportOffset(),
         pOffset = isBody(parent) ? viewportOffset(parent) :
          parent.viewportOffset();
        var retOffset = eOffset.relativeTo(pOffset);

        var layout = element.getLayout();
        var top  = retOffset.top  - layout.get('margin-top');
        var left = retOffset.left - layout.get('margin-left');

        return new Element.Offset(left, top);
      }
    });
  }
})();
window.$$ = function() {
  var expression = $A(arguments).join(', ');
  return Prototype.Selector.select(expression, document);
};

Prototype.Selector = (function() {

  function select() {
    throw new Error('Method "Prototype.Selector.select" must be defined.');
  }

  function match() {
    throw new Error('Method "Prototype.Selector.match" must be defined.');
  }

  function find(elements, expression, index) {
    index = index || 0;
    var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i;

    for (i = 0; i < length; i++) {
      if (match(elements[i], expression) && index == matchIndex++) {
        return Element.extend(elements[i]);
      }
    }
  }

  function extendElements(elements) {
    for (var i = 0, length = elements.length; i < length; i++) {
      Element.extend(elements[i]);
    }
    return elements;
  }


  var K = Prototype.K;

  return {
    select: select,
    match: match,
    find: find,
    extendElements: (Element.extend === K) ? K : extendElements,
    extendElement: Element.extend
  };
})();
Prototype._original_property = window.Sizzle;
/*!
 * Sizzle CSS Selector Engine - v1.0
 *  Copyright 2009, The Dojo Foundation
 *  Released under the MIT, BSD, and GPL Licenses.
 *  More information: http://sizzlejs.com/
 */
(function(){

var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
	done = 0,
	toString = Object.prototype.toString,
	hasDuplicate = false,
	baseHasDuplicate = true;

[0, 0].sort(function(){
	baseHasDuplicate = false;
	return 0;
});

var Sizzle = function(selector, context, results, seed) {
	results = results || [];
	var origContext = context = context || document;

	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
		return [];
	}

	if ( !selector || typeof selector !== "string" ) {
		return results;
	}

	var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context),
		soFar = selector;

	while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) {
		soFar = m[3];

		parts.push( m[1] );

		if ( m[2] ) {
			extra = m[3];
			break;
		}
	}

	if ( parts.length > 1 && origPOS.exec( selector ) ) {
		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
			set = posProcess( parts[0] + parts[1], context );
		} else {
			set = Expr.relative[ parts[0] ] ?
				[ context ] :
				Sizzle( parts.shift(), context );

			while ( parts.length ) {
				selector = parts.shift();

				if ( Expr.relative[ selector ] )
					selector += parts.shift();

				set = posProcess( selector, set );
			}
		}
	} else {
		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
			var ret = Sizzle.find( parts.shift(), context, contextXML );
			context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
		}

		if ( context ) {
			var ret = seed ?
				{ expr: parts.pop(), set: makeArray(seed) } :
				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
			set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;

			if ( parts.length > 0 ) {
				checkSet = makeArray(set);
			} else {
				prune = false;
			}

			while ( parts.length ) {
				var cur = parts.pop(), pop = cur;

				if ( !Expr.relative[ cur ] ) {
					cur = "";
				} else {
					pop = parts.pop();
				}

				if ( pop == null ) {
					pop = context;
				}

				Expr.relative[ cur ]( checkSet, pop, contextXML );
			}
		} else {
			checkSet = parts = [];
		}
	}

	if ( !checkSet ) {
		checkSet = set;
	}

	if ( !checkSet ) {
		throw "Syntax error, unrecognized expression: " + (cur || selector);
	}

	if ( toString.call(checkSet) === "[object Array]" ) {
		if ( !prune ) {
			results.push.apply( results, checkSet );
		} else if ( context && context.nodeType === 1 ) {
			for ( var i = 0; checkSet[i] != null; i++ ) {
				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
					results.push( set[i] );
				}
			}
		} else {
			for ( var i = 0; checkSet[i] != null; i++ ) {
				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
					results.push( set[i] );
				}
			}
		}
	} else {
		makeArray( checkSet, results );
	}

	if ( extra ) {
		Sizzle( extra, origContext, results, seed );
		Sizzle.uniqueSort( results );
	}

	return results;
};

Sizzle.uniqueSort = function(results){
	if ( sortOrder ) {
		hasDuplicate = baseHasDuplicate;
		results.sort(sortOrder);

		if ( hasDuplicate ) {
			for ( var i = 1; i < results.length; i++ ) {
				if ( results[i] === results[i-1] ) {
					results.splice(i--, 1);
				}
			}
		}
	}

	return results;
};

Sizzle.matches = function(expr, set){
	return Sizzle(expr, null, null, set);
};

Sizzle.find = function(expr, context, isXML){
	var set, match;

	if ( !expr ) {
		return [];
	}

	for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
		var type = Expr.order[i], match;

		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
			var left = match[1];
			match.splice(1,1);

			if ( left.substr( left.length - 1 ) !== "\\" ) {
				match[1] = (match[1] || "").replace(/\\/g, "");
				set = Expr.find[ type ]( match, context, isXML );
				if ( set != null ) {
					expr = expr.replace( Expr.match[ type ], "" );
					break;
				}
			}
		}
	}

	if ( !set ) {
		set = context.getElementsByTagName("*");
	}

	return {set: set, expr: expr};
};

Sizzle.filter = function(expr, set, inplace, not){
	var old = expr, result = [], curLoop = set, match, anyFound,
		isXMLFilter = set && set[0] && isXML(set[0]);

	while ( expr && set.length ) {
		for ( var type in Expr.filter ) {
			if ( (match = Expr.match[ type ].exec( expr )) != null ) {
				var filter = Expr.filter[ type ], found, item;
				anyFound = false;

				if ( curLoop == result ) {
					result = [];
				}

				if ( Expr.preFilter[ type ] ) {
					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );

					if ( !match ) {
						anyFound = found = true;
					} else if ( match === true ) {
						continue;
					}
				}

				if ( match ) {
					for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
						if ( item ) {
							found = filter( item, match, i, curLoop );
							var pass = not ^ !!found;

							if ( inplace && found != null ) {
								if ( pass ) {
									anyFound = true;
								} else {
									curLoop[i] = false;
								}
							} else if ( pass ) {
								result.push( item );
								anyFound = true;
							}
						}
					}
				}

				if ( found !== undefined ) {
					if ( !inplace ) {
						curLoop = result;
					}

					expr = expr.replace( Expr.match[ type ], "" );

					if ( !anyFound ) {
						return [];
					}

					break;
				}
			}
		}

		if ( expr == old ) {
			if ( anyFound == null ) {
				throw "Syntax error, unrecognized expression: " + expr;
			} else {
				break;
			}
		}

		old = expr;
	}

	return curLoop;
};

var Expr = Sizzle.selectors = {
	order: [ "ID", "NAME", "TAG" ],
	match: {
		ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
		CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,
		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
		TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,
		CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
		PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
	},
	leftMatch: {},
	attrMap: {
		"class": "className",
		"for": "htmlFor"
	},
	attrHandle: {
		href: function(elem){
			return elem.getAttribute("href");
		}
	},
	relative: {
		"+": function(checkSet, part, isXML){
			var isPartStr = typeof part === "string",
				isTag = isPartStr && !/\W/.test(part),
				isPartStrNotTag = isPartStr && !isTag;

			if ( isTag && !isXML ) {
				part = part.toUpperCase();
			}

			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
				if ( (elem = checkSet[i]) ) {
					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}

					checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
						elem || false :
						elem === part;
				}
			}

			if ( isPartStrNotTag ) {
				Sizzle.filter( part, checkSet, true );
			}
		},
		">": function(checkSet, part, isXML){
			var isPartStr = typeof part === "string";

			if ( isPartStr && !/\W/.test(part) ) {
				part = isXML ? part : part.toUpperCase();

				for ( var i = 0, l = checkSet.length; i < l; i++ ) {
					var elem = checkSet[i];
					if ( elem ) {
						var parent = elem.parentNode;
						checkSet[i] = parent.nodeName === part ? parent : false;
					}
				}
			} else {
				for ( var i = 0, l = checkSet.length; i < l; i++ ) {
					var elem = checkSet[i];
					if ( elem ) {
						checkSet[i] = isPartStr ?
							elem.parentNode :
							elem.parentNode === part;
					}
				}

				if ( isPartStr ) {
					Sizzle.filter( part, checkSet, true );
				}
			}
		},
		"": function(checkSet, part, isXML){
			var doneName = done++, checkFn = dirCheck;

			if ( !/\W/.test(part) ) {
				var nodeCheck = part = isXML ? part : part.toUpperCase();
				checkFn = dirNodeCheck;
			}

			checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
		},
		"~": function(checkSet, part, isXML){
			var doneName = done++, checkFn = dirCheck;

			if ( typeof part === "string" && !/\W/.test(part) ) {
				var nodeCheck = part = isXML ? part : part.toUpperCase();
				checkFn = dirNodeCheck;
			}

			checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
		}
	},
	find: {
		ID: function(match, context, isXML){
			if ( typeof context.getElementById !== "undefined" && !isXML ) {
				var m = context.getElementById(match[1]);
				return m ? [m] : [];
			}
		},
		NAME: function(match, context, isXML){
			if ( typeof context.getElementsByName !== "undefined" ) {
				var ret = [], results = context.getElementsByName(match[1]);

				for ( var i = 0, l = results.length; i < l; i++ ) {
					if ( results[i].getAttribute("name") === match[1] ) {
						ret.push( results[i] );
					}
				}

				return ret.length === 0 ? null : ret;
			}
		},
		TAG: function(match, context){
			return context.getElementsByTagName(match[1]);
		}
	},
	preFilter: {
		CLASS: function(match, curLoop, inplace, result, not, isXML){
			match = " " + match[1].replace(/\\/g, "") + " ";

			if ( isXML ) {
				return match;
			}

			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
				if ( elem ) {
					if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
						if ( !inplace )
							result.push( elem );
					} else if ( inplace ) {
						curLoop[i] = false;
					}
				}
			}

			return false;
		},
		ID: function(match){
			return match[1].replace(/\\/g, "");
		},
		TAG: function(match, curLoop){
			for ( var i = 0; curLoop[i] === false; i++ ){}
			return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
		},
		CHILD: function(match){
			if ( match[1] == "nth" ) {
				var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
					match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);

				match[2] = (test[1] + (test[2] || 1)) - 0;
				match[3] = test[3] - 0;
			}

			match[0] = done++;

			return match;
		},
		ATTR: function(match, curLoop, inplace, result, not, isXML){
			var name = match[1].replace(/\\/g, "");

			if ( !isXML && Expr.attrMap[name] ) {
				match[1] = Expr.attrMap[name];
			}

			if ( match[2] === "~=" ) {
				match[4] = " " + match[4] + " ";
			}

			return match;
		},
		PSEUDO: function(match, curLoop, inplace, result, not){
			if ( match[1] === "not" ) {
				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
					match[3] = Sizzle(match[3], null, null, curLoop);
				} else {
					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
					if ( !inplace ) {
						result.push.apply( result, ret );
					}
					return false;
				}
			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
				return true;
			}

			return match;
		},
		POS: function(match){
			match.unshift( true );
			return match;
		}
	},
	filters: {
		enabled: function(elem){
			return elem.disabled === false && elem.type !== "hidden";
		},
		disabled: function(elem){
			return elem.disabled === true;
		},
		checked: function(elem){
			return elem.checked === true;
		},
		selected: function(elem){
			elem.parentNode.selectedIndex;
			return elem.selected === true;
		},
		parent: function(elem){
			return !!elem.firstChild;
		},
		empty: function(elem){
			return !elem.firstChild;
		},
		has: function(elem, i, match){
			return !!Sizzle( match[3], elem ).length;
		},
		header: function(elem){
			return /h\d/i.test( elem.nodeName );
		},
		text: function(elem){
			return "text" === elem.type;
		},
		radio: function(elem){
			return "radio" === elem.type;
		},
		checkbox: function(elem){
			return "checkbox" === elem.type;
		},
		file: function(elem){
			return "file" === elem.type;
		},
		password: function(elem){
			return "password" === elem.type;
		},
		submit: function(elem){
			return "submit" === elem.type;
		},
		image: function(elem){
			return "image" === elem.type;
		},
		reset: function(elem){
			return "reset" === elem.type;
		},
		button: function(elem){
			return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
		},
		input: function(elem){
			return /input|select|textarea|button/i.test(elem.nodeName);
		}
	},
	setFilters: {
		first: function(elem, i){
			return i === 0;
		},
		last: function(elem, i, match, array){
			return i === array.length - 1;
		},
		even: function(elem, i){
			return i % 2 === 0;
		},
		odd: function(elem, i){
			return i % 2 === 1;
		},
		lt: function(elem, i, match){
			return i < match[3] - 0;
		},
		gt: function(elem, i, match){
			return i > match[3] - 0;
		},
		nth: function(elem, i, match){
			return match[3] - 0 == i;
		},
		eq: function(elem, i, match){
			return match[3] - 0 == i;
		}
	},
	filter: {
		PSEUDO: function(elem, match, i, array){
			var name = match[1], filter = Expr.filters[ name ];

			if ( filter ) {
				return filter( elem, i, match, array );
			} else if ( name === "contains" ) {
				return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
			} else if ( name === "not" ) {
				var not = match[3];

				for ( var i = 0, l = not.length; i < l; i++ ) {
					if ( not[i] === elem ) {
						return false;
					}
				}

				return true;
			}
		},
		CHILD: function(elem, match){
			var type = match[1], node = elem;
			switch (type) {
				case 'only':
				case 'first':
					while ( (node = node.previousSibling) )  {
						if ( node.nodeType === 1 ) return false;
					}
					if ( type == 'first') return true;
					node = elem;
				case 'last':
					while ( (node = node.nextSibling) )  {
						if ( node.nodeType === 1 ) return false;
					}
					return true;
				case 'nth':
					var first = match[2], last = match[3];

					if ( first == 1 && last == 0 ) {
						return true;
					}

					var doneName = match[0],
						parent = elem.parentNode;

					if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
						var count = 0;
						for ( node = parent.firstChild; node; node = node.nextSibling ) {
							if ( node.nodeType === 1 ) {
								node.nodeIndex = ++count;
							}
						}
						parent.sizcache = doneName;
					}

					var diff = elem.nodeIndex - last;
					if ( first == 0 ) {
						return diff == 0;
					} else {
						return ( diff % first == 0 && diff / first >= 0 );
					}
			}
		},
		ID: function(elem, match){
			return elem.nodeType === 1 && elem.getAttribute("id") === match;
		},
		TAG: function(elem, match){
			return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
		},
		CLASS: function(elem, match){
			return (" " + (elem.className || elem.getAttribute("class")) + " ")
				.indexOf( match ) > -1;
		},
		ATTR: function(elem, match){
			var name = match[1],
				result = Expr.attrHandle[ name ] ?
					Expr.attrHandle[ name ]( elem ) :
					elem[ name ] != null ?
						elem[ name ] :
						elem.getAttribute( name ),
				value = result + "",
				type = match[2],
				check = match[4];

			return result == null ?
				type === "!=" :
				type === "=" ?
				value === check :
				type === "*=" ?
				value.indexOf(check) >= 0 :
				type === "~=" ?
				(" " + value + " ").indexOf(check) >= 0 :
				!check ?
				value && result !== false :
				type === "!=" ?
				value != check :
				type === "^=" ?
				value.indexOf(check) === 0 :
				type === "$=" ?
				value.substr(value.length - check.length) === check :
				type === "|=" ?
				value === check || value.substr(0, check.length + 1) === check + "-" :
				false;
		},
		POS: function(elem, match, i, array){
			var name = match[2], filter = Expr.setFilters[ name ];

			if ( filter ) {
				return filter( elem, i, match, array );
			}
		}
	}
};

var origPOS = Expr.match.POS;

for ( var type in Expr.match ) {
	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source );
}

var makeArray = function(array, results) {
	array = Array.prototype.slice.call( array, 0 );

	if ( results ) {
		results.push.apply( results, array );
		return results;
	}

	return array;
};

try {
	Array.prototype.slice.call( document.documentElement.childNodes, 0 );

} catch(e){
	makeArray = function(array, results) {
		var ret = results || [];

		if ( toString.call(array) === "[object Array]" ) {
			Array.prototype.push.apply( ret, array );
		} else {
			if ( typeof array.length === "number" ) {
				for ( var i = 0, l = array.length; i < l; i++ ) {
					ret.push( array[i] );
				}
			} else {
				for ( var i = 0; array[i]; i++ ) {
					ret.push( array[i] );
				}
			}
		}

		return ret;
	};
}

var sortOrder;

if ( document.documentElement.compareDocumentPosition ) {
	sortOrder = function( a, b ) {
		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
			if ( a == b ) {
				hasDuplicate = true;
			}
			return 0;
		}

		var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
		if ( ret === 0 ) {
			hasDuplicate = true;
		}
		return ret;
	};
} else if ( "sourceIndex" in document.documentElement ) {
	sortOrder = function( a, b ) {
		if ( !a.sourceIndex || !b.sourceIndex ) {
			if ( a == b ) {
				hasDuplicate = true;
			}
			return 0;
		}

		var ret = a.sourceIndex - b.sourceIndex;
		if ( ret === 0 ) {
			hasDuplicate = true;
		}
		return ret;
	};
} else if ( document.createRange ) {
	sortOrder = function( a, b ) {
		if ( !a.ownerDocument || !b.ownerDocument ) {
			if ( a == b ) {
				hasDuplicate = true;
			}
			return 0;
		}

		var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
		aRange.setStart(a, 0);
		aRange.setEnd(a, 0);
		bRange.setStart(b, 0);
		bRange.setEnd(b, 0);
		var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
		if ( ret === 0 ) {
			hasDuplicate = true;
		}
		return ret;
	};
}

(function(){
	var form = document.createElement("div"),
		id = "script" + (new Date).getTime();
	form.innerHTML = "<a name='" + id + "'/>";

	var root = document.documentElement;
	root.insertBefore( form, root.firstChild );

	if ( !!document.getElementById( id ) ) {
		Expr.find.ID = function(match, context, isXML){
			if ( typeof context.getElementById !== "undefined" && !isXML ) {
				var m = context.getElementById(match[1]);
				return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
			}
		};

		Expr.filter.ID = function(elem, match){
			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
			return elem.nodeType === 1 && node && node.nodeValue === match;
		};
	}

	root.removeChild( form );
	root = form = null; // release memory in IE
})();

(function(){

	var div = document.createElement("div");
	div.appendChild( document.createComment("") );

	if ( div.getElementsByTagName("*").length > 0 ) {
		Expr.find.TAG = function(match, context){
			var results = context.getElementsByTagName(match[1]);

			if ( match[1] === "*" ) {
				var tmp = [];

				for ( var i = 0; results[i]; i++ ) {
					if ( results[i].nodeType === 1 ) {
						tmp.push( results[i] );
					}
				}

				results = tmp;
			}

			return results;
		};
	}

	div.innerHTML = "<a href='#'></a>";
	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
			div.firstChild.getAttribute("href") !== "#" ) {
		Expr.attrHandle.href = function(elem){
			return elem.getAttribute("href", 2);
		};
	}

	div = null; // release memory in IE
})();

if ( document.querySelectorAll ) (function(){
	var oldSizzle = Sizzle, div = document.createElement("div");
	div.innerHTML = "<p class='TEST'></p>";

	if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
		return;
	}

	Sizzle = function(query, context, extra, seed){
		context = context || document;

		if ( !seed && context.nodeType === 9 && !isXML(context) ) {
			try {
				return makeArray( context.querySelectorAll(query), extra );
			} catch(e){}
		}

		return oldSizzle(query, context, extra, seed);
	};

	for ( var prop in oldSizzle ) {
		Sizzle[ prop ] = oldSizzle[ prop ];
	}

	div = null; // release memory in IE
})();

if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
	var div = document.createElement("div");
	div.innerHTML = "<div class='test e'></div><div class='test'></div>";

	if ( div.getElementsByClassName("e").length === 0 )
		return;

	div.lastChild.className = "e";

	if ( div.getElementsByClassName("e").length === 1 )
		return;

	Expr.order.splice(1, 0, "CLASS");
	Expr.find.CLASS = function(match, context, isXML) {
		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
			return context.getElementsByClassName(match[1]);
		}
	};

	div = null; // release memory in IE
})();

function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
	var sibDir = dir == "previousSibling" && !isXML;
	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
		var elem = checkSet[i];
		if ( elem ) {
			if ( sibDir && elem.nodeType === 1 ){
				elem.sizcache = doneName;
				elem.sizset = i;
			}
			elem = elem[dir];
			var match = false;

			while ( elem ) {
				if ( elem.sizcache === doneName ) {
					match = checkSet[elem.sizset];
					break;
				}

				if ( elem.nodeType === 1 && !isXML ){
					elem.sizcache = doneName;
					elem.sizset = i;
				}

				if ( elem.nodeName === cur ) {
					match = elem;
					break;
				}

				elem = elem[dir];
			}

			checkSet[i] = match;
		}
	}
}

function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
	var sibDir = dir == "previousSibling" && !isXML;
	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
		var elem = checkSet[i];
		if ( elem ) {
			if ( sibDir && elem.nodeType === 1 ) {
				elem.sizcache = doneName;
				elem.sizset = i;
			}
			elem = elem[dir];
			var match = false;

			while ( elem ) {
				if ( elem.sizcache === doneName ) {
					match = checkSet[elem.sizset];
					break;
				}

				if ( elem.nodeType === 1 ) {
					if ( !isXML ) {
						elem.sizcache = doneName;
						elem.sizset = i;
					}
					if ( typeof cur !== "string" ) {
						if ( elem === cur ) {
							match = true;
							break;
						}

					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
						match = elem;
						break;
					}
				}

				elem = elem[dir];
			}

			checkSet[i] = match;
		}
	}
}

var contains = document.compareDocumentPosition ?  function(a, b){
	return a.compareDocumentPosition(b) & 16;
} : function(a, b){
	return a !== b && (a.contains ? a.contains(b) : true);
};

var isXML = function(elem){
	return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
		!!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML";
};

var posProcess = function(selector, context){
	var tmpSet = [], later = "", match,
		root = context.nodeType ? [context] : context;

	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
		later += match[0];
		selector = selector.replace( Expr.match.PSEUDO, "" );
	}

	selector = Expr.relative[selector] ? selector + "*" : selector;

	for ( var i = 0, l = root.length; i < l; i++ ) {
		Sizzle( selector, root[i], tmpSet );
	}

	return Sizzle.filter( later, tmpSet );
};


window.Sizzle = Sizzle;

})();

;(function(engine) {
  var extendElements = Prototype.Selector.extendElements;

  function select(selector, scope) {
    return extendElements(engine(selector, scope || document));
  }

  function match(element, selector) {
    return engine.matches(selector, [element]).length == 1;
  }

  Prototype.Selector.engine = engine;
  Prototype.Selector.select = select;
  Prototype.Selector.match = match;
})(Sizzle);

window.Sizzle = Prototype._original_property;
delete Prototype._original_property;

var Form = {
  reset: function(form) {
    form = $(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) {
    var elements = $(form).getElementsByTagName('*'),
        element,
        arr = [ ],
        serializers = Form.Element.Serializers;
    for (var i = 0; element = elements[i]; i++) {
      arr.push(element);
    }
    return arr.inject([], function(elements, child) {
      if (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)$/i.test(element.tagName);
    });
  },

  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)$/i.test(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);
  }
});
(function() {

  var 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: {}
  };

  var docEl = document.documentElement;
  var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl
    && 'onmouseleave' in docEl;

  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);
    };
  }

  function isLeftClick(event)   { return _isButton(event, 0) }

  function isMiddleClick(event) { return _isButton(event, 1) }

  function isRightClick(event)  { return _isButton(event, 2) }

  function element(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);
  }

  function findElement(event, expression) {
    var element = Event.element(event);
    if (!expression) return element;
    while (element) {
      if (Object.isElement(element) && Prototype.Selector.match(element, expression)) {
        return Element.extend(element);
      }
      element = element.parentNode;
    }
  }

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

  function pointerX(event) {
    var docElement = document.documentElement,
     body = document.body || { scrollLeft: 0 };

    return event.pageX || (event.clientX +
      (docElement.scrollLeft || body.scrollLeft) -
      (docElement.clientLeft || 0));
  }

  function pointerY(event) {
    var docElement = document.documentElement,
     body = document.body || { scrollTop: 0 };

    return  event.pageY || (event.clientY +
       (docElement.scrollTop || body.scrollTop) -
       (docElement.clientTop || 0));
  }


  function stop(event) {
    Event.extend(event);
    event.preventDefault();
    event.stopPropagation();

    event.stopped = true;
  }

  Event.Methods = {
    isLeftClick: isLeftClick,
    isMiddleClick: isMiddleClick,
    isRightClick: isRightClick,

    element: element,
    findElement: findElement,

    pointer: pointer,
    pointerX: pointerX,
    pointerY: pointerY,

    stop: stop
  };


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

  if (Prototype.Browser.IE) {
    function _relatedTarget(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);
    }

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

    Event.extend = function(event, element) {
      if (!event) return false;
      if (event._extendedByPrototype) return event;

      event._extendedByPrototype = Prototype.emptyFunction;
      var pointer = Event.pointer(event);

      Object.extend(event, {
        target: event.srcElement || element,
        relatedTarget: _relatedTarget(event),
        pageX:  pointer.x,
        pageY:  pointer.y
      });

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

  function _createResponder(element, eventName, handler) {
    var registry = Element.retrieve(element, 'prototype_event_registry');

    if (Object.isUndefined(registry)) {
      CACHE.push(element);
      registry = Element.retrieve(element, 'prototype_event_registry', $H());
    }

    var respondersForEvent = registry.get(eventName);
    if (Object.isUndefined(respondersForEvent)) {
      respondersForEvent = [];
      registry.set(eventName, respondersForEvent);
    }

    if (respondersForEvent.pluck('handler').include(handler)) return false;

    var responder;
    if (eventName.include(":")) {
      responder = function(event) {
        if (Object.isUndefined(event.eventName))
          return false;

        if (event.eventName !== eventName)
          return false;

        Event.extend(event, element);
        handler.call(element, event);
      };
    } else {
      if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED &&
       (eventName === "mouseenter" || eventName === "mouseleave")) {
        if (eventName === "mouseenter" || eventName === "mouseleave") {
          responder = function(event) {
            Event.extend(event, element);

            var parent = event.relatedTarget;
            while (parent && parent !== element) {
              try { parent = parent.parentNode; }
              catch(e) { parent = element; }
            }

            if (parent === element) return;

            handler.call(element, event);
          };
        }
      } else {
        responder = function(event) {
          Event.extend(event, element);
          handler.call(element, event);
        };
      }
    }

    responder.handler = handler;
    respondersForEvent.push(responder);
    return responder;
  }

  function _destroyCache() {
    for (var i = 0, length = CACHE.length; i < length; i++) {
      Event.stopObserving(CACHE[i]);
      CACHE[i] = null;
    }
  }

  var CACHE = [];

  if (Prototype.Browser.IE)
    window.attachEvent('onunload', _destroyCache);

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


  var _getDOMEventName = Prototype.K,
      translations = { mouseenter: "mouseover", mouseleave: "mouseout" };

  if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) {
    _getDOMEventName = function(eventName) {
      return (translations[eventName] || eventName);
    };
  }

  function observe(element, eventName, handler) {
    element = $(element);

    var responder = _createResponder(element, eventName, handler);

    if (!responder) return element;

    if (eventName.include(':')) {
      if (element.addEventListener)
        element.addEventListener("dataavailable", responder, false);
      else {
        element.attachEvent("ondataavailable", responder);
        element.attachEvent("onfilterchange", responder);
      }
    } else {
      var actualEventName = _getDOMEventName(eventName);

      if (element.addEventListener)
        element.addEventListener(actualEventName, responder, false);
      else
        element.attachEvent("on" + actualEventName, responder);
    }

    return element;
  }

  function stopObserving(element, eventName, handler) {
    element = $(element);

    var registry = Element.retrieve(element, 'prototype_event_registry');
    if (!registry) return element;

    if (!eventName) {
      registry.each( function(pair) {
        var eventName = pair.key;
        stopObserving(element, eventName);
      });
      return element;
    }

    var responders = registry.get(eventName);
    if (!responders) return element;

    if (!handler) {
      responders.each(function(r) {
        stopObserving(element, eventName, r.handler);
      });
      return element;
    }

    var responder = responders.find( function(r) { return r.handler === handler; });
    if (!responder) return element;

    if (eventName.include(':')) {
      if (element.removeEventListener)
        element.removeEventListener("dataavailable", responder, false);
      else {
        element.detachEvent("ondataavailable", responder);
        element.detachEvent("onfilterchange",  responder);
      }
    } else {
      var actualEventName = _getDOMEventName(eventName);
      if (element.removeEventListener)
        element.removeEventListener(actualEventName, responder, false);
      else
        element.detachEvent('on' + actualEventName, responder);
    }

    registry.set(eventName, responders.without(responder));

    return element;
  }

  function fire(element, eventName, memo, bubble) {
    element = $(element);

    if (Object.isUndefined(bubble))
      bubble = true;

    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 = bubble ? 'ondataavailable' : 'onfilterchange';
    }

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

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

    return Event.extend(event);
  }

  Event.Handler = Class.create({
    initialize: function(element, eventName, selector, callback) {
      this.element   = $(element);
      this.eventName = eventName;
      this.selector  = selector;
      this.callback  = callback;
      this.handler   = this.handleEvent.bind(this);
    },

    start: function() {
      Event.observe(this.element, this.eventName, this.handler);
      return this;
    },

    stop: function() {
      Event.stopObserving(this.element, this.eventName, this.handler);
      return this;
    },

    handleEvent: function(event) {
      var element = event.findElement(this.selector);
      if (element) this.callback.call(this.element, event, element);
    }
  });

  function on(element, eventName, selector, callback) {
    element = $(element);
    if (Object.isFunction(selector) && Object.isUndefined(callback)) {
      callback = selector, selector = null;
    }

    return new Event.Handler(element, eventName, selector, callback).start();
  }

  Object.extend(Event, Event.Methods);

  Object.extend(Event, {
    fire:          fire,
    observe:       observe,
    stopObserving: stopObserving,
    on:            on
  });

  Element.addMethods({
    fire:          fire,

    observe:       observe,

    stopObserving: stopObserving,

    on:            on
  });

  Object.extend(document, {
    fire:          fire.methodize(),

    observe:       observe.methodize(),

    stopObserving: stopObserving.methodize(),

    on:            on.methodize(),

    loaded:        false
  });

  if (window.Event) Object.extend(window.Event, Event);
  else window.Event = Event;
})();

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

  var timer;

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

  function checkReadyState() {
    if (document.readyState === 'complete') {
      document.stopObserving('readystatechange', checkReadyState);
      fireContentLoadedEvent();
    }
  }

  function pollDoScroll() {
    try { document.documentElement.doScroll('left'); }
    catch(e) {
      timer = pollDoScroll.defer();
      return;
    }
    fireContentLoadedEvent();
  }

  if (document.addEventListener) {
    document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false);
  } else {
    document.observe('readystatechange', checkReadyState);
    if (window == top)
      timer = pollDoScroll.defer();
  }

  Event.observe(window, 'load', fireContentLoadedEvent);
})();

Element.addMethods();

/*------------------------------- 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);

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

(function() {
  window.Selector = Class.create({
    initialize: function(expression) {
      this.expression = expression.strip();
    },

    findElements: function(rootElement) {
      return Prototype.Selector.select(this.expression, rootElement);
    },

    match: function(element) {
      return Prototype.Selector.match(element, this.expression);
    },

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

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

  Object.extend(Selector, {
    matchElements: function(elements, expression) {
      var match = Prototype.Selector.match,
          results = [];

      for (var i = 0, length = elements.length; i < length; i++) {
        var element = elements[i];
        if (match(element, expression)) {
          results.push(Element.extend(element));
        }
      }
      return results;
    },

    findElement: function(elements, expression, index) {
      index = index || 0;
      var matchIndex = 0, element;
      for (var i = 0, length = elements.length; i < length; i++) {
        element = elements[i];
        if (Prototype.Selector.match(element, expression) && index === matchIndex++) {
          return Element.extend(element);
        }
      }
    },

    findChildElements: function(element, expressions) {
      var selector = expressions.toArray().join(', ');
      return Prototype.Selector.select(selector, element || document);
    }
  });
})();
/*--------------------------------------------------------------------------
 *  EJS - Embedded JavaScript, version 0.1.0
 *  Copyright (c) 2007 Edward Benson
 *  http://www.edwardbenson.com/projects/ejs
 *  ------------------------------------------------------------------------
 *
 *  EJS is freely distributable under the terms of an MIT-style license.
 *
 *  EJS is a client-side preprocessing engine written in and for JavaScript.
 *  If you have used PHP, ASP, JSP, or ERB then you get the idea: code embedded
 *  in <% // Code here %> tags will be executed, and code embedded in <%= .. %>
 *  tags will be evaluated and appended to the output.
 *
 *  This is essentially a direct JavaScript port of Masatoshi Seki's erb.rb
 *  from the Ruby Core, though it contains a subset of ERB's functionality.
 *
 *  Requirements:
 *      prototype.js
 *
 *  Usage:
 *      // source should be either a string or a DOM node whose innerHTML
 *      // contains EJB source.
 *    var source = "<% var ejb="EJB"; %><h1>Hello, <%= ejb %>!</h1>";
 *      var compiler = new EjsCompiler(source);
 *      compiler.compile();
 *      var output = eval(compiler.out);
 *      alert(output); // -> "<h1>Hello, EJB!</h1>"
 *
 *  For a demo:      see demo.html
 *  For the license: see license.txt
 *
 *--------------------------------------------------------------------------*/

/* Make a split function like Ruby's: "abc".split(/b/) -> ['a', 'b', 'c'] */
String.prototype.rsplit = function(regex) {
  var item = this;
  var result = regex.exec(item);
  var retArr = new Array();
  while (result != null)
  {
    var first_idx = result.index;
    var last_idx = regex.lastIndex;
    if ((first_idx) != 0)
    {
      var first_bit = item.substring(0,first_idx);
      retArr.push(item.substring(0,first_idx));
      item = item.slice(first_idx);
    }
    retArr.push(result[0]);
    item = item.slice(result[0].length);
    result = regex.exec(item);
  }
  if (! item == '')
  {
    retArr.push(item);
  }
  return retArr;
};

/* Chop is nice to have too */
String.prototype.chop = function() {
  return this.substr(0, this.length - 1);
}

/* Adaptation from the Scanner of erb.rb  */
var EjsScanner = function(source, left, right) {
  this.left_delimiter =   left +'%' //<%
  this.right_delimiter =  '%'+right //>
  this.double_left =    left+'%%'
  this.double_right =   '%%'+right
  this.left_equal =     left+'%='
  this.left_comment =   left+'%#'
  if(left=='[')
    this.SplitRegexp = /(\[%%)|(%%\])|(\[%=)|(\[%#)|(\[%)|(%\]\n)|(%\])|(\n)/;
  else
    this.SplitRegexp = new RegExp('('+this.double_left+')|(%%'+this.double_right+')|('+this.left_equal+')|('+this.left_comment+')|('+this.left_delimiter+')|('+this.right_delimiter+'\n)|('+this.right_delimiter+')|(\n)')

  this.source = source;
  this.stag = null;
  this.lines = 0;
};
EjsView = function(data){
  this.data = data
}
EjsView.prototype.partial = function(options, data){
  if(!data) data = this.data;
  return new EJS(options).render(data);
}

EjsScanner.to_text = function(input){
  if(input == null || input === undefined)
        return '';
    if(input instanceof Date)
    return input.toDateString();
  if(input.toString)
        return input.toString()
  return '';
}

EjsScanner.prototype = {

  /* For each line, scan! */
  scan: function(block) {
     scanline = this.scanline;
   regex = this.SplitRegexp;
   if (! this.source == '')
   {
     var source_split = this.source.rsplit(/\n/);
     for(var i=0; i<source_split.length; i++) {
       var item = source_split[i];
       this.scanline(item, regex, block);
     }
   }
  },

  /* For each token, block! */
  scanline: function(line, regex, block) {
   this.lines++
   var line_split = line.rsplit(regex);
   for(var i=0; i<line_split.length; i++) {
     var token = line_split[i];
       if (token != null) {
        try{
            block(token, this);
      }catch(e){
        throw {type: 'EjsScanner', line: this.lines}
      }
       }
   }
  }
};

/* Adaptation from the Buffer of erb.rb  */
var EjsBuffer = function(pre_cmd, post_cmd) {
  this.line = new Array();
  this.script = "";
  this.pre_cmd = pre_cmd;
  this.post_cmd = post_cmd;

  for (var i=0; i<this.pre_cmd.length; i++)
  {
    this.push(pre_cmd[i]);
  }
}
EjsBuffer.prototype = {

  push: function(cmd) {
  this.line.push(cmd);
  },

  cr: function() {
  this.script = this.script + this.line.join('; ');
  this.line = new Array();
  this.script = this.script + "\n";
  },

  close: function() {
  if (this.line.length > 0)
  {
    for (var i=0; i<this.post_cmd.length; i++)
    {
      this.push(pre_cmd[i]);
    }
    this.script = this.script + this.line.join('; ');
    line = null;
  }
  }

};

/* Adaptation from the Compiler of erb.rb  */
EjsCompiler = function(source, left) {
  this.pre_cmd = ['___ejsO = "";'];
  this.post_cmd = new Array();
  this.source = ' ';
  if (source != null)
  {
    if (typeof source == 'string')
    {
        source = source.replace(/\r\n/g, "\n");
            source = source.replace(/\r/g,   "\n");
      this.source = source;
    }
    else if (source.innerHTML)
    {
      this.source = source.innerHTML;
    }
    if (typeof this.source != 'string')
    {
      this.source = "";
    }
  }
  left = left || '<'
  var right = '>'
  switch(left) {
    case '[':
      right = ']'
      break;
    case '<':
      break;
    default:
      throw left+' is not a supported deliminator'
      break;
  }
  this.scanner = new EjsScanner(this.source, left, right);
  this.out = '';
}
EjsCompiler.prototype = {
  compile: function(options) {
    options = options || {};
  this.out = '';
  var put_cmd = "___ejsO += ";
  var insert_cmd = put_cmd;
  var buff = new EjsBuffer(this.pre_cmd, this.post_cmd);
  var content = '';
  var clean = function(content)
  {
      content = content.replace(/\\/g, '\\\\');
        content = content.replace(/\n/g, '\\n');
        content = content.replace(/"/g,  '\\"');
        return content;
  }
  this.scanner.scan(function(token, scanner) {
    if (scanner.stag == null)
    {
      //alert(token+'|'+(token == "\n"))
      switch(token) {
        case '\n':
          content = content + "\n";
          buff.push(put_cmd + '"' + clean(content) + '";');
          buff.cr()
          content = '';
          break;
        case scanner.left_delimiter:
        case scanner.left_equal:
        case scanner.left_comment:
          scanner.stag = token;
          if (content.length > 0)
          {
            // Chould be content.dump in Ruby

            buff.push(put_cmd + '"' + clean(content) + '"');
          }
          content = '';
          break;
        case scanner.double_left:
          content = content + scanner.left_delimiter;
          break;
        default:
          content = content + token;
          break;
      }
    }
    else {
      switch(token) {
        case scanner.right_delimiter:
          switch(scanner.stag) {
            case scanner.left_delimiter:
              if (content[content.length - 1] == '\n')
              {
                content = content.chop();
                buff.push(content);
                buff.cr();
              }
              else {
                buff.push(content);
              }
              break;
            case scanner.left_equal:
              buff.push(insert_cmd + "(EjsScanner.to_text(" + content + "))");
              break;
          }
          scanner.stag = null;
          content = '';
          break;
        case scanner.double_right:
          content = content + scanner.right_delimiter;
          break;
        default:
          content = content + token;
          break;
      }
    }
  });
  if (content.length > 0)
  {
    // Chould be content.dump in Ruby
    buff.push(put_cmd + '"' + clean(content) + '"');
  }
  buff.close();
  this.out = buff.script + ";";
  var to_be_evaled = 'this.process = function(_CONTEXT,_VIEW) { try { with(_VIEW) { with (_CONTEXT) {'+this.out+" return ___ejsO;}}}catch(e){e.lineNumber=null;throw e;}};";

  try{
    eval(to_be_evaled);
  }catch(e){
    if(typeof JSLINT != 'undefined'){
      JSLINT(this.out)
      for(var i = 0; i < JSLINT.errors.length; i++){
        var error = JSLINT.errors[i];
        if(error.reason != "Unnecessary semicolon."){
          error.line++;
          var e = new Error();
          e.lineNumber = error.line;
          e.message = error.reason;
          if(options.url)
            e.fileName = options.url;
          throw e;
        }
      }
    }else{
      throw e;
    }
  }
  }
}


//type, cache, folder
EJS = function( options ){
  this.set_options(options)

  if(options.url){
    var template = EJS.get(options.url, this.cache)
    if (template) return template;
      if (template == EJS.INVALID_PATH) return null;
    this.text = EJS.request(options.url)
    if(this.text == null){
      //EJS.update(options.url, this.INVALID_PATH);
      throw 'There is no template at '+options.url
    }
    this.name = options.url
  }else if(options.element)
  {
    if(typeof options.element == 'string'){
      var name = options.element
      options.element = document.getElementById(  options.element )
      if(options.element == null) throw name+'does not exist!'
    }
    if(options.element.value){
      this.text = options.element.value
    }else{
      this.text = options.element.innerHTML
    }
    this.name = options.element.id
    this.type = '['
  }
  var template = new EjsCompiler(this.text, this.type);

  template.compile(options);


  EJS.update(this.name, this);
  this.template = template
}
EJS.config = function(options){
  EJS.cache = options.cache != null ? options.cache : EJS.cache
  EJS.type = options.type != null ? options.type : EJS.type
  var templates_directory = {} //nice and private container

  EJS.get = function(path, cache){
    if(cache == false) return null;
    if(templates_directory[path]) return templates_directory[path];
      return null;
  }

  EJS.update = function(path, template) {
    if(path == null) return;
    templates_directory[path] = template
  }

  EJS.INVALID_PATH =  -1;


}
EJS.config( {cache: true, type: '<' } )

EJS.prototype = {
  render : function(object){
    var v = new EjsView(object);
    return this.template.process.call(v, object,v);
  },
  out : function(){
    return this.template.out
  },
  set_options : function(options){
    this.type = options.type != null ? options.type : EJS.type
    this.cache = options.cache != null ? options.cache : EJS.cache
    this.text = options.text != null ? options.text : null
    this.name = options.name != null ? options.name : null
  },
  // called without options, returns a function that takes the object
  // called with options being a string, uses that as a url
  // called with options as an object
  update : function(element, options){
    if(typeof element == 'string'){
      element = document.getElementById(element)
    }
    if(options == null){
      _template = this;
      return function(object){
        EJS.prototype.update.call(_template, element, object)
      }
    }
    if(typeof options == 'string'){
      params = {}
      params.url = options
      _template = this;
      params.onComplete = function(request){
        var object = eval( request.responseText )
        EJS.prototype.update.call(_template, element, object)
      }
      EJS.ajax_request(params)
    }else
    {
      element.innerHTML = this.render(options)
    }
  }
}

  EJS.newRequest = function(){
     var factories = [function() { return new ActiveXObject("Msxml2.XMLHTTP"); },function() { return new XMLHttpRequest(); },function() { return new ActiveXObject("Microsoft.XMLHTTP"); }];
     for(var i = 0; i < factories.length; i++) {
          try {
              var request = factories[i]();
              if (request != null)  return request;
          }
          catch(e) { continue;}
     }
  }

  EJS.request = function(path){
     var request = new EJS.newRequest()
     request.open("GET", path, false);

     try{request.send(null);}
     catch(e){return null;}

     if ( request.status == 404 || request.status == 2 ||(request.status == 0 && request.responseText == '') ) return null;

     return request.responseText
  }
  EJS.ajax_request = function(params){
    params.method = ( params.method ? params.method : 'GET')

    var request = new EJS.newRequest();
    request.onreadystatechange = function(){
      if(request.readyState == 4){
        if(request.status == 200){
          params.onComplete(request)
        }else
        {
          params.onComplete(request)
        }
      }
    }
    request.open(params.method, params.url)
    request.send(null)
  }
//}


EjsView.prototype.date_tag = function(name, value , html_options) {
    if(! (value instanceof Date))
    value = new Date()

  var month_names = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
  var years = [], months = [], days =[];
  var year = value.getFullYear();
  var month = value.getMonth();
  var day = value.getDate();
  for(var y = year - 15; y < year+15 ; y++)
  {
    years.push({value: y, text: y})
  }
  for(var m = 0; m < 12; m++)
  {
    months.push({value: (m), text: month_names[m]})
  }
  for(var d = 0; d < 31; d++)
  {
    days.push({value: (d+1), text: (d+1)})
  }
  var year_select = this.select_tag(name+'[year]', year, years, {id: name+'[year]'} )
  var month_select = this.select_tag(name+'[month]', month, months, {id: name+'[month]'})
  var day_select = this.select_tag(name+'[day]', day, days, {id: name+'[day]'})

    return year_select+month_select+day_select;
}

EjsView.prototype.form_tag = function(action, html_options) {


    html_options     = html_options                     || {};
  html_options.action = action
    if(html_options.multipart == true) {
        html_options.method = 'post';
        html_options.enctype = 'multipart/form-data';
    }

    return this.start_tag_for('form', html_options)
}

EjsView.prototype.form_tag_end = function() { return this.tag_end('form'); }

EjsView.prototype.hidden_field_tag   = function(name, value, html_options) {
    return this.input_field_tag(name, value, 'hidden', html_options);
}

EjsView.prototype.input_field_tag = function(name, value , inputType, html_options) {

    html_options = html_options || {};
    html_options.id  = html_options.id  || name;
    html_options.value = value || '';
    html_options.type = inputType || 'text';
    html_options.name = name;

    return this.single_tag_for('input', html_options)
}

EjsView.prototype.is_current_page = function(url) {
  return (window.location.href == url || window.location.pathname == url ? true : false);
}

EjsView.prototype.link_to = function(name, url, html_options) {
    if(!name) var name = 'null';
    if(!html_options) var html_options = {}

  if(html_options.confirm){
    html_options.onclick =
    " var ret_confirm = confirm(\""+html_options.confirm+"\"); if(!ret_confirm){ return false;} "
    html_options.confirm = null;
  }
    html_options.href=url
    return this.start_tag_for('a', html_options)+name+ this.tag_end('a');
}

EjsView.prototype.submit_link_to = function(name, url, html_options){
  if(!name) var name = 'null';
    if(!html_options) var html_options = {}
    html_options.onclick = html_options.onclick  || '' ;

  if(html_options.confirm){
    html_options.onclick =
    " var ret_confirm = confirm(\""+html_options.confirm+"\"); if(!ret_confirm){ return false;} "
    html_options.confirm = null;
  }

    html_options.value = name;
  html_options.type = 'submit'
    html_options.onclick=html_options.onclick+
    (url ? this.url_for(url) : '')+'return false;';
    //html_options.href='#'+(options ? Routes.url_for(options) : '')
  return this.start_tag_for('input', html_options)
}

EjsView.prototype.link_to_if = function(condition, name, url, html_options, post, block) {
  return this.link_to_unless((condition == false), name, url, html_options, post, block);
}

EjsView.prototype.link_to_unless = function(condition, name, url, html_options, block) {
  html_options = html_options || {};
  if(condition) {
    if(block && typeof block == 'function') {
      return block(name, url, html_options, block);
    } else {
      return name;
    }
  } else
    return this.link_to(name, url, html_options);
}

EjsView.prototype.link_to_unless_current = function(name, url, html_options, block) {
  html_options = html_options || {};
  return this.link_to_unless(this.is_current_page(url), name, url, html_options, block)
}


EjsView.prototype.password_field_tag = function(name, value, html_options) { return this.input_field_tag(name, value, 'password', html_options); }

EjsView.prototype.select_tag = function(name, value, choices, html_options) {
    html_options = html_options || {};
    html_options.id  = html_options.id  || name;
    html_options.value = value;
  html_options.name = name;

    var txt = ''
    txt += this.start_tag_for('select', html_options)

    for(var i = 0; i < choices.length; i++)
    {
        var choice = choices[i];
        var optionOptions = {value: choice.value}
        if(choice.value == value)
            optionOptions.selected ='selected'
        txt += this.start_tag_for('option', optionOptions )+choice.text+this.tag_end('option')
    }
    txt += this.tag_end('select');
    return txt;
}

EjsView.prototype.single_tag_for = function(tag, html_options) { return this.tag(tag, html_options, '/>');}

EjsView.prototype.start_tag_for = function(tag, html_options)  { return this.tag(tag, html_options); }

EjsView.prototype.submit_tag = function(name, html_options) {
    html_options = html_options || {};
    //html_options.name  = html_options.id  || 'commit';
    html_options.type = html_options.type  || 'submit';
    html_options.value = name || 'Submit';
    return this.single_tag_for('input', html_options);
}

EjsView.prototype.tag = function(tag, html_options, end) {
    if(!end) var end = '>'
    var txt = ' '
    for(var attr in html_options) {
     if(html_options[attr] != null)
        var value = html_options[attr].toString();
       else
        var value=''
       if(attr == "Class") // special case because "class" is a reserved word in IE
        attr = "class";
       if( value.indexOf("'") != -1 )
            txt += attr+'=\"'+value+'\" '
       else
            txt += attr+"='"+value+"' "
    }
    return '<'+tag+txt+end;
}

EjsView.prototype.tag_end = function(tag)             { return '</'+tag+'>'; }

EjsView.prototype.text_area_tag = function(name, value, html_options) {
    html_options = html_options || {};
    html_options.id  = html_options.id  || name;
    html_options.name  = html_options.name  || name;
  value = value || ''
    if(html_options.size) {
        html_options.cols = html_options.size.split('x')[0]
        html_options.rows = html_options.size.split('x')[1]
        delete html_options.size
    }

    html_options.cols = html_options.cols  || 50;
    html_options.rows = html_options.rows  || 4;

    return  this.start_tag_for('textarea', html_options)+value+this.tag_end('textarea')
}
EjsView.prototype.text_tag = EjsView.prototype.text_area_tag

EjsView.prototype.text_field_tag     = function(name, value, html_options) { return this.input_field_tag(name, value, 'text', html_options); }

EjsView.prototype.url_for = function(url) {
        return 'window.location="'+url+'";'
}
EjsView.prototype.img_tag = function(image_location, alt, options){
  options = options || {};
  options.src = image_location
  options.alt = alt
  return this.single_tag_for('img', options)
}
var Comm = new (function() {

  var revID = 0;
  var status = 0; // 0: not started, 1: polling, 2: nonpolling

  this.start = function(r) {
    if (status != 0) { return; }
    revID = r;
    $$('body').first().insert('<div id="socket"></div>');
    swfobject.embedSWF("/media/socket.swf", "socket", "1", "1", "9.0.0", "", {'scope': 'Comm.Socket'}, {'bgcolor': '000000'});
    // TODO: maybe delay the lines below to give a chance for the socket to connect.
    if (status == 0) { // Status will have changed if the socket has connected already somehow.
      this.switchToPolling();
    }
  };

  this.stop = function() {
    if (status == 1) {
      this.stopPollers();
    } else if (status == 2) {
      this.Socket.close();
    }
    status = 0;
  };

  // == Enqueue Wrappers and Helpers ==
  this.sendGame = function(action, params) {
    if (Object.isUndefined(params)) { params = {}; }
    this.enqueue("engine", "update_game_state", [Game.gid, Object.extend(params, {"action": action})]);
  };

  this.moveCard = function(card, fromName, toName) {
    this.sendGame("move", {"card": card, "from_name": fromName, "to_name": toName});
  };

  this.status = function() {
    return status;
  };

  // == Request and Reply Processing ==
  var queue = [];
  var lastRequest;
  this.enqueue = function(service, callName, args) { // TODO: rename enqueue() to send()
    if (status == 0) { return; }
    if (status == 1) {
      Logger.debug(Comm, "Enqueuing via web: " + Object.toJSON([service, callName, args]));
      queue.push([service, callName, args]);
      delayToNextCall = 500;
    } else if (status == 2) {
      Logger.debug(Comm, "Enqueuing via mux: " + Object.toJSON([service, callName, args]));
      this.Socket.send(Object.toJSON([service, callName, args]));
    }
  };

  var sendPollingRequest = function() {
    if (status != 1) { return; }
    if (lastRequest && !lastRequest['done']) { return; }
    var params = {
      'rev_id': revID,
      'ie': (new Date()).getTime(),
      'num': queue.length
    };
    lastRequest = {'num': queue.length, 'time': (new Date()).getTime()};
    $R(0, queue.length - 1).each(function(i) {
      params['call' + i] = Object.toJSON(queue.shift())
    });
    lastRequest['obj'] = new Ajax.Request("/" + G.lang() + "/services/run", {
      method: 'post',
      parameters: params,
      on401: this.stopPollers.bind(this),
      onComplete: function(response) {
        lastRequest['done'] = true;
        if (response && response.status == 200) {
          var params = response.responseText;
          if (params) { handleResponse(params); }
        }
      }.bind(this)
    });
  }.bind(this);

  var handleResponse = function(txt) {
    try {
      params = txt.evalJSON();
    } catch(e) {
      // Logger.error("Had trouble parsing response JSON.");
    }
    params.each(function(p) {
      Logger.debug(Comm, p);
      try {
        p = p.evalJSON();
      } catch(e) {
        // Logger.error("Had trouble parsing message JSON.");
      }
      var cbn = p.shift();
      if (callbacks[cbn]) { callbacks[cbn].apply(this, p); }
    }, this);
  }.bind(this);

  // == Callbacks ==
  var callbacks = {};
  this.registerCallback = function(n, f) {
    callbacks[n] = f;
  };

  this.registerCallback('updateRevID', function(r) {
    revID = r;
  });

  this.registerCallback('error', function(gid, e, msg) { // TODO: not all errors should show to users. Also, maybe chain callbacks for this callback only to allow code in multiple places.
    switch (e) {
      case "JoinError":
        if (typeof Game != "undefined" && gid != Game.gid) { return false; }
        window.location = "/" + G.lang() + "/games/" + msg + "/hands?_message=BAh7BjoLbm90aWNlIilZb3UgY2FuIG9ubHkgam9pbiBvbmUgZ2FtZSBhdCBh%0AIHRpbWU%3D%0A";
        break;
      default:
        if (gid != 0 && typeof Game != "undefined" && gid != Game.gid) { return false; }
        if (typeof Game == "undefined") { // Remove loading images
          $$("img.joinLoading").invoke('remove');
        }
        alert(G._(msg));
        if (typeof Game != "undefined") {
          Game.revertIllegalMove(); // TODO: this is not always applicable, make something smarter
        }
        break;
    }
  });

  // == Polling vs. NonPolling Management ==
  // The transition to non-polling is only complete when the socket is connected
  var delayToNextCall = 1000; // How much time is between the beginning of a call to sendPollingRequest() and the next call, assuming nothing gets late. This value can be changed dynamically.
  this.stayInPolling = false; // When false, switch to non-polling whenever there is a chance.
  var updatePoller, connectPoller;

  this.stopPollers = function() {
    if (updatePoller) { updatePoller.stop(); }
    if (connectPoller) { connectPoller.stop(); }
  };

  var runPoller = function() { // Calls this.sendPollingRequest(), with a dynamic timing. Cancels requests that contain no calls if they take too long.
    var now = (new Date()).getTime();
    // Decide whether or not to send a polling request
    var doRun = false;
    if (Object.isUndefined(lastRequest)) {
      doRun = true;
    } else {
      if (lastRequest['done']) {
        if (now - lastRequest['time'] > delayToNextCall) {
          doRun = true;
          if (delayToNextCall > 3000) { delayToNextCall = 3000; } // Successfull call, revert delay to default;
        }
      } else {
        if (lastRequest['num'] == 0) {
          if (now - lastRequest['time'] > delayToNextCall + 1000) {
            doRun = true;
          }
        } else {
          // TODO: if it goes really late, show an LB to the user that there is something wrong with a countdown to refresh the page, and hide the LB if everything goes on ok.
        }
      }
    }
    if (!doRun) { return; }
    // Kill any ongoing request
    if (lastRequest && !lastRequest['done'] && lastRequest['obj'] && lastRequest['obj'].transport.readyState == 1) {
      lastRequest['obj'].transport.abort();
      lastRequest['obj'].transport.onreadystatechange = Prototype.emptyFunction; // IE memory leak
      lastRequest['done'] = true;
      delayToNextCall += 1000;
    }
    // Do the request
    sendPollingRequest();
    // Change delayToNextCall as suitable
    if (delayToNextCall == 500) {
      delayToNextCall = 1000;
    } else if (delayToNextCall < 3000) {
      delayToNextCall = 3000;
    } else if (delayToNextCall > 6000) {
      delayToNextCall = 6000;
    }
  }.bind(this);

  this.switchToPolling = function(remain) {
    if (status == 1) { return; }
    Logger.debug(Comm, "Switching to polling.");
    this.Socket.close();
    this.stopPollers();
    updatePoller = new PeriodicalExecuter(function() { runPoller(); }, 0.5);
    sendPollingRequest.defer(); // Do a first poll to avoid any wait.
    if (remain) {
      this.stayInPolling = true;
    } else {
      connectPoller = new PeriodicalExecuter(function() {
        this.switchToNonPolling();
      }.bind(this), 30);
    }
    status = 1;
  };

  this.switchToNonPolling = function() {
    if (status == 2) { return; }
    if (this.stayInPolling) { return; }
    Logger.debug(Comm, "Switching to non-polling.");
    this.Socket.connect();
  };

  var finishedSwitchToNonPolling = function() {
    this.stopPollers();
    status = 2;
    // Send anything that was pending in the queue.
    $R(0, queue.length - 1).each(function(i) {
      this.enqueue.apply(this, queue.shift());
    }, this);
    Logger.debug(Comm, "Finished switching to non-polling.");
  }.bind(this);

  // == Socket ==
  var parent = this;
  this.Socket = new (function() {

    var socket;
    var socketState = 0; // 0: off, 1: loaded, 2: connected
    var buffer = "";

    this.loaded = function() {
      socket = $("socket");
      socketState = 1;
      Logger.debug(Comm, "Socket is loaded.");
      if (!parent.stayInPolling) { this.connect(); }
    };

    this.connect = function() {
      if (socketState == 1 && !parent.stayInPolling) {
        Logger.debug(Comm, "Socket will try to connect.");
        socket.connect(window.location.hostname, 4167);
      }
    };

    this.close = function() { // Notice close() doesn't automtically switch to polling
      if (socketState == 2) {
        Logger.debug(Comm, "Socket is being closed.");
        socketState = 1;
        socket.close();
      }
    };

    this.connected = function() {
      Logger.debug(Comm, "Socket is connected.");
      socketState = 2;
      if (parent.stayInPolling) {
        this.close();
      } else {
        buffer = "";
        this.send(Object.toJSON(["self", "setrevid", [revID]]));
        this.send(Object.toJSON(["self", "authenticate", [User.info['id'] + " " + document.cookie.match(/_session_id=([^;]+)/)[1]]]));
        finishedSwitchToNonPolling();
      }
    };

    this.ioError = function() { 
      if (socketState == 2) {
        parent.switchToPolling();
      }
    };

    this.securityError = function() {
      if (socketState == 2) {
        parent.switchToPolling();
      }
    };

    this.disconnected = function() {
      if (socketState == 2) {
        parent.switchToPolling();
      }
    };

    this.send = function(txt) {
      if (socketState == 2) {
        try {
          socket.write(txt + "\r\n");
        } catch(e) {
          Logger.error("Problems in sending via socket.");
          parent.switchToPolling();
        }
      }
    };

    this.receive = function(txt) {
      buffer += unescape(txt);
      while (buffer.indexOf("\r\n") != -1) {
        var line = buffer.slice(0, buffer.indexOf("\r\n"));
        buffer = buffer.slice(buffer.indexOf("\r\n") + 2);
        handleResponse(line);
      }
    };


  })();

})();
var GameList = new (function() {

  var gameIDs;
  var gameStates;

  // TODO: clean up the mess of filters
  this.filters = $H({'relationship': $H({}), 'timer': $H({}), 'rank': $H({})}); // Functions taking gs
  var filterByName = "";

  var firstTime = true;

  this.render = function() {
    $$("#newGamesStatus, #gamesInProgressStatus").invoke('update', '');
    $$(".gameItem").invoke('remove');
    // Tooltips.unloadAll(); // TODO: can't call unloadAll cause it will also affect miniProfile

    $H(gameIDs).keys().each(function(k) {
      var group = gameIDs[k].flatten();
      if (group.length == 0) {
        $(k + "Status").update(this.getEmptyMessage(k));
      } else {
        var div = $(k);
        group.reverse().each(function(id) {
          try {
            var allFiltersApply = this.filters.values().map(function(h) { 
              if (h.values().length == 0) {
                return true;
              } else {
                return h.values().map(function(f) { return f(gameStates[id]); }).any();
              }
            }).all();
            if (allFiltersApply &&
                (filterByName ? gameStates[id].game_name.match(new RegExp(filterByName, 'i')) : true)) {
              div.insert({'after': this.template.render(gameStates[id])});
              $$("#joinButton" + id).invoke('observe', 'click', function(id, ev) {
                Comm.enqueue("engine", "join_game", [id]);
                $("joinButton" + id).insert("<img src='/images/small-loading.gif' class='joinLoading' />");
                ev.stop();
              }.curry(id));
            }
          } catch(e) {
            if (typeof console != "undefined" && typeof console.log == "function") { console.log(e); }
          }
        }, this);
      }
    }, this);

    Tooltips.loadAll("li.tooltip");
    $$(".newGame").invoke('stopObserving').invoke('observe', 'click', function(ev) { newGame(); ev.stop(); });
  };

  this.helper = function(c) {
    return eval(c);
  };


  Comm.registerCallback('updateGameList', function(resp) {
    if (typeof Game != "undefined") { return; }

    gameIDs = {};
    gameIDs['newGames'] = [];
    gameIDs['gamesInProgress'] = [[], [], []] ;

    gameStates = resp.inject({}, function(h, gs) {
      h[gs.id] = gs;
      gs.players = gs.players;
      if (gs.current_player && gs.players.include(gs.current_player)) {
        gameIDs['gamesInProgress'][0].push(gs.id);
      } else if (gs.state == 'new_game') {
        gameIDs['newGames'].push(gs.id);
      } else if (gs.num_players > gs.players.compact().length) {
        gameIDs['gamesInProgress'][1].push(gs.id);
      } else {
        gameIDs['gamesInProgress'][2].push(gs.id);
      }
      return h;
    });

    this.render();
  }.bind(this));

  Comm.registerCallback('redirectToGame', function(gid) {
    if (typeof Game != "undefined" && gid != Game.gid) { return; }
    window.location = "/" + G.lang() + "/games/" + gid + "/hands";
  });

  this.getEmptyMessage = function(category) {
    var filler = { "newGames":        G._('There are no new games'),
                   "gamesInProgress": G._('There are no games in progress') };
    return filler[category] + ", <a href='#' class='newGame'>" + G._('create a new one') + "</a>.";
  };

  this.setupFilters = function() {
    if (User.hasFeature('creation')) {
      $('filterStar').observe('click', function(ev) {
        ev.stop();
        if (!this.filters.get('relationship').get('star')) {
          this.filters.get('relationship').set('star', function(gs) {
            return CoPlayers.anyIsStar(gs.players);
          });
          this.render();
          $("filterBy").insert({'after': '<li class="filterIcon"><img src="/images/transparent-20-20.gif" class="fullstar sprite" /></li>'});
          $$(".removeFilters").invoke('show');
        }
      }.bind(this));
      $$('.filterTimer').invoke('observe', 'click', function(ev) {
        ev.stop();
        var timer = parseInt(ev.findElement('.filterTimer').readAttribute('rel'), 10);
        if (!this.filters.get('timer').get('timer' + timer)) {
          this.filters.get('timer').set('timer' + timer, function(gs) {
            return gs.game_options.play_timeout == timer;
          });
          this.render();
          $("filterBy").insert({'after': "<li class='filterIcon sprite clock bold'>" + timer + "</li>"});
          $$(".removeFilters").invoke('show');
        }
      }.bind(this));
      $$('.filterRank').invoke('observe', 'click', function(ev) {
        ev.stop();
        var rank = parseInt(ev.findElement('.filterRank').readAttribute('rel'), 10);
        if (!this.filters.get('rank').get('rank' + rank)) {
          this.filters.get('rank').set('rank' + rank, function(gs) {
            return gs.by_rank && gs.by_rank == rank;
          });
          this.render();
          $("filterBy").insert({'after': "<li class='filterIcon'><img src='/images/rank-small-" + rank + ".png' /></li>"});
          $$(".removeFilters").invoke('show');
        }
      }.bind(this));
      $$("a.removeFilters").invoke('observe', 'click', function(ev) {
        ev.stop();
        $$(".filterIcon").invoke('remove');
        $$(".removeFilters").invoke('hide');
        this.filters.get('relationship').keys().each(function(k) { this.filters.get('relationship').unset(k); }, this);
        this.filters.get('timer').keys().each(function(k) { this.filters.get('timer').unset(k); }, this);
        this.filters.get('rank').keys().each(function(k) { this.filters.get('rank').unset(k); }, this);
        this.render();
      }.bind(this));
    }
    new Form.Element.Observer(
      'filterByName', 0.5,
      function(el, val) {
        filterByName = val;
        GameList.render();
      }
    );
  };

})();

newGameForm = new EJS({'text': " \
  <table class='forms'> \
    <% if (!isFB) { %> \
      <tr> \
        <td> \
          <label class='bold' for='newGameModule'><%= G._('Game') %></label> \
        </td> \
        <td> \
          <select name='newGameModule' id='newGameModule'> \
            <% GameList.gameModules.each(function(gm) { %> \
              <option value='<%= gm.gsub(' ', '') %>'<%= gm.gsub(' ', '').underscore() == GameList.gameModule ? ' selected=\"selected\"': '' %>> \
                <%= G._(gm) %> \
              </option> \
            <% }); %> \
          </select> \
        </td> \
      </tr> \
    <% } %> \
    <tr> \
      <td> \
        <label class='bold' for='newGameTimeout'> \
          <%= G._('Timer') %>(<%= G._('seconds') %>) \
        </label> \
      </td> \
      <td> \
        <select name='newGameTimeout' id='newGameTimeout'> \
          <% [8, 10, 15, 20, 30, 45, 60].each(function(v) { %> \
            <option value='<%= v %>'<%= v == 15 ? ' selected=\"selected\"' : '' %>><%= v %></option> \
          <% }); %> \
        </select> \
      </td> \
    </tr> \
    <% if (!isFB) { %> \
      <% if (!User.hasFeature('creation')) { %> \
        <tr> \
          <td colspan='2'> \
            <div class='comingsoon'> \
              <%= G._('The options below are part of Jawaker Basha. Create a private game for your friends or limit the players who join by rank. If you are interested') %> \
              <a href='/basha'><%= G._('learn more') %></a> \
            </div> \
          </td> \
        </tr> \
      <% } %> \
      <tr> \
        <td> \
          <label class='bold<%= User.hasFeature('creation') ? '' : ' grey' %>'><%= G._('Private game?') %></label> \
        </td> \
        <td> \
          <input class='checkbox' id='passwordProtected' type='checkbox'<%= User.hasFeature('creation') ? '' : ' disabled=\"disabled\"' %> /> \
        </td> \
      </tr> \
      <tr> \
        <td> \
          <label class='bold<%= User.hasFeature('creation') ? '' : ' grey' %>' for='newGamePassword'><%= G._('Password') %></label> \
        </td> \
        <td> \
          <input type='text' name='newGamePassword' id='newGamePassword' maxlength='10' disabled='disabled' /> \
        </td> \
      </tr> \
      <tr> \
        <td> \
          <label class='bold<%= User.hasFeature('creation') ? '' : ' grey' %>' for='newGameRank'><%= G._('Minimum rank') %></label> \
        </td> \
        <td> \
          <select name='newGameRank' id='newGameRank'<%= User.hasFeature('creation') ? '' : ' disabled=\"disabled\"' %>> \
            <% $R(0, 12).each(function(v) { %> \
              <option value='<%= v %>'<%= v == 0 ? ' selected=\"selected\"' : '' %>> \
                <%= v == 0 ? G._('Any') : (v <= 8 ? (v + 2) : {9: 'J', 10: 'Q', 11: 'K', 12: 'A'}[v]) %> \
              </option> \
            <% }); %> \
          </select> \
        </td> \
      </tr> \
    <% } %> \
  </table> \
  <div id='createGameContainer'> \
    <a id='createGame' href='#'><%= G._('Create Game') %></a> \
  </div> \
"});

newGameFormForGuests = new EJS({'text': " \
  <%= G._('You cannot create games without being logged in.') %><br/> \
  <a href='/<%= G.lang() %>/users/sign_in'><%= G._('Login') %></a> \
  <a href='/<%= G.lang() %>/users/sign_up'><%= G._('Sign up') %></a> \
"});

function newGame() {
  if (User.info['id']) {
    lightbox.show(newGameForm, {
      afterHook: function() {
        $$("#passwordProtected").invoke('observe', 'change', function() {
          $("newGamePassword").disabled = !$("passwordProtected").checked;
          $("newGamePassword").value = "";
        });
        $("createGame").observe('click', function(ev) {
          if (isFB) {
            var gameModule = GameList.gameModule.dasherize().capitalize().camelize();
            Comm.enqueue('engine', 'create_game_state', [0, gameModule, Object.toJSON({'game_options': {'play_timeout': $F('newGameTimeout')} })]);
          } else {
            var gameModule = $F("newGameModule");
            Comm.enqueue('engine', 'create_game_state', [0, gameModule, Object.toJSON({'game_options': {'play_timeout': $F('newGameTimeout')}, 'password': $F('newGamePassword'), 'by_rank': $F('newGameRank') })]);
          }
          $("createGameContainer").update("<img src='/images/small-loading.gif' />");
          ev.stop();
        });
      }
    });
  } else {
    lightbox.show(newGameFormForGuests, {});
  }
}
eventBox = new EJS({text: " \
  <h3><%= G._('Become a Fan of') %> <%= tName %></h3> \
  <img src='/images/events/<%= teamURL %>' class='teamLBFlag'> \
  <div class='joinTeamInfo'> \
		<ul class='discbullets'> \
      <li><%= G._('Support your favorite team by scoring goals for them! every jawaker game you win will score goals for your team!') %></li> \
      <li><%= G._('You can increase the goals you score for your team with each win by increasing your multiplier') %></li> \
      <li><h4 class='bold'><%= G._('If you become the top scorer for your team and your team wins on Jawaker when the time is up, you will get') %> \
        <span class='underline'><%= G._('Two Weeks Basha subscription') %></span><img src='/images/tarboosh.png' class='teamLBSmallImg'>.</h4> \
      </li> \
      <li><%= G._('By becoming a fan you will receive the') %> \
        <span class='bold underline'><%= G._('world cup') %></span> <img src='/images/goods/world_cup-0.png' class='teamLBSmallImg'> \
        <%= G._('on the Jawaker Store') %>.</li> \
      <li><%= G._('** Only public games in which you play from beginning to end and do not change your seat will be counted as goals.') %></li> \
    </ul> \
		<div class='joinTeamAction'> \
			<p> \
				<%= G._('I would like to score') %> \
				<select id='selectMul' style='width: 50px'> \
          <% $R(nextMul,30).each(function(x) { %> \
            <option><%= x %></option> \
          <% }); %> \
				</select> \
				<%= G._('goals for') %> \
        <%= tName %> \
        <%= G._('each time I win a game on Jawaker') %> \
        <%= G._('in return for') %> \
				<b id='eventTokens'><%= numTokens * (nextMul * nextMul - currMul * currMul) %></b> \
				<%= G._('tokens') %><img src='/images/token.png'>.\
			</p> \
		</div> \
		<a href='#' id='becomeAFan'><%= G._('Become a Fan') %></a> \
		<br/> \
    <p> \
			<% if (!Object.isUndefined(User.info.id)) { %> \
	     <%= G._('You currently have') + ' ' + User.info['game_tokens'] + ' ' + G._('Tokens') %><img src='/images/token.png'>. \
	     <a href='/<%= G.lang() %>/buy/tokens' id='buyToken'><%= G._('Buy More Tokens') %></a> \
	    <% } %> \
		</p> \
  </div> \
"});

var showEventBox = function(eid, eteam, eurl, etokens, teamName, curr_mul) {
  lightbox.show(eventBox, {klass: 'joinTeamLB', afterHook: function() { 
    var updateTokens = function(numGoals) {
      if (numGoals > (curr_mul || 0)) {
        $('eventTokens').update(etokens * numGoals * numGoals - etokens * (curr_mul || 0) * (curr_mul || 0) );
      }
    }
    $('selectMul').observe('change', function(ev) { updateTokens(parseInt(this.value, 10)); ev.stop(); });
    $('becomeAFan').observe('click', function(ev) {
      window.location = '/' + G.lang() + Object.isUndefined(User.info.id) ? '/users/sign_in/' : '/jawaker_events/join/' + eid + '?team_num=' + eteam + '&mul=' + parseInt($('selectMul').value, 10);
      ev.stop();
    });
  }}, {eventId: eid, teamId: eteam, teamURL: eurl, numTokens: etokens, nextMul: (curr_mul || 0) + 1, currMul: curr_mul || 0, tName: (G.lang() == 'en' ? teamName[0] : teamName[1]) }); 
}
/******BEGIN LICENSE BLOCK*******
* 
* Common Public Attribution License Version 1.0.
*
* The contents of this file are subject to the Common Public Attribution 
* License Version 1.0 (the "License") you may not use this file except in 
* compliance with the License. You may obtain a copy of the License at
* http://developers.facebook.com/fbopen/cpal.html. The License is based 
* on the Mozilla Public License Version 1.1 but Sections 14 and 15 have 
* been added to cover use of software over a computer network and provide 
* for limited attribution for the Original Developer. In addition, Exhibit A 
* has been modified to be consistent with Exhibit B.
* Software distributed under the License is distributed on an "AS IS" basis, 
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 
* for the specific language governing rights and limitations under the License.
* The Original Code is Facebook Open Platform.
* The Original Developer is the Initial Developer.
* The Initial Developer of the Original Code is Facebook, Inc.  All portions 
* of the code written by Facebook, Inc are 
* Copyright 2006-2008 Facebook, Inc. All Rights Reserved.
*
*
********END LICENSE BLOCK*********/

/**
 *  @provides animation
 */

/**
 *
 *  A lovely library which animates random stuff. The most basic functions you need to
 *  know are .to(), .go(), and .duration(). To animate an element from its current width
 *  and height to 100px \ 100px over 1 second, you'd do something like this:
 *  animation(obj).to('width', 100).to('height', 100).duration(1000).go();
 *
 *  Getting into the slightly more advanced functions, you'll find .from(), .by(),
 *  .show(), and .hide(). To animate width from 100px to 200px, increase height by
 *  100, and hide at the end of the animation (an awfully awkward animation), you'd do
 *  this:
 *  animation(obj).from('width', 100).to('width', 200).by('height', 100).hide().go();
 *
 *  And the functions with the steepest (but still pretty easy) learning curve are .blind()
 *  and .checkpoint(). blind will essentially create another element inside your element
 *  with an explicity set width and height (which is "intelligently" set). This is useful
 *  when you're animating width either from or to 0px and you want to avoid the text
 *  continually wrapping to fit its parent. You might have to see it in action to
 *  understand. checkpoint will checkpoint the animation you've built and start a new
 *  one after the current one is done.
 *  So... to take a div and shrink it up into nothingness over 1 second, and then reopen
 *  to an explicit width and height of 400px, try this:
 *  animation(obj).to('width', 0).to('height', 0).blind().duration(1000).checkpoint()
 *                .to('width', 400).to('height', 400).blind().duration(1000).go();
 *
 *  checkpoint can also take a parameter between 0 and 1 for distance. A distance of
 *  0.5 means your next animation will actually start halfway through the first animation.
 *
 *  You can also apply an easing function to your animation which essentially just affects
 *  the rate at which your animation occurs. For instance if you want an animation to start
 *  slowly and speed up at the end, try animation(this).ease(animation.ease.both)
 *
 */

//
// Animation. It animates things.
function animation(obj) {
  // Sanity check that request is valid
  if (obj == undefined) {
    Logger.error("Creating animation on non-existant object");
    return;
  }
  if (this == window) {
    return new animation(obj);
  } else {
    this.obj = obj;
    this._reset_state();
    this.queue = [];
    this.last_attr = null;
  }
}
animation.resolution = 20;
animation.offset = 0;

(function() {

  // ===== START OF DEPENDENCIES =====
  // Also notice we are using our Logger instead of their Util
  var get_style = function(element, property) {
    element = $(element);

    function hyphenate(property) {
      // Convert to hyphenated property
      return property.replace(/[A-Z]/g, function(match) {
        return '-' + match.toLowerCase();
      });
    }

    // Preferred W3C method
    if (window.getComputedStyle) {
      return window.getComputedStyle(element, null).getPropertyValue(hyphenate(property));
    }

    // Safari
    if (document.defaultView && document.defaultView.getComputedStyle) {
      var computedStyle = document.defaultView.getComputedStyle(element, null);
      // Safari returns null from computed style if the display of the element is none.
      // This is a bug in Safari. If object's display is none here, we just return
      // "none" if the user is asking for the "display" property, or we error otherwise.
      // It's probably possible to implement this correctly, but there are many details
      // you need to get right. See http://dev.mootools.net/ticket/51
      if (computedStyle)
        return computedStyle.getPropertyValue(hyphenate(property));
      if (property == "display")
        return "none";
      Logger.error("Can't retrieve requested style '" + property + "' due to a bug in Safari");
    }

    // IE and derivatives
    if (element.currentStyle) {
      return element.currentStyle[property];
    }

    // Crappy in-line only lookup
    return element.style[property];
  };
  var set_opacity = function(element, opacity) {
    var opaque = (opacity == 1);

    try {
      element.style.opacity = (opaque ? '' : ''+opacity);
    } catch (ignored) {}

    try {
      element.style.filter  = (opaque ? '' : 'alpha(opacity='+(opacity*100)+')');
    } catch (ignored) {}
  };
  var get_opacity = function(element) {
    var opacity = get_style(element, 'filter');
    var val = null;
    if (opacity && (val = /(\d+(?:\.\d+)?)/.exec(opacity))) {
      return parseFloat(val.pop()) / 100;
    } else if (opacity = get_style(element, 'opacity')) {
      return parseFloat(opacity);
    } else {
      return 1.0;
    }
  };
  // ===== END OF DEPENDENCIES =====

  // Initializes the state to blank values
  animation.prototype._reset_state = function() {
    this.state = {
      attrs: {},
      duration: 500 // default duration
    }
  }

  // Stops any current animation
  animation.prototype.stop = function() {
    this._reset_state();
    this.queue = [];
    return this;
  }

  // Builds an overflow:hidden container for this.obj. Used with .blind()
  animation.prototype._build_container = function() {
    if (this.container_div) {
      this._refresh_container();
      return;
    }
    // ref-counting here on the magic container in case someone decides to start two animations with blind() on the same element
    // Either way it's not ideal because if animating to 'auto' the calculations will probably be incorrect... but at least this
    // way it won't tear up your DOM.
    if (this.obj.firstChild && this.obj.firstChild.__animation_refs) {
      this.container_div = this.obj.firstChild;
      this.container_div.__animation_refs++;
      this._refresh_container();
      return;
    }
    var container = document.createElement('div');
    container.style.padding = '0px';
    container.style.margin = '0px';
    container.style.border = '0px';
    container.__animation_refs = 1;
    var children = this.obj.childNodes;
    while (children.length) {
      container.appendChild(children[0]);
    }
    this.obj.appendChild(container);
    this.obj.style.overflow = 'hidden';
    this.container_div = container;
    this._refresh_container();
  }

  // Refreshes the size of the container. Used on checkpoints and such.
  animation.prototype._refresh_container = function() {
    this.container_div.style.height = 'auto';
    this.container_div.style.width = 'auto';
    this.container_div.style.height = this.container_div.offsetHeight+'px';
    this.container_div.style.width = this.container_div.offsetWidth+'px';
  }

  // Destroys the container built by _build_container()
  animation.prototype._destroy_container = function() {
    if (!this.container_div) {
      return;
    }
    if (!--this.container_div.__animation_refs) {
      var children = this.container_div.childNodes;
      while (children.length) {
        this.obj.appendChild(children[0]);
      }
      this.obj.removeChild(this.container_div);
    }
    this.container_div = null;
  }

  // Generalized attr function. Calls to .to, .by, and .from go through this
  animation.ATTR_TO = 1;
  animation.ATTR_BY = 2;
  animation.ATTR_FROM = 3;
  animation.prototype._attr = function(attr, value, mode) {

    // Turn stuff like border-left into borderLeft
    attr = attr.replace(/-[a-z]/gi, function(l) {
      return l.substring(1).toUpperCase();
    });

    var auto = false;
    switch (attr) {
      case 'background':
        this._attr('backgroundColor', value, mode);
        return this;

      case 'margin':
        value = animation.parse_group(value);
        this._attr('marginBottom', value[0], mode);
        this._attr('marginLeft', value[1], mode);
        this._attr('marginRight', value[2], mode);
        this._attr('marginTop', value[3], mode);
        return this;

      case 'padding':
        value = animation.parse_group(value);
        this._attr('paddingBottom', value[0], mode);
        this._attr('paddingLeft', value[1], mode);
        this._attr('paddingRight', value[2], mode);
        this._attr('paddingTop', value[3], mode);
        return this;

      case 'backgroundColor':
      case 'borderColor':
      case 'color':
        value = animation.parse_color(value);
        break;

      case 'opacity':
        value = parseFloat(value, 10);
        break;

      case 'height':
      case 'width':
        if (value == 'auto') {
          auto = true;
        } else {
          value = parseInt(value, 10);
        }
        break;

      case 'borderWidth':
      case 'lineHeight':
      case 'fontSize':
      case 'marginBottom':
      case 'marginLeft':
      case 'marginRight':
      case 'marginTop':
      case 'paddingBottom':
      case 'paddingLeft':
      case 'paddingRight':
      case 'paddingTop':
      case 'bottom':
      case 'left':
      case 'right':
      case 'top':
      case 'scrollTop':
      case 'scrollLeft':
        value = parseInt(value, 10);
        break;

      default:
        throw new Error(attr+' is not a supported attribute!');
    }

    if (this.state.attrs[attr] === undefined) {
      this.state.attrs[attr] = {};
    }
    if (auto) {
      this.state.attrs[attr].auto = true;
    }
    switch (mode) {
      case animation.ATTR_FROM:
        this.state.attrs[attr].start = value;
        break;

      case animation.ATTR_BY:
        this.state.attrs[attr].by = true;
        // fall through

      case animation.ATTR_TO:
        this.state.attrs[attr].value = value;
        break;
    }
  }

  // Explcit animation to a certain value
  animation.prototype.to = function(attr, value) {
    if (value === undefined) {
      this._attr(this.last_attr, attr, animation.ATTR_TO);
    } else {
      this._attr(attr, value, animation.ATTR_TO);
      this.last_attr = attr;
    }
    return this;
  }

  // Animation by a value (i.e. add this value to the current value)
  animation.prototype.by = function(attr, value) {
    if (value === undefined) {
      this._attr(this.last_attr, attr, animation.ATTR_BY);
    } else {
      this._attr(attr, value, animation.ATTR_BY);
      this.last_attr = attr;
    }
    return this;
  }

  // Start the animation from a value instead of the current value
  animation.prototype.from = function(attr, value) {
    if (value === undefined) {
      this._attr(this.last_attr, attr, animation.ATTR_FROM);
    } else {
      this._attr(attr, value, animation.ATTR_FROM);
      this.last_attr = attr;
    }
    return this;
  }

  // How long is this animation supposed to last (in miliseconds)
  animation.prototype.duration = function(duration) {
    this.state.duration = duration ? duration : 0;
    return this;
  }

  // Checkpoint the animation to start a new one.
  animation.prototype.checkpoint = function(distance /* = 1.0 */, callback) {
    if (distance === undefined) {
      distance = 1;
    }
    this.state.checkpoint = distance;
    this.queue.push(this.state);
    this._reset_state();
    this.state.checkpointcb = callback;
    return this;
  }

  // This animation requires an overflow container (usually used for width animations)
  animation.prototype.blind = function() {
    this.state.blind = true;
    return this;
  }

  // Hide this object at the end of the animation
  animation.prototype.hide = function() {
    this.state.hide = true;
    return this;
  }

  // Show this object at the beginning of the animation
  animation.prototype.show = function() {
    this.state.show = true;
    return this;
  }

  // Use an easing function to adjust the distribution of the animation state over frames
  animation.prototype.ease = function(ease) {
    this.state.ease = ease;
    return this;
  }

  // Let the animation begin!
  animation.prototype.go = function() {
    var time = (new Date()).getTime();
    this.queue.push(this.state);

    for (var i = 0; i < this.queue.length; i++) {
      this.queue[i].start = time - animation.offset;
      if (this.queue[i].checkpoint) {
        time += this.queue[i].checkpoint * this.queue[i].duration;
      }
    }
    animation.push(this);
    return this;
  }

  // Draw a frame for this animation
  animation.prototype._frame = function(time) {
    var done = true;
    var still_needs_container = false;
    var whacky_firefox = false;
    for (var i = 0; i < this.queue.length; i++) {

      // If this animation shouldn't start yet we can abort early
      var cur = this.queue[i];
      if (cur.start > time) {
        done = false;
        continue;
      }

      if (cur.checkpointcb) {
        this._callback(cur.checkpointcb, time - cur.start);
        cur.checkpointcb = null;
      }

      // We need to initialize an animation on the first frame
      if (cur.started === undefined) {
        if (cur.show) {
          this.obj.style.display = 'block';
        }
        for (var a in cur.attrs) {
          if (cur.attrs[a].start !== undefined) {
            continue;
          }
          switch (a) {
            case 'backgroundColor':
            case 'borderColor':
            case 'color':
              // Defer to the left border color, whatever.
              var val = animation.parse_color(get_style(this.obj, a == 'borderColor' ? 'borderLeftColor' : a));

              // I'm not sure why anyone would want to do relative color adjustment... but at least they can
              if (cur.attrs[a].by) {
                cur.attrs[a].value[0] = Math.min(255, Math.max(0, cur.attrs[a].value[0] + val[0]));
                cur.attrs[a].value[1] = Math.min(255, Math.max(0, cur.attrs[a].value[1] + val[1]));
                cur.attrs[a].value[2] = Math.min(255, Math.max(0, cur.attrs[a].value[2] + val[2]));
              }
              break;

            case 'opacity':
              var val = get_opacity(this.obj);
              if (cur.attrs[a].by) {
                cur.attrs[a].value = Math.min(1, Math.max(0, cur.attrs[a].value + val));
              }
              break;

            case 'height':
            case 'width':
              var val = animation['get_'+a](this.obj);
              if (cur.attrs[a].by) {
                cur.attrs[a].value += val;
              }
              break;

            case 'scrollLeft':
            case 'scrollTop':
              var val = (this.obj == document.body) ? (document.documentElement[a] || document.body[a]) : this.obj[a];
              if (cur.attrs[a].by) {
                cur.attrs[a].value += val;
              }
              cur['last'+a] = val;
              break;

            default:
              var val = parseInt(get_style(this.obj, a), 10);
              if (cur.attrs[a].by) {
                cur.attrs[a].value += val;
              }
              break;
          }
          cur.attrs[a].start = val;
        }

        // If we're animating height or width to "auto" we need to do some DOM-fu to figure out what that means in px
        if ((cur.attrs.height && cur.attrs.height.auto) ||
            (cur.attrs.width && cur.attrs.width.auto)) {

          // This is a silly fix for Firefox's whacky redrawing. This bug can be reproduced with the following code:
          /* <div style="width: 300px; height: 300px; background: red" id="test"></div>
             <script>
               setInterval(function() {
                 var obj = document.getElementById('test');
             //    var p = obj.parentNode;
                 obj.style.visibility = 'hidden';
                 obj.offsetWidth; // request a property, requires a re-render
             //    p.removeChild(obj);
                 obj.style.visibility = 'visible';
             //    p.appendChild(obj);
               }, 100);
             </script> */
          // In all browsers that aren't Firefox the div will stay solid red. In Firefox it flickers. The workaround
          // for the bug is included in the code block above, but commented out.
          if (ua.firefox() < 3) {
            whacky_firefox = true;
          }

          // Set any attributes that affect element size to their final desired values
          this._destroy_container();
          for (var a in {height: 1, width: 1,
                         fontSize: 1,
                         borderLeftWidth: 1, borderRightWidth: 1, borderTopWidth: 1, borderBottomWidth: 1,
                         paddingLeft: 1, paddingRight: 1, paddingTop: 1, paddingBottom: 1}) {
            if (cur.attrs[a]) {
              this.obj.style[a] = cur.attrs[a].value + (typeof cur.attrs[a].value == 'number' ? 'px' : '');
            }
          }

          // Record the dimensions of what the element will look like after the animation
          if (cur.attrs.height && cur.attrs.height.auto) {
            cur.attrs.height.value = animation.get_height(this.obj);
          }
          if (cur.attrs.width && cur.attrs.width.auto) {
            cur.attrs.width.value = animation.get_width(this.obj);
          }

          // We don't need to do anything else with temporarily adjusted style because they're
          // about to be overwritten in the frame loop below
        }

        cur.started = true;
        if (cur.blind) {
          this._build_container();
        }
      }

      // Calculate the animation's progress from 0 - 1
      var p = (time - cur.start) / cur.duration;
      if (p >= 1) {
        p = 1;
        if (cur.hide) {
          this.obj.style.display = 'none';
        }
      } else {
        done = false;
      }
      var pc = cur.ease ? cur.ease(p) : p;

      // If this needs a blind container and doesn't have one, we build it
      if (!still_needs_container && p != 1 && cur.blind) {
        still_needs_container = true;
      }

      // Hack documented above
      if (whacky_firefox && this.obj.parentNode) {
        var parentNode = this.obj.parentNode;
        var nextChild = this.obj.nextSibling;
        parentNode.removeChild(this.obj);
      }

      // Loop through each animated attribute and set it where it needs to be
      for (var a in cur.attrs) {
        switch (a) {
          case 'backgroundColor':
          case 'borderColor':
          case 'color':
            this.obj.style[a] = 'rgb('+
              animation.calc_tween(pc, cur.attrs[a].start[0], cur.attrs[a].value[0], true)+','+
              animation.calc_tween(pc, cur.attrs[a].start[1], cur.attrs[a].value[1], true)+','+
              animation.calc_tween(pc, cur.attrs[a].start[2], cur.attrs[a].value[2], true)+')';
            break;

          case 'opacity':
            set_opacity(this.obj, animation.calc_tween(pc, cur.attrs[a].start, cur.attrs[a].value));
            break;

          case 'height':
          case 'width':
            this.obj.style[a] = pc == 1 && cur.attrs[a].auto ? 'auto' :
                                animation.calc_tween(pc, cur.attrs[a].start, cur.attrs[a].value, true)+'px';
            break;

          case 'scrollLeft':
          case 'scrollTop':
            // Special-case here for scrolling. If the user overrides the scroll we immediately terminate this animation
            var val = (this.obj == document.body) ? (document.documentElement[a] || document.body[a]) : this.obj[a];
            if (cur['last'+a] != val) {
              delete cur.attrs[a];
            } else {
              var diff = animation.calc_tween(pc, cur.attrs[a].start, cur.attrs[a].value, true) - val;
              if (a == 'scrollLeft') {
                window.scrollBy(diff, 0);
              } else {
                window.scrollBy(0, diff);
              }
              cur['last'+a] = diff + val;
            }
            break;

          default:
            this.obj.style[a] = animation.calc_tween(pc, cur.attrs[a].start, cur.attrs[a].value, true)+'px';
            break;
        }
      }

      // If this animation is complete remove it from the queue
      if (p == 1) {
        this.queue.splice(i--, 1);
        this._callback(cur.ondone, time - cur.start - cur.duration);
      }
    }

    // Hack documented above
    if (whacky_firefox) {
      parentNode[nextChild ? 'insertBefore' : 'appendChild'](this.obj, nextChild);
    }

    if (!still_needs_container && this.container_div) {
      this._destroy_container();
    }
    return !done;
  }

  // Add a callback to fire when this animation is finished
  animation.prototype.ondone = function(fn) {
    this.state.ondone = fn;
    return this;
  }

  // Call a callback with a time offset (for instantiating more animations)
  animation.prototype._callback = function(callback, offset) {
    if (callback) {
      animation.offset = offset;
      callback.call(this);
      animation.offset = 0;
    }
  }

  // Calculates a value in between two values based on a percentage. Basically a weighted average.
  animation.calc_tween = function(p, v1, v2, whole) {
    return (whole ? parseInt : parseFloat)((v2 - v1) * p + v1, 10);
  }

  // Takes a color like #fff and returns an array of [255, 255, 255].
  animation.parse_color = function(color) {
    var hex = /^#([a-f0-9]{1,2})([a-f0-9]{1,2})([a-f0-9]{1,2})$/i.exec(color);
    if (hex) {
      return [parseInt(hex[1].length == 1 ? hex[1] + hex[1] : hex[1], 16),
              parseInt(hex[2].length == 1 ? hex[2] + hex[2] : hex[2], 16),
              parseInt(hex[3].length == 1 ? hex[3] + hex[3] : hex[3], 16)];
    } else {
      var rgb = /^rgba? *\(([0-9]+), *([0-9]+), *([0-9]+)(?:, *([0-9]+))?\)$/.exec(color);
      if (rgb) {
        if (rgb[4] === '0') {
          return [255, 255, 255]; // transparent
        } else {
          return [parseInt(rgb[1], 10), parseInt(rgb[2], 10), parseInt(rgb[3], 10)];
        }
      } else if (color == 'transparent') {
        return [255, 255, 255]; // not much we can do here...
      } else {
        // When we open this to Platform we'll need a key-value list of names to rgb values
        throw 'Named color attributes are not supported.';
      }
    }
  }

  // Takes a CSS attribute like padding or margin and returns an explicit array of 4 values
  // Ex: '0px 1px' -> ['0px', '1px', '0px', '1px']
  animation.parse_group = function(value) {
    var value = trim(value).split(/ +/);
    if (value.length == 4) {
      return value;
    } else if (value.length == 3) {
      return [value[0], value[1], value[2], value[1]];
    } else if (value.length == 2) {
      return [value[0], value[1], value[0], value[1]];
    } else {
      return [value[0], value[0], value[0], value[0]];
    }
  }

  // Gets the current height of an element which when used with obj.style.height = height+'px' is a visual NO-OP
  animation.get_height = function(obj) {
    var pT = parseInt(get_style(obj, 'paddingTop'), 10),
        pB = parseInt(get_style(obj, 'paddingBottom'), 10),
        bT = parseInt(get_style(obj, 'borderTopWidth'), 10),
        bW = parseInt(get_style(obj, 'borderBottomWidth'), 10);
    return obj.offsetHeight - (pT ? pT : 0) - (pB ? pB : 0) - (bT ? bT : 0) - (bW ? bW : 0);
  }

  // Similar to get_height except for widths
  animation.get_width = function(obj) {
    var pL = parseInt(get_style(obj, 'paddingLeft'), 10),
        pR = parseInt(get_style(obj, 'paddingRight'), 10),
        bL = parseInt(get_style(obj, 'borderLeftWidth'), 10),
        bR = parseInt(get_style(obj, 'borderRightWidth'), 10);
    return obj.offsetWidth - (pL ? pL : 0) - (pR ? pR : 0) - (bL ? bL : 0) - (bR ? bR : 0);
  }

  // Add this animation object to the global animation stack.
  animation.push = function(instance) {
    if (!animation.active) {
      animation.active = [];
    }
    animation.active.push(instance);
    if (!animation.timeout) {
      animation.timeout = setInterval(animation.animate.bind(animation), animation.resolution);
    }
    animation.animate(true);
  }

  // Renders a frame from each animation currently active. By putting all our animations in one
  // stack it gives us the advantage of a single setInterval with all style updates in a single
  // callback. That means the browser will do less rendering and multiple animations will be
  // smoother.
  animation.animate = function(last) {
    var done = true;
    var time = (new Date()).getTime();
    for (var i = last === true ? animation.active.length - 1 : 0; i < animation.active.length; i++) {
      if (animation.active[i]._frame(time)) {
        done = false;
      } else {
        animation.active.splice(i--, 1); // remove from the list
      }
    }
    if (done) {
      clearInterval(animation.timeout);
      animation.timeout = null;
    }
  }

  // Ease functions. These functions all have a domain and (maybe) range of 0 - 1
  animation.ease = {}
  animation.ease.begin = function(p) {
    return p * p;
  }
  animation.ease.end = function(p) {
    p -= 1;
    return -(p * p) + 1;
  }
  animation.ease.both = function(p) {
    if (p <= 0.5) {
      return (p * p) * 2;
    } else {
      p -= 1;
      return (p * p) * -2 + 1;
    }
  }

})();
// Place your application-specific JavaScript functions and classes here
// This file is automatically included by javascript_include_tag :defaults

function fadeFlash() {
  var e = $("flash");
  if (e) {
    e.setStyle({'position': 'relative'});
    animation(e).from('top', 0).to('top', 25).duration(1000).checkpoint().duration(5000).checkpoint().to('opacity', 0).duration(2000).go();
  }
}

Event.observe(window, 'load', function() {
  fadeFlash();
});

document.observe('dom:loaded', function() {
  $$(".loadLater").each(function(e) {
    e.writeAttribute('src', e.readAttribute('rel'));
    e.writeAttribute('rel', '');
    e.show();
  });
  Tooltips.setup();
  Tooltips.loadAll(".miniProfile", true);
});

function submitOnce(e) {
  e.disabled = true;
  e.form.submit();
  return false;
}

function removeMsgBox(h) {
  document.cookie = "closed_msg_hash=" + h + "; path=/; max-age=" + (60*60*24*365);
  $("msgBox").hide();
}

function selectedRadioValue(group) {
  var el = $$("input[name='" + group + "']").find(function(re) {return re.checked;});
  return el ? el.value : undefined;
}

(function() {
  var rankGroupsConstant = 0.75;
  var rankGroups = $R(0, 12).map(function(i) { return Math.pow(rankGroupsConstant, i) * (1 - rankGroupsConstant) / (1 - Math.pow(rankGroupsConstant, 13));  });
  var pp = function(n) {
    return Math.round(n * 10000) / 100;
  };
  RankRanges = rankGroups.inject([], function(r, g) {
    var beginning = (r.last() || [])[1] || 0.0;
    r.push([beginning, beginning + g]);
    return r;
  }).map(function(t) { return [pp(t[0]), pp(t[1])]; });
})()

// Cookies manipulation code, used for stats, remove when no longer needed.
function createCookie(name,value,days) {
	if (days) {
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		var expires = "; expires="+date.toGMTString();
	}
	else var expires = "";
	document.cookie = name+"="+value+expires+"; path=/";
}
function readCookie(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++) {
		var c = ca[i];
		while (c.charAt(0)==' ') c = c.substring(1,c.length);
		if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
	}
	return null;
}
function eraseCookie(name) {
	createCookie(name,"",-1);
}

/* Start of AD-related helpers */
function advertiseSetup(key) {
  if (!isFB) {
    try {
      GS_googleAddAdSenseService(key);
      GS_googleEnableAllServices();
      advertiseKey = key; // Expose as public variable
    } catch(e) {}
  }
}
function advertiseAddSlots(slots) {
  if (!isFB) {
    try {
      advertiseSlots = slots;
      Object.values(slots).each(function(ad) {
        GA_googleAddSlot(advertiseKey, ad)
      });
    } catch(e) {}
  }
}
function advertiseFetch() {
  if (!isFB) {
    try {
      GA_googleFetchAds();
    } catch(e) {}
  }
}
function advertiseFillSlot(n) {
  if (!isFB) {
    try {
      GA_googleFillSlot(advertiseSlots[n]);
    } catch(e) {}
  }
}
/* End of AD-related helpers */

function flashThingy(thingy) {
  if (thingy.visible()) {
    animation(thingy).duration(2000).to('opacity', 0.5).checkpoint().duration(700).to('opacity', 1).checkpoint(1.0, flashThingy.curry(thingy)).go();
  }
}
var ProfilePage = new (function() {

  var playerBox = new EJS({text: " \
    <div class='playerInfo floatleft'> \
      <img class='playerAvatar floatleft' src='<%= avatar %>'> \
      <ul class='floatleft'> \
        <li class='bold'><%= playerName %></li> \
        <li> \
          <% if (rel == 1) { %> \
            <img src='/images/icon-friend.png' /> \
          <% } %> \
          <% if (rel == 3 || rel == 4) { %> \
            <img src='/images/icon-half-friend.png' /> \
          <% } %> \
          <% if (rel == 5) { %> \
            <img src='/images/icon-block.png' /> \
          <% } %> \
          <img class='menuArrow' /> \
          <div class='menuList' style='display: none;'> \
            <% if (rel == 0) { %> \
              <a class='addCoPlayer' href='#' rel='<%= playerID %>-3'> \
                <img src='/images/icon-friend.png' /> \
                <span><%= G._('Add Friend') %> \
              </a> \
            <% } %> \
            <% if (rel == 1) { %> \
              <a class='addCoPlayer' href='#' rel='<%= playerID %>-0'> \
                <img src='/images/icon-unfriend.png' /> \
                <span><%= G._('Delete') %> \
              </a> \
            <% } %> \
            <% if (rel == 3) { %> \
              <a class='addCoPlayer' href='#' rel='<%= playerID %>-0'> \
                <img src='/images/icon-unfriend.png' /> \
                <span><%= G._('Uninvite') %> \
              </a> \
            <% } %> \
            <% if (rel == 4) { %> \
              <a class='addCoPlayer' href='#' rel='<%= playerID %>-1'> \
                <img src='/images/icon-friend.png' /> \
                <span><%= G._('Accept Friend') %> \
              </a> \
              <br /> \
              <a class='addCoPlayer' href='#' rel='<%= playerID %>-0'> \
                <img src='/images/icon-unfriend.png' /> \
                <span><%= G._('Refuse') %> \
              </a> \
            <% } %> \
            <% if (rel == 0 || rel == 4) { %> \
              <br /> \
              <a class='addCoPlayer' href='#' rel='<%= playerID %>-5'> \
                <img src='/images/icon-block.png' /> \
                <span><%= G._('Block') %> \
              </a> \
            <% } %> \
            <% if (rel == 5) { %> \
              <a class='addCoPlayer' href='#' rel='<%= playerID %>-0'> \
                <img src='/images/icon-unblock.png' /> \
                <span><%= G._('Unblock') %> \
              </a> \
            <% } %> \
          </div> \
        </li> \
      </ul> \
    </div> \
  "});
  var listNameToRelation = $H({'Pending': 4, 'Friends': 1, 'Blocked': 5});
  var filterByName = "";

  this.ajaxRequest;
  this.searchResults = $H({});

  this.addItemToList = function(info, listName) {
    $("friends" + listName).insert(playerBox.render(info));
  };

  this.renderLists = function() {
    listNameToRelation.each(function(t) {
      $$("#friends" + t[0] + "1, #friends" + t[0] + "2").invoke('update', '');
      var l = CoPlayers.allInRelationInfo(t[1]);
      $("friends" + t[0] + "All").show();
      if (l.length == 0) {
        if (t[0] == "Pending") {
          $("friends" + t[0] + "All").hide();
        } else {
          $("friendsNo" + t[0]).show();
        }
      } else {
        l.each(function(pi, i) {
          this.addItemToList(pi, t[0] + ((i % 2) + 1));
        }, this);
        $$("#friendsNo" + t[0]).invoke('hide');
      }
    }, this);
  };

  this.renderResults = function() {
    this.searchResults.each(function(t, i) {
      this.addItemToList({
        'playerID': t[0],
        'playerName': t[1][0],
        'avatar': t[1][1],
        'rel': CoPlayers.relation(t[0])
      }, "Search" + ((i % 3) + 1));
    }, this);
  };

  this.render = function() {
    $$("#friendsSearch1, #friendsSearch2, #friendsSearch3").invoke('update', '');
    if (filterByName.length == 0) {
      this.renderLists();
      $("friendsNoSearch").hide();
    } else {
      this.renderResults();
      listNameToRelation.keys().each(function(k) { $("friends" + k + "All").hide(); });
      if (this.searchResults.keys().length == 0) {
        $("friendsNoSearch").show();
      } else {
        $("friendsNoSearch").hide();
      }
    }
    // Setup menu events
    Menus.loadAll(".menuArrow");
    $$(".addCoPlayer").invoke('observe', 'click', function(ev) {
      var tuple = $(this).readAttribute('rel').split('-');
      var playerID = parseInt(tuple[0], 10)
      var actionIndex = parseInt(tuple[1], 10);
      CoPlayers.addCoPlayer(playerID, actionIndex);
      ProfilePage.render.bind(ProfilePage).delay(0);
      ev.stop();
    });
  };

  this.setup = function() {
    new Form.Element.Observer(
      'friendsSearch', 0.5,
      function(el, val) {
        filterByName = val;
        if (this.ajaxRequest && this.ajaxRequest.transport.readyState == 1) { // Old req still running
          this.ajaxRequest.transport.abort();
          this.ajaxRequest.transport.onreadystatechange = Prototype.emptyFunction; // IE memory leak
        }
        if (val) {
          this.ajaxRequest = new Ajax.Request("/users/search", {
            method: 'get',
            parameters: {'ie': (new Date()).getTime(), 'l': val},
            onSuccess: function(transport) {
              this.searchResults = $H(transport.responseText.evalJSON());
              this.render();
            }.bind(this)
          });
        } else {
          this.searchResults = $H({});
          this.render();
        }
      }.bind(this)
    );
    this.render();
  };

})();


/*  Facebox for Prototype, version 2.0
 *  By Robert Gaal - http://wakoopa.com
 *
 *  Heavily based on Facebox by Chris Wanstrath - http://famspam.com/facebox
 *  First ported to Prototype by Phil Burrows - http://blog.philburrows.com
 *
 *  Licensed under the MIT:
 *  http://www.opensource.org/licenses/mit-license.php
 *
 *  Need help?  Join the Google Groups mailing list:
 *  http://groups.google.com/group/facebox/
 *
 *  Dependencies:   prototype & script.aculo.us + images & CSS files from original facebox
 *  Usage:          Append 'rel="facebox"' to an element to call it inside a so-called facebox
 *
 *--------------------------------------------------------------------------*/

var Facebox = Class.create({
  initialize  : function(extra_set){
    var closeText = (G.lang() == "ar") ? 'إغلاق' : 'Close';
    this.settings = {
      loading_image : '/images/lightbox/loading.gif',
      image_types   : new RegExp('\.' + ['png', 'jpg', 'jpeg', 'gif'].join('|') + '$', 'i'),
      inited        : true,
      facebox_html  : '\
    <div id="overlay" style="display: none;"> \
    </div> \
    <div id="facebox" style="display:none;"> \
      <div class="popup"> \
        <table id="facebox_table"> \
          <tbody> \
            <tr> \
              <td class="tl"/><td class="b"/><td class="tr"/> \
            </tr> \
            <tr> \
              <td class="b"/> \
              <td class="body"> \
                <div id="faceboxContent"> \
                </div> \
                <div id="faceboxFooter"> \
                  <a href="#" class="close"> \
                    ' + closeText + '[x] \
                  </a> \
                </div> \
              </td> \
              <td class="b"/> \
            </tr> \
            <tr> \
              <td class="bl"/><td class="b"/><td class="br"/> \
            </tr> \
          </tbody> \
        </table> \
      </div> \
    </div>'
    };
    if (extra_set) Object.extend(this.settings, extra_set);
    $(document.body).insert({bottom: this.settings.facebox_html});

    this.preload = [ new Image() ];
    this.preload[0].src = this.settings.loading_image;

    f = this;
    $$('#facebox .b:first, #facebox .bl, #facebox .br, #facebox .tl, #facebox .tr').each(function(elem){
      f.preload.push(new Image());
      f.preload.slice(-1).src = elem.getStyle('background-image').replace(/url\((.+)\)/, '$1');
    });

    this.facebox = $('facebox');
    this.keyPressListener = this.watchKeyPress.bind(this);

    fb = this;
    Event.observe($$('#facebox .close').first(), 'click', function(e){
      Event.stop(e);
      fb.close();
      if (typeof UIManager != "undefined") { UIManager.reactivateCorrectLightBox(); }
    });

  },

  watchKeyPress : function(e){
    // Close if espace is pressed or if there's a click outside of the facebox
    if (e.keyCode == 27 || !Event.element(e).descendantOf(this.facebox)) this.close();
  },

  loading  : function(ref, hideOverlay) {
    if ($$('#facebox .loading').length == 1) return true;

    contentWrapper = $('faceboxContent');
    contentWrapper.update("");
    if (hideOverlay) { 
      $("overlay").hide(); 
    } else {
      $("overlay").show();
    }
    contentWrapper.insert({bottom: '<div class="loading"><img src="'+this.settings.loading_image+'"/></div>'});

    var co = (ref.cumulativeOffset) ? ref.cumulativeOffset() : ref.getScrollOffsets();
    var fbd = this.facebox.getDimensions();
    var refd = ref.getDimensions();

    this.facebox.setStyle({
      'top': co[1] + (refd['height'] - fbd['height']) / 2 + 'px',
      'left': co[0] + (refd['width'] - fbd['width']) / 2 + 'px'
    });

    //Event.observe(document, 'keypress', this.keyPressListener);
    //Event.observe(document, 'click', this.keyPressListener);
    return true;
  },

  reveal  : function(data, klass, ref, hideOverlay){
    load = $$('#facebox .loading').first();
    if(load) load.remove();

    contentWrapper = $('faceboxContent');
    contentWrapper.removeClassName($A(contentWrapper.classNames()).join(" "));
    if (klass) { contentWrapper.addClassName(klass); }
    contentWrapper.insert({bottom: data});

    $('faceboxContent').childElements().each(function(elem,i){
      elem.show();
    });
    if (hideOverlay) { 
      $("overlay").hide(); 
    } else {
      $("overlay").show();
    }

    if(!this.facebox.visible()) this.facebox.show();

    var co = (ref.cumulativeOffset) ? ref.cumulativeOffset() : ref.getScrollOffsets();
    var fbd = this.facebox.getDimensions();
    var refd = ref.getDimensions();

    var topValue = co[1] + (refd['height'] - fbd['height']) / 2;
    topValue = topValue < 0 ? 0 : topValue;

    this.facebox.setStyle({
      'top': topValue + 'px',
      'left': co[0] + (refd['width'] - fbd['width']) / 2 + 'px'
    });

    //Event.observe(document, 'keypress', this.keyPressListener);
    //Event.observe(document, 'click', this.keyPressListener);
  },

  close    : function(){
    /*
    animation($('facebox')).duration(300).to('opacity', 0).checkpoint(1.0, function() {
      contentWrapper = $("faceboxContent");
      contentWrapper.update("");
      $("facebox").hide();
    }).go();
    */
    $("faceboxContent").update("");
    $('facebox').hide();
    $("overlay").hide();
    if (typeof UIManager != "undefined") { UIManager.currentLightBox = ""; }
  },

  show  : function(href, opts, varsHash){
    // Load parameters from opts
    var klass = opts["klass"] || "";
    var ref = opts["ref"] || $(document.viewport);
    var hideFooter = Object.isUndefined(opts["hideFooter"]) ? false : opts["hideFooter"];
    var afterHook = opts["afterHook"];
    var hideOverlay = opts["hideOverlay"];

    // Prepare and show the lightbox
    hideFooter ? $("faceboxFooter").hide() : $("faceboxFooter").show();
    this.loading(ref, hideOverlay);
    this.facebox.show();
    // animation(this.facebox).duration(300).from('opacity', 0).to('opacity', 1).go();

    // Load the content
    if (typeof EJS != "undefined" && (href instanceof EJS)) {
      var data = new Element("div");
      data.innerHTML = href.render(varsHash ? varsHash : {});
      this.reveal(data, klass, ref, hideOverlay);
      if (afterHook) afterHook.apply();
    } else if (href.match(/#/)){
      var target  = href.replace('#','');
      var d        = $(target);
      var data = new Element(d.tagName); // create a new element so as to not delete the original on close()
      data.innerHTML = d.innerHTML;
      this.reveal(data, klass, ref, hideOverlay);
      if (afterHook) afterHook.apply();
    } else if (href.match(this.settings.image_types)) {
      var image = new Image();
      fb = this;
      image.onload = function() {
        fb.reveal('<div class="image"><img src="' + image.src + '" /></div>', klass, ref, hideOverlay);
        if (afterHook) afterHook.apply();
      };
      image.src = href;
    } else {
      var fb  = this;
      var url = href;
      new Ajax.Request(url, {
        method     : 'get',
        parameters : { 'ie': (new Date()).getTime() },
        onFailure  : function(transport){
          fb.hide();
        },
        onSuccess  : function(transport){
          fb.reveal(transport.responseText, klass, ref, hideOverlay);
          if (afterHook) afterHook.apply();
        }
      });
    }
  }
});

var lightbox;
document.observe('dom:loaded', function(){
  lightbox = new Facebox();
});
var User = new (function() {

  this.info;

  this.update = function(h) {
    this.info = h;
  };

  var featureMasks = {'game_admin': 1, 'profile': 2, 'creation': 4, 'chat': 8, 'theming': 16};

  this.hasFeature = function(f) {
    if (Object.isUndefined(this.info['featurebits'])) { return false; }
    return (this.info['featurebits'] & featureMasks[f]) != 0;
  };

  this.startGuestID = 500 * 1000 * 1000;

})();
document.observe("dom:loaded", function() {
  function handleRemote(element) {
    var method, url, params;

    if (element.tagName.toLowerCase() === 'form') {
      method = element.readAttribute('method') || 'post';
      url    = element.readAttribute('action');
      params = element.serialize(true);
    } else {
      method = element.readAttribute('data-method') || 'get';
      url    = element.readAttribute('href');
      params = {};
    }

    var event = element.fire("ajax:before");
    if (event.stopped) return false;

    new Ajax.Request(url, {
      method: method,
      parameters: params,
      asynchronous: true,
      evalScripts: true,

      onLoading:     function(request) { element.fire("ajax:loading", {request: request}); },
      onLoaded:      function(request) { element.fire("ajax:loaded", {request: request}); },
      onInteractive: function(request) { element.fire("ajax:interactive", {request: request}); },
      onComplete:    function(request) { element.fire("ajax:complete", {request: request}); },
      onSuccess:     function(request) { element.fire("ajax:success", {request: request}); },
      onFailure:     function(request) { element.fire("ajax:failure", {request: request}); }
    });

    element.fire("ajax:after");
  }

  function handleMethod(element) {
    var method, url, token_name, token;

    method     = element.readAttribute('data-method');
    url        = element.readAttribute('href');
    csrf_param = $$('meta[name=csrf-param]').first();
    csrf_token = $$('meta[name=csrf-token]').first();

    var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
    element.parentNode.appendChild(form);

    if (method != 'post') {
      var field = new Element('input', { type: 'hidden', name: '_method', value: method });
      form.appendChild(field);
    }

    if (csrf_param) {
      var param = csrf_param.readAttribute('content');
      var token = csrf_token.readAttribute('content');
      var field = new Element('input', { type: 'hidden', name: param, value: token });
      form.appendChild(field);
    }

    form.submit();
  }

  $(document.body).observe("click", function(event) {
    var message = event.findElement().readAttribute('data-confirm');
    if (message && !confirm(message)) {
      event.stop();
      return false;
    }

    var element = event.findElement("a[data-remote]");
    if (element) {
      handleRemote(element);
      event.stop();
      return true;
    }

    var element = event.findElement("a[data-method]");
    if (element) {
      handleMethod(element);
      event.stop();
      return true;
    }
  });

  // TODO: I don't think submit bubbles in IE
  $(document.body).observe("submit", function(event) {
    var element = event.findElement(),
        message = element.readAttribute('data-confirm');
    if (message && !confirm(message)) {
      event.stop();
      return false;
    }

    var inputs = element.select("input[type=submit][data-disable-with]");
    inputs.each(function(input) {
      input.disabled = true;
      input.writeAttribute('data-original-value', input.value);
      input.value = input.readAttribute('data-disable-with');
    });

    var element = event.findElement("form[data-remote]");
    if (element) {
      handleRemote(element);
      event.stop();
    }
  });

  $(document.body).observe("ajax:after", function(event) {
    var element = event.findElement();

    if (element.tagName.toLowerCase() === 'form') {
      var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
      inputs.each(function(input) {
        input.value = input.readAttribute('data-original-value');
        input.writeAttribute('data-original-value', null);
        input.disabled = false;
      });
    }
  });
});
function getText() {
  var dict = {
    'kingdom': 'مملكة',
    'king': 'ملك',
    'queens': 'بنات',
    'diamonds': 'ديناري',
    'trix': 'تركس',
    'complex': 'كمبلكس',
    'ltoosh': 'لطوش',
    'clubs': 'سنك',
    'hearts': 'كبة',
    'spades': 'بستوني',
    'please choose the hand to play.': 'الرجاء اختيار اللعبة.',
    'waiting for': 'بانتظار',
    'to choose the hand': 'ليختار اللعبة التالية',
    'n/a': 'غير موجود',
    'joined the game': 'انضم إلى اللعبة',
    'waiting for': 'بانتظار',
    'to start the game': 'لتبدأ اللعب',
    'more player': 'لاعبين اخرين',
    'more players': 'لاعبين اخرين',
    'to join': 'لينضموا',
    'is the hand chooser': 'صاحب المملكة',
    'choose': 'اختار',
    'waiting for all players to double any cards': 'بانتظار جميع اللاعبين ليضاعفوا أية ورقة',
    'has not doubled anything': 'لم يضاعف أية ورقة',
    'has doubled the': 'ضاعف ورقة',
    'and is ready': 'و هو جاهز',
    'all players are ready': 'جميع اللاعبين جاهزين',
    'to play': 'ليلعب',
    'has finished': 'انتهت',
    'to deal next hand': 'لتوزع الأوراق',
    'to chose the hand': 'ليختار اللعبة التالية',
    'ate the trick and has a total of': 'أكل الأوراق, و مجموع أكلاته',
    'ate': 'أكل',
    'diamond card': 'كرت ديناري',
    'diamond cards': 'كروت ديناري',
    'and has a total of': 'و مجموعه',
    'ate the queen of': 'أكل ورقة البنت',
    'ate the king of hearts': 'أكل ورقة شيخ الكبة',
    'played the': 'لعب ورقة',
    'two': 'إثنين',
    'three': 'ثلاثة',
    'four': 'أربعة',
    'five': 'خمسة',
    'six': 'ستة',
    'seven': 'سبعة',
    'eight': 'ثمانية',
    'nine': 'تسعة',
    'ten': 'عشرة',
    'jack': 'ولد',
    'queen': 'بنت',
    'king': 'شيخ',
    'ace': 'قص',
    'king already doubled': 'تمت مضاعفة الشيخ مسبقا',
    'you don\'t have the king': 'أنت لا تملك الشيخ',
    'queen already doubled': 'تمت مضاعفة البنت مسبقا',
    'you don\'t have the queen': 'أنت لا تملك البنت',
    'card already doubled': 'تمت مضاعفة الكرت مسبقا',
    'you cannot double this card': 'لا يمكنك مضاعفة هذا الكرت',
    'incorrect suit': 'نوع الكرت غير صحيح',
    'you have already doubled your cards': 'لقد قمت بمضاعفة أوراقك مسبقا',
    'you cannot pass since you have cards to play': 'لا يمكنك تمرير الدور لأنك تملك كروت يمكن لعبها',
    'you must place a jack as the first card': 'يجب أن تضع كرت الولد كأول كرت',
    'hand already played': 'تم لعب هذا الدق مسبقا',
    'player already ready': 'اللاعب جاهز مسبقا',
    'it\'s not your turn': 'هذا ليس دورك',
    'you can\'t play this card now': 'لا يمكنك لعب هذا الكرت الان',
    'close': 'إغلاق',
    'by': 'من قبل',
    'needs': 'تحتاج ل',
    'player(s)': 'لاعبين',
    'players': 'لاعبين',
    'is ready to start': 'جاهزة لتبدأ',
    'trix complex': 'تركس كمبلكس',
    'trixcomplex': 'تركس كمبلكس',
    'The game is back on track': 'عادت اللعبة الى مجراها',
    'are you sure?': 'هل أنت متأكد؟',
    'are you sure?\nthis player will be banned from this table.': 'هل أنت متأكد؟\n هذا اللاعب سيحظر من هذه اللعبة.',
    'the game is over': 'انتهت اللعبة',
    'there are no games': 'لا يوجد ألعاب حاليا',
    'create a new one': 'أنشىء واحدة جديدة',
    'you have no games': 'ليس لديك أية ألعاب',
    'you must be': 'يجب أن',
    'logged in': 'تسجل دخولك',
    'to use this feature': 'لتتمكن من استخدام هذه الميزة',
    'updated': 'تم تحديثها',
    'ago': 'منذ',
    'about': 'حوالي',
    '1 minute': 'دقيقة',
    'minutes': 'دقيقة',
    'back to game': 'العودة إلى اللعبة',
    'join': 'انضم',
    'play now': 'إلعب الآن',
    'next round gives you': 'الجولة التالية تعطيك',
    'tokens': 'توكنز',
    'players can leave and join during the game. the computer plays if players leave.': 'بإمكان اللاعبين المغادرة والدخول خلال اللعبة. الكمبيوتر يلعب في حال مغادرة اللاعبين. ',
    'no leaving and joining during the game. no computer and no timer.': 'لا يستطيع اللاعبون المغادرة أو الدخول خلال اللعبة. من غير كمبيوتر و من غير مؤقت.',
    'timer': 'مؤقت',
    'seconds': 'ثانية',
    'kick after': 'طرد اللاعب بعد',
    'times of not playing': 'مرات من غير لعب',
    'leaving during game is allowed': 'مسموح المغادرة خلال اللعبة',
    'no leaving during game': 'ممنوع المغادرة خلال اللعبة',
    'game in progress and full': 'اللعبة جارية و ممتلئة',
    'game has': 'لديها',
    'player(s) and needs': 'لاعبين و تحتاج ل',
    'to start': 'لتبدأ',
    'to continue': 'لتكمل',
    'game in progress and has': 'اللعبة جارية و لديها',
    'empty seat(s)': 'مقعد فارغ',
    'no timer': 'بلا مؤقت',
    '?': '\u061f',
    'The game can longer be continued.': 'لا يمكن إستكمال هذه اللعبة.',
    'play while you wait': 'إلعب خلال انتظارك',
    'check games in progress': 'تفقد الألعاب الجارية حالياً',
    'are you sure you want to ignore chat messages from': 'هل ترغب بعدم إستلام رسائل من',
    'for the rest of this game?': 'حتى نهاية اللعبة؟',
    'please make sure to report any misuse of the chat at the bottom of the page.': 'الرجاء الإبلاغ عن سوء الاستخدام عند أسفل الصفحة.',
    'to undo this action, refresh the page.': 'للتراجع عن هذه العملية قم بإعادة تحميل الصفحة',
    'the players': 'اللاعبون',
    'or': 'أو',
    'please choose your partner.': 'الرجاء إختيار شريكك.',
    'to choose his partner': 'ليختار شريكه',
    'offered to partner with': 'يريد أن يشارك',
    'accepted the offer.': 'قبل الشراكة',
    'declined the offer.': 'لم يقبل الشراكة',
    'each player is automatically partnered with the opposite player': 'تم مشاركة كل لاعب مع اللاعب المقابل له اتوماتيكياً',
    'to bid': 'ليطلب',
    'please place your bid': 'إختار طلبتك',
    'has bid': 'طلب',
    'did not bid': 'لم يطلب',
    'to choose his tarneeb': 'ليختار الطرنيب',
    'please choose your tarneeb': 'الرجاء إختيار طرنيبك',
    'has chosen': 'إختار',
    'as his tarneeb': 'كطرنيب',
    'waiting for selected partner to accept': 'بإنتظار الشريك ليقبل الشراكة',
    'not your turn to bid': 'ليس دورك للطلب',
    'chat is enabled': 'الدردشة مفعلة',
    'chat is disabled': 'الدردشة غير مفعلة',
    'this game is sponsored by': 'هذه اللعبة برعاية',
    'have a chance to win in 3 great competitions!': 'لديك الآن الفرصة للفوز في ٣ مسابقات رائعة!',
    'create a new game': 'انشىء لعبة جديدة',
    'back to my game': 'العودة إلى لعبتي',
    'there are no games in progress': 'لا يوجد ألعاب جارية حالياً',
    'there are no new games': 'لا يوجد ألعاب جديدة',
    'score summary': 'ملخص النتائج',
    'redeal': 'إعادة التوزيع',
    'back to games': 'العودة إلى الألعاب',
    'second timer': 'ثانية للمؤقت',
    'hand': 'الدق',
    'score totals': 'مجموع النتائج',
    'bidder': 'الطالب',
    'the bid': 'الطلبة',
    'team': 'الفريق',
    'deal next hand': 'وزع الأوراق',
    'chat is disabled for viewers': 'الدردشة غير مفعلة للمشاهدين',
    'chat is disabled for guests': 'الدردشة غير مفعلة للضيوف',
    'chat has been disabled for this game': 'الدردشة غير مفعلة في هذه اللعبة',
    'the game is back on track': 'اللعبة جارية الآن',
    'the game can longer be continued.': 'اللعبة متوقفة كلياً',
    'this hand has finished': 'إنتهى الدق',
    'has left the game': 'غادر اللعبة',
    'has been kicked out from this game': 'قد تم طرده من اللعبة',
    'tarneeb': 'طرنيب',
    'tarneeb egyptian': 'طرنيب مصرية',
    'sounds on': 'فعل الصوت',
    'sounds off': 'أسكت الصوت',
    'sun': 'صن',
    'hokum': 'حكم',
    'ashkal': 'أشكل',
    'us': 'نحن',
    'them': 'هما',
    'buyer': 'مشتري',
    'purchase': 'شراء',
    'bunts': 'أبناط',
    'points': 'نقاط',
    'double': 'دبل',
    'double 3': 'دبل 3',
    'double 4': 'دبل 4',
    'gahwa': 'قهوة',
    'sun before': 'قبلك',
    'switch to sun': 'يقلب صن',
    'confirm hokum': 'تأكيد حكم',
    'not your turn to double': 'ليس دورك أن تدبل',
    'you already used up your two project trials': 'لقد قمت بإضافة جميع المشاريع',
    'at least one of this project\'s cards is already used in another project': 'هناك ورقة أو أكثر مستعملة في مشروع آخر',
    'shortest project is 2 cards long': 'يجب أن يكون المشروع مكون من 2 أوراق كحد أدنى',
    'shortest project is 3 cards long': 'يجب أن يكون المشروع مكون من 3 أوراق كحد أدنى',
    'baloot project must include both queen and king of the hokum suit': 'مشروع البلوت يجب أن يحتوي الشايب و البنت من نوع الحكم',
    'longest project is 5 cards long': 'يجب أن يكون المشروع مكون من 5 أوراق كحد أقصى',
    'cards in a sira project must be of the same suit': 'يجب أن تكون أوراق مشروع السرا من نفس النوع',
    'cards of a sira project must be in order': 'يجب أن تكون أوراق مشروع السرا متسلسلين',
    'cards of a khamseen project must be in order': 'يجب أن تكون أوراق مشروع الخمسين متسلسلين',
    'cards must be of the same rank in miya and arba3miya projects': 'يجب أن تكون أوراق مشاريع المية والأربعمية من نفس المنزلة',
    'rank must be 10, j, q, k or a for a miya or arba3miya project': 'يجب أن تكون أوراق مشاريع المية والأربعمية 10, J ,Q ,K ,أو A',
    'not a valid 4-card project': 'المشروع غير صحيح مع 4 أوراق',
    'cards in a miya project must be of the same suit': 'يجب أن تكون أوراق مشروع المية من نفس النوع',
    'cards of a miya project must be in order': 'يجب أن تكون أوراق مشروع المية متسلسلين',
    'heart': 'هاص',
    'diamond': 'ديمن',
    'club': 'شيريا',
    'spade': 'سبيت',
    'baloot': 'بلوت',
    'playing closed hokum, can\'t start with a card of hokum suit': 'اللعب حكم مقفل، لا تستطيع أن تبدأ بورقة من نوع الحكم',
    'must play card from hokum suit': 'يجب عليك أن تلعب ورقة من نوع الحكم',
    'must play higher than highest hokum card': 'يجب عليك أن تلعب ورقة أعلى من أعلى ورقة حكم على الأرض',
    'must specify a bid value': 'يجب عليك أن تطلب شيء',
    'invalid bid value': 'الطلبة غير قانونية',
    'not a valid suit': 'النوع غير قانوني',
    'your bid is too low': 'يجب أن تكون طلبتك أعلى',
    'you cannot pass': 'لا يجوز لك التمرير',
    'you cannot double when your partner has the highest bid': 'لا يجوز لك أن تدبل لأن أعلى طلبة لشريكك',
    'you cannot double since your partner already did': 'لا يجوز لك أن تدبل لأن شريكك دبل قبلك',
    'no trump': 'بلا طرنيب',
    'tarneebegyptian': 'طرنيب مصرية',
    'can\'t join a full game': 'لا يمكنك الإنضمام إلى لعبة ممتلئة',
    'players have left and the game is on hold. you can choose to wait for other players to join and continue or leave the game and play in another one.': 'لقد غادر اللاعبون او اللعبة متوقفة الآن. يمكنك إما أن تنتظر حتى ينضم لاعبون اخرون و تكمل اللعب، أو تغادر و تلعب في في لعبة أخرى.',
    'leave': 'غادر',
    'wait for players': 'إنتظر لاعبين اخرين',
    'do you want to accept': 'هل تقبل',
    'as your partner?': 'كشريك؟',
    'as your brother?': 'كخويك؟',
    'please choose your brother': 'الرجاء إختيار خويك',
    'please choose your partner': 'الرجاء إختيار شريكك',
    'would you like to double the hand?': 'هل تريد أن تدبل الدق؟',
    'bas': 'بس',
    'are you sure you want to add this project?': 'هل أنت متأكد من هذا المشروع؟',
    'choose your hokum suit': 'إختار نوع الحكم',
    'buying round': 'جولة شراء',
    'current purchase': 'الشراء الحالي',
    'buy': 'اشتري',
    'your purchase': 'شراءك',
    'turn': 'دور',
    'suit': 'النوع',
    'game': 'اللعبة',
    'current bid': 'الطلبة الحالية',
    'your bid': 'طلبتك',
    'choose your tarneeb': 'إختار طرنيبك',
    'please choose the next hand': 'الرجاء اختيار اللعبة التالية',
    'you are about to complain about': 'أنت بصدد تقديم شكوى عن',
    'please provide the reason of your complaint': 'الرجاء تزويدنا بسبب الشكوى',
    'use of bad language': 'إستخدام كلمات مسيئة في الدردشة',
    'cheating or multiple accounts': 'الغش أو حسابات متعددة',
    'slow or annoying play': 'لعب بطيء أو مزعج',
    'other': 'سبب آخر',
    'you will be able to see': 'بإمكانك ملاحظة',
    'you can cancel your complaint about': 'بإمكانك إلغاء شكوتك عن',
    'by clicking': 'بالنقر',
    'complain': 'اشتكي',
    'cancel': 'إلغاء',
    'next to': 'بجانب',
    'you will also be able to see the games that': 'بإمكانك أيضاً معرفة الألعاب التي يشارك',
    'again': 'مرة أخرى',
    'you can now see': 'بإمكانك ملاحظة',
    'because': 'لأن',
    'is now one of your favorite players': 'أصبح أحد اللاعبين المفضلين لديك',
    'you can remove': 'بإمكانك حذف',
    'from your favorite players by clicking': 'من قائمة اللاعبين المفضلين لديك بالنقر على',
    'is playing in': 'فيها',
    'this game can no longer be continued, because no one can join and the computer does not continue.': 'لا يمكن إستكمال هذه اللعبة لأن الكمبيوتر لا يكمل و لا يستطيع أي لاعب الإنضمام في وسط اللعبة.',
    'back to games': 'العودة إلى صفحة الألعاب',
    'connection has timed out because your internet connection is slow. you can continue playing, but you may face more connectivity issues. if this problem persists, please contact your internet service provider to resolve this issue.': 'لقد إنقطع الاتصال بسبب بطء خط الانترنت لديك. بإمكانك الاستمرار باللعب، لكنه من الممكن أن تواجه المزيد من الصعوبات التقنية. إذا استمرت هذه المشكلة، الرجاء الإتصال بمزود الإنترنت لديك لحل المشكلة.',
    'can\'t join a full game': 'لا يمكنك الإنضمام إلى لعبة ممتلئة',
    'please send an': 'الرجاء إرسال',
    'send an': 'إرسال',
    'with the number': 'بالرقم',
    'from your mobile phone to': 'من هاتفك الخلوي إلى',
    'to': 'إلى',
    'from your mobile': 'من هاتفك الخلوي',
    'to enter the competition': 'للدخول في المسابقة',
    'to unlock the next hand': 'للإنتقال إلى الدق التالي',
    'this sms costs': 'تكلفة هذه الرسالة',
    'are you sure? this will deduct': 'هل انت متأكد؟ سيخصم منك',
    'tokens from your account.': 'نقطة.',
    'back to competition': 'العودة إلى المسابقة',
    'the table below shows the standings for the competition.': 'الجدول التالي يبين ترتيب اللاعبين في المسابقة',
    'good luck!': 'نتمنى لك حظاً سعيداً!',
    'your score is': 'نتيجتك',
    'you are currently ranked': 'ترتيبك',
    'you need more tokens to retry hand': 'تحتاج إلى المزيد من النقاط لإعادة التوزيع',
    'in the competition.': 'في المسابقة.',
    'rank': 'التصنيف',
    'score': 'النتيجة',
    'name': 'الإسم',
    'pass': 'تمرير',
    'go': 'ابدأ اللعب',
    'yes': 'نعم',
    'no': 'لا',
    'hokum suit': 'نوع الحكم',
    'bid': 'اطلب',
    'zain': 'زين',
    'orange': 'اورنج',
    'umniah': 'أمنية',
    'jd': 'دينار',
    'after this redeal, you will have': 'بعد إعادة التوزيع التالية سيتبقى لديك',
    'redeals remaining.': 'توزيعة',
    'after this redeal, you will have': 'بعد إعادة التوزيع سيكون عندك',
    'redeals remaining.': 'محاولة إعادة توزيع.',
    'you cannot redeal anymore.': 'لا يمكنك إعادة توزيع الورق.',
    'to redeal': 'لإعادة توزيع الورق',
    'sms': 'رسالة قصيرة',
    'and the sum of your highest 3 scores is': 'و مجموع أعلى 3 نتائج لك',
    'trix partner': 'تركس شراكة',
    'trix complex partner': 'تركس كمبلكس شراكة',
    'estimation': 'إستميشن',
    'tarneeb syrian 41': 'طرنيب سوري 41',
    'basra': 'باصرة',
    'leekha': 'ليخة',
    'trixpartner': 'تركس شراكة',
    'trixcomplexpartner': 'تركس كمبلكس شراكة',
    'tarneebsyrian41': 'طرنيب سوري 41',
    'tarneeb syrian41': 'طرنيب سوري 41',
    'bids': 'طلبات',
    'doubled': 'مضاعف',
    'eaten': 'الأكلات',
    'bunts': 'أبناط',
    'select 3 cards to pass to the person on your left by clicking on each card.': 'إختر 3 أوراق لتمريرهن للشخص على يسارك بالنقر على كل ورقة.',
    'are you sure you want to pass these 3 cards?': 'هل أنت متأكد أنك تريد تمرير هذه 3 أوراق؟',
    'dealer': 'الموزع',
    'leader': 'الأول',
    'wala': 'ولا',
    'closed': 'مقفل',
    'open': 'مفتوح',
    'floor': 'الأرض',
    'projects': 'المشاريع',
    'sira': 'سرا',
    'miya': 'مية',
    'khamseen': 'خمسين',
    'arba3miya': 'أ ربعمية',
    'add': 'أضف',
    'remaining projects': 'مشاريع متبقية',
    'you have selected more projects than those allowed.': 'لقد اخترت أكثر من مشروعين',
    'timer must be a value between 8 and 60 seconds': 'المؤقت يجب أن يكون بين 8 و 60 ثانية',
    'play again': 'إلعب مرة أخرى',
    'or': 'أو',
    'use credit': 'إستعمل نقطة رصيد',
    'credits remaining': 'نقاط رصيد متبقية',
    'vodafone': 'فودافون',
    'mobinil': 'موبينيل',
    'etisalat': 'إتصالات',
    'egp': 'جنيه',
    'to redeal, you can either': 'لإعادة توزيع الأوراق، يمكنك إما',
    'you have': 'لديك',
    'use tokens': 'إستعمال النقاط',
    'for this redeal': 'لمرة واحدة',
    'for the entire game': 'لكل اللعبة',
    'marks are used to rank you among your peers in each type of games': 'العلامات تستخدم لترتيبك ضمن اللاعبين في كل لعبة',
    'you must play a game from the beginning to the end to get marks, and must get a good position in that game': 'عليك أن تلعب لعبة من البداية حتى النهاية لتحصل على علامات، كما أن نتيجتك في اللعبة يجب أن تكون جيدة',
    'the harder the game is, and the higher position you get, the more marks you get': 'كلما كانت اللعبة أصعب، وكلما كانت نتيجتك أفضل، كلما حصلت على علامات أكثر',
    'you have obtained': 'لقد حصلت على',
    'marks': 'علامة',
    'marks in this game': 'علامة في هذه اللعبة',
    'you have obtained no marks since you were not in this game from the beginning': 'لم تحصل على علامات لأنك من تكن في اللعبة منذ بدايتها',
    'you have obtained no marks since you did not play well in this game': 'لم تحصل على علامات لأنك لم تلعب بشكل جيد في هذه اللعبة',
    'this game has ended.': 'لقد انتهت اللعبة.',
    'you will be contacted by email with the results.': 'سيتم إرسال النتيجة إلى بريدك الإلكتروني.',
    'please check the email you registered with within a few hours.': 'الرجاء تفقد صندوق البريد الإلكتروني الذي سجلت فيه في غضون بضع ساعات.',
    'you cannot kick out people in this type of games': 'لا يمكنك طرد اللاعبين في هذا النوع من الألعاب',
    'you are banned from chatting for 1 day': 'أنت محظور من إستخدام صندوق المحادثة لمدة يوم واحد',
    'the better you play the more marks you will earn.': 'كلما لعبت بمستوى أفضل كلما حصلت على علامات إضافية',
    'you must finish a game in order to earn marks. you do not necessarily need to be in the game from the beginning, you can  join a game in the middle and finish it till the end and still earn marks.': ' يجب عليك أن تكمل لعبة لتحصل على علامات. بإمكانك الإنضمام إلى لعبة في منتصفها وستحصل على علامات إذا بقيت في اللعبة حتى نهايتها',
    'even though you will earn marks for joining games in the middle, join games from beginning to end to earn more marks.': ' ستحصل على علامات إذا انضممت إلى لعبة في منتصفها، لكن إنضم إلى الألعاب من البداية حتى النهاية لتحصل على علامات أكثر',
    'we also take into consideration the rank of other players in the game. you will earn more marks for beating better players.': 'يتم أيضاً أخذ مستوى اللاعبين الاخرين في اللعبة بعين الإعتبار. ستحصل على علامات أكثر إذا فزت على لاعبين ذو تصنيف أعلى',
    'you must keep playing in order to retain and improve your rank. if you don\'t play for a long time your rank will go down.': 'يجب عليك أن تستمر باللعب لتحافظ على مرتبتك و تزيدها. إذا لم تلعب لفترة طويلة ستنخفض مرتبتك',
    'how does ranking work?': 'كيف يتم التصنيف؟',
    'your mark in': 'علامتك في',
    'is': 'هي',
    'in': 'في',
    'join games from the begining to get more marks.': 'إنضم إلى الألعاب من البداية لتحصل على علامات أكثر.',
    'too talkative (ignore chat)': 'كثير التحدث (أهمل ما يقوله)',
    'you have obtained enough marks to maintain your current rank of': 'لقد حصلت على علامات كافية للمحافظة على مرتبتك الحالية، و هي',
    'avoid': 'أفويد',
    'with': 'وذ',
    'risk': 'رسك',
    'dash call': 'داش كول',
    'double risk': 'دبل رسك',
    'with risk': 'وذ رسك',
    'with double risk': 'وذ دبل رسك',
    'doubled 10 of diamonds': 'عشرة دياموندز مضاعفة',
    'doubled queen of spades': 'ملكة سبيدز مضاعفة',
    'eaten queen of spades': 'ملكة سبيدز مأكولة',
    'eaten 10 of diamonds': 'عشرة دياموندز مأكولة',
    'eaten doubled 10 of diamonds': 'عشرة دياموندز مضاعفة مأكولة',
    'eaten doubled queen of spades': 'ملكة سبيدز مضاعفة مأكولة',
    'doubled king of hearts': 'شيخ الكبة مضاعف',
    'eaten doubled king of hearts': 'شيخ الكبة مضاعف مأكول',
    'eaten king of hearts': 'شيخ الكبة مأكول',
    'eaten queen': 'ملكة مأكولة',
    'eaten doubled queen': 'ملكة مضاعفة مأكولة',
    'doubled queen': 'ملكة مضاعفة',
    'click on 3 cards to pass, then click on "pass cards" when done.': 'إضغط على 3 أوراق للتمرير ثم إضغط على زر "مرر الأوراق" أدناه',
    'click a card to double it then click "ready" when done.': 'إضغط على ورقة لتضاعفها ثم إضغط على زر "جاهز" عند الانتهاء',
    'would you like to redeal or avoid?': 'هل تريد إعادة توزيع الأوراق أو أفويد؟',
    'avoid': 'أفويد',
    'would you like to dash call or bid?': 'هل تريد إعلان داش كول أو الطلب؟',
    'dash call': 'داش كول',
    'your estimate': 'الأكلات المقدرة',
    'estimate': 'حدد الأكلات المقدرة',
    'turn chat off': 'تجاهل الدردشة',
    'turn chat on': 'إعادة الدردشة',
    'no projects': 'لا يوجد مشاريع',
    'name': 'الإسم',
    'final score': 'النتيجة النهائية',
    'are you sure you want to double this card?': 'هل أنت متأكد بأنك ترغب بمضاعفة هذه الورقة؟',
    'expand cards': 'وسّع الأوراق',
    'collapse cards': 'ضيّق الأوراق',
    'you': 'أنت',
    'beginner': 'مبتدئ',
    'intermediate': 'متوسط',
    'advanced': 'محترف',
    'hover the mouse over any section for more detailed ranking information': 'حرك الفأرة فوق أي قسم لمعرفة المزيد عن تقسيم التصنيف',
    'congratulations! you have improved by': 'مبروك! لقد تحسنت بنسبة',
    'in this game to become': 'في هذه اللعبة لتصبح',
    'overall in': 'بشكل عام في',
    'your rank is now': 'تصنيفك أصبح الآن',
    'you have maintained your rank of': 'لقد حافظت على تصنيفك وهو',
    'sit here': 'إجلس هنا',
    'change game parameters': 'تغيير تفاصيل اللعبة',
    'game admin': 'مشرف اللعبة',
    'submit': 'أرسل',
    'member since': 'عضو منذ',
    'this is a test feature for limited users only. if you would like to participate in testing': 'هذه الميزة تحت التجربة عند عدد محدود من اللاعبين. إذا اردت الاشتراك في مرحلة التجريب',
    'contact us': 'إتصل بنا',
    'join the game to access this view': 'إنضم الى اللعبة لإستعمال هذا العرض',
    'you can only change seats once per game': 'يمكنك تغيير مقعدك مرة واحدة فقط في كل لعبة',
    'kick out': 'اطرد',
    'this is a password-protected game. please provide the password:': 'هذه لعبة خاصة. الرجاء إدخال كلمة السر:',
    '3 to 10 characters': 'بين 3 و 10 أحرف',
    'used to verify account': 'سيستخدم لتفعيل حسابك',
    '4 to 40 characters': 'بين 4 و 40 حرف',
    'must match the above': 'يجب أن يطابق أعلاه',
    'passwords do not match': 'كلمة السر غير مطابقة',
    'already taken': 'مستخدم من لاعب آخر',
    'ok': 'تمام',
    'checking ...': 'التأكد...',
    'invalid format': 'الشكل غير صحيح',
    'cannot be empty': 'لا يجوز أن يكون فارغاً',
    'must be accepted': 'يجب أن تكون مقبولة',
    'forgot password?': 'نسيت كلمة السر؟',
    'invalid characters used': 'تم إستخدام أحرف ممنوعة',
    'new account?': 'حساباً جديداً؟',
    'sign up!': 'إشترك الآن!', 
    'this feature is part of jawaker basha. if you are interested': 'هذه الميزة جزء من ميزات جواكر باشا. إذا كنت مهتماً',
    'private game?': 'لعبة خاصة؟',
    'minimum rank': 'أدنى تصنيف مسموح',
    'password': 'كلمة السر',
    'email': 'البريد الإلكتروني',
    'any': 'بلا',
    'add friend': 'أضف كصديق',
    'delete': 'حذف',
    'uninvite': 'إزالة الدعوة',
    'accept friend': 'قبول الصداقة',
    'block': 'حجب اللاعب',
    'unblock': 'إزالة الحجب',
    'complain': 'تقديم شكوى',
    'actions': 'الإجراءات',
    'refuse': 'رفض الدعوة',
    'friends online': 'صديق على الموقع',
    'game chat': 'دردشة اللعبة',
    'went offline': 'غادر الموقع',
    'went online': 'دخل الموقع',
    'cards': 'أوراق',
    'bonus': 'نقاط إضافية',
    'total': 'المجموع',
    'learn more': 'إعرف المزيد',
    'invalid code': 'رمز غير صحيح',
    'this feature is part of jawaker basha. find your friends online and chat with them privately anywhere on jawaker. if you are interested': 'هذه الميزة جزء من ميزات جواكر باشا. تشمل إمكانية إيجاد أصدقائك على الموقع والدردشة معهم مباشرةً من أي صفحة على جواكر. إذا كنت مهتماً',
    'this feature is part of jawaker basha. see the full profile of all players at the table. if you are interested': 'هذه الميزة جزء من ميزات جواكر باشا. تفقد جميع معلومات اللاعبين الآخرين على الطاولة. إذا كنت مهتماً',
    'this feature is part of jawaker basha. kick people out, change your seat and change the game parameters. if you are interested': 'هذه الميزة جزء من ميزات جواكر باشا. تشمل إمكانية طرد اللاعبين وإمكانية تغيير مقعدك وتعديل تفاصيل اللعبة. إذا كنت مهتماً',
    'hours': 'ساعة',
    'days': 'يوم',
    'game name is too short': 'إسم اللعبة قصير',
    'you can\'t set the rank limit above your own rank': 'لا يمكنك تحديد مستوى اللعبة الى مستوى أعلى من تصنيفك',
    'emails do not match': 'البريد الالكتروني غير مطابق',
    'cards left': 'أوراق متبقية',
    'you cannot chat with friends in the same game': 'لا يمكنك الدردشة مع اصدقاءك في نفس اللعبة',
    'you must have a basha account to initiate chat': 'يجب أن يكون حسابك باشا لبدء الدردشة',
    'invite your friends to play with you on jawaker!<br />choose your email provider then enter the username and password of your email account.': 'اعزم أصدقاءك للعب معك على جواكر!<br /> إختر مزود الخدمة لديك ومن ثم إدخل إسم الستخدم وكلمة السر التابعين لحساب بريدك الالكتروني',
    'invite': 'اعزم',
    'this feature has been disabled for competition games': 'تم إيقاف هذه الميزة في ألعاب المسابقات',
    'this feature has been disabled for competition games': 'تم إيقاف هذه الميزة في ألعاب المسابقات',
    'congratulations, you are the winner of this competition.': 'ألف مبروك، لقد فزت في هذه المسابقة',
    'click on the following link to go back to the competition page': 'إضغط هنا لتذهب إلى صفحة المسابقة',
    'congratualtions, you have won in the current round of the competition and have qualified to the next round': 'ألف مبروك، لقد فزت في هذه الجولة من المسابقة و تأهلت الى الجولة القادمة',
    'click on the following link to know when your next game will be': 'اضغط هنا لتعرف متى ستكون لعبتك القادمة',
    'unfortunately, you lost in the final round of the competition': 'لسوء الحظ، لقد خسرت في الجوالة النهائية من المسابقة',
    'you lost in the current round of the competition, but you still have a chance to qualify to the next round !': 'لسوء الحظ، لقد خسرت في هذه الجولة من المسابقة. لكن لا تزال لديك الفرصة لتتأهل إلى الجولة القادمة !',
    'the jawaker team will contact you soon.': 'فريق جواكر سيتصل بك في أقرب وقت',
    'you can get an argeelah for yourself or gift it to someone on the table for only 35 tokens': 'بإمكانك الحصول على أرجيلة لنفسك أو تهديها لشخص على الطاولة مقابل 35 توكنز فقط',
    'players with an argeelah can use it by hovering their mouse over it!': 'اللاعب الذي يملك أرجيلة على الطاولة بإمكانه أن يستخدمها بتمرير الفأرة من فوقها!',
    'get now': 'إحصل عليها الآن',
    'you gave yourself': 'لقد أهديت لنفسك',
    'you gave': 'لقد أهديت',
    'gave you': 'أهداك',
    'gave themselves': 'أهدى نفسه',
    'gave': 'أهدى',
    'a gift': 'هدية',
    'recipient already has gift': 'المهدى له يمتلك هدية من قبل',
    'you don\'t have enough tokens': 'ليس لديك توكنز كافية',
    'this game will automatically start after 15 minutes or when all the players join': 'ستبدأ هذه المسابقة تلقائياً بعد 15 دقيقة أو عندما ينضم جميع اللاعبين',
    'you have been banned from chatting for 1 day due to using bad language.': 'لقد تم حظرك من الدردشة لمدة يوم واحد بسبب إستخدام كلمات بذيئة.',
    'ranking result (last 30 days)': 'نتيجة التصنيف (في أخر 30 يوم)',
    'you must finish at least 15 games in the last 30 days to get ranked': 'يجب أن تلعب 15 لعبة كحد أدنى خلال آخر 30 يوم لكي تدخل نظام التصنيف',
    'games won': 'مرة فوز',
    'games completed': 'مرة إكمال لعبة',
    'games left': 'مرة مغادرة',
    'loading ...': 'جاري التحميل ...',
    'congratulations, you won!': 'مبروك ، لقد فزت!',
    'you have been rewarded': 'لقد تم اعطاءك',
    'you have unforutantely lost.': 'للأسف، لقد خسرت.',
    'nevertheless, you have been given': 'رغم ذلك، تم اعطاءك',
    'congratulations! you scored': 'مبروك! لقد سجلت',
    'goals for': 'هدف لصالح',
    'become a fan of': 'شجًع فريق',
    'support your favorite team by scoring goals for them! every jawaker game you win will score goals for your team!': 'إدعم فريقك المفضل بتسجيل أهداف لهم! كل لعبة تفوزها على جواكر ستسجل أهداف لفريقك!',
    'you can increase the goals you score for your team with each win by increasing your multiplier': 'بإمكانك تسجيل أهداف أكثر عند فوزك بلعبة بزيادة قوة المضاعفة',
    'if you become the top scorer for your team and your team wins on jawaker when the time is up, you will get': 'إذا فاز فريقك على جواكر عند إنتهاء الوقت وكنت الهداف الاول لدى الفريق ستربح',
    'i would like to score': 'أريد أن اسجل',
    'each time i win a game on jawaker': 'عند فوزي بلعبة على جواكر',
    'become a fan': 'شجًع الفريق',
    'you currently have': 'لديك حالياً',
    'buy more tokens': 'إحصل على المزيد من التوكنز',
    'rules': 'شروط',
    'in return for': 'مقابل',
    'cappuccino':'كباشينو',
    'coffee':'قهوة',
    'arabic coffee':'قهوة عربية',
    'cupcake':'كعكة فراولة',
    'falafel pieces':'فلافل حب',
    'falafel sandwich':'ساندويش فلافل',
    'french fries':'بطاطا مقلية',
    'hamburger':'همبرغر',
    'black hatta':'حطة سوداء',
    'red hatta':'حطة حمراء',
    'white hatta':'حطة بيضاء',
    'hommus':'حمص',
    'apple argileh':'أرجيلة تفاحتين',
    'grape argileh':'أرجيلة عنب',
    'lemon argileh':'أرجيلة ليمون',
    'melon argileh':'أرجيلة شمام',
    'strawberry argileh':'أرجيلة فراولة',
    'watermelon argileh':'أرجيلة بطيخ',
    'cherry argileh':'أرجيلة كرز',
    'ice cream':'بوظة',
    'mansaf':'صدر منسف',
    'masbaha':'مسبحة',
    'mint tea':'شاي مع نعنع',
    'tea':'شاي',
    'msakhan':'صحن مسخن',
    'muffin':'كعكة شوكولا',
    'popcorn':'بوشار',
    'shawerma':'شاورما',
    'skull ring':'خاتم الجمجمة',
    'world cup':'كأس العالم',
    'slush':'شراب سلاش',
    'turkey sandwich':'ساندويش حبش',
    'algeria':'الجزائر',
    'argentina':'الأرجنتين',
    'australia':'أستراليا',
    'uruguay':'اورجواي',
    'italy':'إيطاليا',
    'france':'فرنسا',
    'brazil':'البرازيل',
    'south africa':'إفريقيا الجنوبية',
    'mexico':'المكسيك',
    'ghana':'غانا',
    'ivory coast':'ساحل العاج',
    'nigeria':'نجيريا',
    'cameroon':'الكاميرون',
    'japan':'اليابان',
    'south korea':'كوريا الجنوبية',
    'north korea':'كوريا الشمالية',
    'england':'إنجلترا',
    'netherlands':'هولندا',
    'spain':'إسبانيا',
    'serbia':'الصرب',
    'denmark':'الدنمارك',
    'germany':'ألمانيا',
    'switzerland':'سويسرا',
    'slovakia':'سلوفاكيا',
    'portugal':'البرتغال',
    'greece':'اليونان',
    'slovenia':'سلوفينيا',
    'paraguay':'بارغواي',
    'chile':'شيلي',
    'honduras':'هوندوراس',
    'new zealand':'نيوزيلندا',
    'united states':'أمريكا',
    'my items': 'مشترياتي',
    'special': 'مميز',
    'food': 'طعام',
    'drink': 'مشروبات',
    'clothing': 'ثياب',
    'smoke': 'دخان',
    'worldcup flags': 'أعلام كأس العالم',
    'buy tokens': 'اشتري توكنز',
    'click on any item above to get more information here': 'إنقر على أي بضاعة في الأعلى للحصول على المزيد من المعلومات هنا',
    'you have no goods.': 'ليس لديك مشتريات',
    'price': 'السعر',
    'this good is valid till the end of this game': 'هذه البضاعة صالحة حتى نهاية اللعبة فقط',
    'this good is valid': 'هذه البضاعة صالحة',
    'for': 'لمدة',
    'forever': 'للأبد',
    'days': 'يوم',
    'remaining': 'متبقي',
    'you already own this good': 'أنت تمتلك هذه البضاعة',
    'this good is sold out': 'تم بيع هذه البضاعة بالكامل',
    'get this good for': 'اشتري هذه البضاعة ل',
    'place it on table': 'ضعها على الطاولة',
    'buy': 'اشتري',
    'two weeks basha subscription': 'إشتراك جواكر باشا لمدة اسبوعين',
    'by becoming a fan you will receive the': 'إذا أصبحت مشجعاً ستحصل على',
    'flag of your team': 'علم فريقك',
    'on the jawaker store': 'في متجر جواكر',
    'we\'re sorry, this feature is temporarily unavailable.': 'نأسف، هذه الميزة غير متوفرة لفترة محدودة.',
    '** only public games in which you play from beginning to end and do not change your seat will be counted as goals.': 'فقط الألعاب العامة التي تكملها من البداية حتى النهاية من دون تغيير مقعدك ستحسب كاهداف.',
    'you do not have enough tokens': 'ليس لديك توكنز كافية',
    'sign up now!': 'إشترك الآن!',
    'did you like this game?': 'هل أعجبتك اللعبة؟',
    'create game': 'انشئ اللعبة',
    'the options below are part of Jawaker Basha. create a private game for your friends or limit the players who join by rank. if you are interested': 'الخيارات أدناه جزء من جواكر باشا. أنشئ لعبة خاصة مع أصدقائك أو قم بتحديد أدنى تصنيف مسموح باللعبة. إذا كنت مهتماً',
    'login': 'سجل الدخول',
    'sign up': 'اشترك الآن',
    'you cannot create games without being logged in.': 'يجب عليك تسجيل الدخول لإنشاء ألعاب جديدة.',
    'already have an account?': 'هل تملك حسابا مسبقا؟',
    'congratulations! you received a fanoos': 'مبروك، لقد حصلت على فانوس',
    'amar el-deen drink': 'مشروب قمر الدين',
    'amar el-deen': 'قمر الدين',
    'atayef': 'قطايف',
    'dates': 'تمر',
    'fatoosh': 'فتوش',
    'kanafeh': 'كنافة',
    'ramadan decorations': 'زينة رمضانية',
    'soup': 'شوربة',
    'water': 'ماء',
    'ramadan': 'رمضان',
    'unfortunately, you lost in the current round of the competition.': 'لسوء الحظ، لقد خسرت في هذه الجولة من المسابقة.'
  };

  this._ = function () {
    if (arguments[0].toString().toLowerCase() in dict && window.location.pathname.substring(0,3) != "/en") {
      return dict[arguments[0].toString().toLowerCase()];
    } else {
      return arguments[0];
    }
  };

  this.lang = function() {
    try {
      if (window.location.pathname.substring(0,3) == "/en") { return 'en'; }
      return 'ar';
    } catch(e) { return 'ar'; }
  };

}
G = new getText();
var Chat = new (function() {

  var Pane = function(id, n, type) {

    this.type = type; // 0: player to player, 1: game chat, 2: online friends
    this.id = id;
    this.n = n;

    if (type == 0 || type == 1) {
      // Setup markup and references
      this.title = new Element("div");
      this.content = new Element("div");
      this.title.addClassName('paneTitle');
      if (type == 1) { n = G._('Game Chat'); }
      this.title.update("<div class='leftPaneTitle'>" + n + "</div><div class='rightPaneTitle'></div>");
      this.link = new Element("a");
      this.title.select(".rightPaneTitle").first().update(this.link);
      var table = new Element("table");
      table.addClassName('chat');
      table.update(new Element("tbody"));
      this.content.update(table);
      this.content.setStyle({height: '0px'});
      this.content.addClassName('paneContent');
      this.title.setStyle({display: 'none'});
      $("friendsList").insert({after: this.content});
      $("friendsList").insert({after: this.title});
      var fullTitleHeight = this.title.getHeight();
      this.title.setStyle({height: '0px', display: 'block'});
      animation(this.title).from('height', 0).to('height', fullTitleHeight).go();
    } else if(type == 2) {
      this.title = $("friendsTitle");
      this.content = $("friendsList");
    }

    // Setup events
    this.title.observe('click', function(ev) {
      if (Chat.activePane == this && Chat.defaultPane != this) {
        Chat.activateDefaultPane();
      } else {
        Chat.activatePane(this);
      }
      ev.stop();
    }.bind(this));
    if (type == 2 && User.hasFeature('chat')) { // TODO: maybe this doesn't belong here
      $("friendsInfo").observe('click', function(ev) {
        if (Chat.activePane == this && Chat.defaultPane != this) {
          Chat.activateDefaultPane();
        } else {
          Chat.activatePane(this);
        }
        ev.stop();
      }.bind(this));
    }
    if (type == 0) {
      this.link.update("[x]");
      this.link.observe('click', function(ev) {
        this.close();
        ev.stop();
      }.bind(this));
    } else if (type == 1) {
      /* // Temporary out
      this.link.update("[off]");
      this.link.observe('click', function(ev) {
        // TODO: do something
        ev.stop();
      }.bind(this));
      */
    }

    // Methods
    this.addLine = function(person, text) {
      if (this.type == 2) { return; }
      if (person) {
        this.content.select("tbody").first().insert("<tr><td><span>" + person + ":</span> " + text + "</td></tr>");
      } else {
        this.content.select("tbody").first().insert("<tr><td><strong>(" + text + ")</strong></td></tr>");
      }
      // Scroll to end
      this.content.scrollTop = this.content.scrollHeight;
    };

    this.addItem = function(pid, login) {
      if (this.type != 2) { return; }
      var li = new Element("li");
      li.update("<img src='/images/icon-friend.png' />" + login);
      this.content.select("ul").first().insert(li);
      return li;
    };

    this.highlight = function(b) {
      if (b) {
        this.title.addClassName('highlightTitle');
      } else {
        this.title.removeClassName('highlightTitle');
      }
    };

    this.close = function() {
      if (this.type != 0) { return; }
      this.title.stopObserving();
      this.link.stopObserving();
      Chat.panes.unset(this.n);
      if (this == Chat.activePane) {
        Chat.activateDefaultPane(function() {
          this.content.remove();
        }.bind(this));
      } else {
        Chat.activatePane(Chat.activePane);
        this.content.remove();
      }
      animation(this.title).to('height', 0).checkpoint(1.0, function() { this.title.remove(); }.bind(this)).go();
    };

  };

  var allHeight;
  var cached = {};
  var highlightYellowTimer;
  this.panes = $H({});
  this.activePane;
  this.defaultPane;
  this.onlinePlayerStructures = $H({}); // Hash from player ids to an array of [login, list-item, pane].

  this.addPane = function(id, n, type, dontReflow) {
    var p = new Pane(id, n, type);
    this.panes.set(n, p);
    if (!dontReflow && this.activePane) { this.activatePane(this.activePane) }; // To reflow
  };

  var freeHeight = function() { // TODO: can cache parts of this since they aren't expected to change.
    var res = allHeight - (cached['friendsTitle'].getHeight() * (this.panes.keys().length) + cached['friendsInfo'].getHeight() + cached['chatSendArea'].getHeight());
    res -= (this.panes.keys().length - 1) * 4; // TODO: hack, there is an unexplainable padding of 2px around every tbody
    return res;
  }.bind(this);

  this.setup = function() {
    this.loaded = true;
    if (User.hasFeature('chat')) {
      $("friendsInfo").update("0 " + G._('Friends Online'));
      $("friendsInfo").addClassName("linkLike");
    } else {
      $("friendsInfo").update(G._('This feature is part of Jawaker Basha. Find your friends online and chat with them privately anywhere on Jawaker. If you are interested') + " <a href='/" + G.lang() + "/basha' target='_blank'>" + G._('learn more') + "</a>");
      $("friendsInfo").addClassName("comingsoon");
    }
    allHeight = $("chatContainer").getHeight() - 4; // getHeight() returns external height, we need the internal one.
    ['friendsTitle', 'friendsInfo', 'chatSendArea'].each(function(n) {
      cached[n] = $(n);
    });
    this.addPane(0, "online-friends", 2);
    this.defaultPane = this.panes.get(typeof Game == "undefined" ? "online-friends" : "game-chat");
    this.activateDefaultPane();
    $('chatMsg').observe('keydown', function(ev) {
      if (ev.keyCode == Event.KEY_RETURN) {
        Chat.sendChat();
        ev.stop();
      }
    });
    $('submitChat').observe('click', function(ev) { Chat.sendChat(); ev.stop(); });
    if (User.info['id']) { Comm.enqueue('friends', 'online_friends', [(typeof Game == "undefined" ? 0 : Game.gid)]); }
  };

  this.route = function(id, target, person, text) {
    if (!this.panes.get(target)) {
      this.addPane(id, target, 0);
    }
    var p = this.panes.get(target);
    p.addLine(person, text);
    if (p == this.activePane) {
      if (person != User.info.login && !isFB) { Sound.play("chat"); } // TODO: generalize the isFB. Use instead a hideChat variable; not playing this sound when chat is hidden.
    } else {
      if (!p.title.hasClassName('highlightTitle')) {
        Sound.play("new_chat_panel");
      }
      p.highlight(true);
    }
  };

  this.statusChange = function(id, login, online) {
    if (online) {
      if (!this.onlinePlayerStructures.get(id)) {
        var li = this.panes.get("online-friends").addItem(id, login);
        li.addClassName('goingOnline');
        this.onlinePlayerStructures.set(id, [login, li]);
        setTimeout(function() { li.removeClassName('goingOnline'); }, 4000);
        Sound.play("logon");
        if (this.panes.get(login)) {
          this.panes.get(login).addLine(false, login + " " + G._('went online'));
          if (this.activePane.id == id) {
            this.enableSubmit(true);
          }
        }
        li.observe('click', function(ev, pid, plogin) {
          if (!this.panes.get(plogin)) { this.addPane(pid, plogin, 0, true); }
          this.activatePane(this.panes.get(plogin));
          ev.stop();
        }.bindAsEventListener(this, id, login));
      }
    } else {
      var ops;
      if (ops = this.onlinePlayerStructures.get(id)) {
        var li = ops[1];
        login = ops[0];
        li.addClassName('goingOffline');
        setTimeout(function() { li.remove(); }, 4000);
        this.onlinePlayerStructures.unset(id);
        Sound.play("logoff");
        if (this.panes.get(login)) {
          this.panes.get(login).addLine(false, login + " " + G._('went offline'));
          if (this.activePane.id == id) {
            this.enableSubmit(false);
          }
        }
      }
    }
    $("friendsInfo").update(this.onlinePlayerStructures.keys().length + " " + G._('Friends Online'));
    $("friendsInfo").addClassName('highlightYellow');
    if (highlightYellowTimer) { clearTimeout(highlightYellowTimer); }
    highlightYellowTimer = setTimeout(function() { $("friendsInfo").removeClassName('highlightYellow'); }, 4000);
  };

  this.activatePane = function(p, cb) {
    var cbUsed = false;
    if (Object.isUndefined(cb)) { cb = function() {}; }
    if (this.activePane != p) {
      if (this.activePane) {
        animation(this.activePane.content).to('height', 0).checkpoint(1.0, cb).go();
        cbUsed = true;
      }
      p.highlight(false);
    }
    // Disable or enable submit
    if (p.type == 0) {
      this.enableSubmit(this.onlinePlayerStructures.get(p.id) || !User.hasFeature('chat')); // TODO: the or part is a hack to allow non-basha users to respond
    } else if (p.type == 1) {
      this.enableSubmit(this.gameEnableSubmit());
    } else if (p.type == 2) {
      this.enableSubmit(false);
    }
    var tempAnimation = animation(p.content).to('height', freeHeight());
    if (!cbUsed) { tempAnimation = tempAnimation.checkpoint(1.0, cb); }
    tempAnimation.go();
    this.activePane = p;
  };

  this.activateDefaultPane = function(cb) {
    return this.activatePane(this.defaultPane, cb);
  };

  this.sendChat = function() {
    var txt = $F("chatMsg").replace(/^[\s]*/, "").replace(/[\s]*$/, "");
    if (txt.length === 0) { return; }
    switch(this.activePane.type) {
      case 0:
        if (!this.onlinePlayerStructures.get(this.activePane.id) && User.hasFeature('chat')) { // TODO: the and part is a hack to allow non-basha users to respond
          return; // Friend is no longer online
        }
        Comm.enqueue('friends', 'send_chat', [(typeof Game == "undefined" ? 0 : Game.gid), this.activePane.id, txt]);
        this.route(0, this.activePane.n, User.info.login, txt);
        break;
      case 1:
        if (!this.gameSendChat(txt)) { return; }
        break;
      case 2:
        return;
    }
    $("chatMsg").clear();
  };

  Comm.registerCallback('updateFriends', function(res) {
    if (!Chat.loaded) { return; }
    res.each(function(ev) {
      switch(ev[0]) {
        case "chat":
          this.route(ev[1], ev[2], ev[2], ev[3]);
          break;
        case "online":
          this.statusChange(ev[1], ev[2], true);
          break;
        case "offline":
          this.statusChange(ev[1], "", false);
          break;
      }
    }, this);
  }.bind(this));

  this.enableSubmit = function(enable) { 
    $("chatMsg").disabled = !enable;
    $("submitChat").disabled = !enable;
  };

})();
var Logger = new (function() {

  var buffer = [];
  var header = [];

  this.reportUserVar = function(variable, value) {
    var queryString = "value=" + value;
    var scriptTag = new Element("script", { src: "http://rorschach.jawaker.com/uservar/" + variable + "?" + queryString });
    $$("head").first().insert(scriptTag);
  };

  this.now = function() {
    var d = new Date();
    return d.getUTCDay() + "/" + d.getUTCMonth() + " " + d.getUTCHours() + ":" + d.getUTCMinutes() + ":" + d.getUTCSeconds();
  };

  this.log = function(level, msg) {
    msg = "(" + level + " @ " + this.now() + ") " + msg;
    buffer.push(msg);
    // if (typeof console != "undefined" && console.log) { console.log(msg); }
  };
  this.info = this.log.bind(this, "info");

  this.error = function(msg) {
    this.log("error", msg);
    this.report();
  };

  this.debug = function(obj, msg) {
    if (obj.debug) { this.log("debug", msg); }
  };

  this.assert = function(cond, msg) {
    if (!cond) { this.error(msg) }
  };

  this.report = function() {
    /*
    if (header.length == 0) {
      try {
        header.push(navigator.userAgent);
      } catch(e) {
        header.push("Couldn't get User Agent.");
      }
    }
    if (typeof InfoBase != "undefined") { header.push("Game: " + InfoBase.gameName()); }
    var data = header.join("\n") + "\n" + buffer.join("\n");
    data = data.substring(0, 1024 * 4);
    var scriptTag = new Element("script", { src: "http://scrooge.jawaker.com/?" + Object.toQueryString({'dataset': 'reports', 'data': data }) });
    $$("head").first().insert(scriptTag);
    */
  };

})();
var Menus = new (function() {

  var elements = {};

  var Menu = function(arrow, menu) {
    this.arrow = arrow;
    this.menu = menu;
    this.opened = false;
    this.destroy = function() {
      this.arrow.stopObserving();
      $$("#" + this.menu.identify() + " a").invoke('stopObserving');
    };
    this.arrow.observe('click', Menus.click.bind(this));
    this.arrow.writeAttribute('src', '/images/redarrow.png');
    $$("#" + this.menu.identify() + " a").invoke('observe', 'click', function(ev) {
      this.close();
    }.bind(this));
    this.close = function() {
      this.menu.hide();
      this.opened = false;
      this.arrow.writeAttribute('src', '/images/redarrow.png');
    }
  };

  this.loadAll = function(selector) {
    $$(selector).each(function(e) { elements[e] = new Menu(e, e.next()); });
  };

  this.unloadAll = function() {
    elements.values.each(function(e) { e.destroy(); });
    elements = {};
  };

  this.click = function(ev) {
    if (this.opened) {
      this.close();
    } else {
      this.opened = true;
      this.menu.show();
      this.menu.setStyle({'position': 'absolute'}); // Set position beforehand to make the calculations of position correct
      this.menu.setStyle(Menus.locate(this.arrow, this.menu));
      this.arrow.writeAttribute('src', '/images/redarrowup.png');
    }
  };

  this.locate = function(a, m) {
    var offset = a.cumulativeOffset();
    var dimensions = a.getDimensions();
    var boxDimensions = m.getDimensions();
    if (G.lang() == "en") {
      return {left: offset[0] + 'px', top: (offset[1] + dimensions['height']) + 'px'};
    } else {
      return {left: (offset[0] + dimensions['width'] - boxDimensions['width']) + 'px', top: (offset[1] + dimensions['height']) + 'px'};
    }
  };

  this.get = function(e) { 
    return elements[e];
  }

})();
/* Inspired by:
   Bubble Tooltips by Alessandro Fulciniti
   http://pro.html.it - http://web-graphics.com  */

var Tooltips = new (function() {

  var elements = [];

  var ajaxCache = {};

  this.ajaxRequest = null;

  var Tooltip = function(el, clickable) {
    this.e = el;
    if (clickable) {
      el.observe('click', Tooltips.mouseover.bind(this));
    } else {
      el.observe('mouseenter', Tooltips.mouseover.bind(this));
    }
    this.destroy = function() { this.e.stopObserving(); };
  };

  this.setup = function() {
    $$("body").first().insert({bottom: "<span id='tipcontainer' style='display: none;'></span>" });
    $("tipcontainer").update("<div id='tooltipMiddle'></div>");
    $("tipcontainer").observe('mouseleave', this.mouseleave.bind(this));
  };

  this.loadAll = function(selector, clickable) {
    $$(selector).each(function(e) { elements.push(new Tooltip(e, clickable)); });
  };

  this.unloadAll = function() {
    elements.each(function(e) { e.destroy(); });
    elements.clear();
  };

  this.mouseover = function() {
    if (Tooltips.fillContent(this.e)) {
      $("tipcontainer").show();
      $("tipcontainer").setStyle(Tooltips.locate(this.e));
    }
  };

  this.mouseleave = function(ev) {
    $("tipcontainer").hide();
    ev.stop();
  };

  this.locate = function(e) {
    var offset = $(e).cumulativeOffset();
    var dimensions = $(e).getDimensions();
    var boxDimensions = $("tipcontainer").getDimensions();
    var location = {left: offset[0], top: offset[1]};
    if (location['left'] + boxDimensions['width'] > document.viewport.getWidth() + document.viewport.getScrollOffsets()[0]) {
      location['left'] = offset[0] - boxDimensions['width'] + dimensions['width'];
    }
    if (location['top'] + boxDimensions['height'] > document.viewport.getHeight() + document.viewport.getScrollOffsets()[1]) {
      location['top'] = offset[1] - boxDimensions['height'] + dimensions['height'];
    }
    return {left: location['left'] + 'px', top: location['top'] + 'px'};
  };

  this.fillContent = function(e) {
    var rel = e.readAttribute('rel');
    var contentContainer = $("tooltipMiddle");
    contentContainer.update("");
    if (!rel) { return false; }
    if (rel == "(next)") {
      contentContainer.update(e.next().innerHTML);
    } else if (rel == "(alt)") {
      contentContainer.update(e.readAttribute('alt'));
    } else if (rel.charAt(0) == "#") {
      contentContainer.update($(rel.substring(1)).innerHTML);
    } else if (rel.charAt(0) == "/") {
      if (ajaxCache[rel] && ajaxCache[rel][0] + 60000 > (new Date()).getTime()) { // Check cache
        contentContainer.update(ajaxCache[rel][1]);
        $("tipcontainer").setStyle(Tooltips.locate(e));
      } else {
        contentContainer.update("<span style='line-height: 20px;'>" + G._("Loading ...") + "</span>");
        if (this.ajaxRequest && this.ajaxRequest.transport.readyState == 1) { // Kill the current poll if still running
          this.ajaxRequest.transport.abort();
          this.ajaxRequest.transport.onreadystatechange = Prototype.emptyFunction; // IE memory leak
        }
        this.ajaxRequest = new Ajax.Request(rel, {
          method: 'get',
          parameters: {'ie': (new Date()).getTime()},
          onSuccess: function(transport) {
            if (transport.status == 200) {
              contentContainer.update(transport.responseText);
              ajaxCache[rel] = [(new Date()).getTime(), transport.responseText];
              $("tipcontainer").setStyle(Tooltips.locate(e));
            }
          }
        });
      }
    } else {
      contentContainer.update(rel);
    }
    return true;
  };

})();
/* SWFObject v2.1 <http://code.google.com/p/swfobject/>
	Copyright (c) 2007-2008 Geoff Stearns, Michael Williams, and Bobby van der Sluis
	This software is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
*/
var swfobject=function(){var b="undefined",Q="object",n="Shockwave Flash",p="ShockwaveFlash.ShockwaveFlash",P="application/x-shockwave-flash",m="SWFObjectExprInst",j=window,K=document,T=navigator,o=[],N=[],i=[],d=[],J,Z=null,M=null,l=null,e=false,A=false;var h=function(){var v=typeof K.getElementById!=b&&typeof K.getElementsByTagName!=b&&typeof K.createElement!=b,AC=[0,0,0],x=null;if(typeof T.plugins!=b&&typeof T.plugins[n]==Q){x=T.plugins[n].description;if(x&&!(typeof T.mimeTypes!=b&&T.mimeTypes[P]&&!T.mimeTypes[P].enabledPlugin)){x=x.replace(/^.*\s+(\S+\s+\S+$)/,"$1");AC[0]=parseInt(x.replace(/^(.*)\..*$/,"$1"),10);AC[1]=parseInt(x.replace(/^.*\.(.*)\s.*$/,"$1"),10);AC[2]=/r/.test(x)?parseInt(x.replace(/^.*r(.*)$/,"$1"),10):0}}else{if(typeof j.ActiveXObject!=b){var y=null,AB=false;try{y=new ActiveXObject(p+".7")}catch(t){try{y=new ActiveXObject(p+".6");AC=[6,0,21];y.AllowScriptAccess="always"}catch(t){if(AC[0]==6){AB=true}}if(!AB){try{y=new ActiveXObject(p)}catch(t){}}}if(!AB&&y){try{x=y.GetVariable("$version");if(x){x=x.split(" ")[1].split(",");AC=[parseInt(x[0],10),parseInt(x[1],10),parseInt(x[2],10)]}}catch(t){}}}}var AD=T.userAgent.toLowerCase(),r=T.platform.toLowerCase(),AA=/webkit/.test(AD)?parseFloat(AD.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,q=false,z=r?/win/.test(r):/win/.test(AD),w=r?/mac/.test(r):/mac/.test(AD);/*@cc_on q=true;@if(@_win32)z=true;@elif(@_mac)w=true;@end@*/return{w3cdom:v,pv:AC,webkit:AA,ie:q,win:z,mac:w}}();var L=function(){if(!h.w3cdom){return }f(H);if(h.ie&&h.win){try{K.write("<script id=__ie_ondomload defer=true src=//:><\/script>");J=C("__ie_ondomload");if(J){I(J,"onreadystatechange",S)}}catch(q){}}if(h.webkit&&typeof K.readyState!=b){Z=setInterval(function(){if(/loaded|complete/.test(K.readyState)){E()}},10)}if(typeof K.addEventListener!=b){K.addEventListener("DOMContentLoaded",E,null)}R(E)}();function S(){if(J.readyState=="complete"){J.parentNode.removeChild(J);E()}}function E(){if(e){return }if(h.ie&&h.win){var v=a("span");try{var u=K.getElementsByTagName("body")[0].appendChild(v);u.parentNode.removeChild(u)}catch(w){return }}e=true;if(Z){clearInterval(Z);Z=null}var q=o.length;for(var r=0;r<q;r++){o[r]()}}function f(q){if(e){q()}else{o[o.length]=q}}function R(r){if(typeof j.addEventListener!=b){j.addEventListener("load",r,false)}else{if(typeof K.addEventListener!=b){K.addEventListener("load",r,false)}else{if(typeof j.attachEvent!=b){I(j,"onload",r)}else{if(typeof j.onload=="function"){var q=j.onload;j.onload=function(){q();r()}}else{j.onload=r}}}}}function H(){var t=N.length;for(var q=0;q<t;q++){var u=N[q].id;if(h.pv[0]>0){var r=C(u);if(r){N[q].width=r.getAttribute("width")?r.getAttribute("width"):"0";N[q].height=r.getAttribute("height")?r.getAttribute("height"):"0";if(c(N[q].swfVersion)){if(h.webkit&&h.webkit<312){Y(r)}W(u,true)}else{if(N[q].expressInstall&&!A&&c("6.0.65")&&(h.win||h.mac)){k(N[q])}else{O(r)}}}}else{W(u,true)}}}function Y(t){var q=t.getElementsByTagName(Q)[0];if(q){var w=a("embed"),y=q.attributes;if(y){var v=y.length;for(var u=0;u<v;u++){if(y[u].nodeName=="DATA"){w.setAttribute("src",y[u].nodeValue)}else{w.setAttribute(y[u].nodeName,y[u].nodeValue)}}}var x=q.childNodes;if(x){var z=x.length;for(var r=0;r<z;r++){if(x[r].nodeType==1&&x[r].nodeName=="PARAM"){w.setAttribute(x[r].getAttribute("name"),x[r].getAttribute("value"))}}}t.parentNode.replaceChild(w,t)}}function k(w){A=true;var u=C(w.id);if(u){if(w.altContentId){var y=C(w.altContentId);if(y){M=y;l=w.altContentId}}else{M=G(u)}if(!(/%$/.test(w.width))&&parseInt(w.width,10)<310){w.width="310"}if(!(/%$/.test(w.height))&&parseInt(w.height,10)<137){w.height="137"}K.title=K.title.slice(0,47)+" - Flash Player Installation";var z=h.ie&&h.win?"ActiveX":"PlugIn",q=K.title,r="MMredirectURL="+j.location+"&MMplayerType="+z+"&MMdoctitle="+q,x=w.id;if(h.ie&&h.win&&u.readyState!=4){var t=a("div");x+="SWFObjectNew";t.setAttribute("id",x);u.parentNode.insertBefore(t,u);u.style.display="none";var v=function(){u.parentNode.removeChild(u)};I(j,"onload",v)}U({data:w.expressInstall,id:m,width:w.width,height:w.height},{flashvars:r},x)}}function O(t){if(h.ie&&h.win&&t.readyState!=4){var r=a("div");t.parentNode.insertBefore(r,t);r.parentNode.replaceChild(G(t),r);t.style.display="none";var q=function(){t.parentNode.removeChild(t)};I(j,"onload",q)}else{t.parentNode.replaceChild(G(t),t)}}function G(v){var u=a("div");if(h.win&&h.ie){u.innerHTML=v.innerHTML}else{var r=v.getElementsByTagName(Q)[0];if(r){var w=r.childNodes;if(w){var q=w.length;for(var t=0;t<q;t++){if(!(w[t].nodeType==1&&w[t].nodeName=="PARAM")&&!(w[t].nodeType==8)){u.appendChild(w[t].cloneNode(true))}}}}}return u}function U(AG,AE,t){var q,v=C(t);if(v){if(typeof AG.id==b){AG.id=t}if(h.ie&&h.win){var AF="";for(var AB in AG){if(AG[AB]!=Object.prototype[AB]){if(AB.toLowerCase()=="data"){AE.movie=AG[AB]}else{if(AB.toLowerCase()=="styleclass"){AF+=' class="'+AG[AB]+'"'}else{if(AB.toLowerCase()!="classid"){AF+=" "+AB+'="'+AG[AB]+'"'}}}}}var AD="";for(var AA in AE){if(AE[AA]!=Object.prototype[AA]){AD+='<param name="'+AA+'" value="'+AE[AA]+'" />'}}v.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+AF+">"+AD+"</object>";i[i.length]=AG.id;q=C(AG.id)}else{if(h.webkit&&h.webkit<312){var AC=a("embed");AC.setAttribute("type",P);for(var z in AG){if(AG[z]!=Object.prototype[z]){if(z.toLowerCase()=="data"){AC.setAttribute("src",AG[z])}else{if(z.toLowerCase()=="styleclass"){AC.setAttribute("class",AG[z])}else{if(z.toLowerCase()!="classid"){AC.setAttribute(z,AG[z])}}}}}for(var y in AE){if(AE[y]!=Object.prototype[y]){if(y.toLowerCase()!="movie"){AC.setAttribute(y,AE[y])}}}v.parentNode.replaceChild(AC,v);q=AC}else{var u=a(Q);u.setAttribute("type",P);for(var x in AG){if(AG[x]!=Object.prototype[x]){if(x.toLowerCase()=="styleclass"){u.setAttribute("class",AG[x])}else{if(x.toLowerCase()!="classid"){u.setAttribute(x,AG[x])}}}}for(var w in AE){if(AE[w]!=Object.prototype[w]&&w.toLowerCase()!="movie"){F(u,w,AE[w])}}v.parentNode.replaceChild(u,v);q=u}}}return q}function F(t,q,r){var u=a("param");u.setAttribute("name",q);u.setAttribute("value",r);t.appendChild(u)}function X(r){var q=C(r);if(q&&(q.nodeName=="OBJECT"||q.nodeName=="EMBED")){if(h.ie&&h.win){if(q.readyState==4){B(r)}else{j.attachEvent("onload",function(){B(r)})}}else{q.parentNode.removeChild(q)}}}function B(t){var r=C(t);if(r){for(var q in r){if(typeof r[q]=="function"){r[q]=null}}r.parentNode.removeChild(r)}}function C(t){var q=null;try{q=K.getElementById(t)}catch(r){}return q}function a(q){return K.createElement(q)}function I(t,q,r){t.attachEvent(q,r);d[d.length]=[t,q,r]}function c(t){var r=h.pv,q=t.split(".");q[0]=parseInt(q[0],10);q[1]=parseInt(q[1],10)||0;q[2]=parseInt(q[2],10)||0;return(r[0]>q[0]||(r[0]==q[0]&&r[1]>q[1])||(r[0]==q[0]&&r[1]==q[1]&&r[2]>=q[2]))?true:false}function V(v,r){if(h.ie&&h.mac){return }var u=K.getElementsByTagName("head")[0],t=a("style");t.setAttribute("type","text/css");t.setAttribute("media","screen");if(!(h.ie&&h.win)&&typeof K.createTextNode!=b){t.appendChild(K.createTextNode(v+" {"+r+"}"))}u.appendChild(t);if(h.ie&&h.win&&typeof K.styleSheets!=b&&K.styleSheets.length>0){var q=K.styleSheets[K.styleSheets.length-1];if(typeof q.addRule==Q){q.addRule(v,r)}}}function W(t,q){var r=q?"visible":"hidden";if(e&&C(t)){C(t).style.visibility=r}else{V("#"+t,"visibility:"+r)}}function g(s){var r=/[\\\"<>\.;]/;var q=r.exec(s)!=null;return q?encodeURIComponent(s):s}var D=function(){if(h.ie&&h.win){window.attachEvent("onunload",function(){var w=d.length;for(var v=0;v<w;v++){d[v][0].detachEvent(d[v][1],d[v][2])}var t=i.length;for(var u=0;u<t;u++){X(i[u])}for(var r in h){h[r]=null}h=null;for(var q in swfobject){swfobject[q]=null}swfobject=null})}}();return{registerObject:function(u,q,t){if(!h.w3cdom||!u||!q){return }var r={};r.id=u;r.swfVersion=q;r.expressInstall=t?t:false;N[N.length]=r;W(u,false)},getObjectById:function(v){var q=null;if(h.w3cdom){var t=C(v);if(t){var u=t.getElementsByTagName(Q)[0];if(!u||(u&&typeof t.SetVariable!=b)){q=t}else{if(typeof u.SetVariable!=b){q=u}}}}return q},embedSWF:function(x,AE,AB,AD,q,w,r,z,AC){if(!h.w3cdom||!x||!AE||!AB||!AD||!q){return }AB+="";AD+="";if(c(q)){W(AE,false);var AA={};if(AC&&typeof AC===Q){for(var v in AC){if(AC[v]!=Object.prototype[v]){AA[v]=AC[v]}}}AA.data=x;AA.width=AB;AA.height=AD;var y={};if(z&&typeof z===Q){for(var u in z){if(z[u]!=Object.prototype[u]){y[u]=z[u]}}}if(r&&typeof r===Q){for(var t in r){if(r[t]!=Object.prototype[t]){if(typeof y.flashvars!=b){y.flashvars+="&"+t+"="+r[t]}else{y.flashvars=t+"="+r[t]}}}}f(function(){U(AA,y,AE);if(AA.id==AE){W(AE,true)}})}else{if(w&&!A&&c("6.0.65")&&(h.win||h.mac)){A=true;W(AE,false);f(function(){var AF={};AF.id=AF.altContentId=AE;AF.width=AB;AF.height=AD;AF.expressInstall=w;k(AF)})}}},getFlashPlayerVersion:function(){return{major:h.pv[0],minor:h.pv[1],release:h.pv[2]}},hasFlashPlayerVersion:c,createSWF:function(t,r,q){if(h.w3cdom){return U(t,r,q)}else{return undefined}},removeSWF:function(q){if(h.w3cdom){X(q)}},createCSS:function(r,q){if(h.w3cdom){V(r,q)}},addDomLoadEvent:f,addLoadEvent:R,getQueryParamValue:function(v){var u=K.location.search||K.location.hash;if(v==null){return g(u)}if(u){var t=u.substring(1).split("&");for(var r=0;r<t.length;r++){if(t[r].substring(0,t[r].indexOf("="))==v){return g(t[r].substring((t[r].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(A&&M){var q=C(m);if(q){q.parentNode.replaceChild(M,q);if(l){W(l,true);if(h.ie&&h.win){M.style.display="block"}}M=null;l=null;A=false}}}}}();
var Tabs = Class.create({
  initialize : function(activeClass) {
    this.activeClass = activeClass;
    this.hashLink = window.location.hash;
    this.tabs = $H({});
  },
  addTab: function(tabName, tabElement, linkElement, contentElement) {
    this.tabs.set(tabName, [tabElement, contentElement]);
    linkElement.observe('click', function(ev) {
      this.activate(tabName, true);
      ev.stop();
    }.bind(this));
    if (Object.isUndefined(this.activeTab) || this.hashLink == "#" + tabName) { this.activate(tabName, false); }
  },
  activate: function(tabName, changeHash) {
    if (!this.tabs.keys().include(tabName)) { return; }
    this.activeTab = tabName;
    if (changeHash) { window.location.hash = tabName; }
    this.tabs.each(function(t) {
      if (t[0] == tabName) {
        t[1][0].addClassName(this.activeClass);
        t[1][1].show();
      } else {
        t[1][0].removeClassName(this.activeClass);
        t[1][1].hide();
      }
    }, this);
  }
});
var JawakerFormElement = Class.create({
  initialize: function(elem, opts) {
    this.elem   = $(elem);
    this.opts   = opts;
    this.form   = null;
    this.findex = null;
    this.old_ajax = null;
    this.status = this.opts['optional'] ? true : this.elem.tagName == "SELECT"; // Selects are preselected and are valid
    // [ [f, msg], ... ]
    this.validators = this.opts.validators;
    new Form.Element.Observer(this.elem, 0.3, this.check.bind(this));
    this.elem.observe('focus', this.show_tip.bind(this));
    this.elem.observe('blur', this.hide_tip.bind(this));
    this.illegal_re = /['"~!@#\$%^&*()=+\[\]{};:|\\,?\/<>\s]/;
  },
  set_form: function(frm, idx) {
    this.form = frm; this.findex = idx;
  },
  show_valid: function() {
    var e = this.elem.up().next();
    e.writeAttribute('class', 'correct');
    e.update("<img src='/images/OK.png' alt='OK'/> " + G._("OK")).show();
  },
  show_checking: function() {
    var e = this.elem.up().next();
    e.writeAttribute('class', 'checking');
    e.update("<img src='/images/checking.gif' alt='Checking'/> " + G._("Checking ...")).show();
  },
  show_invalid: function(msg) {
    msg = msg || "Invalid Field";
    var e = this.elem.up().next();
    e.writeAttribute('class', 'error');
    e.update("<img src='/images/error.png' alt='Error'/> " + G._(msg)).show();
  },
  doAjax: function(url, prms) {
    if (this.old_ajax && this.old_ajax.transport.readyState == 1) { // old req still running
      this.old_ajax.transport.abort();
      this.old_ajax.transport.onreadystatechange = Prototype.emptyFunction; // IE memory leak
    }
    this.old_ajax = new Ajax.Request(url, {method: 'get', parameters: Object.extend(prms, {jfi: this.findex})});
    return "async";
  },
  check: function() {
    var async = false;
    for (var i = 0; i < this.validators.length; i++) {
      var res = true;
      try {
        res = this.validators[i][0].apply(this);
      } catch(e) { if (typeof console != "undefined" && typeof console.log == "function") { console.log(e); } }
      if (res === "async") {
        async = true;
        this.status = false;
        this.show_checking();
        this.msg = this.validators[i][1];
        this.finished = function(resp) {
          if (resp == 'true') {
            this.status = true;
            this.show_valid();
          } else {
            this.status = false;
            this.show_invalid(this.msg);
          }
        };
      }
      else if(!res) {
        this.status = false;
        this.show_invalid(this.validators[i][1]);
        break;
      }
    }
    if (i == this.validators.length && !async) { this.status = true; this.show_valid(); }
  },
  flash: function() {
    var f = function() { this.elem.up('tr').toggleClassName("focusederror"); }.bind(this);
    var t = setInterval(f, 400);
    setTimeout(function() { clearTimeout(t); }, 400 * 6);
  },
  show_tip: function() {
    var e = this.elem.up().next();
    if (!e.visible() && this.opts.tip && this.opts.tip.length != 0) {
      e.writeAttribute('class', 'onclick');
      e.update("<img src='/images/arrow_" + G.lang() + ".gif' alt='tip'/> " + G._(this.opts.tip)).show();
    }
  },
  hide_tip: function() {
    var e = this.elem.up().next();
    (function() { if (e.visible() && e.readAttribute('class') == 'onclick') { e.hide(); } }).delay(0.15);
  }
});

var JawakerForm = Class.create({
  initialize: function(elem, opts) {
    this.elem = $(elem);
    this.opts = opts;
    this.form_elems = [];
    this.elem.observe("submit", this.validate.bind(this));

    Ajax.Responders.register({
      onComplete: function(req) {
        if ('jfi' in req.parameters) { // A checking request
          this.form_elems[parseInt(req.parameters.jfi, 10)].finished(req.transport.responseText);
        }
      }.bind(this)
    });
  },
  set_focus: function() {
    this.form_elems.first().elem.focus();
  },
  add_form_elem: function(e) {
    e.set_form(this, this.form_elems.length);
    this.form_elems.push(e);
    return true;
  },
  validate: function(ev) {
    for (var i = 0; i < this.form_elems.length; i++) {
      if (!this.form_elems[i].status) {
        this.form_elems[i].flash();
        ev.stop();
        return false;
        break;
      }
    }
    if (this.opts.beforeSubmit) { this.opts.beforeSubmit(); }
    return true;
  }
});
