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

var Prototype = {
  Version: '1.6.0.2',

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

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

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

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

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


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

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

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

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

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

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

    klass.prototype.constructor = klass;

    return klass;
  }
};

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

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

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

    return this;
  }
};

var Abstract = { };

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Function.prototype.defer = Function.prototype.delay.curry(0.01);

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

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

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

    return returnValue;
  }
};

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

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

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

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

    this.registerCallback();
  },

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return camelized;
  },

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

var $break = { };

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Array.from = $A;

Object.extend(Array.prototype, Enumerable);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// use native browser JS 1.6 implementation if available
if (Object.isFunction(Array.prototype.forEach))
  Array.prototype._each = Array.prototype.forEach;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    get: function(key) {
      return this._object[key];
    },

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

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

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

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

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

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

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

    toQueryString: function() {
      return this.map(function(pair) {
        var key = encodeURIComponent(pair.key), values = pair.value;

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

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

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

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

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

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

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

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

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

  activeRequestCount: 0
};

Ajax.Responders = {
  responders: [],

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

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

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

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

Object.extend(Ajax.Responders, Enumerable);

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

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

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

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

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

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

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

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

    this.parameters = params;

    if (params = Object.toQueryString(params)) {
      // when GET, append parameters to URL
      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';
    }

    // user-defined headers
    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') {
      // avoid memory leak in MSIE: clean up
      this.transport.onreadystatechange = Prototype.emptyFunction;
    }
  },

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

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

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

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

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

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

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

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

  status:      0,
  statusText: '',

  getStatus: Ajax.Request.prototype.getStatus,

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

  getHeader: Ajax.Request.prototype.getHeader,

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

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

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

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

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

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

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

    $super(url, options);
  },

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

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

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

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

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

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

    this.start();
  },

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

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

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

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

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

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

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

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

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

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

Element.cache = { };

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

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

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

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

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

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

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

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

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

    var content, insert, tagName, childNodes;

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

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

      content = Object.toHTML(content);

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

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

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

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

    return element;
  },

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  // removes whitespace-only text node children
  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);
    var originalAncestor = ancestor;

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

    if (element.sourceIndex && !Prototype.Browser.Opera) {
      var e = element.sourceIndex, a = ancestor.sourceIndex,
       nextAncestor = ancestor.nextSibling;
      if (!nextAncestor) {
        do { ancestor = ancestor.parentNode; }
        while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
      }
      if (nextAncestor && nextAncestor.sourceIndex)
       return (e > a && e < nextAncestor.sourceIndex);
    }

    while (element = element.parentNode)
      if (element == originalAncestor) return true;
    return false;
  },

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

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

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

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

    return element;
  },

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

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

    // All *Width and *Height properties give 0 on elements with display none,
    // so enable the element temporarily
    var els = element.style;
    var originalVisibility = els.visibility;
    var originalPosition = els.position;
    var originalDisplay = els.display;
    els.visibility = 'hidden';
    els.position = 'absolute';
    els.display = 'block';
    var originalWidth = element.clientWidth;
    var originalHeight = element.clientHeight;
    els.display = originalDisplay;
    els.position = originalPosition;
    els.visibility = originalVisibility;
    return {width: originalWidth, height: originalHeight};
  },

  makePositioned: function(element) {
    element = $(element);
    var pos = Element.getStyle(element, 'position');
    if (pos == 'static' || !pos) {
      element._madePositioned = true;
      element.style.position = 'relative';
      // Opera returns the offset relative to the positioning context, when an
      // element is position relative but top and left have not been defined
      if (window.opera) {
        element.style.top = 0;
        element.style.left = 0;
      }
    }
    return element;
  },

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

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

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

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

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

  absolutize: function(element) {
    element = $(element);
    if (element.getStyle('position') == 'absolute') return;
    // Position.prepare(); // To be done manually by Scripty when it needs it.

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

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

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

  relativize: function(element) {
    element = $(element);
    if (element.getStyle('position') == 'relative') return;
    // Position.prepare(); // To be done manually by Scripty when it needs it.

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

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

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

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

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

    return $(document.body);
  },

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

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

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

    } while (element = element.offsetParent);

    element = forElement;
    do {
      if (!Prototype.Browser.Opera || element.tagName == '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] || { });

    // find page position of source
    source = $(source);
    var p = source.viewportOffset();

    // find coordinate system to use
    element = $(element);
    var delta = [0, 0];
    var parent = null;
    // delta [0,0] will do fine with position: fixed elements,
    // position:absolute needs offsetParent deltas
    if (Element.getStyle(element, 'position') == 'absolute') {
      parent = element.getOffsetParent();
      delta = parent.viewportOffset();
    }

    // correct by body offsets (fixes Safari)
    if (parent == document.body) {
      delta[0] -= document.body.offsetLeft;
      delta[1] -= document.body.offsetTop;
    }

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

Element.Methods.identify.counter = 1;

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

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

if (Prototype.Browser.Opera) {
  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
    function(proceed, element, style) {
      switch (style) {
        case 'left': case 'top': case 'right': case 'bottom':
          if (proceed(element, 'position') === 'static') return null;
        case 'height': case 'width':
          // returns '0px' for hidden elements; we want it to return null
          if (!Element.visible(element)) return null;

          // returns the border-box dimensions rather than the content-box
          // dimensions, so we subtract padding and borders from the value
          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) {
  // IE doesn't report offsets correctly for static elements, so we change them
  // to "relative" to get the values, then change them back.
  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
    function(proceed, element) {
      element = $(element);
      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);
        var position = element.getStyle('position');
        if (position !== 'static') return proceed(element);
        // Trigger hasLayout on the offset parent so that IE6 reports
        // accurate offsetTop and offsetLeft values for position: fixed.
        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 = {
    read: {
      names: {
        'class': 'className',
        'for':   'htmlFor'
      },
      values: {
        _getAttr: function(element, attribute) {
          return element.getAttribute(attribute, 2);
        },
        _getAttrNode: function(element, attribute) {
          var node = element.getAttributeNode(attribute);
          return node ? node.value : "";
        },
        _getEv: function(element, attribute) {
          attribute = element.getAttribute(attribute);
          return attribute ? attribute.toString().slice(23, -2) : null;
        },
        _flag: function(element, attribute) {
          return $(element).hasAttribute(attribute) ? attribute : null;
        },
        style: function(element) {
          return element.style.cssText.toLowerCase();
        },
        title: function(element) {
          return element.title;
        }
      }
    }
  };

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

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

  Element._attributeTranslations.has = {};

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

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

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

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

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

    return element;
  };

  // Safari returns margins on body which is incorrect if the child is absolutely
  // positioned.  For performance reasons, redefine Element#cumulativeOffset for
  // KHTML/WebKit only.
  Element.Methods.cumulativeOffset = function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      if (element.offsetParent == document.body)
        if (Element.getStyle(element, 'position') == 'absolute') break;

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

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

if (Prototype.Browser.IE || Prototype.Browser.Opera) {
  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
  Element.Methods.update = function(element, content) {
    element = $(element);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Element.Methods.ByTag = { };

Object.extend(Element, Element.Methods);

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

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

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

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

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

    // extend methods for specific tags
    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);

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

    element._extendedByPrototype = Prototype.emptyFunction;
    return element;

  }, {
    refresh: function() {
      // extend methods for all tags (Safari doesn't need this)
      if (!Prototype.BrowserFeatures.ElementExtensions) {
        Object.extend(Methods, Element.Methods);
        Object.extend(Methods, Element.Methods.Simulated);
      }
    }
  });

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    var e = this.expression;

    // Safari 3 chokes on :*-of-type and :empty
    if (Prototype.Browser.WebKit &&
     (e.include("-of-type") || e.include(":empty")))
      return false;

    // XPath can't do namespaced attributes, nor can it read
    // the "checked" property from DOM nodes
    if ((/(\[[\w-]*?:|:checked)/).test(this.expression))
      return false;

    return true;
  },

  compileMatcher: function() {
    if (this.shouldUseXPath())
      return this.compileXPathMatcher();

    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
        c = Selector.criteria, le, p, m;

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

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

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

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

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

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

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

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

  findElements: function(root) {
    root = root || document;
    if (this.xpath) return document._getElementsByXPath(this.xpath, root);
    return this.matcher(root);
  },

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

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

    while (e && le !== e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        p = ps[i];
        if (m = e.match(p)) {
          // use the Selector.assertions methods unless the selector
          // is too complex.
          if (as[i]) {
            this.tokens.push([i, Object.clone(m)]);
            e = e.replace(m[0], '');
          } else {
            // reluctantly do a document-wide search
            // and look for a match in the array
            return this.findElements(document).include(element);
          }
        }
      }
    }

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

    return match;
  },

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

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

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

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

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

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

  patterns: {
    // combinators must be listed first
    // (and descendant needs to be last combinator)
    laterSibling: /^\s*~\s*/,
    child:        /^\s*>\s*/,
    adjacent:     /^\s*\+\s*/,
    descendant:   /^\s/,

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

  // for Selector.match and Element#match
  assertions: {
    tagName: function(element, matches) {
      return matches[1].toUpperCase() == element.tagName.toUpperCase();
    },

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

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

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

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

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

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

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

    // mark each child node with its position (for nth calls)
    // "ofType" flag indicates whether we're indexing for nth-of-type
    // rather than nth-child
    index: function(parentNode, reverse, ofType) {
      parentNode._countedByPrototype = Prototype.emptyFunction;
      if (reverse) {
        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
          var node = nodes[i];
          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
        }
      } else {
        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
      }
    },

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    'empty': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        // IE treats comments as element nodes
        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
        results.push(node);
      }
      return results;
    },

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

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

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

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

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

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

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

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

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

if (Prototype.Browser.IE) {
  Object.extend(Selector.handlers, {
    // IE returns comment nodes on getElementsByTagName("*").
    // Filter them out.
    concat: function(a, b) {
      for (var i = 0, node; node = b[i]; i++)
        if (node.tagName !== "!") a.push(node);
      return a;
    },

    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
    unmark: function(nodes) {
      for (var i = 0, node; node = nodes[i]; i++)
        node.removeAttribute('_countedByPrototype');
      return nodes;
    }
  });
}

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

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

    var data = elements.inject({ }, function(result, element) {
      if (!element.disabled && element.name) {
        key = element.name; value = $(element).getValue();
        if (value != null && (element.type != 'submit' || (!submitted &&
            submit !== false && (!submit || key == submit) && (submitted = true)))) {
          if (key in result) {
            // a key is already present; construct an array of values
            if (!Object.isArray(result[key])) result[key] = [result[key]];
            result[key].push(value);
          }
          else result[key] = value;
        }
      }
      return result;
    });

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

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

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

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

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

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

    return matchingInputs;
  },

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  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) {
    // extend element because hasAttribute may not be native
    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
  }
};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  cache: { },

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

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

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

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

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

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

    element: function(event) {
      var node = Event.extend(event).target;
      return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
    },

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

    pointer: function(event) {
      return {
        x: event.pageX || (event.clientX +
          (document.documentElement.scrollLeft || document.body.scrollLeft)),
        y: event.pageY || (event.clientY +
          (document.documentElement.scrollTop || document.body.scrollTop))
      };
    },

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      return element;
    },

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

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

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

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

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

      destroyWrapper(id, eventName, handler);

      return element;
    },

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

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

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

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

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

Object.extend(Event, Event.Methods);

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

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

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

  var timer;

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

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

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

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

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

Hash.toQueryString = Object.toQueryString;

var Toggle = { display: Element.toggle };

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

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

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

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

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

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

// This should be moved to script.aculo.us; notice the deprecated methods
// further below, that map to the newer Element methods.
var Position = {
  // set to true if needed, warning: firefox performance problems
  // NOT neeeded for page scrolling, only if draggable contained in
  // scrollable elements
  includeScrollOffsets: false,

  // must be called before calling withinIncludingScrolloffset, every time the
  // page is scrolled
  prepare: function() {
    this.deltaX =  window.pageXOffset
                || document.documentElement.scrollLeft
                || document.body.scrollLeft
                || 0;
    this.deltaY =  window.pageYOffset
                || document.documentElement.scrollTop
                || document.body.scrollTop
                || 0;
  },

  // caches x/y coordinate pair to use with overlap
  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);
  },

  // within must be called directly before
  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;
  },

  // Deprecation layer -- use newer Element methods now (1.5.2).

  cumulativeOffset: Element.Methods.cumulativeOffset,

  positionedOffset: Element.Methods.positionedOffset,

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

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

  realOffset: Element.Methods.cumulativeScrollOffset,

  offsetParent: Element.Methods.getOffsetParent,

  page: Element.Methods.viewportOffset,

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Element.addMethods();// script.aculo.us effects.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008

// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
//  Justin Palmer (http://encytemedia.com/)
//  Mark Pilgrim (http://diveintomark.org/)
//  Martin Bialasinki
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

// converts rgb() and #xxx to #xxxxxx format,
// returns self (or first argument) if not convertable
String.prototype.parseColor = function() {
  var color = '#';
  if (this.slice(0,4) == 'rgb(') {
    var cols = this.slice(4,this.length-1).split(',');
    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
  } else {
    if (this.slice(0,1) == '#') {
      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
      if (this.length==7) color = this.toLowerCase();
    }
  }
  return (color.length==7 ? color : (arguments[0] || this));
};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// Bug in opera makes the TD containing this element expand for a instance after finish
Effect.Squish = function(element) {
  return new Effect.Scale(element, window.opera ? 1 : 0, {
    restoreAfterFinish: true,
    beforeSetup: function(effect) {
      effect.element.makeClipping();
    },
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping();
    }
  });
};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  return styleRules;
};

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

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

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

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

Element.addMethods(Effect.Methods);function QueryString(key){var value = null;for (var i=0;i<QueryString.keys.length;i++){if (QueryString.keys[i]==key){value = QueryString.values[i];break;}}return value;}
QueryString.keys = new Array();
QueryString.values = new Array();
function QueryString_Parse(){var query = window.location.search.substring(1);var pairs = query.split("&");for (var i=0;i<pairs.length;i++){var pos = pairs[i].indexOf('=');if (pos >= 0){var argname = pairs[i].substring(0,pos);var value = pairs[i].substring(pos+1);QueryString.keys[QueryString.keys.length] = argname;QueryString.values[QueryString.values.length] = value;}}}
QueryString_Parse();
function getCookie(Name){var search = Name + "=";if(document.cookie.length>0){offset=document.cookie.indexOf(search);if(offset!=-1){offset+=search.length;end=document.cookie.indexOf(";",offset);if(end==-1)end=document.cookie.length;return unescape(document.cookie.substring(offset, end));}}}
function setCookie(name,value,days){var myDomain = document.domain;var today=new Date();var expires=new Date();expires.setTime(today.getTime()+1000*60*60*24*days);document.cookie=name+"="+escape(value)+"; expires=" + expires.toGMTString()+"; domain="+myDomain.split(".")[1] + "." + myDomain.split(".")[2] + "; path=/";}
function rfr_ept(){ var rfr=document.referrer; if(rfr==null||rfr.length<1){ }else{ var istart=rfr.indexOf('.com'); if(rfr.indexOf('.altrec.com') < 0 || rfr.indexOf('.altrec.com') > istart || istart < 0){ setCookie('rfr',rfr,1); setCookie('ept',document.URL,1); var eti=new Date().getTime(); setCookie('eti',eti + '',1);}
}
var abc = getCookie("abc"); if(abc==null||abc.length<1){ abc = (new Date().getTime()) % 2; setCookie("abc",abc,3);}
}
function focusGL(){if(window.name == "GLshopWindow"){if(opener&&!opener.closed&&opener.document.title=='Gear List'){opener.focus();}}}
function init(){rfr_ept();}
var fc_track=""; function SAmark(ver){SAhtml="";return SAhtml;}
var s_alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz '; var s_alphabet_crypt='NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm_'; var s_fulln = ''; var myfull = ''; var fulln = getCookie('fulln'); if(fulln != null && fulln.length > 0){ var s = ''; for(i=0;i < fulln.length;i++){ for(x=0;x < s_alphabet_crypt.length;x++){ 
  if( fulln.charAt(i) == s_alphabet_crypt.charAt(x)){ s = s + s_alphabet.charAt(x);}
}
}
myfull = s; 
   s_fulln = '<'+'b>' + s + '<'+'/b'+ '>, we have clothing and gear r' + 'ecommendations <a h' + 'ref=/serv' + 'lets/mypage/>tailored just for you.</a>';
}else{ 
   s_fulln = 'Sign-in to <' + 'a h' + 'ref="/se' + 'rvlets/mypage/"><b' + '>My Store<' + '/b><' + '/a> for gear recommendations &' + 'amp; ratings - <a h' + 'ref="/servlets/mypage/"><' + 'b>tailored just for you<' + '/b><' + '/a>.';
}
function classToggle(element,class1,class2) {if (element.className==class1) {element.className = class2;} else if (element.className==class2) {element.className = class1;}}
function PageQuery(q) {
	if(q.length > 1) 
		this.q = q.substring(1, q.length);
	else 
		this.q = null;
	this.keyValuePairs = new Array();
	if(q) {
		for(var i=0; i < this.q.split("&").length; i++) {
			this.keyValuePairs[i] = this.q.split("&")[i];
		}
	}
	
	this.getKeyValuePairs = function() { return this.keyValuePairs; }
	
	this.getValue = function(s) {
		for(var j=0; j < this.keyValuePairs.length; j++) {
			if(this.keyValuePairs[j].split("=")[0] == s)
				return this.keyValuePairs[j].split("=")[1];
		}
		return false;
	}
	
	this.getParameters = function() {
		var a = new Array(this.getLength());
		for(var j=0; j < this.keyValuePairs.length; j++) {
			a[j] = this.keyValuePairs[j].split("=")[0];
		}
		return a;
	}
	this.getLength = function() { return this.keyValuePairs.length; }
}

function queryStringReferrer(key){
	var page = new PageQuery('?' + document.referrer.split('?')[1]);
	return unescape(page.getValue(key));
}
function launchURL(url,w,h,scroll,resize,title,status) { var features = new Array(); features = "height="+h+",width="+w+",scrollbars="+scroll+",resizable="+resize+",toolbar=0,location=0"; window.open (url,title,features); window.name = "main";} function mypick(pid){ if(pid != null && pid != "undefined" && pid.length > 0 && pid.indexOf('%%id%%') < 0){ var shw = getCookie('shw');if( shw == null || shw.length < 1 || shw.length > 100){setCookie("shw",pid,30);}else{ setCookie("shw",shw + "," + pid,30);} } }
var last; function toggleLayer(whichLayer) { if (last != null && last != whichLayer) { if (document.getElementById) { var style2 = document.getElementById(last).style; var color2 = document.getElementById(last + 'Link').style; style2.display = ""; color2.color = "";} else if (document.all) { var style2 = document.all[last].style; style2.display = "";} else if (document.layers) { var style2 = document.layers[last].style; style2.display = "";}
}
if (document.getElementById) { var style2 = document.getElementById(whichLayer).style; var color2 = document.getElementById(whichLayer + 'Link').style; style2.display = style2.display? "":"block"; color2.color = color2.color? "":"#FFF";} else if (document.all) { var style2 = document.all[whichLayer].style; style2.display = style2.display? "":"block";} else if (document.layers) { var style2 = document.layers[whichLayer].style; style2.display = style2.display? "":"block";}
last = whichLayer;}
function checkSignup(myform) { myemail = myform.email.value; if(myemail.indexOf('@') < 0 || myemail.indexOf('.') < 0){ alert("Please enter a valid e-mail address to sign up."); return(false);}else{ return(true) } }
function switchDesc(id) { var button = document.getElementById("switch_"+id); if(button==null)
return true; var more = document.getElementById("more_"+id); var part2 = document.getElementById("fullDesc_"+id); if(button.className=='plusDesc') { button.className='minusDesc'; if(part2!=null) part2.style.display='inline'; if(more!=null) more.style.display='none';} else if(button.className=='minusDesc') { button.className='plusDesc'; if(part2!=null) part2.style.display='none'; if(more!=null)more.style.display='inline';}
if( navigator != null && navigator.userAgent != null && navigator.userAgent.indexOf("Firefox") != -1 ){ }
return false;}
var tipwidth='200px'
var tipbgcolor='#FFC'
var disappeardelay='150'
var vertical_offset='0px'
var horizontal_offset='-3px'
var ie4=document.all
var ns6=document.getElementById&&!document.all
function getposOffset(what, offsettype){ var totaloffset=(offsettype=="left")? what.offsetLeft : what.offsetTop; var parentEl=what.offsetParent; while (parentEl!=null){ totaloffset=(offsettype=="left")? totaloffset+parentEl.offsetLeft : totaloffset+parentEl.offsetTop; parentEl=parentEl.offsetParent;}
return totaloffset;}
function showhide(obj, e, visible, hidden, tipwidth){ if (ie4||ns6)
dropmenuobj.style.left=dropmenuobj.style.top=-500
if (tipwidth!=""){ dropmenuobj.widthobj=dropmenuobj.style
dropmenuobj.widthobj.width=tipwidth
}
if (e.type=="click" && obj.visibility==hidden || e.type=="mouseover")
obj.visibility=visible
else if (e.type=="click")
obj.visibility=hidden
}
function iecompattest(){ return (document.compatMode && document.compatMode!="BackCompat")? document.documentElement : document.body
}
function clearbrowseredge(obj, whichedge){ var edgeoffset=(whichedge=="rightedge")? parseInt(horizontal_offset)*-1 : parseInt(vertical_offset)*-1
if (whichedge=="rightedge"){ var windowedge=ie4 && !window.opera? iecompattest().scrollLeft+iecompattest().clientWidth-15 : window.pageXOffset+window.innerWidth-15
dropmenuobj.contentmeasure=dropmenuobj.offsetWidth
if (windowedge-dropmenuobj.x < dropmenuobj.contentmeasure)
edgeoffset=dropmenuobj.contentmeasure-obj.offsetWidth
}
else{ var windowedge=ie4 && !window.opera? iecompattest().scrollTop+iecompattest().clientHeight-15 : window.pageYOffset+window.innerHeight-18
dropmenuobj.contentmeasure=dropmenuobj.offsetHeight
if (windowedge-dropmenuobj.y < dropmenuobj.contentmeasure)
edgeoffset=dropmenuobj.contentmeasure+obj.offsetHeight
}
return edgeoffset
}
function popNote(menucontents, obj, e, tipwidth){ if (window.event) event.cancelBubble=true
else if (e.stopPropagation) e.stopPropagation()
clearhidetip()
dropmenuobj=document.getElementById? document.getElementById("popNote") : popNote
dropmenuobj.innerHTML=menucontents
if (ie4||ns6){ showhide(dropmenuobj.style, e, "visible", "hidden", tipwidth)
dropmenuobj.x=getposOffset(obj, "left")
dropmenuobj.y=getposOffset(obj, "top")
dropmenuobj.style.left=dropmenuobj.x-clearbrowseredge(obj, "rightedge")+"px"
dropmenuobj.style.top=dropmenuobj.y-clearbrowseredge(obj, "bottomedge")+obj.offsetHeight+"px"
}
}
function hidetip(e){ if (typeof dropmenuobj!="undefined"){ if (ie4||ns6)
dropmenuobj.style.visibility="hidden"
}
}
function delayhidepopNote(){ if (ie4||ns6)
delayhide=setTimeout("hidetip()",disappeardelay)
}
function clearhidetip(){ if (typeof delayhide!="undefined")
clearTimeout(delayhide)
}
function hideSelect()
{ if (document.all){ document.all.detailSelect.style.visibility="hidden";}
if (document.all){ document.all.detailSelect2.style.visibility="hidden";}
}
function unhideSelect()
{ if (document.all){ document.all.detailSelect.style.visibility="visible";}
if (document.all){document.all.detailSelect2.style.visibility="visible";}
}
/* nam = cookie name
   tval= true value
   pres= possible results
 */
function isTestGroup(nam,tval,pres){
   return true;
	var ck = getCookie(nam);
	if(ck == null){
	   ck = (new Date().getTime()) % pres;
	   setCookie(nam,ck,365);
	}
	if(ck == tval){
	   return true;
	}else{
	   return false;
	}
}
//baynote API
function getRecommenders(ajaxURL, pageLoc, cmtag)
{
	if(cmtag != ""){
		cmtag = "?cm_sp="+cmtag;
	}
  var url_str = "/servlets/xml_to_json_proxy?url=" + escape(ajaxURL);
  new Ajax.Request(url_str, {
		method: 'get',
		onException: function(requestor, exception){
			alert('request encountered an exception ' + exception.toString());
			alert('hello');
		},
		onComplete: function(transport){
			var myJSON = transport.responseText.evalJSON(true);
			var results = "";
			var rootElement = document.getElementById(pageLoc);
			for(i=0; i<myJSON.guides.rtn; i++){
				//create the price variable
				var price = "$" + myJSON.guides.r[i].a[1].v;
				if(myJSON.guides.r[i].a.length > 2){
					price += " - $" + myJSON.guides.r[i].a[2].v;
				}
				
				//create a new divs
				var Div1 = document.createElement('div');
				var Div3 = document.createElement('div');
				//var Div4 = document.createElement('div');
				var Div5 = document.createElement('b');

				Div1.setAttribute("id", "bn_g_result0_" + (i+1));
				Div1.className = "bn_g_result";
				Div1.className += " bn_g_result" + (i+1);


				Div3.className = "bn_g_result_title";

				//Div4.className = "bn_g_result_attributes";
				//Div4.className += " bn_g_result_attr" + (i+1);

				//Div5.className = "bn_price";
				
				//create a new image
				var productImage = document.createElement('img');

				//create anchors
				var anchor1 = document.createElement("a");
				var anchor2 = document.createElement("a");
				
				if(window.location.protocol == "https:"){
					var secureImage = myJSON.guides.r[i].a[0].v.replace("http://mirror.altrec.com","https://secure.altrec.com/");
					productImage.setAttribute("src", secureImage);
					anchor1.setAttribute("href",myJSON.guides.r[i].u + cmtag);
					anchor2.setAttribute("href",myJSON.guides.r[i].u + cmtag);
				}else{
					productImage.setAttribute("src", myJSON.guides.r[i].a[0].v);
					anchor1.setAttribute("href",myJSON.guides.r[i].u.replace("http://www.altrec.com","")+cmtag);
					anchor2.setAttribute("href",myJSON.guides.r[i].u.replace("http://www.altrec.com","")+cmtag);
				}
				
				
				//create a new text nodes
				var productName = document.createTextNode(myJSON.guides.r[i].t);
				var productPrice = document.createTextNode(price);

				anchor1.appendChild(productImage);
				anchor2.appendChild(productName);
				Div3.appendChild(anchor2);
				Div5.appendChild(productPrice);
				Div4.appendChild(Div5);
				Div1.appendChild(anchor1);
				Div1.appendChild(Div3);
				Div1.appendChild(Div5);
				rootElement.appendChild(Div1);
			}
		}
	});
}
function Recommender(locID, cm_tag, URL){
	//checking parameters
	//alert('checking content:\nlocID: '+locID+'\ncm_tag: '+cm_tag+'\nURL: '+URL);
	
	//properties
	this.locationID = locID;
	this.cmTag = cm_tag;
	this.URLrequested = URL;
	//alert('checking content properties:\nlocID: '+this.locationID+'\ncm_tag: '+this.cmTag+'\nURL: '+this.URLrequested);
	//methods
	this.init = function(){
		//alert('a recommender has been created');
		this.getResults();
	}
	this.getResults = function(){
		var url_str = "/servlets/xml_to_json_proxy?url=" + escape(this.URLrequested);
		new Ajax.Request(url_str, {
			method: 'get',
			onException: function(requestor, exception){
				//alert('request encountered an exception ' + exception.toString());
				//alert('hello');
				document.getElementById(this.locationID).parentNode.style.display = 'none';
			}.bind(this),
			onComplete: function(transport){
				var myJSON = transport.responseText.evalJSON(true);
				if(myJSON.guides.rtn == 0){
					document.getElementById(this.locationID).parentNode.style.display = 'none';
				}
				for(i=0; i<myJSON.guides.rtn; i++){
					document.getElementById(this.locationID).appendChild(recommendItem('horizontalStandard',myJSON.guides.r[i], i, this.cmTag, myJSON.guides.gr, myJSON.guides.g));
				}
			}.bind(this)
		});
	}
}
function recommendItem(layout, info, number, cmtag, req, guide){
	if(layout == "horizontalStandard"){
		return horizontalStandard(info, number, cmtag);
	}
	function horizontalStandard(info, number, cmtag){
		//create the price variable
		var price = "$" + info.a[1].v;
		if(cmtag != ''){
			cmtag = "?cm_sp="+cmtag;	
		}
		if(info.a.length > 2){
			price += " - $" + info.a[2].v;
		}
			
		//create a new divs
		var Div1 = document.createElement('div');
		var Div3 = document.createElement('div');
		//var Div4 = document.createElement('div');
		var Div5 = document.createElement('b');

		//Div1.setAttribute("id", "bn_g_result0_" + (number+1));
		Div1.className = "bn_g_result";
		Div1.className += " bn_g_result" + (number+1);


		Div3.className = "bn_g_result_title";

		//Div4.className = "bn_g_result_attributes";
		//Div4.className += " bn_g_result_attr" + (i+1);

		//Div5.className = "bn_price";
		
		//create a new image
		var productImage = document.createElement('img');

		//create anchors
		var anchor1 = document.createElement("a");
		var anchor2 = document.createElement("a");
		anchor2.className = 'bn_g_result_link';
		
		if(window.location.protocol == "https:"){
			var secureImage = info.a[0].v.replace("http://mirror.altrec.com","https://secure.altrec.com/");
			productImage.setAttribute("src", secureImage);
			anchor1.setAttribute("href",AltrecGlobals.Links.NonSecureDomain + info.u.replace("http://www.altrec.com","") + cmtag);
			anchor2.setAttribute("href",AltrecGlobals.Links.NonSecureDomain + info.u.replace("http://www.altrec.com","") + cmtag);
		}else{
			productImage.setAttribute("src", info.a[0].v);
			anchor1.setAttribute("href",AltrecGlobals.Links.NonSecureDomain + info.u.replace("http://www.altrec.com","") + cmtag);
			anchor2.setAttribute("href",AltrecGlobals.Links.NonSecureDomain + info.u.replace("http://www.altrec.com","") + cmtag);
		}
		//add the tracking for baynote onto the a tags
		anchor1.setAttribute('baynote_bnrank',number+1);
		anchor1.setAttribute('baynote_req',req);
		anchor1.setAttribute('baynote_guide',guide);
		
		anchor2.setAttribute('baynote_bnrank',number+1);
		anchor2.setAttribute('baynote_req',req);
		anchor2.setAttribute('baynote_guide',guide);
		
		
		//create a new text nodes
		var productName = document.createTextNode(info.t);
		var productPrice = document.createTextNode(price);

		anchor1.appendChild(productImage);
		anchor2.appendChild(productName);
		Div3.appendChild(anchor2);
		Div5.appendChild(productPrice);
		Div1.appendChild(anchor1);
		Div1.appendChild(Div3);
		Div1.appendChild(Div5);
		return Div1;
	}
}
function show(obj){
	obj.style.display = 'block';
}
function hide(obj){
	obj.style.display = 'none';
}
//baynote API


//start of nav.scroller.js

/***********************************************
* Pausing up-down scroller- © Dynamic Drive (www.dynamicdrive.com)
* This notice MUST stay intact for legal use
* Visit http://www.dynamicdrive.com/ for this script and 100s more.
***********************************************/

function pausescroller(content, divId, divClass, delay){
this.content=content; //message array content
this.tickerid=divId; //ID of ticker div to display information
this.delay=delay; //Delay between msg change, in miliseconds.
this.mouseoverBol=0; //Boolean to indicate whether mouse is currently over scroller (and pause it if it is)
this.hiddendivpointer=1; //index of message array for hidden div
document.write('<div id="'+divId+'" class="'+divClass+'" style="overflow: hidden"><div class="innerDiv" style="position: absolute; width: 100%" id="'+divId+'1">'+content[0]+'</div><div class="innerDiv" style="position: absolute; width: 100%; visibility: hidden" id="'+divId+'2">'+content[1]+'</div></div>');
var scrollerinstance=this;
if (window.addEventListener) //run onload in DOM2 browsers
window.addEventListener("load", function(){scrollerinstance.initialize()}, false);
else if (window.attachEvent) //run onload in IE5.5+
window.attachEvent("onload", function(){scrollerinstance.initialize()});
else if (document.getElementById) //if legacy DOM browsers, just start scroller after 0.5 sec
setTimeout(function(){scrollerinstance.initialize()}, 700);
}


pausescroller.prototype.initialize=function(){
this.tickerdiv=document.getElementById(this.tickerid);
this.visiblediv=document.getElementById(this.tickerid+"1");
this.hiddendiv=document.getElementById(this.tickerid+"2");
this.visibledivtop=parseInt(pausescroller.getCSSpadding(this.tickerdiv));
//set width of inner DIVs to outer DIV's width minus padding (padding assumed to be top padding x 2)
this.visiblediv.style.width=this.hiddendiv.style.width=this.tickerdiv.offsetWidth-(this.visibledivtop*2)+"px";
this.getinline(this.visiblediv, this.hiddendiv);
this.hiddendiv.style.visibility="visible";
var scrollerinstance=this;
document.getElementById(this.tickerid).onmouseover=function(){scrollerinstance.mouseoverBol=1};
document.getElementById(this.tickerid).onmouseout=function(){scrollerinstance.mouseoverBol=0};
if (window.attachEvent) //Clean up loose references in IE
window.attachEvent("onunload", function(){scrollerinstance.tickerdiv.onmouseover=scrollerinstance.tickerdiv.onmouseout=null});
setTimeout(function(){scrollerinstance.animateup()}, this.delay);
}


pausescroller.prototype.animateup=function(){
var scrollerinstance=this;
if (parseInt(this.hiddendiv.style.top)>(this.visibledivtop+5)){
this.visiblediv.style.top=parseInt(this.visiblediv.style.top)-5+"px";
this.hiddendiv.style.top=parseInt(this.hiddendiv.style.top)-5+"px";
setTimeout(function(){scrollerinstance.animateup()}, 50);
}
else{
this.getinline(this.hiddendiv, this.visiblediv);
this.swapdivs();
setTimeout(function(){scrollerinstance.setmessage()}, this.delay);
}
}


pausescroller.prototype.swapdivs=function(){
var tempcontainer=this.visiblediv;
this.visiblediv=this.hiddendiv;
this.hiddendiv=tempcontainer;
}

pausescroller.prototype.getinline=function(div1, div2){
div1.style.top=this.visibledivtop+"px";
div2.style.top=Math.max(div1.parentNode.offsetHeight, div1.offsetHeight)+"px";
}


pausescroller.prototype.setmessage=function(){
var scrollerinstance=this;
if (this.mouseoverBol==1) //if mouse is currently over scoller, do nothing (pause it)
setTimeout(function(){scrollerinstance.setmessage()}, 100);
else{
var i=this.hiddendivpointer;
var ceiling=this.content.length;
this.hiddendivpointer=(i+1>ceiling-1)? 0 : i+1;
this.hiddendiv.innerHTML=this.content[this.hiddendivpointer];
this.animateup();
}
}

pausescroller.getCSSpadding=function(tickerobj){ //get CSS padding value, if any
if (tickerobj.currentStyle)
return tickerobj.currentStyle["paddingTop"];
else if (window.getComputedStyle) //if DOM2
return window.getComputedStyle(tickerobj, "").getPropertyValue("padding-top");
else
return 0;
}
//start of nav.scroller.js

var BaynoteJSVersion="$Revision: 1.25 $";var BN_READY_SIGNAL="ReadySignal";if(typeof(baynote_globals)=="undefined")var baynote_globals=new Object();baynote_globals.waitForReady=false;function BNLog(){this.timeBase=new Date().getTime();this.lines=new Array();this.lastLine="";this.repCount=0;}
BNLog.prototype.log=function(str){if(str==this.lastLine){++this.repCount;return;}
if(this.repCount>0){this.lines.push("___ ABOVE REPEATED "+this.repCount+" TIME"+((this.repCount>1)?"S":""));}
this.lastLine=str;this.repCount=0;var elapsed=new Date().getTime()-this.timeBase;
this.lines.push(elapsed+": "+str);}
BNLog.prototype.toString=function(){if(this.repCount>0){this.lines.push("___ ABOVE REPEATED "+this.repCount+" TIME"+((this.repCount>1)?"S":""));this.lastLine="";this.repCount=0;}
return this.lines.join("\n");}
if(typeof(bnLog)=="undefined"){var bnLog=new BNLog();}
function BNCriticalSectionQueue(){this.waitList=new Object();this.lastId=0;}
BNCriticalSectionQueue.prototype.issueId=function(){return++this.lastId;}
BNCriticalSectionQueue.prototype.enqueue=function(id,item){this.waitList[id]=item;}
BNCriticalSectionQueue.prototype.getWaiter=function(id){return(id==null)?null:this.waitList[id];}
BNCriticalSectionQueue.prototype.firstWaiter=function(){return this.getWaiter(this.nextWaiterKeyAfter(null));}
BNCriticalSectionQueue.prototype.nextWaiterAfter=function(id){return this.getWaiter(this.nextWaiterKeyAfter(id));}
BNCriticalSectionQueue.prototype.nextWaiterKeyAfter=function(id){for(var currKey in this.waitList){if(typeof(this.waitList[currKey])!="object")continue;if(id==null)return currKey;if(id==currKey)id=null;}
return null;}
BNCriticalSectionQueue.prototype.nextPredecessor=function(target,start){for(var currWaiter=start;currWaiter!=null;currWaiter=this.nextWaiterAfter(currWaiter.id)){if(currWaiter.enter||(currWaiter.number!=0&&(currWaiter.number<target.number||(currWaiter.number==target.number&&currWaiter.id<target.id)))){return currWaiter;}}
return null;}
function BNCriticalSection(csQueue){this.csQueue=csQueue;this.debug=1;}
BNCriticalSection.prototype.enter=function(enterFunc){this.enterFunc=enterFunc;this.id=this.csQueue.issueId();this.csQueue.enqueue(this.id,this);this.enter=true;this.number=(new Date()).getTime();this.enter=false;this.attempt(this.csQueue.firstWaiter());}
BNCriticalSection.prototype.leave=function(){if(this.debug)bnLog.log("LEAVE "+this.id);this.number=0;}
BNCriticalSection.prototype.attempt=function(start){var nextReady=this.csQueue.nextPredecessor(this,start);if(nextReady!=null){if(this.debug)bnLog.log("WAIT "+this.id);var me=this;return setTimeout(function(){me.attempt(nextReady);},50);}
if(this.debug)bnLog.log("ENTER "+this.id);this.enterFunc();}
function BNResourceManager(){this.csQueue=new BNCriticalSectionQueue();this.critSec=null;this.debug=1;this.resources=new Object();this.waiting=new Object();}
BNResourceManager.prototype.getResource=function(rId){return this.resources[rId];}
BNResourceManager.prototype.loadResource=function(rId,rAddress,rType){if(typeof(this.resources[rId])!="undefined")return;this.resources[rId]=null;var critSec=new BNCriticalSection(this.csQueue);critSec.enter(function(){bnResourceManager.inject(rId,rAddress,rType,critSec);});}
BNResourceManager.prototype.inject=function(rId,rAddress,rType,critSec){this.critSec=critSec;if(this.debug)bnLog.log("INJECT "+this.critSec.id+" ("+rId+")");if(!rType||rType=="script"){var scriptTag1=document.createElement("script");scriptTag1.language="javascript";scriptTag1.src=rAddress;var head=document.getElementsByTagName("head");head[0].appendChild(scriptTag1);}
else if(rType=="img"){var img=document.createElement("IMG");var handler=function(){bnResourceManager.registerAndAddResource(rId,img);};if(img.addEventListener)img.addEventListener("load",handler,false);else if(img.attachEvent)img.attachEvent("onload",handler);else img["onload"]=handler;img.src=rAddress;}
else alert("Unexpected resource type to loadResource: "+rType);}
BNResourceManager.prototype.waitForResource=function(rId,callbackCode,rAddress,rType){with(this){if(getResource(rId)){this.runCallback(callbackCode);}
else{if(typeof(waiting[rId])=="undefined")waiting[rId]=new Array();var waitingList=waiting[rId];waitingList[waitingList.length]=callbackCode;if(rAddress)this.loadResource(rId,rAddress,rType);}}}
BNResourceManager.prototype.wakeUpWaiting=function(rId){with(this){var waitingList=waiting[rId];if(!waitingList)return;for(var i=0;i<waitingList.length;i++){if(waitingList[i]){var codeToEval=waitingList[i];waitingList[i]=null;if(this.debug&&codeToEval)bnLog.log("CALLBACK "+rId+": "+codeToEval);this.runCallback(codeToEval);}}}}
BNResourceManager.prototype.registerAndAddResource=function(rId,resource){if(this.debug)bnLog.log("REGISTER "+(this.critSec?this.critSec.id:"")+" ("+rId+")");this.resources[rId]=resource;this.wakeUpWaiting(rId);this.critSec.leave();setTimeout("bnResourceManager.wakeUpWaiting('"+rId+"')",5000);}
BNResourceManager.prototype.registerResource=function(rId){this.registerAndAddResource(rId,true);}
BNResourceManager.prototype.runCallback=function(callback){if(typeof(callback)=="string")eval(callback);else if(typeof(callback)=="function")callback();else alert("Invalid callback, type="+typeof(callback));}
if(typeof(bnResourceManager)=="undefined"){var bnResourceManager=new BNResourceManager();}
function BNSystem(){this.testServer=null;}
BNSystem.prototype.getCookieValue=function(cookieName,cookieSubDomain){if(!cookieSubDomain)cookieSubDomain=baynote_globals.cookieSubDomain;if(cookieSubDomain)cookieName+=("-"+cookieSubDomain);var sRE="(?:; )?"+cookieName+"=([^;]*);?";var oRE=new RegExp(sRE);if(oRE.test(document.cookie)){return decodeURIComponent(RegExp["$1"]);}else{return null;}}
BNSystem.prototype.setCookie=function(cookieName,cookieValue,cookiePath,cookieExpires,cookieDomain,cookieSubDomain){cookieValue=encodeURIComponent(cookieValue);if(cookieExpires=="NEVER"){var nowDate=new Date();nowDate.setFullYear(nowDate.getFullYear()+500);cookieExpires=nowDate.toGMTString();}
else if(cookieExpires=="SESSION")cookieExpires="";if(cookiePath!="")cookiePath=";Path="+cookiePath;if(cookieExpires!="")cookieExpires=";expires="+cookieExpires;if(!cookieDomain)cookieDomain=(baynote_globals.cookieDomain)?baynote_globals.cookieDomain:"";if(cookieDomain!="")cookieDomain=";domain="+cookieDomain;if(!cookieSubDomain)cookieSubDomain=baynote_globals.cookieSubDomain;if(cookieSubDomain)cookieName+=("-"+cookieSubDomain);var cookieStr=cookieName+"="+cookieValue+cookieExpires+cookiePath+cookieDomain;if(cookieStr.length>4096)return false;document.cookie=cookieStr;return true;}
BNSystem.prototype.removeCookie=function(cookieName,cookieDomain){this.setCookie(cookieName,"","/","Mon, 1 Jan 1990 00:00:00",cookieDomain);}
BNSystem.prototype.getURLParam=function(name,url){if(!url)var url=window.location.href;var regex=new RegExp("[\\?&]"+name+"=([^&#]*)");var match=regex.exec(url);if(!match)return null;else return match[1];}
BNSystem.prototype.getTestServer=function(){if(this.testServer!=null)return this.testServer;var testServer=this.getURLParam("bn_test");if(testServer)this.setCookie("bn_test",testServer,"/","SESSION");else if(testServer=="")this.removeCookie("bn_test");else{testServer=this.getCookieValue("bn_test");if(!testServer)testServer="";}
this.testServer=testServer;return testServer;}
if(typeof(bnSystem)=="undefined"){var bnSystem=new BNSystem();}
if(typeof(BNTag)=="undefined"){function BNTag(previousTag){if(previousTag){this.id=previousTag.id+1;this.server=previousTag.server;this.customerId=previousTag.customerId;this.code=previousTag.code;}
else this.id=0;this.attrs=new Object();this.docAttrs=new Object();this.css=new Object();}}
BNTag.prototype.getCommonResourceId=function(){return"Common";}
BNTag.prototype.getCommonResourceAddress=function(tag){return(this.server+"/baynote/tags2/common.js");}
BNTag.prototype.getFailsafeResourceId=function(){return"Failsafe";}
BNTag.prototype.getFailsafeResourceAddress=function(){var v=BaynoteJSVersion.split(" ")[1];var u=bnSystem.getCookieValue("bn_u");return(this.server+"/baynote/customerstatus2?customerId="+this.customerId+"&code="+this.code+"&x="+this.id+(new Date().getTime())+"&v="+v+"&u="+u);}
BNTag.prototype.show=function(parentElemId){if(this.id==0)document.write("<span id='bn_placeholder_global'></span>");this.placeHolderId="bn_placeholder"+this.id;var placeHolderType;if(this.placeHolderElement)placeHolderType=this.placeHolderElement;else placeHolderType=this.popup?"span":"div";if(parentElemId){var placeHolder=document.createElement(placeHolderType);placeHolder.id=this.placeholderId;document.getElementById(parentElemId).appendChild(placeHolder);}
else document.write("<"+placeHolderType+" id='"+this.placeHolderId+"'></"+placeHolderType+">");window["bn_tags"][this.id]=this;var testServer=bnSystem.getTestServer();if(testServer){var reValidTestServer=new RegExp("^https?://[^/]*\.baynote\.(com|net)(:\d+)?(/.*)?");if(reValidTestServer.test(testServer))this.server=testServer;else alert("Ignoring invalid test server \""+testServer+"\"");}
this.showWhenReady(this);baynote_tag=new BNTag(this);}
BNTag.prototype.showWhenReady=function(tag){if(baynote_globals.waitForReady&&!bnResourceManager.getResource(BN_READY_SIGNAL)){bnResourceManager.waitForResource(BN_READY_SIGNAL,function(){tag.showWhenReady(tag);});return;}
var failsafeId=this.getFailsafeResourceId();if(!bnResourceManager.getResource(failsafeId)){bnResourceManager.waitForResource(failsafeId,function(){tag.showWhenReady(tag);},this.getFailsafeResourceAddress(),"img");return;}
var commonId=this.getCommonResourceId();if(!bnResourceManager.getResource(commonId)){bnResourceManager.waitForResource(commonId,function(){tag.showWhenReady(tag);},this.getCommonResourceAddress(),"script");return;}
bnTagManager.show(tag.id);}
BNTag.prototype.noshow=function(){window["bn_tags"][this.id]=this;baynote_tag=new BNTag(this);}
BNTag.prototype.getParam=function(name,defaultValue){var value=this[name];if(typeof(value)=="undefined"||value==null)return defaultValue;else return value;}
if(typeof(baynote_tag)=="undefined"){window["bn_tags"]=new Array();var baynote_tag=new BNTag(null);}
function bnReadySignal(){bnResourceManager.registerResource(BN_READY_SIGNAL);}
function bnCall(resName,methodName,methodArg){var resource=bnResourceManager.getResource(resName);if(!resource){bnResourceManager.waitForResource(resName,function(){bnCall(resName,methodName,methodArg);});return;}
if(typeof(resource)!="object"){return;}
var method=resource[methodName];if(typeof(method)!="function"){return;}
method.call(resource,methodArg);}var url = window.location.href;
var testMode = (url.indexOf('http') == -1);
var imageRoot = '/new_includes/';
if (testMode) imageRoot = '';

var instanceObjects = {};
var InstanceContainer = {
            Init : function()
            {
	            this.hasBeenInit = true;
            },
            GetInstance : function(id)
            {
	            return instanceObjects[id];
            },
    RegisterInstance: function(obj, idProp)
            {
	            if (!this.hasBeenInit)
	            {
		            this.Init();
	            }
        if (!idProp) idProp = 'id';
        instanceObjects[obj[idProp]] = obj;
            },
            FireEvent : function(id, event)
            {
	            if (arguments.length > 2)
		            this.GetInstance(id)[event](arguments[2]);
        else this.GetInstance(id)[event]();
            },
            FireEventAsync : function(id, event, delay)
            {
	            var args = '';
	            if (arguments.length > 3)
	            {
		            args = ", '" + arguments[3] + "'";
	            }
        setTimeout("try{InstanceContainer.FireEvent('" + id + "', '" +
        event +
        "'" +
        args +
        ");}catch(e){}", delay);
    },
    destroy : function()
    {
    	var objList = [];
    	for (var obj in instanceObjects)
    	{
    		objList.push(obj);
            }
    	
    	for (var i=0; i<objList.length; i++)
    	{
    		instanceObjects[objList[i]] = null;
    	}
    	instanceObjects[objList[i]] = null;
    	
    }
        };



var AltrecGlobals = {
    Variables: {
        freeShippingTreashold: 45,
        Messages : {
			ServerBusy : "We're sorry, we're having technical difficulties. Please call our Customer Service team to complete your order: <b>(800) 369-3949</b>."
		}
    },
    Site: {
        LoggedInUser: null,
        DomainPrefix: "",
        isUserLoggedIn: function()
        {
            if (this.LoggedInUser) 
                return true;
            else                 
                return false;
        },
        Search : 
        {	 
        	autocompleteEnabled : true,
        	minChars : 3,
        	msDelay : 0,
			GetUrl : function()
			{
        		return AltrecGlobals.Site.AjaxPrefix + '/servlets/autocomplete';
			},
        	Init : function()
        	{
				 PayloadMediator.renderPayload('SearchPayload', { defaultText : 'Search our Site'});
        	}
        }
    },
    Pages: {
        DefaultWidgetMode: 'edit',
        PageName: ''
    },
    Coremetrics: {},
    Links: {},
    Objects : {
    	CMReg : 
    	{
    		id : 'CMRegistration',
    		fireCMReg : function(payloadData)
    		{
    			this.payloadData = payloadData;
    			InstanceContainer.FireEventAsync(this.id, 'sendCMReg', 500);
    		},
	    	sendCMReg : function()
	    	{
    			var payloadData = this.payloadData;
    			var key = payloadData.Customer.email + payloadData.BillingAddress.city + payloadData.BillingAddress.stateprov + 
    				payloadData.BillingAddress.postcode + payloadData.AccountAttribute.subscribedAltrec;
    			
    			if (this.lastKey != key)
    			{
    				this.lastKey = key;
		    		if (payloadData && payloadData.Customer && payloadData.Customer.email && payloadData.Customer.email.length > 0)
		    			CreateRegistrationTag(payloadData.Customer.email,
		    					payloadData.Customer.email, 
		    					payloadData.BillingAddress.city,
		    					payloadData.BillingAddress.stateprov,
		    					payloadData.BillingAddress.postcode,
		    					"Dispatch", (( payloadData.AccountAttribute.subscribedAltrec == 'true') ? "Y" :"N"));
    			}
	    	}
    		
    	}
    }
};

InstanceContainer.RegisterInstance(AltrecGlobals.Objects.CMReg);


AltrecGlobals.Functions = {
	Element : {
		getFirstClassName : function(element, className)
		{
			if (element.className && element.className.toLowerCase && element.className.toLowerCase().contains(className.toLowerCase()))
				return element;
			
			for (var i = 0; i < element.childNodes.length; i++) 
			{
				var val = AltrecGlobals.Functions.Element.getFirstClassName(element.childNodes[i], className);
				if (val) return val;
			}
			
			return null;
		}
	},
	

    Cookies: {
        get: function(Name)
        {
            var search = Name + "=";
            if (document.cookie.length > 0) 
            {
                offset = document.cookie.indexOf(search);
                if (offset != -1) 
                {
                    offset += search.length;
                    end = document.cookie.indexOf(";", offset);
                    if (end == -1) end = document.cookie.length;
                    return unescape(document.cookie.substring(offset, end));
                }
            }
        },
        set: function(params)
        {
            var myDomain = AltrecGlobals.Links.TopLevelDomain || document.domain;
			myDomain = myDomain.split(".")[1] + "." + myDomain.split(".")[2];
			if (params.domain)
				myDomain = params.domain;
				
            var today = new Date();
            var expires = new Date();
            expires.setTime(today.getTime() + 1000 * 60 * 60 * 24 * (params.days || 365));
            document.cookie = params.name + "=" + escape(params.value) + "; expires=" + expires.toGMTString() + "; domain=" + myDomain + "; path=/";
        }
        
    },
    Object: {
       
		CompareTo: function(obj1, obj2, deep, level)
		{
			if ((obj1 && !obj2) ||
			(!obj1 && obj2)) 			
				return false;
			
			if (!obj1 && !obj2) 			
				return true;
			
			if (!level) level = 0;
			
			if ((Object.isString(obj1) && Object.isString(obj2)) ||
			(Object.isNumber(obj1) && Object.isNumber(obj2))) 
			{ return (obj1 === obj2); }
			
			if (Object.isArray(obj1) && Object.isArray(obj2)) 
			{
				if (obj1.length != obj2.length) 				
					return false;
				if (deep || level == 0) 
				{
					for (var i = 0; i < obj1.length; i++) 
					{
						if (!this.CompareTo(obj1[i], obj2[i], deep, (level + 1))) 
						{ return false; }
					}
					return true;
				}
			}
			
			if (!deep && level > 0) 
			{ 
				return true; 
			}
			
			var keys1 = Object.keys(obj1).sort();
			var keys2 = Object.keys(obj2).sort();
			if (keys1.length != keys2.length) 			
				return false;
			
			for (var i = 0; i < keys1.length; i++) 
			{
				if (!(this.CompareTo(obj1[keys1[i]], obj2[keys1[i]], deep, (level + 1)))) 
				{ 
					return false; 
				}
			}
			
			return true;
		},
		
		Inherit : function(baseClass, inheritingClass)
		{
			baseClass.call(inheritingClass);
			inheritingClass.base = {};
			
			var baseFunctions = {};
			for (i in inheritingClass)
			{
				if (Object.isFunction(inheritingClass[i]))
				{
					baseFunctions[i] = i;
				}
			}
			
			for (i in baseFunctions) 
			{
				inheritingClass.base[i] = inheritingClass[i].bind(inheritingClass);
			}
		},
		Destroy : function(obj)
		{
			if (obj == null || obj == undefined)
				return;
				
			if (Object.isString(obj) || Object.isNumber(obj)) 
			{ 
				return; 
			}
			
			if (Object.isArray(obj)) 
			{
				for (var i = 0; i < obj.length; i++) 
				{
					this.Destroy(obj[i]);
					obj[i] = null;
				}
			}
			else if (!Object.isFunction(obj)) 
			{
				var keys = Object.keys(obj).sort();
				for (var i = 0; i < keys.length; i++) 
				{
					this.Destroy(obj[keys[i]]);
					obj[keys[i]] = null;
				}
			}
			obj = null;
		}
    }
};


function getIEVersionNumber()
{
	var ua = navigator.userAgent;
	var MSIEOffset = ua.indexOf("MSIE ");

	if (MSIEOffset == -1)
    { return 0; }
	else
    { return parseFloat(ua.substring(MSIEOffset + 5, ua.indexOf(";", MSIEOffset))); }
	}

function gup(name)
{
	name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
	var regexS = "[\\?&]" + name + "=([^&#]*)";
	var regex = new RegExp(regexS);
	var results = regex.exec(window.location.href);
	if (results == null)
		return "";
	else
		return results[1];
}

function FormatCurrency(AmountNumber, withDollarSign)
{
	var amount = Math.round(AmountNumber * 100) / 100;
	amount = amount.toFixed(2);

	var delimiter = ","; // replace comma if desired
	var a = amount.split('.', 2);
	var d = a[1];
	var i = parseInt(a[0]);
	if (isNaN(i))
    { return ''; }
	var minus = '';
	if (i < 0)
	{
		minus = '-';
	}
	i = Math.abs(i);
	var n = new String(i);
	var a = [];
	while (n.length > 3)
	{
		var nn = n.substr(n.length - 3);
		a.unshift(nn);
		n = n.substr(0, n.length - 3);
	}
	if (n.length > 0)
	{
		a.unshift(n);
	}
	n = a.join(delimiter);
	if (d.length < 1)
	{
		amount = n;
	}
	else
	{
		amount = n + '.' + d;
	}
	amount = minus + amount;
	return ((withDollarSign) ? "$" : "") + amount;
}

function bindJSON(elementToBind, dataIslandAttrName, jsonObject)
{
	var elements = elementToBind.getElementsByTagName("*");
	for ( var i = 0; i < elements.length; i++)
	{
		var element = elements[i];
		var val = element.getAttribute(dataIslandAttrName);
		if (val != null && val != 'undefined')
		{
			var propertyToSet = 'innerHTML';
			var splitAttr = val.split(':');
			var objToSet = element;
			for ( var j = 1; j < splitAttr.length; j++)
			{
				propertyToSet = splitAttr[j - 1];
				val = splitAttr[j];
                if (j != (splitAttr.length - 1)) objToSet = objToSet[propertyToSet];
			}

			if (val.split('(').length > 1)
				objToSet[propertyToSet] = jsonObject[val.split('(')[0]]();
            else objToSet[propertyToSet] = jsonObject[val];
			bindJSON(element, dataIslandAttrName, jsonObject);
		}
	}
}



Element.addMethods({
    bindJSON: function(element, dataIslandAttrName, jsonObj)
    {
        element = $(element);
        bindJSON(element, dataIslandAttrName, jsonObj, true);
        return element;
    }
});


Element.addMethods({
    findElementsByClassName: function(element, className)
    {
        element = $(element);
        if (Element.getElementsByClassName) 
        { return Element.getElementsByClassName(element, className); }
        else 
        { return element.getElementsByClassName(className); }
    }
});

/* new_includes/javascript/detail/prodSelector...js */
String.prototype.trim = function()
{
	return this.replace(/^\s+|\s+$/g, "");
};
String.prototype.ltrim = function()
{
	return this.replace(/^\s+/, "");
};
String.prototype.rtrim = function()
{
	return this.replace(/\s+$/, "");
};
String.prototype.contains = function(str)
{
    return this.indexOf(str) >= 0;
};

function Left(str, n)
{
	if (n <= 0)
		return "";
	else if (n > String(str).length)
		return str;
	else
		return String(str).substring(0, n);
}

function Right(str, n)
{
	if (n <= 0)
		return "";
	else if (n > String(str).length)
		return str;
	else
	{
		var iLen = String(str).length;
		return String(str).substring(iLen, iLen - n);
	}
}

function basicAcsSort(thisObject, thatObject)
{
	if (thisObject > thatObject)
    { return 1; }
	else if (thisObject < thatObject)
    { return -1; }
	return 0;
}

function basicDescSort(thisObject, thatObject)
{
	if (thisObject > thatObject)
    { return -1; }
	else if (thisObject < thatObject)
    { return 1; }
	return 0;
}

var TimerQueue = {
            queue :new Array(),
            messages :new Array(),
            enabled :true,
            addTimer : function(timerID)
            {
        if (this.enabled) this.queue.push({
		                time :new Date(),
		                id :timerID
		            })
            },
            dequeueTimer : function()
            {
    	try
    	{
	            if (!this.enabled)
		            return '';
	            var now = new Date();
	            var timerObj = this.queue.pop();
	        this.messages.push(timerObj.id + ' took ' +
	        (now.getTime() - timerObj.time.getTime()));
    	}
    	catch(e){}
            },

            getAllMessage : function(deliminator)
            {
	            if (!this.enabled)
		            return '';
	            var queueLength = this.queue.length;
	            for ( var i = 0; i < queueLength; i++)
	            {
		            this.dequeueTimer();
	            }

	            var returnStr = '';
	            for ( var i = 0; i < this.messages.length; i++)
	            {
		            returnStr += this.messages[i];
	            }
	            this.queue = new Array();
	            this.messages = new Array();
	            return returnStr;
            }
        };

function PostForm(form, callback)
{
	try
	{
		var inputs = form.getElementsByTagName('input');
		var parameters = {};
		for ( var i = 0; i < inputs.length; i++)
		{
			var input = inputs[i];
			var type = input.getAttribute('type');
            if (!type) type = input.type;
			if (type != 'submit' && type != 'button')
			{
				var name = input.getAttribute('name');
				var value = input.getAttribute('value');
                if (!name) name = input.name;
                if (!value) value = input.value;
				parameters[name] = value;
			}
		}
		var selects = form.getElementsByTagName('select');
		for ( var i = 0; i < selects.length; i++)
		{
			var selectElement = selects[i];
			for ( var i = 0; i < selectElement.options.length; i++)
			{
				var option = selectElement.options[i];
				if (option.selected)
				{
                    if (!parameters[selectElement.name]) parameters[selectElement.name] = [];
					parameters[selectElement.name].push(option.value);
				}
			}
		}

		var action = form.getAttribute('action');
        if (!action) action = form.action;
		var request = new Ajax.Request(action, {
		    method :'post',
		    parameters :parameters,
		    onSuccess : function(transport)
		    {
			    var response = transport.responseText;
			    callback(response);
            }
.bind(this)            ,
		    onFailure : function(transport)
		    {
			    var response = transport.statusText;
			    callback(response);
            }
.bind(this)
		});
	}
	catch (e)
	{
	}

	return false;
}

function GetElement(elementId)
{
	if (elementId.id)
    { return elementId; }
	else
		return document.getElementById(elementId);
}

var ProtoLite = {

            Browser : {
                IE :!!(window.attachEvent && !window.opera),
                Opera :!!window.opera,
                WebKit :navigator.userAgent.indexOf('AppleWebKit/') > -1,
        Gecko: navigator.userAgent.indexOf('Gecko') > -1 &&
        navigator.userAgent.indexOf('KHTML') == -1,
        MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
            },

            Object : {
                Extend : function(destination, source)
                {
	                for ( var property in source)
		                destination[property] = source[property];
	                return destination;
                },
                CopyProperties : function(destination, source)
                {
	                for ( var property in source)
	                {
		                if (typeof source[property] != "function")
		                {
			                destination[property] = source[property];
		                }
	                }
	                return destination;
                },
                CopyFunctions : function(destination, source)
                {
	                for ( var property in source)
	                {
		                if (typeof source[property] == "function")
		                {
			                destination[property] = source[property];
		                }
	                }
	                return destination;
                },

                Clone : function(object)
                {
	                return this.Extend( {}, object);
                }
            },

            String : {
                capitalize : function(str)
                {
            return str.charAt(0).toUpperCase() +
            str.substring(1).toLowerCase();
                },

                camelize : function(str)
                {
	                var parts = str.split('-'), len = parts.length;
	                if (len == 1)
		                return parts[0];

            var camelized = str.charAt(0) == '-' ? parts[0].charAt(0).toUpperCase() +
            parts[0].substring(1) : parts[0];

	                for ( var i = 1; i < len; i++)
                camelized += parts[i].charAt(0).toUpperCase() +
                parts[i].substring(1);

	                return camelized;
                }
            },

            Element : {
                getStyle : function(elementId, style)
                {
	                var element = GetElement(elementId);
            style = style == 'float' ? 'cssFloat' : ProtoLite.String.camelize(style);
	                var value = element.style[style];

	                if (ProtoLite.Browser.IE)
	                {
                if (!value && element.currentStyle) value = element.currentStyle[style];

		                if (style == 'opacity')
		                {
                    if (value = (this.getStyle(elementId, 'filter') || '').match(/alpha\(opacity=(.*)\)/)) 
				                if (value[1])
					                return parseFloat(value[1]) / 100;
			                return 1.0;
		                }

		                if (value == 'auto')
		                {
                    if ((style == 'width' || style == 'height') &&
                    (this.getStyle(elementId, 'display') != 'none'))                         
                        return element['offset' +
                        this.capitalize(style)] +
                        'px';
			                return null;
		                }
		                return value;
	                }
	                else
	                {
		                if (!value)
		                {
                    var css = document.defaultView.getComputedStyle(element, null);
			                value = css ? css[style] : null;
		                }
	                }

	                if (style == 'opacity')
		                return value ? parseFloat(value) : 1.0;
	                alert(value);
	                return value == 'auto' ? null : value;
                },

                getOpacity : function(elementId)
                {
	                var element = GetElement(elementId);
	                return this.getStyle(element, 'opacity');
                },

                setOpacity : function(elementId, value)
                {
	                var element = GetElement(elementId);

	                if (ProtoLite.Browser.IE)
	                {
		                function stripAlpha(filter)
		                {
			                return filter.replace(/alpha\([^\)]*\)/gi, '');
		                }
		                var currentStyle = element.currentStyle;
                if ((currentStyle && !currentStyle.hasLayout) ||
                (!currentStyle && element.style.zoom == 'normal')) element.style.zoom = 1;

                var filter = this.getStyle(elementId, '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;
	                }
	                else
	                {
                element.style.opacity = (value == 1 || value === '') ? '' : (value < 0.00001) ? 0 : value;
	                }
	                return element;
                }
            },

            Array : {
	            IndexOf : function(arr, item)
	            {
		            var i = 0;
		            var length = arr.length;
            if (i < 0) i = length + i;
		            for (; i < length; i++)
			            if (arr[i] === item)
				            return i;
		            return -1;
	            }
            }
};




AltrecGlobals.Pages.Checkout = {
	lastUniqueWidgetId: 1000,
	getNextUniqueId : function()
	{
		return this.lastUniqueWidgetId++;
	},
	Timeout : 30
};

function AltrecPageWidget()
{
	this.id = null;
	this.payloadData;
	this.IsSingleton = true;
	this.eventMediator;
	this.childWidgets = {};
	this.renderCount = 0;
	this.renderCallbacks = [];
	this.dirty = false;
	this.skipMerge = false;
	this.eventCache = [];
	
	var attached = true;
	var WidgetType;
	var self = this;
	this.timedOutRequest = {};
	this.compareToMode = 'none';
	
	this.identify = function()
	{
		alert(this.Container.id + "\n"+ this.constructor.toString());
	};
	
	this.getWidgetType = function()
	{
		if (!WidgetType)
		{
			var constructor = this.constructor.toString();
			constructor = constructor.replace("function ", "");
			WidgetType = constructor.substring(0, constructor.indexOf("("));
		}
		return WidgetType;
	};
	
	this.getDisplayName = function()
	{
		return this.getWidgetType();
	};
	
	this.initWidget = function(pContainer, pParent)
	{
		this.Container = $(pContainer);
		if (!this.Container) 
		{
			attached = false;
			return;
		}
		this.Parent = pParent;
		this.id = this.Container.id;
		this.eventMediator = (this.useParentMediator) ? this.Parent.eventMediator : this;
		this.uniqueWidgetId = "WidgetID_" + AltrecGlobals.Pages.Checkout.getNextUniqueId();
		InstanceContainer.RegisterInstance(this, 'uniqueWidgetId');
		
		this.containersToBind = [this.Container];
	};
	
	this.isAttached = function()
	{
		return attached;
	};
	
	this.comparePayload = function()
	{
		if (this.compareToMode.contains('compare')) 
		{
			var deep = this.compareToMode.toLowerCase().contains('deep');
			if (this.payloadData && this.lastPayload) 
			{
				TimerQueue.addTimer(" - ");
				if (AltrecGlobals.Functions.Object.CompareTo(this.payloadData, this.lastPayload, deep)) 
				{
					TimerQueue.addTimer("\nCompareTrue");
					TimerQueue.dequeueTimer();
					TimerQueue.dequeueTimer();
					
					return true;
				}
				TimerQueue.addTimer("\nCompareFalse");
				TimerQueue.dequeueTimer();
				TimerQueue.dequeueTimer();
			}
			
			return false;
		}
	};
	
	this.rerender = function()
	{
		this.render(this.payloadData);
	};
	
	this.render = function(pPayload)
	{
		if (!attached)
			return;
		
		this.payloadData = pPayload;	
		this.blankPayload = (!pPayload);
		
		if (this.startRender && this.startRender() == false)
			return;

		if (this.massagePayload && this.payloadData)
		{
			if (this.massagePayload() == false)
				return;
		}

		if (!this.skipMerge && this.renderCount>0 && this.comparePayload() == true)
			return;
		this.skipMerge = false;
		
		this.lastPayload = {};
		Object.extend(this.lastPayload, this.payloadData);

		if (this.preDraw) 
		{
			if (this.preDraw() == false)
				return;
		}
		
		if (this.draw)
			this.draw();
		else
			this.bindContainer();
			
		if (this.postDraw)
			this.postDraw();
		this.renderCount++;
		this.lastRenderedPayload = this.payloadData;
		
		var message_name = (this.payloadData.results && this.payloadData.results.message_name) ? (this.payloadData.results.message_name) : this.payloadData.message_name;
		var eventWidget = this.cmEventProps.getWidget();
		if (eventWidget)
			this.sendCmEvent(message_name, eventWidget);
			
		this.renderCallbacks.each(function(callbackDelegate){
			callbackDelegate(this);
		}.bind(this));
		
	};
	
	this.registerRenderEvent = function(callbackDelegate)
	{
		this.renderCallbacks.push(callbackDelegate);
	};
	
	this.isDirty = function()
	{
		this.dirtyWidgets = [];
		var isDirty = JsonBinder.isDirty(this.payloadData);
		if (isDirty) 
		{
			this.dirtyWidgets.push(this.getDisplayName());
			return true;
		}
		for (var i in this.childWidgets)
		{
			if (this.childWidgets[i].isDirty && this.childWidgets[i].isDirty()) 
			{
				this.dirtyWidgets.push(this.childWidgets[i].getDisplayName());
				return true;
			}
		}
		
		return false;
	};
	
	this.validate = function()
	{
		this.validationErrors = [];
		this.fieldErrors = {};
		
		if (!this.isAttached())
			return true;
			
		var allValid = this.doValidate();
		for (var i in this.childWidgets)
		{
			if (!this.childWidgets[i].validate)
				continue;
				
			allValid = ( this.childWidgets[i].validate() && allValid);
			this.validationErrors.push(this.childWidgets[i].validationErrors);
			this.validationErrors = this.validationErrors.flatten();
		}
		
		if (this.validationErrors.length>0 && this.handleValidationErrors)
		{
			this.handleValidationErrors(this.validationErrors);
		}
		
		for (var field in this.fieldErrors)
		{
			this.sendCmEvent('field_validation::'+field);
			this.fieldErrors[field] = null;
		}
		this.fieldErrors = null;
		return allValid;
	};
	
	this.doValidate = function()
	{
		return true;
	};
	
	this.fieldValidationCallback = function(result, elem)
	{
		if (!result && this.fieldErrors) 
		{
			var name = (!elem.name) ? elem.name : elem.id;
			this.fieldErrors[name] = elem;
		}
	};
	
	this.mergePayloadData = function(payloadToMerge)
	{
		if (!this.payloadData)
			this.payloadData = {};
			
		for(var prop in payloadToMerge)
		{
			this.payloadData[prop] = payloadToMerge[prop];
		}
	};
	
	this.getTemplateVar = function()
	{
		return this.getWidgetType() + 'Var';
	};
	
	this.getAsyncVar = function()
	{
		return 'asyncVar';
	};
	
	this.getContainersToBind = function()
	{
		return this.containersToBind;
	};
	
	this.addContainerToBind = function(container)
	{
		this.containersToBind.push(container);
	};
	
	this.bindContainer = function()
	{
		Element.bindJSON(this.getContainersToBind(), this.getTemplateVar(), this.payloadData);
	};
	
	this.bindAsyncStart = function(containersToBind, templateVar)
	{
		this.payloadData.asyncRequest = 'true';
		Element.bindJSON((containersToBind || this.getContainersToBind()), (templateVar || this.getAsyncVar()), this.payloadData);
	};
	
	this.bindAsyncEnd = function(containersToBind, templateVar)
	{
		delete this.payloadData['asyncRequest'];
		Element.bindJSON((containersToBind || this.getContainersToBind()), (templateVar || this.getAsyncVar()), this.payloadData);
	};
	
	this.constructJavascriptLinkEvent = function(mediatorEvent, argsArrayObj, mediator)
	{
		if (!attached)
			return '';
		
		var argsArray = [];
		if (Object.isArray(argsArrayObj))
		{
			argsArray = argsArrayObj;
		}
		else
		{
			if (argsArrayObj)
				argsArray.push(argsArrayObj);
		}
		var mediatorToUse = (mediator) ? mediator : this.eventMediator;
		
		var eventStr = "javascript:InstanceContainer.GetInstance('"+mediatorToUse.uniqueWidgetId+"')."+mediatorEvent+"(";  
		
		var first = true;
		argsArray.each(function(arg){
			if (!first)
				eventStr += ",";
				
			eventStr += "'"+arg+"'";
			first = false;
		});
		
		eventStr += ")";
		return eventStr;
	};
	
	this.attachReturnKey = function(domElement, functionParam, objectToBind)
	{
		if (!objectToBind)
		{
			objectToBind = this.eventMediator;
		}
		
		var closure = function(event)
		{
			this[functionParam](event);
		}.bind(objectToBind);
		
		var returnKeyPress = function(event)
		{
			if (Event.KEY_RETURN == event.keyCode) {
				closure(event);
			}
		};
		
		this.attachEvent(domElement, 'keypress', returnKeyPress, this);
	};
	
	this.attachKeypress = function(domElement, functionParam, objectToBind, arg1) {this.attachEvent(domElement, 'keypress', functionParam, objectToBind, arg1);};
	this.attachClick = function(domElement, functionParam, objectToBind, arg1) {this.attachEvent(domElement, 'click', functionParam, objectToBind, arg1);};
	this.attachChange = function(domElement, functionParam, objectToBind, arg1) {this.attachEvent(domElement, 'change', functionParam, objectToBind, arg1);};
	this.attachKeydown = function(domElement, functionParam, objectToBind, arg1) {this.attachEvent(domElement, 'keydown', functionParam, objectToBind, arg1);};
	this.attachKeyup = function(domElement, functionParam, objectToBind, arg1) {this.attachEvent(domElement, 'keyup', functionParam, objectToBind, arg1);};
	this.attachMouseOver = function(domElement, functionParam, objectToBind, arg1) {this.attachEvent(domElement, 'mouseover', functionParam, objectToBind, arg1);};
	this.attachMouseOut = function(domElement, functionParam, objectToBind, arg1) {this.attachEvent(domElement, 'mouseout', functionParam, objectToBind, arg1);};
	this.attachFocus = function(domElement, functionParam, objectToBind, arg1) {this.attachEvent(domElement, 'focus', functionParam, objectToBind, arg1);};
	this.attachBlur = function(domElement, functionParam, objectToBind, arg1) {this.attachEvent(domElement, 'blur', functionParam, objectToBind, arg1);};
	this.attachEvent = function(domElement, eventName, functionParam, objectToBind, arg1)
	{
		if (!$(domElement))
			return;
			
		if (!objectToBind)
		{
			objectToBind = this.eventMediator;
		}
		
		var func;
		if (Object.isFunction(functionParam)) 
		{
			func = functionParam.bind(objectToBind);
		}
		else 
		{
			if (!arg1)
				arg1 = this;
			func = objectToBind[functionParam].bindAsEventListener(objectToBind, arg1);	
		}
		Event.observe($(domElement), eventName, func);
		this.eventCache.push({element : $(domElement), event : eventName, func : func});
	};
	
	this.buildBoundObject = function()
	{
		return JsonBinder.buildObjFromBindings(this.payloadData);
	};
	
	this.remakeRequest = function(requestObj, widget)
	{
		var originalRequest = InstanceContainer.GetInstance(PayloadMediator.lastMakeRequestId);
		for (var prop in requestObj)
		{
			originalRequest[prop] = requestObj[prop];
		}
	
		if (!widget)
			widget = originalRequest.widget;
			
		var doRemake = function()
		{
			this.makeRequest(originalRequest, widget)
		}.bind(originalRequest.widget);
			
		doRemake();
	};
	
	this.timeoutRequest = function(requestId)
	{
		this.timedOutRequest[requestId] = true;
		this.endAsyncRequest(requestId);
		var requestObj = InstanceContainer.GetInstance(requestId);
		
		requestObj.timeoutCount++;
		requestObj.onTimeOutHandler();
	};
	
	this.endAsyncRequest = function(requestId)
	{
		var request = InstanceContainer.GetInstance(requestId);
		if (this.blockingRequest == requestId) 
			this.blockingRequest = null;
		if (PayloadMediator.blockingRequest == requestId)
			PayloadMediator.blockingRequest = null;
		this.bindAsyncEnd(request.asyncContainers, request.asyncVar);
	};
	
	this.isBlockingRequests = function()
	{
		return (this.blockingRequest || PayloadMediator.blockingRequest)
	};
	
	this.makeRequest = function(requestObj, widget)
	{
		if (!attached)
			return;
			
		if (!widget)
			widget = this;
			
		var requestBase =  {
			id : "Request_" + this.uniqueWidgetId +"_" + AltrecGlobals.Pages.Checkout.getNextUniqueId(),
			hasRedirects : function()
			{
				return (this.responsePayload && this.responsePayload.MessagePayloads.RedirectPayload);
			},
			timeoutCount : 0,
			onTimeOutHandler : function()
			{
				TopMessagingWidget.ShowError("Request timed out.<br />Please resubmit request.");
			}
		};
		
		if (!requestObj.id)
			requestObj.widget = widget;
		requestObj.invokingWidget = this;
		
		Object.extend(requestBase, requestObj);
		requestObj = requestBase;
		PayloadMediator.lastMakeRequestId = requestObj.id;
		InstanceContainer.RegisterInstance(requestObj);
		if (!requestObj.blockingType)
			requestObj.blockingType = 'widget';//default
		
		if (this.isBlockingRequests())
			return;
				
		if (requestObj.blockingType == 'widget') 
		{
			this.blockingRequest = requestObj.id;
		}
		if (requestObj.blockingType == 'page')
			PayloadMediator.blockingRequest = requestObj.id;
		
		this.bindAsyncStart(requestObj.asyncContainers, requestObj.asyncVar);

		if ((requestObj.validateLevel == 'widget' && !this.validate()) ||
			(requestObj.validateLevel == 'page' && !PayloadMediator.validateWidgets()))
		{
			this.endAsyncRequest(requestObj.id);
			return;
		}
		
		if ((requestObj.dirtCheckLevel == 'widget' && this.isDirty() == true) ||
			(requestObj.dirtCheckLevel == 'page' && PayloadMediator.isPageDirty() == true))
		{
			OverlaySingleton.show(this.getDirtyMessage(requestObj.dirtCheckLevel));
			this.endAsyncRequest(requestObj.id);
			return;
		}
			
		var timeoutEventLink = this.constructJavascriptLinkEvent('timeoutRequest', requestBase.id).replace("javascript:", '');
		//closure will clear this
		var timeout = setTimeout(timeoutEventLink, (1000*(requestObj.timeout || AltrecGlobals.Pages.Checkout.Timeout)));
		
		var responseClosure = function(responseObj)
		{
			var invokingWidget = this.invokingWidget;
			if (invokingWidget.timedOutRequest[requestBase.id])
				return;
				
			if (responseObj && responseObj.responseText === '')
				return;
				
			clearTimeout(timeout);
			
			try 
			{
				var jsonText = '';
				if (responseObj && responseObj.responseText) 
				{
					jsonText = responseObj.responseText;
					responseObj = eval('(' + responseObj.responseText + ')');
				}
				else
				{
					jsonText = Object.toJSON(responseObj);
				}
				
				this.responsePayload = responseObj;
				
				var clearAsync = true;
				if (this.clearAsyncCheck)
					clearAsync = this.clearAsyncCheck();
				if (clearAsync)
					invokingWidget.endAsyncRequest(requestBase.id);
				
				if ($('JSONDump')) 
					$('JSONDump').value = jsonText;
				
				if (this.callback)
					this.callback(this.responsePayload);
				else
				{
					PayloadMediator.renderPayloads(this.responsePayload);
				}
				
				if (this.onComplete)
					this.onComplete(this.responsePayload);
					
				if (this.syncCookies)
				{
					triggerCookieShareIframe();
				}
			} 	
			catch(e)
			{
				//alert('Response Error\n'+e);
				throw e;
			}
	
		}.bind(requestObj);
		
		TopMessagingWidget.HideError();
		if (requestObj.javaMethod) 
		{
			var paramArr = (requestObj.getParamArray) ? requestObj.getParamArray() : [];
			if (requestObj.getOverrideParameters)
			{
				var overrideParameters = requestObj.getOverrideParameters();
				for (var paramToOverride in overrideParameters)
				{
					var idx = parseInt(paramToOverride);
					paramArr[idx] = overrideParameters[paramToOverride];
				}
				requestObj.getOverrideParameters = null;
			}
			
			switch(paramArr.length){case 0:Remote[requestObj.javaMethod](responseClosure);break;case 1:Remote[requestObj.javaMethod](paramArr[0],responseClosure);break;case 2:Remote[requestObj.javaMethod](paramArr[0],paramArr[1],responseClosure);break;case 3:Remote[requestObj.javaMethod](paramArr[0],paramArr[1],paramArr[2],responseClosure);break;case 4:Remote[requestObj.javaMethod](paramArr[0],paramArr[1],paramArr[2],paramArr[3],responseClosure);break;case 4:Remote[requestObj.javaMethod](paramArr[0],paramArr[1],paramArr[2],paramArr[3],paramArr[4],responseClosure);break;case 5:Remote[requestObj.javaMethod](paramArr[0],paramArr[1],paramArr[2],paramArr[3],paramArr[4],paramArr[5],responseClosure);break;}
		}
		else 
		{
			var parameters = {};
			if (requestObj.getParameters) 
				parameters = Object.extend(parameters, requestObj.getParameters() || {});
			if (requestObj.getOverrideParameters)
			{
				var overrideParameters = requestObj.getOverrideParameters();
				for (var paramToOverride in overrideParameters)
				{
					parameters[paramToOverride] = overrideParameters[paramToOverride];
				}
				requestObj.getOverrideParameters = null;
			}
			
			if (requestObj.getScriptUrl) 
			{
				parameters.callback = 'PayloadMediator.callback';
				parameters.callback_key = PayloadMediator.registerCallback(responseClosure);
				parameters.d = (new Date()).getTime();
				
				var paramStr = '';
				for (var paramName in parameters)
				{
					if (paramStr.length>0)
						paramStr += '&';
					paramStr += paramName+'='+escape(parameters[paramName]);
				}
				
				var head = document.getElementsByTagName("head")[0];
				var script = document.createElement("script");
				 
				script.setAttribute("type", "text/javascript", 0); 
				script.setAttribute("src", requestObj.getScriptUrl()+"?"+paramStr, 0); 
				head.appendChild(script);
			}
			else 
			{
				var method = "get";
				if (requestObj.method) 
					method = requestObj.method;
				var request = new Ajax.Request(requestObj.getUrl(), {
					method: method,
					parameters: parameters,
					onSuccess: responseClosure,
					onFailure: function(e)
					{
						alert('Communication Error' + e);
					}
				});
			}
		}
	};
	
	this.getDirtyMessage = function(level)
	{
		var message;
		var extraMsg = '';
		if (level == "page")
		{
			extraMsg = '<b>'+PayloadMediator.dirtyWidgets[0]+ '</b> ';
		}
		else
		{
			extraMsg = '<b>'+this.dirtyWidgets[0]+ '</b> ';
		}
		
		message = "There is unsaved "+extraMsg+"data on the page.";
		message += "  Please cancel or save your changes.";
		return message;
	};
	
	this.addChildWidget = function(widgetName, widget)
	{
		this.childWidgets[widgetName] = widget;
	};
	
	this.getChildWidget = function(widgetName)
	{
		return this.childWidgets[widgetName];
	};
	
	this.childWidgetCount = function()
	{
		var count = 0;
		for (var key in this.childWidgets) 
		{
			count++;
		}
		return count;
	};
	
	this.cmEventProps = {
		blockEvent : false,
		getWidget : function()
		{
			return this;
		}.bind(this)
	};
	
	this.sendCmEvent = function(event, widget)
	{
		if (!event || event == '' || this.lastEventName == event)
			return;
			
		this.lastEventName = event;
			
		var widgetToSend = widget || this;
		if (event && event.trim() != '')
		{
			if (this.checkCmEvent && this.checkCmEvent()==false)
				return;
				
			var asyncObj = new CoremetricsAyncHolder(widgetToSend.getWidgetType(), event);
			InstanceContainer.RegisterInstance(asyncObj);
			InstanceContainer.FireEventAsync(asyncObj.id, 'send', 0);
		}
	};

	this.getBoundElement = function(boundField)
	{
		try{
		return JsonBinder.getBoundElement(this.payloadData.jsonBindProp[boundField]);
		}catch(e){return null;}
	};
	
	this.destroy = function()
	{
		for (var i in this.childWidgets) 
		{
			this.childWidgets[i].destroy();
		}
		
		for (var i=0; i<this.eventCache.length; i++)
		{
			Event.stopObserving(this.eventCache[i].element, this.eventCache[i].event);
		}
			
		AltrecGlobals.Functions.Object.Destroy(this.payloadData);
		this.payloadData = null;
		this.childWidgets = null;
		this.Container = null;
		self = null;
	};
}


var WidgetExtender = 
{
	Setup : function(inheritingClass)
	{
		AltrecGlobals.Functions.Object.Inherit(AltrecPageWidget, inheritingClass);
	}
};

var CoremetricsAyncHolderId = 0;
function CoremetricsAyncHolder(widgetType, event)
{
	this.widgetType = widgetType;
	this.event = event;
	this.id = 'CoremetricsAyncHolderId'+(CoremetricsAyncHolderId++);
	
	this.send = function()
	{
		AltrecGlobals.Coremetrics.SendCheckoutEvent(this.widgetType, this.event);
	}
}


function DropDownWidget()
{
	WidgetExtender.Setup(this);
	
	this.initElement = function(widgetElement, parent)
	{
		this.initWidget(widgetElement, parent);
	};

	this.draw = function()
	{
		var optionsElement = this.Container;
		for(var i=optionsElement.options.length-1;i>=0;i--)
		{
			optionsElement.remove(i);
		}
		
		for(var i=0; i<this.payloadData.length; i++)
		{
			var optn = document.createElement("OPTION");
			optn.text = this.payloadData[i].text;
			optn.value = this.payloadData[i].value;
			var selected = null;
			if (optn.value == this.defaultValue)
			{
				optn.setAttribute("selected","selected");
				selected = i;
			}
			optionsElement.options.add(optn);
			if (selected)
				optionsElement.selectedIndex = i;
		}
	};
	
	this.addOption = function(text, value)
	{
		var optn = document.createElement("OPTION");
		optn.text = text;
		optn.value = value;
		this.Container.options.add(optn);
	};
	
	this.setPayload = function(pPayload, pDefaultValue)
	{
		this.defaultValue = pDefaultValue;
		this.render(pPayload);
	};
	
	this.getSelectedValue = function()
	{
		for (var i = 0; i < this.Container.options.length; i++) 
		{
			if (this.Container.options[i].selected == true) 
			{
				return this.Container.options[i].value;
				break;
			}
		}
	};
	
	this.attachDropDownChange = function(func, objectToBind)
	{
		if (!objectToBind)
			objectToBind = this;
		this.attachChange(this.Container.id, func, objectToBind);
	};
	
	
	this.destroy = function()
	{
		AltrecGlobals.Functions.Object.Destroy(this.Container.bindJSONProps);
		this.Container.bindJSONProps = null;
		this.Container.bindJSON = null;;
		this.Container.tempId;
		this.base.destroy();
	};
}

function DropDownQtyWidget(parent, widgetElement)
{
	DropDownWidget.call(this);
	this.initElement(widgetElement, parent);
	
	this.setQuantity = function(max, defaultQty)
	{
		this.max = max;
		var arr = [];
		for(var i=0; i<=max; i++)
		{
			arr[i] = {
				text: i,
				value: i
			};
		}
		
		if (max<defaultQty)
			defaultQty = max;
			
		this.setPayload(arr, defaultQty);		
	};
	
	this.addMaxPlus = function()
	{
		var qty = this.max+1;
		this.addOption(qty+"+", qty);
	};
}

AltrecGlobals.Variables.RadioBtnClass = 'shipRadio';

function RadioBtnWidget(id, parent, groupName)
{
	WidgetExtender.Setup(this);
	this.initWidget(id, parent);
	this.useParentMediator = false;
	
	this.addOption = function(elementToAttach, value, clickArea)
	{
		var option = AltrecGlobals.Functions.Element.getFirstClassName(elementToAttach, AltrecGlobals.Variables.RadioBtnClass);
		var btnItem = new RadioBtnItemWidget(option, this, AltrecGlobals.Variables.RadioBtnClass, clickArea);
		btnItem.render({value : value});
		this.addChildWidget(btnItem.value, btnItem);
		return btnItem;
	};
	
	this.selectOption = function(e, item)
	{
		for (var i in this.childWidgets)
		{
			if (!this.childWidgets[i].isDisabled())
			{
				if (i != item.value)
					this.childWidgets[i].unselectItem();
			}
		}

		if (!item.isDisabled())
		{
			item.selectItem();
		}
	};
	
	this.clearOptions = function()
	{
		for (var i in this.childWidgets)
		{
			this.childWidgets[i].destroy();
		}
		this.childWidgets = {};
	};
}

function RadioBtnItemWidget(id, parent, baseClass, clickArea)
{
	WidgetExtender.Setup(this);
	this.useParentMediator = true;
	this.initWidget(id, parent);
	this.valueClass = 'shipRadioValue';
	this.baseClass = baseClass;
	if (!clickArea) clickArea = this.Container;
	this.clickArea = clickArea;

	this.massagePayload = function()
	{
		if (this.payloadData.value === undefined || this.payloadData.value === null)
			this.payloadData.value = AltrecGlobals.Functions.Element.getFirstClassName(this.Container, this.valueClass).innerHTML;
		this.value = this.payloadData.value;
		
	};
	
	this.getValue = function()
	{
		return this.value;
	};
	this.getTemplateVar = function()
	{
		return "rdBtn";
	};
	
	this.getSelectedClass = function()
	{
		return this.baseClass + 'Selected';
	};
	
	this.isSelected = function()
	{
		return Element.hasClassName(this.Container, this.getSelectedClass());
	};
	
	this.isDisabled = function()
	{
		return (Element.getOpacity(this.Container) < 1)
	};
	
	this.selectItem = function()
	{
		if (!this.isSelected())
		{
			Element.addClassName(this.Container, this.getSelectedClass());
			if (this.onSelect) this.onSelect(this.value);
		}
	};
	
	this.unselectItem = function()
	{
		if (this.isSelected())
			Element.removeClassName(this.Container, this.getSelectedClass());
	};
	
	this.postDraw = function()
	{
		if (this.renderCount == 0)
		{
			this.attachClick(this.clickArea, 'selectOption');
			Element.setOpacity(this.Container, 1);
		}
	};
}

var PayloadMediator = {

	id : "PayloadMediator",
	topLevelWidgets : {},
	lastMakeRequestId : null,
	lastCallbackId : 0,
	callbacks : {},
	blockingRequest : null,
	renderStatistics : '',
	
	init: function()
	{
		this.CompletedInitialLoad = false;
	},
	
	getWidgets : function(widgetName)
	{
		var createNew = false;
		if (this.topLevelWidgets[widgetName] && !this.topLevelWidgets[widgetName][0].IsSingleton) 
		{
			createNew = true;
		}
		if (!this.topLevelWidgets[widgetName]) 
			createNew = true;
			
		if (createNew)
		{
			if (window[widgetName]) 
			{
				var widget = new window[widgetName](null);
				this.topLevelWidgets[widgetName] = new Array();
				this.topLevelWidgets[widgetName].push(widget);
			}
			else
				return [null];
		}
		
		return this.topLevelWidgets[widgetName];
	},
	
	getWidget : function(widgetName)
	{
		var widgetArray = this.getWidgets(widgetName);
		return widgetArray[widgetArray.length-1];
	},
	
	getPayload : function(widgetName)
	{
		var widgetPayloadEnv = widgetName.replace('Widget', '');
		if (this.PayloadEnvelope.MessagePayloads[widgetPayloadEnv])
			return this.PayloadEnvelope.MessagePayloads[widgetPayloadEnv];
			
		var widget = this.getWidget(widgetName);
		if (widget && widget.payloadData)
		{
			return widget.payloadData;
		}
		
		return null;
	},
	
	isPageDirty : function()
	{
		this.dirtyWidgets = [];
		for (var i in this.topLevelWidgets)
		{
			for (var j = 0; j<this.topLevelWidgets[i].length; j++)
			{
				var isWidgetDirty = this.topLevelWidgets[i][j].isDirty();
				if (isWidgetDirty)
				{
					this.topLevelWidgets[i][j].dirtyWidgets.each(function(widgetName)
					{
						this.dirtyWidgets.push(widgetName);
					}.bind(this));
				}
				if (this.topLevelWidgets[i][j].isDirty && isWidgetDirty)
					return true;
			}
		}
		
		return false;
	},
	
	destroy : function()
	{
		
		for (var i in this.topLevelWidgets) 
		{
			for (var j = 0; j < this.topLevelWidgets[i].length; j++) 
			{
				this.topLevelWidgets[i][j].destroy();
			}
		}
		
		this.topLevelWidgets = null;
		this.lastMakeRequestId = null;
		this.lastCallbackId = null;
		this.callbacks = null;
		this.blockingRequest = null;
		this.PayloadEnvelope = null;
	},
	
	validateWidgets : function()
	{
		var allValid = true;
		for (var i in this.topLevelWidgets)
		{
			for (var j = 0; j<this.topLevelWidgets[i].length; j++)
			{
				if (this.topLevelWidgets[i][j].validate)
					allValid = (this.topLevelWidgets[i][j].validate() && allValid);
			}
		}
		
		return allValid;
	},
	
	renderPayload : function(payloadName, payload)
	{
		var obj = {};
		obj.MessagePayloads = {};
		obj.MessagePayloads[payloadName] = payload;
		this.renderPayloads(obj);
	},
	
	renderPayloads : function(payloadObject)
	{
		
		TimerQueue.addTimer('renderStart');
		this.PayloadEnvelope = payloadObject;
		if (payloadObject.MessagePayloads.RedirectPayload)
		{
			this.getWidget('RedirectPayloadWidget').render(payloadObject.MessagePayloads.RedirectPayload);
			return;
		}
		
		var payloadMap = {};
		var payloadToRender = [];
		function addPayload(payloadName)
		{
			if (!payloadMap[payloadName] && payloadObject.MessagePayloads[payloadName])
			{
				payloadToRender.push(payloadName);
				payloadMap[payloadName] = true;
			}
		}
		
		addPayload('CartPayload');
		addPayload('ShippingPayload');
		addPayload('ShippingOptionsPayload');
		addPayload('LoginPayload');
		addPayload('CustomerPayload');
		
		for (var payloadName in payloadObject.MessagePayloads)
		{
			addPayload(payloadName);
		}
		
		for (var i=0; i<payloadToRender.length; i++)
		{
			var payloadName = payloadToRender[i];
			try 
			{
				TimerQueue.addTimer('\n'+payloadName);
				var widget = null;
				if (!window[payloadName + 'Widget']) 
				{
					var defaultWidget = "window." + payloadName + "Widget=function(parent){WidgetExtender.Setup(this);this.initWidget('topNav',parent);this.draw=function(){};}";
					eval(defaultWidget);
				}
				widget = this.getWidget(payloadName + 'Widget');
				if (widget) 
					widget.render(payloadObject.MessagePayloads[payloadName]);
				TimerQueue.dequeueTimer();
			}
			catch(e)
			{
				alert('Error in mediation creation\n'+payloadName+'\n'+e.message);
			}
		}
		
		this.CompletedInitialLoad = true;
		
		this.renderStatistics += '\n\n'+ TimerQueue.getAllMessage();
	},
	
	registerCallback : function(callback)
	{
		var callbackId = 'callback'+(this.lastCallbackId++);
		this.callbacks[callbackId] = callback;
		return callbackId;
	},
	
	callback : function(payloadEnvelopes, id)
	{
		this.callbacks[id](payloadEnvelopes);
		this.callbacks[id] = null;
		delete this.callbacks[id];
	}
	
};
PayloadMediator.init();

function RunMediator()
{
	this.id = 'RunMediator';
	InstanceContainer.RegisterInstance(this);
	
	this.run = function()
	{
		if (AltrecGlobals.WaitForScript)
		{
			InstanceContainer.FireEventAsync(this.id, 'run', 300);
			return;
		}
		
		this.renderPayload();
	};
	
	this.renderPayload = function()
	{
		if (!onloadEnvelope.MessagePayloads.DiscountPayload) 
			onloadEnvelope.MessagePayloads.DiscountPayload = {};
			
		if (!onloadEnvelope.MessagePayloads.CheckoutPayload) 
			onloadEnvelope.MessagePayloads.CheckoutPayload = {};
		
		if (AltrecGlobals.MassageOnloadCallback)
			AltrecGlobals.MassageOnloadCallback();
		
		PayloadMediator.renderPayloads(onloadEnvelope);
	};
}

//make this seperate to avoid closures
var bindJSONElementContainer = {
	
	boundElements : {},
	addElement : function(element)
	{
		if (!this.boundElements[element.tempId]) 
		{
			if (!element.id || element.id == '')
				element.id = element.tempId;
			this.boundElements[element.tempId] = element.id;
		}
	},
	getElement : function(id)
	{
		return $(this.boundElements[id]);
	},
	
	hasElement : function(id)
	{
		return (this.boundElements[id]);
	}
};

var bindJSONInterpreter = 
{
	elementTempId : 0,
	displayCmdMap : {},
	getProp : function(obj, prop)
	{
		var arr = prop.split('(');
		if (arr.length > 1) 
			return ''+obj[arr[0]]();
		else 
		{
			if (prop.length >= 2 && prop.charAt(0) == "'" && prop.charAt(prop.length - 1) == "'") 
			{
				return prop.substring(1, prop.length - 1);
			}
			else if (!obj || (obj[prop] != 0 && !obj[prop])) 
			{
				//I think isNaN is spendy in IE
				if ((prop.charAt(0) != "0" &&
					prop.charAt(0) != "1" && 
					prop.charAt(0) != "2" && 
					prop.charAt(0) != "3" && 
					prop.charAt(0) != "4" && 
					prop.charAt(0) != "5" && 
					prop.charAt(0) != "6" && 
					prop.charAt(0) != "7" && 
					prop.charAt(0) != "8" && 
					prop.charAt(0) != "9"))
					return '';
					
				if (isNaN(prop))
					return '';
				else
					return prop;
			}
			else 
				
				return obj[prop];
		}
	},
	
	evalProperty : function(obj, props)
	{
		var objToEval = obj;
		if (props.indexOf('AltrecGlobals')==0)
		{
			props = props.replace('AltrecGlobals.', '');
			objToEval = AltrecGlobals;	
		}
	
		if (props.indexOf('Window') == 0) 
		{
			props = props.replace('Window.', '');
			objToEval = window;
		}
		
		if (props.charAt(0) == "'" || !props.contains('.'))
		{
			return this.getProp(objToEval, props);
		}
		else
		{
			var dom = props.split('.');
			var remainingProps = [];
			for(var i=1; i<dom.length; i++)
			{
				remainingProps.push(dom[i]);
			}
			return this.evalProperty(objToEval[dom[0]], remainingProps.join('.'));
		}
	},
	
	getFirstParamStmtParams : function(token)
	{
		return this.tokenize(token, ',');
	},
	
	tokenize : function(token, delminiter)
	{
		var stack =0;
		var params = [];
		var leftOverStmt = null;
		var i = token.indexOf('(')+1;
		var lastParamIdx = i;
		var done = false;
		for (; i<token.length-1; i++)
		{
			if (token.charAt(i) == '(')  stack++;
			else if (token.charAt(i) == ')') stack--;
			if (done) continue;
			
			if ((token.charAt(i) == ',' && stack == 0) || (token.charAt(i) == ')' && stack == -1)) 
			{
				params.push(token.substring(lastParamIdx, i).trim());
				lastParamIdx = i+1;
				if (stack == -1) 
				{
					stack = 0;
					done = true;
				}
			}
		}
		
		if (!done) params.push(token.substring(lastParamIdx, token.length-1).trim());
		else
		{
			leftOverStmt = token.substring(lastParamIdx, token.length-1).trim();
			if (stack>0) leftOverStmt += ")";
		}
		
		return {
			params: params,
			leftOverStmt : leftOverStmt
		};
	},
	
	evalArgValues :  function(args, jsonObject)
	{
		var argValues = [];
		for(var i=0; i<args.length; i++)
		{
			argValues.push(this.evalProperty(jsonObject, args[i]));
		}
		
		return argValues;
	},
	
	evalBooleanStmts : function(val, jsonObject, element)
	{
		var returnBoolVal = true;
		if (val.indexOf('Else') == 0 && element.bindJSONProps && element.bindJSONProps.lastSiblingId)
		{
			var sibling = bindJSONElementContainer.getElement(element.bindJSONProps.lastSiblingId);
			if (sibling && sibling.bindJSONProps.evaledBool)
				returnBoolVal = false;
		}
		
		if (val.indexOf('And(')==0)
		{
			var stmtsStr = val.replace('And', '');
			var boolStmts = this.getFirstParamStmtParams(stmtsStr);
			
			var result = true;
			boolStmts.params.each(function(stmt){
				result = (result && this.evalBooleanStmts(stmt, jsonObject).result);
			}.bind(this));
			
			if (element)
				element.bindJSONProps.evaledBool = result;
			return {
				'result': result,
				leftOverStmt: boolStmts.leftOverStmt
			}; 
		}
		if (val.indexOf('Or(')==0)
		{
			var stmtsStr = val.replace('Or', '');
			var boolStmts = this.getFirstParamStmtParams(stmtsStr);
			
			var result = false;
			boolStmts.params.each(function(stmt){
				if (!result)
					result = (result || this.evalBooleanStmts(stmt, jsonObject).result);
			}.bind(this));
			
			if (element)
				element.bindJSONProps.evaledBool = result;
			return {
				'result': result,
				leftOverStmt: boolStmts.leftOverStmt
			}; 
		}
		
		var originalCondition = val;
		var leftOverStmt = null;
		
		var stmtParmas = this.getFirstParamStmtParams(val);
		var args = stmtParmas.params;
		var paramValues = this.evalArgValues(args, jsonObject);
		
		if (val.contains('Empty')) 
		{
			var hasValues = false;
			paramValues.each(function(columnToCheck)
			{
				if (originalCondition.contains('IsNotEmpty') && columnToCheck && columnToCheck.trim && columnToCheck.trim().length > 0) hasValues = true;
				if (originalCondition.contains('IsEmpty') && (!columnToCheck || (columnToCheck.trim && columnToCheck.trim().length == 0))) hasValues = true;
				
			}.bind(this));
			
			if (!hasValues)
				returnBoolVal = false;
		}
		else if (val.contains('Equal')) 
		{	
			var param1 = paramValues[0];
			var param2 = paramValues[1];
			
			if (!isNaN(param1) && !isNaN(param2))
			{
				param1 = parseFloat(param1);
				param2 = parseFloat(param2);
			}
			
			if (originalCondition.contains('IsNotEqual')) 
			{
				if (param1 == param2) returnBoolVal = false;
			}
			else if (originalCondition.contains('IsEqual')) 
			{
				if (param1 != param2) returnBoolVal = false;
			}
		}
		else if (val.contains('Than')) 
		{
			var param1 = paramValues[0];
			var param2 = paramValues[1];
			
			if (!isNaN(param1) && !isNaN(param2))
			{
				param1 = parseFloat(param1);
				param2 = parseFloat(param2);
			}
						
			if (originalCondition.contains('IsLessThan') && param1 >= param2) returnBoolVal = false;
			if (originalCondition.contains('IsGreaterThan') && param1 <= param2) returnBoolVal = false;
		}
		else if (val.contains('With'))
		{
			var param1 = paramValues[0];
			var param2 = paramValues[1];
			
			if (!isNaN(param1) && !isNaN(param2))
			{
				param1 = parseFloat(param1);
				param2 = parseFloat(param2);
			}
			
			if (originalCondition.contains('NotStartsWith')) 
			{
				if (param1.startsWith(param2)) returnBoolVal = false;
			}
			else if (originalCondition.contains('NotEndsWith')) 
			{
				if (param1.endsWith(param2)) returnBoolVal = false;
			}
			else if (originalCondition.contains('StartsWith')) 
			{
				if (!param1.startsWith(param2)) returnBoolVal = false;
			}
			else if (originalCondition.contains('EndsWith')) 
			{
				if (!param1.endsWith(param2)) returnBoolVal = false;
			}
		}
		else if (val.contains('IsTrue')) 
		{	
			var param1 = paramValues[0];
			if (param1 != true && param1 != 'true') returnBoolVal = false;
		}
		else if (val.contains('IsFalse')) 
		{	
			var param1 = paramValues[0];
			if (param1 != false && param1 != 'false') returnBoolVal = false;
		}
		
		if (element)
			element.bindJSONProps.evaledBool = returnBoolVal;
		
		return {
			result: returnBoolVal,
			leftOverStmt: stmtParmas.leftOverStmt
		};
	},
	
	controlDisplay : function(element, showElement, transCmdStr, setValues)
	{
			
		var cmd = null;
		if (transCmdStr && transCmdStr.indexOf('.Efx') == 0) 
		{
			cmd = this.getFirstParamStmtParams(transCmdStr.replace(".Efx", ""));
		}
		
		var transEfx = (cmd) ? cmd.params[0].toLowerCase() : null;
	
		var efxOptions = {
			duration: .3,
			queue: {
				scope: element.tempId,
				position : 'end'
			}
		};
				
		efxOptions.duration = 1;
		if (cmd && cmd.params.length == 2)  
		{
			var obj = this.evalStatement(cmd.params[1]);
			for (var i in obj) 
			{
				efxOptions[i] = obj[i];
			}
		}

		var id = (element && element.tempId) ? element.tempId : 'temp_' + this.elementTempId++;
		var key = ''+showElement + transEfx;
		var sameCmd = (this.displayCmdMap[id] == key);
		if (!transEfx || !PayloadMediator.CompletedInitialLoad) 
		{
			transEfx = '';
		}
		
		if (showElement) 
		{
			if (transEfx.contains('appear') && !sameCmd)
			{
				Effect.Appear(element, efxOptions);
			}
			else if (transEfx.contains('blind') && transEfx.contains('down') && !sameCmd)
			{
				Effect.BlindDown(element, efxOptions);
			}
			else if (transEfx.contains('slide') && transEfx.contains('down') && !sameCmd)
			{
				Effect.SlideDown(element, efxOptions);
			}
			else if (PayloadMediator.CompletedInitialLoad && setValues && setValues.before != setValues.after)
			{
				if (transEfx.contains('highlight')) 
				{
					new Effect.Highlight(element, efxOptions);
				}
				else if (transEfx.contains('pulsate')) 
				{
					efxOptions.afterFinish = function()
					{
						Element.setOpacity(element, 1);
					};
					Effect.Pulsate(element, efxOptions);
				}
			}
			else 
			{
				element.style.display = '';
			}
		}
		else 
		{
			if (transEfx.contains('fade') && !sameCmd)
			{
				Effect.Fade(element, efxOptions);
			}
			else if (transEfx.contains('blind') && transEfx.contains('up') && !sameCmd)
			{
				Effect.BlindUp(element, efxOptions);
			}
			else if (transEfx.contains('slide') && transEfx.contains('up') && !sameCmd)
			{
				Effect.SlideUp(element, efxOptions);
			}
			else
			{
				element.style.display = 'none';
			}
		}
		
		element.bindJSONProps.display = showElement;
		
		this.displayCmdMap[id] = key;
	},
	
	evalStatement : function(stmt, element, jsonObject) 
	{
		if (element && (!element.tempId || element.tempId == ''))
		{
			element.tempId = 'tempId_' + this.elementTempId++;
			bindJSONElementContainer.addElement(element);
		}
			
		if (stmt.indexOf('Display.')==0) 
		{
			var showElement = true;
			var evaledResponse = this.evalBooleanStmts(stmt.replace("Display.", ""), jsonObject, element);
			
			this.controlDisplay(element, evaledResponse.result, evaledResponse.leftOverStmt);
		}
		else if (stmt.indexOf('Set(') == 0 || stmt.indexOf('S(') == 0) 
		{
			var start = 1;
			if (stmt.indexOf('Set(') == 0)
				start = 3;
			stmt = stmt.substring(start, stmt.length);
			var cmd = this.getFirstParamStmtParams(stmt);
				
			var setValues = this.evalStatement(cmd.params[0], element, jsonObject);
			
			var showElement = true;
			if (setValues.after == '')
				showElement = false;
				
			this.controlDisplay(element, showElement, cmd.leftOverStmt, setValues);
		}
		else if (stmt.indexOf('ToObj(') == 0) 
		{
			var cmd = this.getFirstParamStmtParams(stmt);
			var params = this.evalArgValues(cmd.params, jsonObject);
			var obj = {};
			var field = '';
			for (var i=0; i<params.length; i++)
			{
				if ((i % 2) == 0)
				{
					field = params[i];
				}
				else
				{
					obj[field] = params[i];
				}
			}
			
			return obj;
		}
		else if (stmt.indexOf('Cond.') == 0) 
		{
			var evaledResponse = this.evalBooleanStmts(stmt.trim().replace("Cond.", ""), jsonObject, element);
			
			if (evaledResponse.leftOverStmt.indexOf('.ThenElse') == 0) 
			{
				var cmdResponse = this.getFirstParamStmtParams(evaledResponse.leftOverStmt.replace(".ThenElse", ""));
				if (evaledResponse.result) 
					this.evalStatement(cmdResponse.params[0], element, jsonObject);
				else 
					this.evalStatement(cmdResponse.params[1], element, jsonObject);
			}
			else if (evaledResponse.leftOverStmt.indexOf('.Then') == 0) 
			{
				var cmdResponse = this.getFirstParamStmtParams(evaledResponse.leftOverStmt.replace(".Then", ""));
				if (evaledResponse.result) 
					this.evalStatement(cmdResponse.params[0], element, jsonObject);
			}
			else if (evaledResponse.leftOverStmt.indexOf('.Toggle') == 0) 
			{
				var cmdResponse = this.getFirstParamStmtParams(evaledResponse.leftOverStmt.replace(".Toggle", ""));
				var toggleType = this.getProp(jsonObject, cmdResponse.params[0]).toLowerCase();
				if (toggleType.contains('class')) 
				{
					if (evaledResponse.result) 
					{
						this.evalStatement('addClassName:' + cmdResponse.params[1], element, jsonObject);
						if (cmdResponse.params[2])
						this.evalStatement('removeClassName:' + cmdResponse.params[2], element, jsonObject);
					}
					else 
					{
						if (cmdResponse.params[2])
						this.evalStatement('addClassName:' + cmdResponse.params[2], element, jsonObject);
						this.evalStatement('removeClassName:' + cmdResponse.params[1], element, jsonObject);
					}
				}
				if (toggleType.contains('opacity'))
				{
					var opacity = (evaledResponse.result) ? parseFloat(cmdResponse.params[1]) : parseFloat(cmdResponse.params[2]);
					Element.setOpacity(element, opacity);
				}
				if (toggleType.contains('display')) 
				{
					this.controlDisplay(element, evaledResponse.result, cmdResponse.leftOverStmt);
				}
				if (toggleType.contains('disabled')) 
				{
					element.disabled = (evaledResponse.result);
				}
			}
		}
		else 
		{
			var propertyToSet = 'innerHTML';
			var splitAttr = stmt.split(':');
			var attrArr = [];
			//fixes incidental colon's in value
			var itr = 0;
			for (var j = 0; j < splitAttr.length; j++) 
			{
				if (itr < j) 
				{
					attrArr[itr] += ":" + splitAttr[j];
				}
				else 
				{
					attrArr.push(splitAttr[j]);
					if (splitAttr[j].charAt(0) != "'") 
						itr++;
				}
			}
			var objToSet = element;
			for (var j = 1; j < attrArr.length; j++) 
			{
				propertyToSet = attrArr[j - 1];
				stmt = attrArr[j];
				if (j != (attrArr.length - 1)) 
					objToSet = objToSet[propertyToSet];
			}
			if (propertyToSet == 'addClassName') 
			{
				Element.addClassName(element, this.evalProperty(jsonObject, stmt));
			}
			else if (propertyToSet == 'removeClassName') 
			{
				Element.removeClassName(element, this.evalProperty(jsonObject, stmt));
			}
			else 
			{
				var evaledProp = this.evalProperty(jsonObject, stmt);
				var valBefore = objToSet[propertyToSet];
				objToSet[propertyToSet] = evaledProp;
				
				if (propertyToSet == 'value')
				{
					if (!jsonObject.jsonBindProp) 
						jsonObject.jsonBindProp = {};
					
					var originalValue = valBefore;
					if (jsonObject.jsonBindProp[stmt])
						originalValue = evaledProp;
					else
					{
						jsonObject.jsonBindProp[stmt] = {
							originalValue : originalValue,
							history: [],
							elementTempId : element.tempId
						};
					}
					
					jsonObject.jsonBindProp[stmt].history.push(valBefore);
					jsonObject.jsonBindProp[stmt].current = evaledProp;
				}
				
				return {
					before: valBefore,
					after: evaledProp
				};
			}
		}
	},
	
	getBoundElement : function(binding)
	{
		return bindJSONElementContainer.getElement(binding.elementTempId);
	},
	
	getBoundElementValue : function(binding, preserveScripts)
	{
		var field = this.getBoundElement(binding);
		if (field.type && field.type == 'checkbox') 
			return ($F(field)) ? true : false;
		else 
		{ 
			if (preserveScripts)
				return field.value;
			else
				return field.value.stripScripts().stripTags();
		}
	},
	
	buildObjFromBindings : function(boundJsonObject)
	{
		var returnObj = {};
		if (boundJsonObject.jsonBindProp) 
		{
			for (var prop in boundJsonObject.jsonBindProp) 
			{
				returnObj[prop] = this.getBoundElementValue(boundJsonObject.jsonBindProp[prop]);
			}
		}
		
		return returnObj;
	},
	
	isDirty : function(boundJsonObject)
	{
		if (boundJsonObject && boundJsonObject.jsonBindProp)
		{
			for (var prop in boundJsonObject.jsonBindProp)
			{
				var bindings = boundJsonObject.jsonBindProp[prop];
				var currentVal = this.evalProperty(boundJsonObject, prop);
				
				if (currentVal != null && currentVal != this.getBoundElementValue(bindings, true)) 
				{ 
					return true;
				}
			}	
		}
		
		return false;
	},
	
	collectBoundFields : function(boundJsonObject)
	{
		var returnArr = [];
		if (boundJsonObject && boundJsonObject.jsonBindProp)
		{
			for (var prop in boundJsonObject.jsonBindProp)
			{
				var bindings = boundJsonObject.jsonBindProp[prop];
				returnArr.push(this.getBoundElement(bindings));
			}	
		}
		return returnArr;
	}
};

var JsonBinder = bindJSONInterpreter;

function bindJSON2(elementToBind, dataIslandAttrName, jsonObject, includeRoot, callback)
{
	if (elementToBind instanceof Array)
	{
		for (var i=0; i<elementToBind.length; i++)
		{
			var element = elementToBind[i];
			if (element && $(element))
				bindJSON2($(element), dataIslandAttrName, jsonObject, includeRoot, callback);
		}
		return;
	}
	
	if (!elementToBind)
		return;
	
	var elementArray = [];
	var numChildren = elementToBind.childNodes.length;
	for (var i = 0; i < numChildren; i++) 
	{
		if (elementToBind.childNodes[i].nodeName.indexOf('text')<0)
			elementArray.push(elementToBind.childNodes[i]);
	}
	
	var itemsToRecurse = elementArray.length;
	if (includeRoot) 
	{
		elementArray.push(elementToBind);
	}
		
	for (var i = 0; i < elementArray.length; i++) 
	{
		var element = elementArray[i];
		var statementsStr = null;
		try 
		{
			statementsStr = Element.readAttribute(element, "tmpl:" + dataIslandAttrName) || Element.readAttribute(element, dataIslandAttrName);
		}
		catch(e)
		{
		}
		
		if (callback)
			callback(element);
		if (statementsStr)
		{
			element.bindJSONProps = {
				parentId: ((elementToBind.tempId) ? elementToBind.tempId : null),
				lastSiblingId : ((i>0) ? elementArray[i-1].tempId : null)
			};
			
			var statements = statementsStr.split(";");
			for (var stItr = 0; stItr < statements.length; stItr++) 
			{
				try 
				{
					bindJSONInterpreter.evalStatement(statements[stItr], element, jsonObject);
				}
				catch(e) {throw e;}
			}	
		}

		if ((!element.bindJSONProps || element.bindJSONProps.display != false) &&
			 i < itemsToRecurse && element.nodeName.toLowerCase() !='select') 
			bindJSON2(element, dataIslandAttrName, jsonObject, false, callback);
	}
}

bindJSON = bindJSON2;

Event.observe(document, 'keydown', function(event){
		if (event.altKey && event.shiftKey){
			if (event.keyCode == (Event['KEY_I'] || 'I'.charCodeAt(0)))
				$('debugging').style.display = 'block';
			else if (event.keyCode == (Event['KEY_M'] || 'M'.charCodeAt(0))) alert('meow');
			else if (event.keyCode == (Event['KEY_M'] || 'T'.charCodeAt(0))) {AltrecGlobals.Pages.Checkout.Timeout = 1000000;alert('Ajax timeout cancelled')}
			else if (event.keyCode == (Event['KEY_U'] || 'U'.charCodeAt(0))) showUnloadStats = true;
			else if (event.keyCode == (Event['KEY_R'] || 'R'.charCodeAt(0))) alert(PayloadMediator.renderStatistics);
			else if (event.keyCode == (Event['KEY_G'] || 'G'.charCodeAt(0))) alert(PayloadMediator.renderStatistics);
			else if (event.keyCode == (Event['KEY_K'] || 'K'.charCodeAt(0))) $('debugging').innerHTML = '<img src="/images/shop/checkout/darth-easter.png" />';
			else if (event.keyCode == (Event['KEY_C'] || 'C'.charCodeAt(0)))
			{
				var serverTime = new Date(AltrecGlobals.Site.ServerTime);
				var cookie = unescape(getCookie('cart_recovery_time'));
				if (cookie.charAt(0) == '"')
					cookie = cookie.substring(1, getCookie('cart_recovery_time').length - 1);
				var cartExpiry = new Date(cookie);
				
				alert(cartExpiry +'\n'+serverTime);
			}
			
		}});

var showUnloadStats = false;
function destroyWindow()
{
	if (!PayloadMediator)
		return;
	if (showUnloadStats)
		alert('Unloading Closures 2');

	TimerQueue.addTimer("onunload");
	PayloadMediator.destroy();
	//AltrecGlobals.Functions.Object.Destroy(PayloadMediator);
	PayloadMediator = null;
	InstanceContainer.destroy();
	AltrecGlobals.Functions.Object.Destroy(InstanceContainer);
	InstanceContainer = null;
	
	if (showUnloadStats)
		alert(TimerQueue.getAllMessage());
}

if (window.attachEvent) {
	window.attachEvent("onunload", destroyWindow);
}
  

AltrecGlobals.Coremetrics.SendCheckoutEvent = function(Widget, Error)
	{
		this.SendEvent(Error, AltrecGlobals.Pages.PageName+"_"+Widget);
		return true;
	}.bind(AltrecGlobals.Coremetrics);
	
AltrecGlobals.Coremetrics.SendEvent = function(eventName, eventCategory)
	{
		cmCreatePageElementTag(eventName, eventCategory);
	}.bind(AltrecGlobals.Coremetrics);

var TopMessagingWidget = {
		showing : false,
		
		ShowError : function(errorMessage, message_name)
		{
		
		},
		
		HideError : function()
		{
		}
	};
	
	
function SearchPayloadWidget(parent)
{
	WidgetExtender.Setup(this);
	this.initWidget('searchBox', parent);
	this.lastSearchTerm = '';
	
	this.postDraw = function()
	{
		if (this.renderCount==0)
		{
			this.searchField = this.getBoundElement('defaultText');
			this.searchField.setAttribute("autocomplete","off"); 
			this.attachReturnKey(this.searchField, 'searchTerm');
			this.attachKeyup(this.searchField, 'keypress');
			this.attachKeydown(this.searchField, 'keydown');
			this.attachFocus(this.searchField, 'focus');;
			this.attachBlur(this.searchField, 'blur');

		}
	};
	
	this.getResultsWidget = function()
	{
		return PayloadMediator.getWidget('SearchResultsPayloadWidget');
	};
	
	this.searchTerm = function()
	{
		var defaultSearcher = {
				search : function()
				{
					if (this.searchField.value)
						window.location = AltrecGlobals.Links.NonSecureDomain+"/"+this.searchField.value.replace(/ /g, "-") + "/search.htm";
				}.bind(this)
		};
		var searcher = (this.getResultsWidget() == null ) ? defaultSearcher : this.getResultsWidget().getSearchObj(this.searchField, defaultSearcher);
		searcher.search();
	};
	
	this.focus = function()
	{
		if (this.searchField.value == this.payloadData.defaultText)
			this.searchField.value = '';
	};
	
	this.blur = function()
	{
		this.getResultsWidget().hide();
	};
	
	this.keypress = function(event)
	{
		var searchTerm = this.searchField.value;
		if (AltrecGlobals.Site.Search.autocompleteEnabled && this.lastSearchTerm != searchTerm && Event.KEY_RETURN != event.keyCode)
		{
			if (searchTerm.trim().length>=AltrecGlobals.Site.Search.minChars)
			{
				this.lastKeypress = new Date();
				InstanceContainer.FireEventAsync(this.uniqueWidgetId, 'sendAutocomplete', AltrecGlobals.Site.Search.msDelay);
			}
			else
			{
				var widget= this.getResultsWidget();
				if (widget!=null)
				{
					widget.hide();
				}
			}
		}
		this.lastSearchTerm = searchTerm;
	};
	
	this.sendAutocomplete = function()
	{
		var now = new Date();
		if ((now.getTime() - this.lastKeypress.getTime()) >= (AltrecGlobals.Site.Search.msDelay))
		{
			this.makeRequest(
					{
						getScriptUrl: function()
						{
							return AltrecGlobals.Site.Search.GetUrl();
						},
						getParameters : function()
						{
							return {
								words: this.searchField.value
							};
						}.bind(this),
						blockingRequest : 'none'
					}
			);
		}
	};
	
	this.keydown = function(event)
	{
		switch(event.keyCode) {
	       case Event.KEY_UP:
	    	 this.getResultsWidget().selectUp();
	         break;
	       case Event.KEY_DOWN:
	    	 this.getResultsWidget().selectDown();
	         break;
		}
	};
	
	this.getTemplateVar = function()
	{
		return "spw";
	};		
}

function SearchResultsPayloadWidget(parent)
{
	WidgetExtender.Setup(this);
	this.initWidget('autocomplete_choices', parent);
	this.items = [];
	this.bucketsElements = [];
	this.renderState = 'off';
	this.selectedItem = null;

	this.draw = function()
	{
		if (this.renderCount==0)
		{
			this.listElement = $('auto_list');
			this.itemTemplate = $('auto_list_item');
			this.bucketHeader = $('auto_list_item_header');
		}
		
		var currentBucketName = '';
		for(var i=0; i<this.bucketsElements.length; i++)
		{
			this.listElement.removeChild(this.bucketsElements[i]);
		}
		
		this.bucketsElements = [];
		this.payloadData.results = [];
		for (var i=0; i<this.payloadData.buckets.length; i++)
		{
			for (var j=0; j<this.payloadData.buckets[i].terms.length; j++)
			{
				var resultsObj = this.payloadData.buckets[i].terms[j];
				resultsObj.bucket = this.payloadData.buckets[i];
				this.payloadData.results.push(resultsObj);
			}
		}
		
		for (var i=0; i<this.payloadData.results.length; i++)
		{
			var item;
			if (i<this.items.length)
			{
				item = this.items[i];
			}
			else
			{
				item = this.itemTemplate.cloneNode(true);
				this.listElement.appendChild(item);
				item.id = 'auto_item'+i;
				item.currentIndex = i;
				this.items.push(item);
				this.attachClick(item, 'clickItem', this, item);
				this.attachMouseOver(item, 'selectItem', this, item);
				this.attachMouseOut(item, 'unselectItem', this, item);
			}
			
			if (currentBucketName != this.payloadData.results[i].bucket.name)
			{
				var header = this.bucketHeader.cloneNode(true);
				currentBucketName = this.payloadData.results[i].bucket.name;
				Element.insert(item, {'before' : header});
				header.style.display = 'block';
				this.bucketsElements.push(header);
				Element.bindJSON(header, this.getTemplateVar()+'h', this.payloadData.results[i].bucket);
			}

			Element.bindJSON(item, this.getTemplateVar()+'i', this.payloadData.results[i]);
		}
		
		for (var i = this.items.length; i > this.payloadData.results.length; i--)
		{
			var item = this.items.pop();
			this.listElement.removeChild(item);
		}
		
		this.unselectLast();
		if (this.renderState == 'off')
		{
			this.renderState = 'animatingOn';
			Effect.Appear(this.Container, {duration: 0.3, fps : 15, afterFinish : this.doneAnimatingOn.bind(this)});
		}
	};
	
	this.selectUp = function()
	{
		if (this.hasItemSelected())
		{
			var idx = this.selectedItem.currentIndex;
			if (idx > 0)
			{
				this.selectItem(null, this.items[idx-1]);
			}
		}
	};
	
	this.selectDown = function()
	{
		if (!this.hasItemSelected())
		{
			this.selectItem(null, this.items[0]);
		}
		else
		{
			var idx = this.selectedItem.currentIndex;
			if (idx < this.items.length-1)
			{
				this.selectItem(null, this.items[idx+1]);
			}
		}
	};
	
	this.clickItem = function(event, item)
	{
		this.selectedItem = item;
		PayloadMediator.getWidget('SearchPayloadWidget').searchTerm();
	};
	
	this.selectItem = function(event, item)
	{
		this.unselectLast();
		this.selectedItem = item;
		Element.addClassName(item, 'selected');
	};
	
	this.unselectItem = function(event, item)
	{
		Element.removeClassName(item, 'selected');
		this.selectedItem = null;
	};
	
	this.hasItemSelected = function()
	{
		return (this.selectedItem != null);
	};
	
	this.unselectLast = function()
	{
		if (this.hasItemSelected())
			this.unselectItem(null, this.selectedItem)
	};
	
	this.getSearchObj = function(field, defaultSearcher)
	{
		if (!this.hasItemSelected()) return defaultSearcher;
		
		var searchTermObj = this.payloadData.results[this.selectedItem.currentIndex];
		field.value = searchTermObj.p.stripTags();
		if (searchTermObj.bucket.search == "PID")
		{
			return {
					search : function()
					{
						AltrecGlobals.Coremetrics.SendEvent(searchTermObj.bucket.name, 'Site_Autocomplete');
						window.location = AltrecGlobals.Links.NonSecureDomain+"/"+searchTermObj.PID;
					}
			};
		}
		else
			return defaultSearcher;
	};
	
	this.hide = function()
	{
		if (this.renderState == 'on')
		{
			this.renderState = 'animatingOff';
			Effect.Fade(this.Container, {duration: 0.3, fps : 15, delay : .2, afterFinish : this.doneAnimatingOff.bind(this)});
		}
	};
	
	this.doneAnimatingOn = function()
	{
		this.renderState = 'on';
	};
	
	this.doneAnimatingOff = function()
	{
		this.renderState = 'off';
	};
	
	this.getTemplateVar = function()
	{
		return 'srpw';
	};
	
	this.destroy = function()
	{
		//first break circular references
		for (var i=0; i<this.payloadData.results.length; i++)
		{
			var item = this.items[i];
			item.bucket = null;
		}
		this.payloadData = null;
		this.base.destroy();
	};
	}



var WebModal =
{
	modalId : 0,

	Modals : {},
	lastModal : null,

	Show : function(modalElement, options)
	{
		var modal = new WebModalObj();
		modal.Show(modalElement, options);
		
		this.Modals[modal.id] = modal;
		this.lastModal = modal;
		return modal;
	},
	
	Hide : function(id)
	{
		var modal = (id) ? this.Modals[id] : this.lastModal;
		if (modal)
			modal.Hide();
	},

	Close : function(id)
	{
		var modal = (id) ? this.Modals[id] : this.lastModal;
		if (modal)
		{
			modal.options.effects = null;
			modal.Hide();
		}
	},
	
	
	RegisterPostHide : function(func)
	{
		var modal = this.lastModal.RegisterPostHide(func);
	},
	
	RemoveModal : function(modal)
	{
		delete this.Modals[modal.id];
	}
};

var WebModalObj = Class.create({
	bgMaskUrl : '/new_includes/images/maskBG.png',
	PositionElements : function()
	{
		var theBody = this.theBody;
		var screenWidth = 0;
		if (window.innerWidth!=window.undefined) screenWidth = window.innerWidth; 
		else if (document.compatMode=='CSS1Compat') screenWidth = document.documentElement.clientWidth; 
		else if (document.body) screenWidth = document.body.clientWidth; 
		
		var screenHeight = 0;
		if (window.innerHeight!=window.undefined) screenHeight = window.innerHeight;
		else if (document.compatMode=='CSS1Compat') screenHeight = document.documentElement.clientHeight;
		else if (document.body) screenHeight = document.body.clientHeight; 
		var windowHeight = screenHeight;
		
		if (screenHeight < theBody.scrollHeight) 
		{
			screenHeight = theBody.scrollHeight;
		}
	
		if (screenWidth > theBody.scrollWidth) {
			screenWidth = theBody.scrollWidth;
		}
		
		var scrollTop = 0;
		if (self.pageYOffset){scrollTop = self.pageYOffset;}
		else if (document.documentElement && document.documentElement.scrollTop){scrollTop = document.documentElement.scrollTop;}
		else if (document.body) {scrollTop = document.body.scrollTop;}

		if (this.options.reposition || !this.placedOnce) 
		{
			this.container.style.top = (scrollTop + Math.round(windowHeight * (this.options.topPercentage / 100))) + "px";
		}


		if (this.options.maskBackground) 
		{
		this.bgMask.style.width = screenWidth+'px';
		var bgMaskTop = scrollTop - 200;
		if (scrollTop<0)
			scrollTop =0;
		var bgMaskLeft = 0;
		if (!(document.all && !/opera/i.test(navigator.userAgent)) && this.theBody.style.marginLeft)
			bgMaskLeft = -1 * Number(this.theBody.style.marginLeft.split('px')[0]);
		this.bgMask.style.left = bgMaskLeft + 'px';
		this.bgMask.style.top = scrollTop+'px';
			this.bgMask.style.top = '0px';
			this.bgMask.style.height = screenHeight + 'px';
		}
		
		this.container.style.marginLeft = '-'+(Element.getWidth(this.container)/2)+'px';
		this.placedOnce = true;

	},
	
	Show : function(modalElement, options)
		{
			this.id = "WebModal"+WebModal.modalId++;
			var defaults = {
				width : 400,
				height : null,
				topPercentage : 10,
				maxOpacity : .4,
				effects : true,
				reposition : true,
				callback : null,
				nocontent : false,
				animation : .4,
				maskBackground : true,
				hideBleeders : true,
				backgroundColor : '#000000'
			};
		//	if (document.all && !/opera/i.test(navigator.userAgent))
		//		defaults.maxOpacity = .80;
	
			this.options = Object.extend(defaults, options || {});
			if (!this.initialized)
			{
				this.initialized = true;
				this.theBody = document.body;
		
				this.eventWindowResize = this.PositionElements.bindAsEventListener(this);
				this.eventKeyPress = this.KeyPress.bindAsEventListener(this);
				this.eventKeyPressBody = this.KeyPress.bindAsEventListener(this);
				this.bgMask = document.createElement('div');

				this.container = document.createElement('div');
				this.theBody.appendChild(this.container);
			}
			
			if (this.options.maskBackground) 
			{
				//this.bgMask = document.createElement('img');
				//this.bgMask.src = this.bgMaskUrl;
				this.bgMask = document.createElement('div');
				this.bgMask.innerHTML = '&nbsp;';
				this.bgMask.style.backgroundColor = this.options.backgroundColor;
				
				this.theBody.appendChild(this.bgMask);
				this.bgMask.style.zIndex = 11000;
				this.bgMask.style.position = 'absolute';
				this.bgMask.style.top = '0px';
				this.bgMask.style.left = '0px';
			}
				
				this.bgMask.style.display = 'none';
				
				this.container.style.position = 'absolute';
				this.container.style.zIndex = 11005;
			this.container.style.backgroundColor = (this.options.transparent) ? 'transparent' : 'white';
				this.container.style.left = '50%';
				if (this.options.width)
					this.container.style.width = this.options.width+'px';
				if (this.options.height)
					this.container.style.height = this.options.height+'px';
				this.container.style.display = 'none';
			
			
			if (this.options.hideBleeders) 
			{
			//otherwise these will bleed
				for (var i = 0; i < document.forms.length; i++) 
				{
					for (var e = 0; e < document.forms[i].length; e++) 
					{
						if (document.forms[i].elements[e].tagName == "SELECT") 
						{
						document.forms[i].elements[e].style.visibility="hidden";
					}
				}
			}
			}
			
			Event.observe(window, "resize", this.eventWindowResize);
			Event.observe(window, "scroll", this.eventWindowResize);
			Event.observe(window, "keypress", this.eventKeyPress);
			Event.observe(this.theBody, "keyup", this.eventKeyPressBody);
			
			if (this.options.maskBackground) 
			{
			Event.observe(this.bgMask, "click", this.Hide.bind(this));
				Element.setOpacity(this.bgMask, 0);
			}
			
			Element.setOpacity(this.container, 0);

			if (this.options.nocontent)
			{
				this.container.style.visibility = 'hidden';
				modalElement = document.createElement('span');
			}
				
			if (this.options.effects)
			{
				if (this.options.maskBackground) 
					Element.setOpacity(this.bgMask, this.options.maxOpacity);
					//new Effect.Opacity(this.bgMask, { from: 0, to: this.options.maxOpacity, duration: this.options.animation });
				new Effect.Opacity(this.container, { from: 0, to: 1, duration: this.options.animation});
				
			}
			else
			{
				Element.setOpacity(this.container, 1);
				if (this.options.maskBackground) 
				Element.setOpacity(this.bgMask, 1);
			}
			
			if (this.options.maskBackground) 		
			this.bgMask.style.display = 'block';
			this.container.style.display = 'block';
			
			this.modalDiv = $(modalElement);
			this.modalDiv.style.display = 'block';
			
			this.parent = Element.up(this.modalDiv);
			this.container.appendChild(this.modalDiv);
			this.PositionElements();
			if (this.options.callback)
				this.options.callback();
			return this.container;
		},
	KeyPress : function(e)
	{
      	if(e.keyCode == Event.KEY_ESC)
         	this.Hide();
	},
	Hide : function()
		{
			if (!this.modalDiv)
				return;
				
			if (this.options.hideBleeders) 
			{
				for (var i = 0; i < document.forms.length; i++) 
				{
					for (var e = 0; e < document.forms[i].length; e++) 
					{
						if (document.forms[i].elements[e].tagName == "SELECT") 
						{
						document.forms[i].elements[e].style.visibility="visible";
					}
				}
			}
			}
			
			this.placedOnce = false;
			Event.stopObserving(window, 'resize', this.eventWindowResize);
			Event.stopObserving(window, "scroll", this.eventWindowResize);
			Event.stopObserving(window, "keypress", this.eventKeyPress);
			Event.stopObserving(this.theBody, "keyup");
			Event.stopObserving(this.bgMask, "click");
			
			if (this.options.effects)
			{
				new Effect.Opacity(this.container, {
					from: this.options.maxOpacity,
					to: 0,
					queue : "parallel",
					duration: this.options.animation / 1.5,
					afterFinish: function()
					{
						this.RemoveFromDom();
					}.bind(this)
				});
				if (this.options.maskBackground) 
				{
					new Effect.Opacity(this.bgMask, {
						from: this.options.maxOpacity,
						to: 0,
						queue : "parallel",
						duration:this.options.animation / 2
					});
			}
			}
			else
			{
				this.RemoveFromDom();
			}
			
			if (this.onPostHide) 
			{
				this.onPostHide();
				this.onPostHide = null;
			}
		},
	RegisterPostHide : function(func)
	{
		this.onPostHide = func;
	},
	RemoveFromDom : function()
		{	
			if (this.options.maskBackground) 
			this.bgMask.style.display = 'none';
			if (this.modalDiv) 
			{
				this.modalDiv.style.display = 'none';
				this.container.style.display = 'none';
				if (this.parent) 
					this.parent.appendChild(this.modalDiv);
				else 
					this.theBody.appendChild(this.modalDiv);
				this.modalDiv = null;
			}
			
			WebModal.RemoveModal(this);
		}
});




function getItemTotal()
{
	var itemTotal = 0;
	try {
		var tempTotalStr = unescape(getCookie('carttotal'));
		if(tempTotalStr.indexOf('"')==0)
			tempTotalStr = tempTotalStr.split("\"")[1];
		if (tempTotalStr.indexOf('$')>=0)
			tempTotalStr = tempTotalStr.replace("$", "");
		if (tempTotalStr.indexOf(',')>0)
			tempTotalStr = tempTotalStr.replace(",", "");
	
		itemTotal = Number(tempTotalStr);
		if (isNaN(itemTotal)) {
			itemTotal = 0;}}
	catch(e)
	{
		itemTotal=0;
	}
	
	return itemTotal;
}

function getItemCount()
{
	var itemCount = 0;
	try
	{
		itemCount = Number(getCookie('cartcount'));
		if (isNaN(itemCount)) {
			itemCount = 0;}
	}catch(e){itemCount=0;}
	
	return itemCount;
}

	
var AltrecCookieManager = {
	trashedKey : 'trashed',
	lastAccessName : 'lastaccessdatetime',
	_setLastAccessCookie : function()
	{
		var now = expires = new Date();
		now.setTime(now.getTime());
		setCookie(this.lastAccessName, expires.toGMTString(), 365);
		this.lastAccessCookie = getCookie(this.lastAccessName);
	},
	
	GetLocID : function()
	{
		return getCookie('locid');
	},
	
	HasLocID : function()
	{
		return (this.GetLocID()) ? true : false;
	},
	
	Init : function()
	{
		var droppedNewCookie = false;
		if (!this.init)
		{
			this.init= true;
			var lastAccessCookie = getCookie(this.lastAccessName);
			if (!lastAccessCookie)
			{
				this._setLastAccessCookie();
				droppedNewCookie = true;
			}
			
			this.lastAccessCookie = getCookie(this.lastAccessName);
		}
		return droppedNewCookie;
	},
	UpdateLastCartAccess : function()
	{
		this.Init();
		this._setLastAccessCookie();
	},
	
	TrashLastCartAccess : function ()
	{
		setCookie(this.lastAccessName, this.trashedKey, 365);
		this.lastAccessCookie = getCookie(this.lastAccessName);
	},
	
	NeedToRefreshCart : function()
	{
		try
		{
			var serverTime = new Date(AltrecGlobals.Site.ServerTime);
			var cookie = getCookie('cart_recovery_time');
			if (cookie.charAt(0) == '"')
				cookie = cookie.substring(1, getCookie('cart_recovery_time').length - 1);
			cookie = unescape(cookie).replace(/\+/g," ");
			var cartExpiry = new Date(cookie);
				
			return (cartExpiry < serverTime);

		}
		catch(e){
			return false;
		}
		return false;
		}
};
	
function CartSummary(itemCount, itemTotal)
{
	this.itemCount = itemCount;
	this.itemTotal = itemTotal;
	this.cartLink = (AltrecGlobals.Links.getCartUrl) ? AltrecGlobals.Links.getCartUrl() : '/shop/cart';
	
	this.getItemCount = function()
	{
		var itemsStr = this.itemCount + ' item';
		if (this.itemCount != 1)
			itemsStr += 's';
		return itemsStr;
	};
	
	this.getItemTotal = function()
	{
		return FormatCurrency(this.itemTotal, true);
	};
	
	this.qualifiesFreeShipping = function()
	{
		return (this.itemTotal >= AltrecGlobals.Variables.freeShippingTreashold);
	};
}

function CartItem()
{
	var self = this;
	this.name;
	this.href;
	this.store;
	this.uniqueId;
	this.topMessage;
	this.description1;
	this.description2;
	this.quantity;
	this.image;
	this.price;
	this.brandName;
	this.pid;
	this.productName;
	
	this.getFormattedPrice = function()
	{
		return FormatCurrency(this.price, true);
	};
}
CartItem.prototype.equals = function(objToCompare) {
	return (this.store == objToCompare.store && this.uniqueId == objToCompare.uniqueId);
};

function AltrecItem() {
	
	this.store = 'Altrec';
	var skuData;
	CartItem.call(this);
	
	this.setData = function(pSkudData)
	{
		skuData = pSkudData;
		this.pid = skuData.pid;
		this.productName = skuData.productName;
		this.brandName = skuData.brandName;
		this.name = skuData.productName;
		this.href = skuData.href;
		this.uniqueId = skuData.sid;
		this.topMessage = "Just Added:";
		this.description1 = '<b>Color:</b> '+skuData.color;
		if (skuData.color=='') {
			this.description1 = '';}
		this.description2 = '<b>Size:</b> '+skuData.size;
		if (skuData.size=='') {
			this.description2 = '';}
		
		var swatchImg = skuData.sku_swatch_image;
		var domainPrefixVar = 'http://mirror.altrec.com';
		try
		{
			domainPrefixVar = domainPrefix;
		}
		catch(ex){}
		if (!swatchImg || swatchImg=='')
		{
			if (skuData.sku_swatch_image_d && skuData.sku_swatch_image_d !='')
				swatchImg = domainPrefixVar + skuData.sku_swatch_image_d;
			else
				swatchImg = 'http://mirror.altrec.com/images/shop/photos/nophoto_x.gif';
		}
		else
		{
			swatchImg = domainPrefixVar + swatchImg;
		}
		
		this.image = swatchImg;
		this.quantity = 1;
		this.price = Number(skuData.sku_price);
	};
}

AltrecItem.prototype = new CartItem();

var executeCartSisterCall = false;
var count=0;
function CartMediator()
{
	var self = this;
	this.cartSummary;
	this.cartItems = new Array();
	this.cartEpanded = false;
	this.cartEpandable = false;
	this.altrecCookieManager = AltrecCookieManager;

	this.init = function()
	{
		if (this.altrecCookieManager.NeedToRefreshCart())
		{
			var request = new Ajax.Request("/servlets/Cart_Service", {
				method : 'get',
				parameters: {
					format: 'json',
					version: '1.0',
					review_cart: true
				},
				onSuccess: function(transport){
					var response =  transport.responseText;
			  		var JSONResponse = eval('('+response+')');
					
					var cartInfo = this.getCartInfo(JSONResponse.MessagePayloads.CartPayload);
					this.cartSummary = new CartSummary(cartInfo.count, cartInfo.total);
					this._redrawCartSummary(false);
					
			    }.bind(this),
				onFailure: function()
				{
					this.cartSummary = new CartSummary(0, 0);
					this._redrawCartSummary(false);
			    }.bind(this)
			});
		}
		else
		{
			var itemCount = getItemCount();
			var itemTotal = getItemTotal();
			
			this.cartSummary = new CartSummary(itemCount, itemTotal);
			this._redrawCartSummary(false);
		}

		this.collapseCartEvent = this.collapseCart.bindAsEventListener(this);
		
		this.collapseObservers = new Array();
		this.AddCollapseObserver(window, 'click');
		this.AddCollapseObserver(document.body, 'click');
	};
	
	this.expandCart = function(event, args)
	{
		if (this.cartSummary.itemCount==0) {
			return;}
		
		this._drawExpandedCart();
		this._redrawCartSummary(true);
	};
	
	function drawSecondaryDropdowns()
	{
		if (self.dropDownToRender < self.secondaryDropdowns.length)
			InstanceContainer.FireEventAsync(self.id, '_renderSecondaryDropdown', 500, self.dropDownToRender);
	}
	
	this.drawSecondaryDropdownsComplete = function()
	{
		this.dropDownToRender++;
		drawSecondaryDropdowns()
	};
	
	this.expandCartComplete = function ()
	{
		this.cartEpanded = true;
		this.collapseObservers.each(function(observer){
			Event.observe(observer.element, observer.event, this.collapseCartEvent);
		}.bind(this));
				
		this.dropDownToRender = 0;
		drawSecondaryDropdowns();
	};
	
	this.AddCollapseObserver = function(element, event)
	{
		this.collapseObservers.push({element : element, event : event});
	};

	this.collapseCart = function(event, args)
	{
		if (!this.cartEpanded) {
			return;}
			
		this.cartEpanded = false;
		this.collapsingCart = true;
		this.collapseObservers.each(function(observer){
			Event.stopObserving(observer.element, observer.event, this.collapseCartEvent);
		}.bind(this));
		
		this.dropDownToCollapse = this.secondaryDropdowns.length-1;
		drawCollapsedSecondaryDropdowns();
	};
	
	function drawCollapsedSecondaryDropdowns()
	{
		if (self.dropDownToCollapse >= 0) {
			InstanceContainer.FireEventAsync(self.id, '_drawCollapsedSecondaryDropdown', 0, self.dropDownToCollapse);}
		else {
			mediateCollapseCart();}
	}
	
	this._renderCollapsedSecondaryComplete = function()
	{
		this.dropDownToCollapse--;
		drawCollapsedSecondaryDropdowns();
	};
	
	function mediateCollapseCart()
	{
		self.secondaryDropdowns.each(function(dropdown){
			dropdown.elem.style.display = 'none';
		}.bind(self));
		self._drawCollapsedCart();
		self._redrawCartSummary(false);
	}
	
	this.collapseCartComplete = function()
	{
		this.onCollapseCartComplete();
		this.collapsingCart = false;
		if (this.deferedCartItemAdd)
		{
			var deferedItem = this.deferedCartItemAdd;
			this.deferedCartItemAdd  = null;
			this.addToCart(null, deferedItem);
		}
	};
	
	this.addToCart = function(event, cartItem)
	{
		if (this.cartEpanded)
		{
			if (!this.deferedCartItemAdd)
				this.deferedCartItemAdd = cartItem;
			return;
		}
		
		this.cartEpandable = true;
		this._removeAllItems();
		this.cartItems = [];
		this.cartItems.push(cartItem);
		if (this.cartSummary.itemCount == 0) {
			this._disableCheckoutBtns();}
		this.altrecCookieManager.TrashLastCartAccess();
		
		try
		{
			if (!testMode) 
			{
				CreateCartTag(cartItem.pid + '', cartItem.brandName + ": " + cartItem.productName, "1", cartItem.price, this.getCmCategory());
				DisplayShop5s();
			}
		}catch(e){}
		
		var parameters = {ajax : 'true' , SKU : cartItem.uniqueId, action : 'Add', quantity : 1};
		if (this.getExtraParameters)
			parameters = Object.extend(parameters, this.getExtraParameters() || { });
			
		var url = '/servlets/Cart_Service?format=json&action=Add&SKU='+cartItem.uniqueId
			+'&quantity=1&version=1.0';
			
		//these will probably be overwriten
		this.cartSummary.itemCount++;
		this.cartSummary.itemTotal += cartItem.price;

		var request = new Ajax.Request(url, {
			method : 'get',
			onSuccess: function(transport){
		      var response =  transport.responseText;
			  this.altrecCookieManager.UpdateLastCartAccess();
			  this._enableCheckoutBtns();
			  if (response.indexOf('DOCTYPE')>0)
			  {
			  	//do nothing
			  }
			  else
			  {
			  		var JSONResponse = eval('('+response+')');
		      		addToCartComplete(JSONResponse);
			  }
		    }.bind(this)
		});
		
		this._renderItem(cartItem);
		this.expandCart(event, cartItem);
		
		if (testMode) 
		{
			addToCartComplete({
				total: "$364.90",
				error_id: 0,
				qty_added: 1,
				error: "",
				count: 2
			});
		}
	};
	
	this.getCartInfo = function(cartObj)
	{
		var count = 0;
		if (cartObj.cart_info) 
		{
			cartObj.cart_info.each(function(shipment)
			{
				shipment.package_items.each(function(item)
				{
					count += parseInt(item.quantity);
				});
			});
		}
		var total = (cartObj.invoice_sub_total) ? cartObj.invoice_sub_total.replace(',', '') : 0;
		
		return {
			count: count,
			total: Number(total)
		};
	}
	
	function addToCartComplete(JSONResponse)
	{
	
		var cartInfo = null;
		var errorMessage = null;
		var oosMsg = "We're Sorry, that item is out of stock. It cannot be added.";
		
		try 
		{
			cartInfo = self.getCartInfo(JSONResponse.MessagePayloads.CartPayload);
		
		
			if (JSONResponse.MessagePayloads.OverlayInteractivePayload && 
			   JSONResponse.MessagePayloads.OverlayInteractivePayload.message_name == 'QUAN_AVAIL')
			  	errorMessage = oosMsg;
			else if (JSONResponse.MessagePayloads.OverlayInteractivePayload &&
				JSONResponse.MessagePayloads.OverlayInteractivePayload.message_name == 'QTY_THRESHOLD')
			  	errorMessage = 'For bulk orders please contact customer service at (800) 369-3949';
		}
		catch(e)
		{
			errorMessage = oosMsg;
		}
		
		if (errorMessage)
		{
			self.cartItems = [];
			self._displayErrorMessage(errorMessage);
		}
		
		if (cartInfo) 
		{
			self.cartSummary.itemCount = cartInfo.count;
			self.cartSummary.itemTotal = cartInfo.total;
		}
		else 
		{
			self.cartSummary = new CartSummary(getItemCount(), getItemTotal());
		}
			
		self._updateCartSummary(true);
		
		
		triggerCookieShareIframe();
		
	}
	
	this.HideCheckoutBtns = function()
	{
		this._renderHiddenCheckoutBtns();
	};
}

function AltrecCart(parameters)
{
	CartMediator.call(this);
	
	var self = this;
	this.id = parameters.cartElement;
	var cartElement = getElement(parameters.cartElement);
	var innerCartContainer = getElement(parameters.innerCartContainer);
	var closeOpenBtn = getElement(parameters.closeOpenBtn);
	var itemContainer = getElement(parameters.itemContainer);
	var itemTemplate = getElement(parameters.itemTemplate);
	this.editCartBtns = parameters.cartEditBtns;
	var cartUserMessage = getElement(parameters.cartUserMessage);
	var itemTemplateHeight = parameters.itemTemplateHeight;
	var cartSummaryElement = getElement(parameters.cartSummary);
	var cartSummaryShippingLbl = getElement(parameters.cartSummaryShipping);
	var cartSummaryClosedElem = getElement(parameters.cartSumClosed);
	var cartSummaryOpenElem = getElement(parameters.cartSumOpen);
	var cartStatusPane = getElement(parameters.cartStatusPane);
	var emptyCartPane = getElement(parameters.cartEmptyCart);
	var cartBottomRightCorner = getElement(parameters.cartBottomRightCorner);
	this.secondaryDropdowns = parameters.secondaryDropdowns || [];
	this.secondaryDropdowns.each(function(dropdown){
		dropdown.elem = getElement(dropdown.id);
		dropdown.originalHeight = Element.getHeight(dropdown.elem);
	}.bind(this));
	var elementStack = new Array();
	var animationDuration = .26;

	closeOpenBtn.onclick = toggleCart.bindAsEventListener(this, null);
	
	function toggleCart(event, args)
	{
		if (!this.cartEpanded && this.cartEpandable)
		{
			this.expandCart(null, null);
		}
	}
	
	this._displayErrorMessage = function(message)
	{
		Element.setOpacity(elementStack[elementStack.length-1], .2);
		cartUserMessage.innerHTML = message;
		cartUserMessage.style.display = 'block';
	};
	
	this._drawExpandedCart = function()
	{
		closeOpenBtn.style.backgroundPosition = '0px -22px';
		closeOpenBtn.style.cursor = 'pointer';
		var height = itemTemplateHeight * elementStack.length;
		itemContainer.style.height = height +'px';
		
		new Effect.Morph(innerCartContainer, { style: 'width:233px;left:-42px;', duration: animationDuration});
		Effect.SlideDown(itemContainer.id, {duration : animationDuration , afterFinish : this.expandCartComplete.bind(this)});
	};
	
	this._renderSecondaryDropdown = function(dropDownIdx)
	{
		if (!this.cartEpanded || this.collapsingCart)
			return;
		var top = 0;
		var elementToGetHeight = innerCartContainer;
		var elem = this.secondaryDropdowns[dropDownIdx].elem;
		if (dropDownIdx>0)
		{
			var elemAbove = this.secondaryDropdowns[dropDownIdx-1].elem;
			top = elemAbove.offsetTop;
			elementToGetHeight = elemAbove;
		}
		elem.style.top = Element.getHeight(elementToGetHeight) + top - 6 +'px';
		Element.immediateDescendants(elem)[0].style.bottom = '0px';
		elem.style.height = this.secondaryDropdowns[dropDownIdx].originalHeight;
		elem.style.left = innerCartContainer.style.left;
		new Effect.SlideDown(elem, {duration : animationDuration*2, afterFinish : this.drawSecondaryDropdownsComplete.bind(this)});
	};
		
	this._drawCollapsedCart = function()
	{
		cartUserMessage.style.display = 'none';
		Element.setOpacity(elementStack[elementStack.length-1], 1);
		closeOpenBtn.style.backgroundPosition = '0px 0px';
		new Effect.Morph(innerCartContainer, { style: 'width:191px;left:0px', duration: animationDuration});
		Effect.SlideUp(itemContainer.id, {duration : animationDuration, afterFinish : this.collapseCartComplete.bind(this)});
	};
	
	this.onCollapseCartComplete =function()
	{
		cartBottomRightCorner.style.right = '0px';
		cartBottomRightCorner.style.bottom = '0px';
	};
	
	this._drawCollapsedSecondaryDropdown = function(dropDownIdx)
	{
		var elem = this.secondaryDropdowns[dropDownIdx].elem;
		Effect.SlideUp(elem, {duration : animationDuration/2, afterFinish : this._renderCollapsedSecondaryComplete.bind(this)});
	};
	
	this._redrawCartSummary = function(showDetails)
	{
		if (this.cartSummary.itemCount == 0) 
		{
			cartStatusPane.style.display = 'none';
			emptyCartPane.style.display = 'block';
			closeOpenBtn.style.backgroundPosition = '0px -44px';
			return;
		}
		
		cartStatusPane.style.display = 'block';
		emptyCartPane.style.display = 'none';
		
		if (showDetails) 
		{
			cartSummaryClosedElem.style.display = 'none';
			cartSummaryOpenElem.style.display = 'block';
		}
		else if (cartSummaryOpenElem.style.display == 'block') 
		{
			cartSummaryClosedElem.style.display = 'block';
			cartSummaryOpenElem.style.display = 'none';
		}
		this.cartSummary.shippingLblDisplay = (this.cartSummary.qualifiesFreeShipping()) ? 'block' :'none';
		this._updateCartSummary(showDetails);
	};
	
	this._updateCartSummary = function(showDetails)
	{	
		this.cartSummary.shippingInfo = (this.cartSummary.qualifiesFreeShipping()) ? 'FREE SHIPPING': 'FREE SHIPPING on $'+AltrecGlobals.Variables.freeShippingTreashold+' Orders';
		if (showDetails)
		{
			this.cartSummary.shippingInfo = '&nbsp;';
		}
		bindJSON(cartSummaryElement, 'cartSummaryVar', this.cartSummary);
	};
	
	this._removeAllItems = function()
	{
		elementStack.each(function(element){
				itemContainer.removeChild(element);
			});
			
		elementStack = new Array();
	};
	
	this._renderItem = function(cartItem)
	{	
		scroll(0,0);
		var newItem = itemTemplate.cloneNode(true);
		bindJSON(newItem, 'cartItemVar', cartItem);
		newItem.style.display='block';
		newItem.id = newItem.id + elementStack.length;
		
		itemContainer.insertBefore(newItem, itemContainer.firstChild);
		itemContainer.insertBefore(document.createElement('span'), newItem);
		if (elementStack.length>0)
		{
			elementStack[elementStack.length-1].style.borderTopStyle = 'solid';
			elementStack[elementStack.length-1].style.borderTopWidth = '1px';
		}
		elementStack.push(newItem);
	};
	

	function getElement(id)
	{
		var returnElem = $(id);
		if (returnElem==null || returnElem=='undefined')
		{
			var message = id+' not in AltrecCart constructor object';
			alert(message);
		}
		return returnElem;
	}
	
	this.getCmCategory = function()
	{
		return this.getExtraParameters().cm_catid;
	};
	
	this.getExtraParameters = function()
	{
		try
		{
			var form = document.getElementsByName('orderbox')[0];
			return {shop_id : document.getElementsByName('shop_id')[0].value ,
				cm_catid : document.getElementsByName('cm_catid')[0].value
			};
		}
		catch(e){return {cm_catid : ''};}
	};
	
	this._disableCheckoutBtns = function()
	{
		this.editCartBtns.each(function(btn){
			btn.onclick = returnFalse;
			Element.setOpacity(btn, .6);
		});
	};
	
	this._enableCheckoutBtns = function()
	{
		this.editCartBtns.each(function(btn){
			btn.onclick = returnTrue;
			Element.setOpacity(btn, 1);
		});
	};
	
	this._renderHiddenCheckoutBtns = function()
	{
		this.editCartBtns.each(function(btn){
			btn.style.visibility = 'hidden';
		});
		cartSummaryShippingLbl.style.borderBottom = 'none';
	}
	
	this.init();
}

AltrecCart.prototype = new CartMediator();
var cart = {
	HideCheckoutBtns : function(){}
};

function formOverrideSubmit(e, args)
{
	try
	{
		var selectBox = $('detailSelect');
		var selectedValue = '';
		if (selectBox.nodeName=='DIV' && ($('detailSelect2')!=null && $('detailSelect2').nodeName=='DIV'))
			selectedValue = document.orderbox.SKU.value;
		else
		{	
			selectedValue = document.orderbox.SKU.options[document.orderbox.SKU.selectedIndex].value;
		}
		if(selectedValue == null || selectedValue == '' || selectedValue == 'xxx')
		{
			return false;
		}

		var cartItem = new AltrecItem();
		sku_list.product[0].sku_data.each(function(item){
				if (item.sid == selectedValue)
				{
					item.href = window.location.href;
					item.productName = sku_list.product[0].product_name;
					item.brandName = sku_list.product[0].pd_brand_name;
					item.pid = sku_list.product[0].pid;
								
					cartItem.setData(item);
					cart.addToCart(null, cartItem);
				}
			}.bind(this)
		);
		
	}
	catch(e){alert(e.message);}
	return false;
}

function returnFalse(event, args)
{
	return false;
}

function returnTrue(event, args)
{
	return true;
}

function initCart()
{
	var oldCart = $('cartvalues');
	var newCart = $('CartContainer');


	var itemTotal = getItemTotal();
	
	newCart.style.display = 'block';
	cart = new AltrecCart({
		cartElement: 'CartContainer',
		innerCartContainer: 'innerCartContainer',
		closeOpenBtn: 'closeOpenBtn',
		itemContainer: 'cartItemContainer',
		itemTemplate: 'cartItemTemplate',
		cartEditBtns : [$('cartEditBtn'), $('cartCheckout').getElementsByTagName('a')[0]],
		cartUserMessage: 'cartUserMessage',
		cartSummary: 'cartStatusPane',
		cartSummaryShipping: 'cartSummaryShipping',
		cartSumClosed: 'cartSumClosed',
		cartSumOpen: 'cartSumOpen',
		cartStatusPane: 'cartStatusPane',
		cartEmptyCart: 'cartEmptyCart',
		cartBottomRightCorner: 'cartBottomRightCorner',
		itemTemplateHeight: 120,
		secondaryDropdowns: [{
			id: "secondaryDropdown"
		}]//, {id :"secondaryDropdown2"}]
	});
	InstanceContainer.RegisterInstance(cart);
	
	try 
	{
		if (typeof document.body.style.maxHeight == "undefined") 
		{
			$('cartBodyContainer').style.marginRight = "1px";
		}
		if (Prototype.Browser.IE) document.execCommand("BackgroundImageCache", false, true);
	} 
	catch (err) 
	{
	}
	
}

function attachNewCart()
{
	try
	{
		
		var elements = document.getElementsByName('orderbox');
		var form;
		for (var i=0; i<elements.length; i++)
		{
			var element = elements[i];
			if (element.tagName == 'FORM')
				form = element;
		}
		
		if ($('CartContainer').style.display == 'block')
		{
			form.onsubmit = returnFalse.bindAsEventListener(this);
		
			var button = (Element.getElementsByClassName) ? Element.getElementsByClassName(form, 'addCart')[0] : form.getElementsByClassName('addCart')[0];
			button.onclick = formOverrideSubmit.bindAsEventListener(this);
		}
		
		
	}
	catch(e){}
}




function delete_cookie ( cookie_name )
{
  var cookie_date = new Date ( );  // current date & time
  cookie_date.setTime ( cookie_date.getTime() - 1 );
  document.cookie = cookie_name += "=; expires=" + cookie_date.toGMTString();
}

var url = location.href;

if (url.indexOf("www.altrec.com") > 0 && url.indexOf("avad=") > 0) {
	var affdata;
	var merchant_id = 10032;
	var cookie_name = 'avl';
	var cookie_days = 120;
	var cookie_domain = '.altrec.com';
	var aUrl = url.split("avad=");
	
	// Delete any existing cookie
	delete_cookie(cookie_name);
	
	// Parse out tracking data from the url
	if (aUrl[1].indexOf("&") > 0) {
		affdata = aUrl[1].substring(0, aUrl[1].indexOf("&"));
	}
	else {
		affdata = aUrl[1];
	}
	
	var expdate = new Date();
	expdate.setTime(expdate.getTime() + cookie_days*24*60*60*1000);
	document.cookie = cookie_name + "=" + escape(affdata) + "; expires=" + expdate + "; path=/; domain=" + cookie_domain + ";";
}

function CreateTechPropsTag(pageid,catid){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmCreateTechPropsTag(pageid,catid.toUpperCase());
       }
}
function CreatePageviewTag(pageid,catid,search,cnt){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmCreatePageviewTag(pageid,catid.toUpperCase(),search,cnt);
       }
}
function CreateProductviewTag(pid,product,catid){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmCreateProductviewTag(pid,product,catid.toUpperCase());
       }
}
function CreateCartTag(pid,product,qty,val,catid){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmCreateShopAction5Tag(pid,product,qty,val,catid.toUpperCase());
       }
}
function DisplayShop5s(){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmDisplayShop5s();
       }
}
function DisplayShop9s(){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmDisplayShop9s();
       }
}
function CreateInvoiceDetailTag(pid,product,qty,price,cust,inv,subt,catid){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmCreateShopAction9Tag(pid,product,qty,price,cust,inv,subt,catid.toUpperCase());
       }
}
function CreateOrderTag(inv,subt,ship,cust,city,state,zip){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmCreateOrderTag(inv,subt,ship,cust,city,state,zip);
       }
}
function CreateRegistrationTag(cust,email,city,state,zip,newsltr,subs){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmCreateRegistrationTag(cust,email,city,state,zip,newsltr,subs);
       }
}
function CreateErrorTag(pageid,catid){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmCreateErrorTag(pageid,catid.toUpperCase());
       }
}

