// File: /okcontent/js/Oryx/Cookies.js
AUTOCORE_SELF_CHECK.push("Oryx/Cookies.js");
// 

function setOkCookie(name,value,expires) {
    setCookie(name,value,expires);
}

function deleteOkCookie(name) {
    deleteCookie(name);
}

// -----------------------------------------------------------------------------------

function getPageTopDomain() {
 var host = window.location.host;
 var parts = host.split('.');
 var res = parts[parts.length-2] + "." + parts[parts.length-1];
 if (res.indexOf(':') != -1) {
	 res = res.substr(0,res.indexOf(':'));
 }
 return res;
}

function secondsFromNow (sec) {
   res = new Date();
   res.setTime(new Date().getTime() + sec * 1000);
   return res;
}

// -----------------------------------------------------------------------------------

function setCookie(name,value,expires) {
  var curCookie = name + "=" + escape(value) +
      ((expires) ? "; expires=" + expires.toGMTString() : "") +
      "; path=/"+
      "; domain=" + getPageTopDomain();
  document.cookie = curCookie;
}
function deleteCookie(name) {
  if (getCookie(name)) {
    document.cookie = name + "=" +
    "; domain=" + getPageTopDomain() + 
    "; path=/"+
    "; expires=Thu, 01-Jan-70 00:00:01 GMT";
  }
}
function getCookie(name) {
	var dc = document.cookie;
  	var prefix = name + "=";
  	var begin = dc.indexOf("; " + prefix);
  	if (begin == -1) {
    	begin = dc.indexOf(prefix);
    	if (begin != 0) return null;
  	} 
	else
    	begin += 2;
  	var end = document.cookie.indexOf(";", begin);
  	if (end == -1)
    	end = dc.length;
  return unescape(dc.substring(begin + prefix.length, end));
}

 /* 
 */

function addNewCoresToCookie(file_nums, hash) {
	 var old_cookie = getCookie("core");
	 var old_cookie_parts = [];
	 var new_cookie = old_cookie;
	 if (old_cookie)
		old_cookie_parts = old_cookie.split(":");
	 if (old_cookie_parts.length == 0 || old_cookie_parts[0] != hash)
		old_cookie
	 if (! old_cookie || old_cookie.split(":")[0] != hash)
		new_cookie = hash;
	 var new_addition = file_nums.join(",");
	 for (var i = 1; i < old_cookie_parts.length && old_cookie_parts[0] == hash; i++) {
		 if (old_cookie_parts[i] == new_addition) {
			 return;
		 }
	 }
	 new_cookie += ":" + new_addition;
	 setCookie("core", new_cookie, secondsFromNow(86400 * 30));
}

// Backwards compatibility, but limit miniCookies to 3600 seconds.
 
function setMiniCookie(name,value) {
	NanoCookie.set(name,value,{ms:3600*1000});
}

function deleteMiniCookie(name) {
	NanoCookie.remove(name);
}

function getMiniCookie(name) {
	return NanoCookie.get(name);
}

// 
NanoCookie = {
	
	// 
	set : function(key,value,expires,test) {
		var cookies = this.deserialize();
		var d = new Date();
		var obj = new Object();
		obj["k"] = key;
		obj["v"] = value;
		if (expires.ms)
			obj["e"] = d.getTime() + parseInt(expires.ms);
		else if (expires.gmt)
			obj["e"] = expires.gmt;
		else
			obj["e"] = expires.date.getTime();
		if (isNaN(obj["e"]))
			obj["e"] = 0;
		var found = false;

		for (var i = 0; i < cookies.length; i++) {
			if (cookies[i]["k"] == key) {
				cookies[i] = obj;
				found = true;
			}
		}
		if (! found)
			cookies.push(obj);
		this.serializeAndStore(cookies);
	},
	get : function(key) {
		var cookies = this.deserialize();
		for (var i = 0; i < cookies.length; i++) {
			if (cookies[i]["k"] == key) {
				return cookies[i]["v"];
			}
		}
		return null;
	},	
	// 
	getAll : function(nano_cookie_str) {
		var cookies = this.deserialize(nano_cookie_str);
		var res = [];
		for (var i = 0; i < cookies.length; i++) {
			var obj = new Object();
			obj["key"] = cookies[i]["k"];
			obj["value"] = cookies[i]["v"];
			obj["expires"] = new Date(cookies[i]["e"]);
			res.push(obj);
		}
		return res;		
	},
	// 
	findRegExp : function(regexp) {
		var cookies = this.deserialize();
		var res = [];
		for (var i = 0; i < cookies.length; i++) {
			if (regexp.test(cookies[i]["k"])) {
				var obj = new Object();
				obj["key"] = cookies[i]["k"];
				obj["value"] = cookies[i]["v"];
				obj["expires"] = new Date(cookies[i]["e"]);
				res.push(obj);
			}
		}
		return res;
	},
	remove : function(key) {
		this.set(key,"",{ms:-1},true);		
	},
	removeAll : function() {
		deleteCookie("nano");
	},
	//
	// gets from cookie called "nano" ; alternatively you 
	// can pass it your own cookie and it'll deserialize that
	deserialize : function(nano_cookie_str) {

		var x = nano_cookie_str ? nano_cookie_str : getCookie("nano");
		
		var result = [];
		if (! x)
			return result;
		else {
			var individuals = x.split("|");
			for (var i = 0; i < individuals.length; i++) {
				var obj = new Object();
				var pairs = individuals[i].split(",");
				for (var j = 0; j < pairs.length; j++) {
					var pair = pairs[j].split("=");
					var val = unescape(pair[1]);
					var pval = parseInt(val);
					if (pval == val && ! isNaN(pval))
						val = pval;
					obj[pair[0]] = val;
				}
				result.push(obj);				
			}
			result.sort(this.compareDates);
			// 
			var d = new Date();
			var any_stripped = false;
			while(result.length > 0 && new Date(result[result.length - 1]["e"]) < d) {
				result.length--;
				any_stripped = true;
			}
			return result;
		}
	},
	// 
	serializeAndStore : function(cookies) {
		var res = "";
		for (var i = 0; i < cookies.length; i++) {
			res += (i == 0) ? "" : "|";
			res += "k=" + escape(cookies[i]["k"]);
			res += ",e=" + escape(cookies[i]["e"]);
			res += ",v=" + escape(cookies[i]["v"]);
		}
		setCookie("nano", res, secondsFromNow(3600*24*365));
	},
	compareDates : function(a,b) {
		if (a["e"] && b["e"])
			return ((a["e"] < b["e"]) ? 1 : ((a["e"] > b["e"]) ? -1 : 0));
	}
};
 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/browser_detect.js
AUTOCORE_SELF_CHECK.push("browser_detect.js");
var BrowserDetect = {
	init: function () {
		this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
		this.version = this.searchVersion(navigator.userAgent)
			|| this.searchVersion(navigator.appVersion)
			|| "an unknown version";
		this.OS = this.searchString(this.dataOS) || "an unknown OS";
	},
	searchString: function (data) {
		for (var i=0;i<data.length;i++)	{
			var dataString = data[i].string;
			var dataProp = data[i].prop;
			this.versionSearchString = data[i].versionSearch || data[i].identity;
			if (dataString) {
				if (dataString.indexOf(data[i].subString) != -1)
					return data[i].identity;
			}
			else if (dataProp)
				return data[i].identity;
		}
	},
	searchVersion: function (dataString) {
		var index = dataString.indexOf(this.versionSearchString);
		if (index == -1) return;
		return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
	},
	dataBrowser: [
		{
			string: navigator.userAgent,
			subString: "Chrome",
			identity: "Chrome"
		},
		{ 	string: navigator.userAgent,
			subString: "OmniWeb",
			versionSearch: "OmniWeb/",
			identity: "OmniWeb"
		},
		{
			string: navigator.vendor,
			subString: "Apple",
			identity: "Safari",
			versionSearch: "Version"
		},
		{
			prop: window.opera,
			identity: "Opera"
		},
		{
			string: navigator.vendor,
			subString: "iCab",
			identity: "iCab"
		},
		{
			string: navigator.vendor,
			subString: "KDE",
			identity: "Konqueror"
		},
		{
			string: navigator.userAgent,
			subString: "Firefox",
			identity: "Firefox"
		},
		{
			string: navigator.vendor,
			subString: "Camino",
			identity: "Camino"
		},
		{		// for newer Netscapes (6+)
			string: navigator.userAgent,
			subString: "Netscape",
			identity: "Netscape"
		},
		{
			string: navigator.userAgent,
			subString: "MSIE",
			identity: "Explorer",
			versionSearch: "MSIE"
		},
		{
			string: navigator.userAgent,
			subString: "Gecko",
			identity: "Mozilla",
			versionSearch: "rv"
		},
		{ 		// for older Netscapes (4-)
			string: navigator.userAgent,
			subString: "Mozilla",
			identity: "Netscape",
			versionSearch: "Mozilla"
		}
	],
	dataOS : [
		{
			string: navigator.platform,
			subString: "Win",
			identity: "Windows"
		},
		{
			string: navigator.platform,
			subString: "Mac",
			identity: "Mac"
		},
		{
			   string: navigator.userAgent,
			   subString: "iPhone",
			   identity: "iPhone/iPod"
	    },
		{
			string: navigator.platform,
			subString: "Linux",
			identity: "Linux"
		}
	]

};
BrowserDetect.init();
 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/prototype-1.6.0.3.js
AUTOCORE_SELF_CHECK.push("prototype-1.6.0.3.js");
/*  Prototype JavaScript framework, version 1.6.0.3
 *  (c) 2005-2008 Sam Stephenson
 *
 *  Prototype is freely distributable under the terms of an MIT-style license.
 *  For details, see the Prototype web site: http://www.prototypejs.org/
 *
 *--------------------------------------------------------------------------*/

var Prototype = {
  Version: '1.6.0.3',

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

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

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

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

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


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

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

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

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

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

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

    klass.prototype.constructor = klass;

    return klass;
  }
};

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

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

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

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

    return this;
  }
};

var Abstract = { };

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return returnValue;
  }
};

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

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

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

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

    this.registerCallback();
  },

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return camelized;
  },

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

var $break = { };

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

if (Prototype.Browser.WebKit) {
  $A = function(iterable) {
    if (!iterable) return [];
    // In Safari, only use the `toArray` method if it's not a NodeList.
    // A NodeList is a function, has an function `item` property, and a numeric
    // `length` property. Adapted from Google Doctype.
    if (!(typeof iterable === 'function' && typeof iterable.length ===
        'number' && typeof iterable.item === 'function') && iterable.toArray)
      return iterable.toArray();
    var length = iterable.length || 0, results = new Array(length);
    while (length--) results[length] = iterable[length];
    return results;
  };
}

Array.from = $A;

Object.extend(Array.prototype, Enumerable);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// 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, context) {
    $R(0, this, true).each(iterator, context);
    return this;
  },

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  activeRequestCount: 0
};

Ajax.Responders = {
  responders: [],

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

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

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

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

Object.extend(Ajax.Responders, Enumerable);

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

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

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

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

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

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

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

    if (!['get', 'post'].include(this.method)) {
      // 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 || { });
  if (element) this.Element.prototype = element.prototype;
}).call(window);

Element.cache = { };

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

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

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

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

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

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

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

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

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

    var content, insert, tagName, childNodes;

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

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

      content = Object.toHTML(content);

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

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

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

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

    return element;
  },

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return false;
  },

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

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

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

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

    return element;
  },

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

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

    // 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 (Prototype.Browser.Opera) {
        element.style.top = 0;
        element.style.left = 0;
      }
    }
    return element;
  },

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

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

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

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

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

  absolutize: function(element) {
    element = $(element);
    if (element.getStyle('position') == 'absolute') return element;
    // 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 element;
    // 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 && (element.tagName.toUpperCase() == 'BODY'))) {
        valueT -= element.scrollTop  || 0;
        valueL -= element.scrollLeft || 0;
      }
    } while (element = element.parentNode);

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

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

    // 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);
      // IE throws an error if element is not in document
      try { element.offsetParent }
      catch(e) { return $(document.body) }
      var position = element.getStyle('position');
      if (position !== 'static') return proceed(element);
      element.setStyle({ position: 'relative' });
      var value = proceed(element);
      element.setStyle({ position: position });
      return value;
    }
  );

  $w('positionedOffset viewportOffset').each(function(method) {
    Element.Methods[method] = Element.Methods[method].wrap(
      function(proceed, element) {
        element = $(element);
        try { element.offsetParent }
        catch(e) { return Element._returnOffset(0,0) }
        var position = element.getStyle('position');
        if (position !== 'static') return proceed(element);
        // 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.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
    function(proceed, element) {
      try { element.offsetParent }
      catch(e) { return Element._returnOffset(0,0) }
      return proceed(element);
    }
  );

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

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

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

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

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

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

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

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

  Element._attributeTranslations.has = {};

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

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

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

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

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

    return element;
  };

  // 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.toUpperCase(), 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 = { }, B = Prototype.Browser;
    $w('width height').each(function(d) {
      var D = d.capitalize();
      if (B.WebKit && !document.evaluate) {
        // Safari <3.0 needs self.innerWidth/Height
        dimensions[d] = self['inner' + D];
      } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) {
        // Opera <9.5 needs document.body.clientWidth/Height
        dimensions[d] = document.body['client' + D]
      } else {
        dimensions[d] = document.documentElement['client' + D];
      }
    });
    return dimensions;
  },

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

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

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

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

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

  },

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

    var e = this.expression;

    // 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
    // PW - tweaked for local load
    if ((/(\\[\[\w-]*?:|:checked)/).test(e))
      return false;

    return true;
  },

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

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

    // Make sure the browser treats the selector as valid. Test on an
    // isolated element to minimize cost of this check.
    try {
      Selector._div.querySelector(this.expression);
    } catch(e) {
      return false;
    }

    return true;
  },

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

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

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

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

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

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

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

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

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

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

    switch (this.mode) {
      case 'selectorsAPI':
        // querySelectorAll queries document-wide, then filters to descendants
        // of the context element. That's not what we want.
        // Add an explicit context to the selector if necessary.
        if (root !== document) {
          var oldId = root.id, id = $(root).identify();
          e = "#" + id + " " + e;
        }

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

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

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

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

    while (e && le !== e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        p = ps[i];
        if (m = e.match(p)) {
          // 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)]",
      'checked':     "[@checked]",
      'disabled':    "[(@disabled) and (@type!='hidden')]",
      'enabled':     "[not(@disabled) and (@type!='hidden')]",
      'not': function(m) {
        var e = m[6], p = Selector.patterns,
            x = Selector.xpath, le, v;

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

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

  patterns: {
    // 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]+:)?[\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) continue;
        results.push(node);
      }
      return results;
    },

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

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

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

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

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

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

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

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

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

if (Prototype.Browser.IE) {
  Object.extend(Selector.handlers, {
    // 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 != 'file' && (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.disabled = true;
    return element;
  },

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

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

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

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

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

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

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

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

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

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

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

  optionValue: function(opt) {
    // 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) {
      event = Event.extend(event);

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

      if (currentTarget && currentTarget.tagName) {
        // Firefox screws up the "click" event when moving between radio buttons
        // via arrow keys. It also screws up the "load" and "error" events on images,
        // reporting the document as the target instead of the original image.
        if (type === 'load' || type === 'error' ||
          (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
            && currentTarget.type === 'radio'))
              node = currentTarget;
      }
      if (node.nodeType == Node.TEXT_NODE) node = node.parentNode;
      return Element.extend(node);
    },

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


  // Internet Explorer needs to remove event handlers on page unload
  // in order to avoid memory leaks.
  if (window.attachEvent) {
    window.attachEvent("onunload", destroyCache);
  }

  // Safari has a dummy event handler on page unload so that it won't
  // use its bfcache. Safari <= 3.1 has an issue with restoring the "document"
  // object when page is returned to via the back button using its bfcache.
  if (Prototype.Browser.WebKit) {
    window.addEventListener('unload', Prototype.emptyFunction, false);
  }

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

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

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

      return element;
    },

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

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

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

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

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

      destroyWrapper(id, eventName, handler);

      return element;
    },

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

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

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

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

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

Object.extend(Event, Event.Methods);

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

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

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

  var timer;

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

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

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

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

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

Hash.toQueryString = Object.toQueryString;

var Toggle = { display: Element.toggle };

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

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

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

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

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

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

// 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(); 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/Oryx/Utilities.js
AUTOCORE_SELF_CHECK.push("Oryx/Utilities.js");
//
/* ----------------------------------------------------------------

OkCupid Utilities file

	- usefulish stuff
	
This file contains general utilities used through the site

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

var Utilities = {
	
	/* Browser Functioning ------------------------------------------------------------------------ */
	
	handleEnter:function(e,f) //Needs testing
	{
		/* 
		Important: f cannot return ANYTHING.  No returns can be 
		made except the negated keyCode
		*/
		f = f || function (){};
		var Ucode=e.keyCode? e.keyCode : e.charCode;
		if (Ucode == 13){f();}
		e = (e) ? e : ((window.event) ? window.event : "");
		if (e) {return !( e.keyCode==13 || e.which==13 );}
	},
	preloadImages:function() 
	{
		var preload_arr = new Array();
		for(var i = 0; i < arguments.length; i++) {
			var preload_img = new Image();
			preload_img.src = arguments[i];
			preload_arr.push(preload_img);
		}
	},
	
	/* String functions --------------------------------------------------------------------------- */
	
	quickTrim:function(str) 
	{
        return str.replace(/(^\s+)([^\s]*)(\s+$)/, '$2');
    },
	stripBreaks:function(str) 
	{
		var res = str.replace(/\n/g," ");  	// ??
		res = res.replace(/\n/g," ");		// ??
		return res;
	},
	stripComments:function(str) 
	{
	    str = str.replace(/<!--[\w\s\/\-,]*-->/g, " ");
	    return str;
	},
    jogf:function(string,key)
    {
		// IE needs the var
        for(var item in key)
            string = string.replace("%" + item,key[item]);
        return string;
    },
    
	/* Math functions ----------------------------------------------------------------------------- */
	
	leadZero:function(n) {
		if (n < 10) n = "0" + n;
		return n;
	},
	formatBigInteger:function(integer) // Can this be simplified?
	{
	    var result = '',
	    	pattern = "###,###,###,###";

	    // cast that to a string.
	    integer = "" + integer + "";

	    integerIndex = integer.length - 1;
	    patternIndex = pattern.length - 1;

	    while ( (integerIndex >= 0) && (patternIndex >= 0) )
	    {
	        var digit = integer.charAt( integerIndex );
	        integerIndex--;

	        // Skip non-digits from the source integer (eradicate current formatting).
	        if ( (digit < '0') || (digit > '9') )  continue;

	        // Got a digit from the integer, now plug it into the pattern.
	        while ( patternIndex >= 0 )
	        {
	            var patternChar = pattern.charAt( patternIndex );
	            patternIndex--;

	            // Substitute digits for '#' chars, treat other chars literally.
	            if ( patternChar == '#' )
	            {
	                result = digit + result;
	                break;
	            }
	            else
	            {
	                result = patternChar + result;
	            }
	        }
	    }
	    return result;
	},
	
	/* Array Functions ---------------------------------------------------------------------------- */
	
	// note: currently doubled up below for backwards compatibility
	
	pushFront:function(A, x, maxsize) 
	{
	 	var i=A.length;
	 	if (maxsize && i>maxsize - 1)
	  	i=maxsize - 1;
	 	while (i>0) {
	  		A[i] = A[i-1];
	  		i--;
	 	}
	 	A[0] = x;
	},

	popMiddle:function(A,i) 
	{
	  	var x;
	  	if (i>A.length-1) return (false);
	  	for (x=i; x<A.length-1; x++) A[x]=A[x+1];
	  	A.length=A.length-1;
	  	return (true);
	},

	popFront:function(A) 
	{
	  	return (popMiddle(A,0));
	},

	moveToFront:function(A,i) 
	{
	 	var temp=A[i];
	 	while (i>0) {
	  		A[i]=A[i-1];
	  		i--;
	 	}
	 	A[0]=temp;
	},
	
	
	/* Interface ---------------------------------------------------------------------------------- */

	// Takes a form and wings it to a service.  Dependencies underway.  Localize needs a little more thought
	jackForm:function(form, url, success, dependent, localize)
	{
	    
		this.parameters = $(form).serialize();
		this.success = success;
		
		var pass = this;
				
		new Ajax.Request(url,
			{
				parameters:pass.parameters,
				method:"post",
				onSuccess:pass.success
			}
		);
	},

	setDefaultText:function(objt,text) //browse usage, may be more automated, drop dual calls, maybe action bind
	{
		var obj = objt,
			txt = text;
			
		util.doOnDomLoad(
				function() {
					if ($(obj)) {
						obj = $(obj);
						if (obj.value == '') 
							obj.value = txt;
						obj.observe("focus", function(){
							if (obj.value == txt) 
								obj.value = '';
							obj.addClassName('focus');
						});
						obj.observe("blur", function(){
							if (obj.value == '') 
								obj.value = txt;
							obj.removeClassName('focus');
						});
					}
				}
		);
	},
	
	/*
	Toggle has become fairly complex, so I'm stopping the feat creep here.  
	If you need to expand on it, or it's not doing what it should, let me
	know.  If it gets any bulkier its going to need to be broken down into
	more functions. Check the documentation.
	*/
	toggle_set:new Array(),
	triggers:new Array(),
	toggle:function(obj,bit,destiny,set,trig_obj)
	{
		var element = $(obj),
			display = element.getStyle("display"),
			trigger;
		
		/* 
		The below code is a bit ugly, but not wrong.  
		Dislike nested ifs, but that's sort of the 
		way these animated funcs work themsleves out.

		Will tackle it another time - PW 
		*/
		
		if(trig_obj) 
			trigger = $(trig_obj);
		else 
			trigger = false;
		
		if(set || set == 0) {
			if(typeof(this.toggle_set[set])=="undefined") this.toggle_set[set] = new Array();
			this.toggle_set[set][this.toggle_set[set].length] = element;
		}

		destiny = (destiny ? destiny : "block");
		if (bit) $bit.toggle(bit);
		
		if (display == "none") {
			if(set || set == 0) for(iter=0;iter<this.toggle_set[set].length;++iter) this.toggle_set[set][iter].style.display = "none";
			element.style.display = destiny;
		}
		else element.style.display = "none";
		
		if(trigger && (set || set == 0)) {
			if(typeof(this.triggers[set]) == "undefined") this.triggers[set] = new Array();
			this.triggers[set][this.triggers[set].length] = trigger;
		}
		
		if((set || set == 0) && typeof(this.triggers[set]) != "undefined") {
			for(iter=0;iter<this.triggers[set].length;++iter) this.triggers[set][iter].removeClassName("active");

			if (display == "none" && trigger != false) 
				trigger.addClassName("active");
			else if(trigger != false)
				trigger.removeClassName("active");
		}
	},

	randomQuip:function(lines,returnit)
	{
	    pickquip = Math.round(Math.random()*(lines.length-1));
	    if(!returnit)
	        document.write(lines[pickquip]);
        else
            return lines[pickquip];
	},
	

	/*
	This function needs some work.
	*/
	buddyCallWrapper:function(params,addRemove,notify,optmessage) {
		params.ajax = 1;
		new Ajax.Request("/profile", {
			parameters:params,
			onSuccess:function(response) {
				var text = "";
				switch(parseInt(response.responseText)){
					case 0:
						text = "User has been removed from your favorites.";
						if(addRemove == 1) text = (notify ? "User saved! They've been sent a message to let them know they're one of your favorites." : "User Saved!");
						break;
					case 12: text = "User saved! They've been sent a message to let them know they're one of your favorites."; break;
					default: text = "Couldn't save for some reason. Sorry. Try later."; break;
				}
				var target = (optmessage ? optmessage : 'save_buttons');
				if($(target)) $(target).innerHTML = '<a class="buddy_removed" href="#nogo">'+text+'</a>';
				if (Mailbox && Mailbox.m_current_thread) Mailbox.addSystemMessage ('buddy', text);
			}
		});
	},
	
	/* Site specific ------------------------------------------------------------------------------ */


    // 
    
    updateStats: function(name, value, type, hash, optional_params) {
         new Ajax.Request("/poststat",{
             method : "get",
             parameters : {"name" : name, "value" : value, "type" : type, "hash" : hash, "rnd" : Math.random()},
             onSuccess  : util.updateStats_cb.bindAsEventListener(util, optional_params)
             });
    },
    
    updateStats_cb : function(transport, optional_params) {
               
         var res = transport.responseText.evalJSON();
         if (res.error) {
             alert("Stat posting error" + res.error);
         }
 	 if (optional_params && optional_params["cb"]) {
	     optional_params.cb();
         }
         if (optional_params && optional_params["redirect_to"]) {
             window.location.href = optional_params["redirect_to"];
         }
    },

    
	checkNonbotAjax:function(url) // may move this to profile if it has no other use
	{
		jax = new Ajax.Request(
			url,
			{
				method: "get",
				parameters: {"nonbot": 1},
				onSuccess: function(){},
				onFailure: function(){}
			}
		);
		return true;//jax;  // why pass object?
	},

    printOptimateAd: function() {
         var optimates = ["business","green","technology","entertainment","relationships","astrology"];
         var i = Math.floor(Math.random() * optimates.length);
         var choice = optimates[i];
         $("optimate-ad").innerHTML = '<a target="new" href="/ads3?adClicked='+(i+10)+'&sourcePage=unavailable&sourceViewer=unavailable&redirect=/adcode/optimate_' + choice + '.html">Here\'s something random: <strong>'
         + choice + ' movie</strong></a>';
    },
    
	displayAdvert:function(position,keywords,isloggedin,slot_name)
	{

	    slot_name = slot_name || "Other";
	
		switch(position)
		{
			case "sky" 	: slot_addon = "Sky"; break;
			case "LB" 	: slot_addon = "Ldr"; break;
			case "rect" : slot_addon = "Box"; break;
		}
	
	    if (position != "transitional") { slot_name = slot_name + "_" +  slot_addon;}

	    if (position == "Left" || position == "sky") {
	        width = 160; height = 600;
	    } else if (position == "Top" || position == "LB") {
	        width = 728; height = 90;
	    } else if (position == "Middle" || position == "rect") {
	        width = 300; height = 250;
	    } else if (position == "Right3") {
	        width = 200; height = 500;
	    } else if (position == "Transitional" || position == "transitional") {
	        width = 500; height = 500;
		} else {
	        width = 40; height = 40;
	    }
	    
	    // Width and height added to url to pass cgi to ZEDO
	    var url = "http://ads.okcimg.com/google/ad_manager?ad_slot="+slot_name+"&keywords="+keywords+"&pass_height="+height+"&pass_width="+width;

	    document.write("<iframe id=\"ad_frame_"+position+"\"  width=" + width + " height=" + height + 
	            " marginwidth=0 marginheight=0 hspace=0 vspace=0" + 
	            " frameborder=0 scrolling=no bordercolor=\"#000000\"" +
	            " src=\"" + url + "\"></iframe>");
	},

    ads3_call:function(id, params) {
        
        if(GOOGLE_PUNCH == true) {
            return;
        }

        var get_src = window.location.toString();
        var parseit = get_src.substring(get_src.indexOf("/",7));
        if(parseit.indexOf("?") != -1) parseit = parseit.substring(0,parseit.indexOf("?"));

        params += "&sourcePage=" + parseit;
        params += "&section=" + id;



        new Ajax.Request(
            "/ads3",
            {
                method:'get',
                parameters: params,
                evalScripts: true,
                onSuccess:function(response)
                {
                    if(id=='ad_set_top_profile' && response.responseText.indexOf("good_to_show") != -1)
                    {
                        $('topset').style.background = "#CDD9E6";
                        $('top_cycle').style.display = "block";
                        $('ad_set_top_profile').innerHTML = response.responseText;
                    } else {
                        $('topset').style.display = "none";
                    }   
                }
            }
        );
     },
     
     // Moved back here because we'll have to flip for non-admin accounts
     flipDesign:function() {
         var c = getCookie("redesign");
         if (c && c == "1") {
	     util.updateStats("ui - flip from redesign", 1, "counter", "usxK7602Ep+KxarPcj+Cs4vm3YA=");
                setCookie("redesign","0",secondsFromNow(86400*30));
         }
         else {
	     util.updateStats("ui - flip to redesign", 1, "counter", "N+mQqECk84TokAOpPNLkOJ0glcs=");
                setCookie("redesign","1",secondsFromNow(86400*30));
         }
	 setTimeout("document.location.reload();",500);
     },

	
	/* Fixes the left_bar/main_content height problem ------------------------ */

	adjustMCHeight:function() {
		// none of this matters if the left_bar isn't present
		//
		if ($('left_bar')) {
			var lb_height = $('left_bar').getHeight() + 10; // 10px for margin
			var mc_height = $('main_content').getHeight();
		
			if (!$('hold_mc_height')) {
				// create the hidden input to hold previous size
				var hold_mc_height = '<p class="hidden"><input type="hidden" name="hold_mc_height" value="'+ mc_height +'" id="hold_mc_height" /><\/p>';
			
				// insert it into the bottom of the footer
				$('footer').insert(hold_mc_height, { position: "bottom" });
			}
			else if (lb_height < $F('hold_mc_height')) {					
			  if(BrowserDetect.browser =="Explorer" && BrowserDetect.version == 6)
				    $('main_content').style.height = $F('hold_mc_height') + "px";
				else 
				    $('main_content').style.minHeight = $F('hold_mc_height') + "px";
			}
		
			if (lb_height > mc_height) {
			    if(BrowserDetect.browser =="Explorer" && BrowserDetect.version == 6)
				    $('main_content').style.height = lb_height + "px";
				else 
				    $('main_content').style.minHeight = lb_height + "px";
				
			}
		}
	},

	update_buzz_topic:function(topic)
	{
		$('set_topic_btn').style.display = "none";
		$('set_topic_spinner').style.display = "inline";
		
		$('lb_set_chat_input').style.background = "#1E3F66";
		$('lb_set_chat_input').style.color = "#325279";
		
		new Ajax.Request(
			"/instantevents",
			{
				method:"get",
				parameters:{
					buzz_topic:topic,
					"im.add_topic_ajax":1
				},
				onSuccess:function(response)
				{
					$('lb_set_chat_input').disabled = true;
					$('set_topic_success').style.display = "inline";
					$('set_topic_spinner').style.display = "none";
					
					if($("topic_name"))
						$("topic_name").value = $('lb_set_chat_input').value;
				}
			}
		);
	},
    
	/* IE fixes ----------------------------------------------------------------------------------- */

	// because IE seems to only like document.getElementsByClassName() and
	// not element.getElementsByClassName()
	//
	getElementsByClassName:function(parent, class_name)
	{
		var o = parent.getElementsByTagName('*'); // get all elements under parent
		var a = new Array();
	
		for (var i = 0; i < o.length; i++) {
			// does the element have the class attribute and does class_name exist 
			if (o[i].attributes["class"] && o[i].attributes["class"].value.toString().indexOf(class_name) != -1) {
				a.push(o[i]); // push elements onto array
			}
		}
	
		return a;
	},
	
	clearRecentlyViewed : function() {
		new Ajax.Request("/sookie/clear_recently_viewed.html?" + Math.random());	
		if($("lsRecent")) $("lsRecent").hide();
		else if($("recently_viewed")) $("recently_viewed").hide();
		if($("lsRecentContent")) $("lsRecentContent").hide();	
	},
	
	toggleSavedProfiles:function(id)
	{
		var saved_display = NanoCookie.get('saved_display');
		
		// toggle the section
		this.toggle(id, 32);
		
		// set the cookie for 2 hours if it isn't set
		if (!saved_display) NanoCookie.set('saved_display', '1', {ms: 7200*1000});
	},
    
    // 
    uidBit: function(uid_str, which_bit) {
        if (which_bit < 1 || which_bit > 8) alert ("Whoa; UID bit function works 1..8");
        var tail = uid_str.substr(uid_str.length - 9);
        while(tail.charAt(0) == "0") {
            tail = tail.substr(1);
        }
        var num = parseInt(tail);
        if (num & Math.pow(2,(which_bit-1))) {
            return 1;
        }
        else {
            return 0;
        }
    },

    // simple profiling

	startTimer: function(label) {
		if (! this.timerStats) {
			this.timerStats = {};
		}
		if (! this.timerStats[label]) {
			this.timerStats[label] = {"starts" : [], "stops" : []};
		}
		this.timerStats[label].starts.push(new Date());		
	},

	stopTimer : function(label) {
		if (! this.timerStats 
			|| ! this.timerStats[label] 
			|| this.timerStats[label].stops.length >= this.timerStats[label].starts.length) {
			alert("prematurely called stopTimer. Make sure you call startTimer first for label " + label);
		}
		else {
			this.timerStats[label].stops.push(new Date());
		}
	},
	
	summarizeTimers : function() {
		var res = "";
		for (var key in this.timerStats) {
			var label_time = 0;
			var samples = this.timerStats[key].stops.length;
			for (var i = 0; i < samples; i++) {
				label_time += this.timerStats[key].stops[i].getTime() - this.timerStats[key].starts[i].getTime();		
			}
			res+="<br /> " + key + ": " + (label_time / samples) + "ms avg. -- " + samples + " sample(s) -- " + label_time + "ms total";
		}
		return res;
	},
	
	doOnDomLoad : function(func) {
		if (! this.m_dom_load_funcs) {
			this.m_dom_load_funcs = [];	
		}
		this.m_dom_load_funcs.push(func);
	},
	
	executeDomLoad : function() {
		if (this.m_dom_load_funcs) {
			for (var i = 0; i < this.m_dom_load_funcs.length; i++) {
				setTimeout(this.m_dom_load_funcs[i], 500);
			}
		}		
     },


	fillInTheP:function() {
		var temp=window.location.href;

		if (temp.indexOf("login?p=") != -1) {
			$('page_url_p').value = temp.substring(temp.indexOf("login?p=")+8);
		}
		else if (temp.indexOf("signup") != -1 && $('page_url_p').value == "") {
			$('page_url_p').value ="/home";
		}
		else if (temp.indexOf("login") == -1 && temp.indexOf(".com") != -1) {
			temp=temp.substring(temp.indexOf(".com"));
			if (temp.indexOf("/") != -1) {
				temp=temp.substring(temp.indexOf("/"));
			}
			if (temp.length > 3) {
				$('page_url_p').value = temp;
			}
		}
	},
	
	hideBottomBar:function() {
		if ($('footer_signup_wrapper'))
			$('footer_signup_wrapper').hide();
	},
	
	toggleClass:function(b, c) {
		// get class the old school way because Prototype's hasClass() doesn't work well in IE
		var hasClass = b.attributes['class'].value.search(c);
		
		// using this method because add/removeClass() doesn't work well in IEs either
		if (hasClass != -1)
			b.className = b.className.replace(new RegExp(' '+c+'\\b'), '');
		else
			b.className += ' '+c;
	},
	// 
	constrainImageWidthAfterLoad : function(im, maxwidth) {
		if (im.width > maxwidth) {
			im.style.width = "";
			im.style.height = "";
			im.height = Math.round(maxwidth * im.height / im.width);
			im.width = maxwidth;
		}
	},

	// 

	floatADivOnDomLoad : function(id, parentid) {
		util.doOnDomLoad( function() {
			if ($(id)) document.observe('scroll', util.floatADivOnScrollHandler.bind(this,id,parentid));
			// this is for the IEs                                                                                   
			if (window.attachEvent && $(id)) window.attachEvent("onscroll", util.floatADivOnScrollHandler.bind(this,id, parentid));	
		});
	},
	
	// 
	floatADivOnScrollHandler : function(id, parentid) {
		var b = $(id);
		var par = $(parentid);
		var coff = par.cumulativeOffset()[1];
		var csoff = par.cumulativeScrollOffset()[1];
		if (coff < csoff) 
			b.style.position = "fixed";
		else 
			b.style.position = "static";
	},
	// 
	toMask : function() {
	  	var res = 0;
	
	  	if(typeof arguments[0] == "object")
			bits = arguments[0];
		else 
			bits = arguments;
			
	  	for (var i = 0; i < bits.length;i++) {
		 	if (bits[i] > 31 || bits[i] < 0)
				throw("fillBitsInMask expects ints in [0..30] due to JS bitwise limitations");
			res = res | Math.pow(2,bits[i]);
	  	}
	  	return res;
	},
	// 
	fromMaskToList : function(mask) {
		var res = [];
		for (var i = 0; i < 31; i++)
			if (Math.pow(2,i) & mask)
				res.push(i);
		return res;
	},
	// 
	isBitSetInMask : function(mask, bit) {
		return Math.pow(2, bit) & mask ? 1 : 0;
	},
	
	/* Mobile toggle */
	
	flipMobile:function(mobile) {
		if(mobile) {
			target = 0;
			cgi = "enable_mobile=";
		} else {
			target = 1;
			cgi = "disable_mobile=";
		}
		
		url = window.location.toString().replace(/[&?](en|dis)able_mobile=1/g,"");

		gluon = (url.indexOf("?") != -1 ? "&" : "?");
		url = url + gluon + cgi + "1";
		
		new Ajax.Request(
			"/settings", 
			{
				method: 'get', 
				parameters: {
					update_ui_prefs:1,
					bit:62,
					val:target
				},
				onSuccess:function() { window.location = url; }
			}
		);
	}
};
/*  */


/* Extensions to the Element class ------------------------------------------ */

// Use Object.extend instead of Element.prototype= because IE6 doesn't support that.  Thanks Prototype!
Object.extend(Element.Methods, {
	
	// Finds inputs with the given class name and returns the values of the ones that are selected.
	getValues: function (element, class_name) {
		return element.select('input.' + class_name).collect(function(input) {return input.checked ? input.value : 0}).without(0).reduce() || null;
	},
	
	// Pulses an element if it's visible; otherwise, makes it visible.
	showOrPulse: function (element) {
		element.visible() ? element.pulsate ({pulses: 2, duration: 0.6}) : element.show ();
	},
	
	// Remove the class names from elements in the array passed in.
	removeClassNames: function (element, class_names) {
		for (var i = 0; i < class_names.length; i++) {
			element.removeClassName (class_names[i]);
		}
		return element;
	}
});

// Add the methods to the element class
Element.addMethods ();

/* /extensions to the Element class ----------------------------------------- */


/* Further work and future integration */

function updateFavCount() {
	var num_favs_online = document.getElementById('favs_online').getElementsByTagName('li').length - 1;
	var favs_count_span = document.getElementById('favs_count');
	
	if (num_favs_online > 0) {
		favs_count_span.getElementsByTagName('strong')[0].innerHTML = num_favs_online;
		favs_count_span.style.display = "";
	}
}

function checkForm(tab){ // check if user has made a from entry

    if((FormId == "profileeditform") && (tab == "profileedit" ||  tab == "details")) {

        tab = (tab == "details" ? tab : "essays");

        $("tab-" + OldId).removeClassName("tab-on");
        $("tab-" + tab).addClassName("tab-on");
        displayDiv(tab);
    } else {
        if(FormId != 'picturerows' && SerializedForm != Form.serialize(FormId)) {

            $(FormId).action += "?tab=" + tab; // redirect to tabbed page after form submission

            if(FormId == "settingsform"){ // check if user entered password
    	        if(!document.getElementById("settingsSubmit").disabled) {
        		    $(FormId).submit(); // submit changes
        	    } else {
        		    alert("Please enter your password to save your settings.");
        		    document.getElementById("oldPassword").focus();
        	    }
            } else {
                $(FormId).submit(); // submit changes
            }

        } else { // send user to tabbed page

            if(tab == "details"){ // "details" and "essays" tab share the same page
                page = "profileedit";
            } else {
                page = tab;
            }
            document.location.href = "/" + page + "?tab=" + tab;
        }
    }

}

var util = Utilities;

// these functions probably have prototype equivalents; deprecate or move to utility array funcs
// Note: these do NOT have prototype equivalents; assess later, fine for now

/* standard?  how far back does this go? */

if(typeof Array.prototype.push=='undefined')
  Array.prototype.push=function(){
    var i=0;
    b=this.length;a=arguments;
    for(i;i<a.length;i++)this[b+i]=a[i];
    return this.length;
  };



function pushFront(A, x, maxsize) {
 var i=A.length;
 if (maxsize && i>maxsize - 1)
  i=maxsize - 1;
 while (i>0) {
  A[i] = A[i-1];
  i--;
 }
 A[0] = x;
}

function popMiddle(A,i) {
  var x;
  if (i>A.length-1)
    return (false);
  for (x=i; x<A.length-1; x++)
     A[x]=A[x+1];
  A.length=A.length-1;
  return (true);
}

function popFront(A) {
  return (popMiddle(A,0));
}

function moveToFront(A,i) {
 var temp=A[i];
 while (i>0) {
  A[i]=A[i-1];
  i--;
 }
 A[0]=temp;
}

// 

function $RF(el, radioGroup) {
    if($(el).type && $(el).type.toLowerCase() == 'radio') {
        var radioGroup = $(el).name;
        var el = $(el).form;
    } else if ($(el).tagName.toLowerCase() != 'form') {
        return false;
    }

    var checked = $(el).getInputs('radio', radioGroup).find(
        function(re) {return re.checked;}
    );
    return (checked) ? $F(checked) : null;
}


function favorites_toggle() {
    var toggle_text = $("favs_toggle");
    
    if($bit.get(33) == 1)
        toggle_text.innerHTML = "Show offline users";
    else
        toggle_text.innerHTML = "Hide offline users";
        
    util.toggle('favs_offline', 33);
}

function topics_toggle() {
    var topic_toggle = $("topic_toggle");
    
    if($bit.get(36) == 0)
        topic_toggle.innerHTML = "Show Topics";
    else
        topic_toggle.innerHTML = "Hide Topics";
        
	util.toggle('lb_recent_topics',36);
}





/* On DOM/window load events */
util.doOnDomLoad(
			function()
			{
				if($("ads3_set_leftbar")) Utilities.ads3_call("ads3_set_leftbar","showAd=1&adEvent=4&adEventAdId=1");
			
					// fill in the hidden p on logged out pages only
					if ($('header_login')) util.fillInTheP();
			
					// show the save profiles menu in the left bar for two ours (based on the cookie)
					if ($('lb_favorites_menu') && !NanoCookie.get('saved_display')) $('lb_favorites_menu').style.display = "";
					
					// adjust the page's height on load
					// you can move this if it makes you unhappy here [AS]
					Utilities.adjustMCHeight();
			}
);

Utilities.setDefaultText("mainMenuSearchBox", ""); // old search box
Utilities.setDefaultText("site_search_query", ""); // new search box

var GOOGLE_PUNCH = false;
 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/prototype_abort.js
AUTOCORE_SELF_CHECK.push("prototype_abort.js");
if (typeof(Ajax) != "undefined") {
   Ajax.Request.prototype.abort = function() {
    var abort_type = typeof this.transport.abort;
    if (abort_type == "function") {
        this.transport.onreadystatechange = Prototype.emptyFunction;
        this.transport.abort();
        Ajax.activeRequestCount--;
    }
   };
					       } 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/scriptaculous-1.8.1-for-autocore/scriptaculous.js
AUTOCORE_SELF_CHECK.push("scriptaculous-1.8.1-for-autocore/scriptaculous.js");

var Scriptaculous = {
  Version: '1.8.1',
  require: function(libraryName) {
    // inserting via DOM fails in Safari 2.0, so brute force approach
    document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
  },
  REQUIRED_PROTOTYPE: '1.6.0',
  load: function() {
    function convertVersionString(versionString){
      var r = versionString.split('.');
      return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
    }
 
//    if((typeof Prototype=='undefined') || 
//       (typeof Element == 'undefined') || 
//       (typeof Element.Methods=='undefined') ||
//       (convertVersionString(Prototype.Version) < 
//        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
//       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
//        Scriptaculous.REQUIRED_PROTOTYPE);
    
//    $A(document.getElementsByTagName("script")).findAll( function(s) {
//      return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
//    }).each( function(s) {
//      var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
//      var includes = s.src.match(/\?.*load=([a-z,]*)/);
//      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
//       function(include) { Scriptaculous.require(path+include+'.js') });
//    });
  }
}

Scriptaculous.load(); 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/scriptaculous-1.8.1-for-autocore/effects.js
AUTOCORE_SELF_CHECK.push("scriptaculous-1.8.1-for-autocore/effects.js");
// script.aculo.us effects.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008

// Copyright (c) 2005-2007 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) + 0.5;
    },
    reverse: function(pos) {
      return 1-pos;
    },
    flicker: function(pos) {
      var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
      return pos > 1 ? 1 : pos;
    },
    wobble: function(pos) {
      return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
    },
    pulse: function(pos, pulses) { 
      pulses = pulses || 5; 
      return (
        ((pos % (1/pulses)) * pulses).round() == 0 ? 
              ((pos * pulses * 2) - (pos * pulses * 2).floor()) : 
          1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
        );
    },
    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;
    
    eval('this.render = function(pos){ '+
      'if (this.state=="idle"){this.state="running";'+
      codeForEvent(this.options,'beforeSetup')+
      (this.setup ? 'this.setup();':'')+ 
      codeForEvent(this.options,'afterSetup')+
      '};if (this.state=="running"){'+
      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
      'this.position=pos;'+
      codeForEvent(this.options,'beforeUpdate')+
      (this.update ? 'this.update(pos);':'')+
      codeForEvent(this.options,'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(),
    max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();  

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

  return new Effect.Tween(null,
    scrollOffsets.top,
    elementOffsets[1] > max ? max : 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] || { };
  var oldOpacity = element.getInlineOpacity();
  var transition = options.transition || Effect.Transitions.sinoidal;
  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
  reverser.bind(transition);
  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);
 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/OkLocation.js
AUTOCORE_SELF_CHECK.push("OkLocation.js");
//

//
// 
//

if (typeof Prototype =='undefined'  || Prototype.Version < "1.6" ) {
	throw("OkLocation requires Prototype 1.6 or higher.");
}



var OkLocation = Class.create({

	initialize: function(params) {
		          
		this.AjaxPath = "/locquery";
        this.Status = "pending";
		this.FormType = "text";
		this.TempId = "loc_" + Math.round(Math.random() * 1000000000);
        this.Params = params;
		this.LocId = "";
		this.QueryString = "";
		this.PossibleMatches = new Array();		
		this.AjaxResult = new Object;
        
        this.startExperience();

	},
	                
	getStatus: function() {
		return this.Status;
	},
	
	submitFormIfLocationReady: function(form_id, failure_cb, loc_mandatory) {
		if (this.Status == "success" || (!loc_mandatory && $F('text_' + this.TempId) == "")) {
			$(form_id).submit();
		}
		else if (loc_mandatory && $F('text_' + this.TempId) == "") {
			alert('You must enter a location!');
		}
		else {
			failure_cb(this);
		}
	},
	
	startExperience: function() {

		if (this.Params.previous_loc_id && this.Params.previous_loc_id != 0 && this.Params.previous_loc_id != "") {
			this.Status = "success";
			this.LocId = this.Params.previous_loc_id;
		}
		if (this.Params.previous_query_string) {
			this.QueryString = this.Params.previous_query_string;
		}
		
		this.drawInputs();
	},
	
	drawInputs: function() {
		var res = "";

		if (this.FormType == "select") {
			res += '<select id="sel_' + this.TempId + '">';
			res += '<option value=""> - choose one - </option>';
			res += '<option value="reset"> - start over - </option>';
			for (var i = 0; i < this.PossibleMatches.length; i++) {
				res += '<option value="' + this.PossibleMatches[i].locid + '">' + this.PossibleMatches[i].text + '</option>';
			}
			res += '</select>';	
			this.Status = "pulldown";
		}
		else {
            if(this.Params.replaceEnter == true)
                res += '<input class="location_input" onkeydown="if(event.keyCode==13 || event.which==13) {'+this.Params.newEnter+';return false;}" type="text" id="text_' + this.TempId + '" name="text_' + this.TempId + '" value="' + this.QueryString + '"/>';
            else
			    res += '<input class="location_input" type="text" id="text_' + this.TempId + '" name="text_' + this.TempId + '" value="' + this.QueryString + '"/>';
		}

		var input_type = "hidden";
		var id_label = "";
		var query_label = "";
		if (this.Params.debug) {
			input_type="text";
			id_label = "LocID hidden input ('" + this.Params.loc_id_input_name + "'): ";
			query_label = "Query hidden input ('" + this.Params.query_input_name + "'): ";
		}
		res += id_label + '<input type="' + input_type + '" name="' + this.Params.loc_id_input_name 
				+ '" id="' + this.Params.loc_id_input_name 
				+ '" name="' + this.Params.loc_id_input_name 
				+ '" value="' + this.LocId + '" />';
		res += query_label + '<input type="' + input_type + '" name="' + this.Params.query_input_name
				+ '" id="' + this.Params.query_input_name 
				+ '" name="' + this.Params.query_input_name 
				+ '" value="' + this.QueryString + '" />';
				
		$(this.Params.dest_div).innerHTML = res;
		if (this.FormType == "select") {
			Event.observe('sel_' + this.TempId, 'change', this.selectChange.bindAsEventListener(this));
		}
		else {
			Event.observe('text_' + this.TempId, 'keyup', this.textChange.bindAsEventListener(this));
			Event.observe('text_' + this.TempId, 'blur', this.performLookup.bindAsEventListener(this));
		}
	},

	textChange: function() {
		if ($F(this.Params.query_input_name) != $F("text_" + this.TempId)) {
			this.Status = "pending";
			this.LocId = "";
			$(this.Params.loc_id_input_name).value = "";			
			if (this.Params.cb_lost_success) {
	            this.Params.cb_lost_success(this);			
			}				
			$(this.Params.query_input_name).value = $F("text_" + this.TempId);
		}
	},

	selectChange: function() {
		var id = $F("sel_" + this.TempId);

		if (id == "reset" || id == "") {
			if($("find_btn")) $("find_btn").style.display = "inline";
			this.Status = "pulldown";
			this.LocId = "";
			this.FormType="text";
			$(this.Params.loc_id_input_name).value = "";
			$(this.Params.query_input_name).value = "";
			this.QueryString = "";
			this.PossibleMatches = new Array();		
			this.AjaxResult = new Object;
			this.drawInputs();
			if (this.Params.cb_reset) {
	            this.Params.cb_reset(this);			
			}
		}
		else {
			this.Status = "success";
			this.LocId = id;
			$(this.Params.loc_id_input_name).value = this.LocId;
			$(this.Params.query_input_name).value = this.AjaxResult.query;
			if (this.Params.cb_success) {			
	            this.Params.cb_success(this);			
			}
		}
		$(this.Params.loc_id_input_name).value = this.LocId;		
	},
	
	performLookup: function() {
		var req = new Ajax.Request(this.AjaxPath, {
			method: "get",
			parameters: {func : "query", query : $F("text_" + this.TempId), cbust : Math.round(Math.random() * 1000000000)},
			onSuccess: this.performLookup_cb.bindAsEventListener(this),
			onFailure: function() { alert("Location lookup failed!"); }
		});		
	},

	performLookup_cb: function(data) {
		this.AjaxResult = data.responseText.evalJSON();
		// Make sure the request is what the user still has in the text box to avoid race condition
		if ($("text_" + this.TempId) && $F("text_" + this.TempId) == this.AjaxResult.query) {
			if (this.AjaxResult.locid && this.AjaxResult.locid != 0 && this.AjaxResult.locid != "") {
				this.handleSuccess();
			}
			else {
				this.handleError();
			}
		}
	},
	
	handleError: function() {
		this.LocId = "";
		if (this.AjaxResult.results.length > 0) {
			this.FormType = "select";
			this.PossibleMatches = this.AjaxResult.results;
			this.drawInputs();
		}
		if (this.Params.cb_error) {
			this.Params.cb_error(this);
		}
	},
	
	handleSuccess: function() {
		this.Status = "success";
		this.LocId = this.AjaxResult.locid;
		$(this.Params.loc_id_input_name).value = this.LocId;
		if (this.Params.cb_success) {			
            this.Params.cb_success(this);			
		}
	}


});
 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/OkLoginJoin.js
AUTOCORE_SELF_CHECK.push("OkLoginJoin.js");

// 

function killEnterAll(e) {
	var Ucode=e.keyCode? e.keyCode : e.charCode;
	if (Ucode == 13){}
	e = (e) ? e : ((window.event) ? window.event : "");
	if (e) {return !( e.keyCode==13 || e.which==13 );}	
}

var OKLJ = Class.create({

	inititalize : function() {
		
	},
	
	
	//
	//   optional inside params object:
	//   --------------------------------
	//   success_cb : a function to call after a successful login
	//   success_redirect : where to redirect browser after successful login
	//   failure_cb : a function to call after a failed login
	//   failure_redirect : where to redirect browser after failed login

	login : function(screenname, password, params) {
		var ajax_params = { "ajax" : 1, "username" : screenname, "password" : password, enable: params.enable ? 1 : 0};
		var req = new Ajax.Request("/login", {
			method: 'post', 
			parameters: ajax_params ,
			onSuccess: this.login_cb.bindAsEventListener(this, params), 
			onFailure: this.login_cb_failed.bindAsEventListener(this, params)});
		return req;
		
	},


	// This used to be fairly simple, but we also may get a paramter back from the login
	// service telling us we need to visit an authlink, in a hidden iframe. Can't use
	// AJAX for domain security reasons.  So we need to visit that before calling it a day. So this function
	// isn't the last one; it'll invoke login_cb_post_authlink when it's done

	login_cb : function(data, params) {
		var response = data.responseText.evalJSON();
		this.tempHoldings = {'data' : data, 'params' : params };
		if (response.hq_okc_authlink && response.hq_okc_authlink != "") {
			Element.insert(document.body, {  bottom: '<iframe style="display:none;" onload="OkLoginJoin.login_cb_post_authlink()" src="' + response.hq_okc_authlink + '" id="auth-ajax-login"></iframe>'  }  );			
			// doubly-set src for old IE bug where iframes added with src didn't work
			$('auth-ajax-login').src = response.hq_okc_authlink;
            setTimeout("OkLoginJoin.login_cb_post_authlink()", 1000);
		}
		else {
			this.login_cb_post_authlink();
		}		
	},
	
	login_cb_post_authlink : function() {
		
		var data = this.tempHoldings.data;
		var params = this.tempHoldings.params;
		var response = data.responseText.evalJSON();
		if (response.status == "success") {
			if (params.success_cb) {
				params.success_cb(response);
			}
			if (params.success_redirect) {
				document.location.href = params.success_redirect;
			}
		}
		else {
			if (params.failure_cb) {
				params.failure_cb(response);
			}
			if (params.failure_redirect) {
				document.location.href = params.failure_redirect;
			}			
		}
		
	},
	
	login_cb_failed : function(data, params) {
		alert("AJAX login Failed!");		
	},
	
	//
	//   optional inside params object:
	//   --------------------------------
	//   success_cb : a function to call after a successful logout
	//   success_redirect : where to redirect browser after successful logout

	logout : function(params) {
		var ajax_params = { "ajax" : 1 };
		var req = new Ajax.Request("/logout", {
			method: 'post',
			parameters: ajax_params ,
			onSuccess: this.logout_cb.bindAsEventListener(this, params), 
			onFailure: this.logout_cb_failed.bindAsEventListener(this, params)});
		return req;
	},

	logout_cb : function(data, params) {
		var response = data.responseText.evalJSON();
		if (response.status == "success") {
			if (params.success_cb) {
				params.success_cb(response);
			}
			if (params.success_redirect) {
				document.location.href = params.success_redirect;
			}
			Element.insert(document.body,
					{  bottom: '<iframe style="display:none;" src="http://www.okcupid.com/logout" id="auth-ajax-okc-logout"></iframe>'  }  );
		}
		else {
			alert("Logout somehow failed");
		}
	},
	
	logout_cb_failed : function(data, params) {
		alert("AJAX logout Failed!");		
	},
	

	//
	//   optional inside params object:
	//   --------------------------------
	//   success_cb : a function to call after a successful request (user exists, mail sent)
	//   success_redirect : where to redirect browser after successful request (user exists, mail sent)
	//   failure_cb : a function to call after a failed request
	//   failure_redirect : where to redirect browser after failed request

	lostPassword : function(screenname_or_email, params) {
		var ajax_params = { "ajax" : 1, "email" : screenname_or_email};
		var req = new Ajax.Request("/lostpassword", {
			method: 'post', 
			parameters: ajax_params ,
			onSuccess: this.lostPassword_cb.bindAsEventListener(this, params), 
			onFailure: this.lostPassword_cb_failed.bindAsEventListener(this, params)});
		return req;		
	},
	
	lostPassword_cb : function(data, params) {
		var response = data.responseText.evalJSON();
		if (response.status == "SUCCESS") {
			if (params.success_cb) {
				params.success_cb(response);
			}
			if (params.success_redirect) {
				document.location.href = params.success_redirect;
			}
		}
		else {
			if (params.failure_cb) {
				params.failure_cb(response);
			}
			if (params.failure_redirect) {
				document.location.href = params.failure_redirect;
			}			
		}
	},
	
	lostPassword_cb_failed : function(data, params) {
		alert("AJAX lostPassword Failed!");		
	},



	//
	// for signup, params state maintained in member, since we can't pass through cgi safely;
	// with other functions we use bindAsEventListener to pass to callback
	//
	optionalParams : { },

	//
	//   dest_div: where on the page they'll be doing the signup.
	//   signup_path : required, e.g. "/signup/paths/inga/1.html" -- path to first file in process
    //   cgi_extras : extra cgi parameters to pass to the signup service. ("cf", "gender", etc.)
	//     
	//   optional inside params object:
	//   --------------------------------
	//   success_cb : a function to call after a successful request (user exists, mail sent)
	//   success_redirect : where to redirect browser after successful request (user exists, mail sent)
	
	join : function(dest_div, signup_path, cgi_extras, params) {

			this.optionalJoinParams = params;
		
			var default_params = { "ajax" : 1, "reqs" : 0, "cbust" : Math.random() };
			var ajax_params = this.combineTwoObjs(default_params, cgi_extras);
			ajax_params["next_page"] = signup_path;
			var req = new Ajax.Updater(dest_div, "/signup", {
				method: 'post', 
				evalScripts: true,
				parameters: ajax_params });
	},

	//
	// Called by the HTML inserted via signup
	//
	// This used to be fairly simple, but we also may get a paramter back from join
	// service telling us we need to visit an authlink, in a hidden iframe. Can't use
	// AJAX for domain security reasons.  So we need to visit that before calling it a day. So this function
	// isn't the last one; it'll invoke login_cb_post_authlink when it's done

	join_cb : function (data) {
		this.tempHoldings = {'data' : data };
		if (data.hq_okc_authlink && data.hq_okc_authlink != "") {
			Element.insert(document.body, {  bottom: '<iframe style="display:none;" onload="OkLoginJoin.join_cb_post_authlink()" src="' + data.hq_okc_authlink + '" id="auth-ajax-join"></iframe>'  }  );
            setTimeout("OkLoginJoin.join_cb_post_authlink()", 1000);
		}
		else {
			this.join_cb_post_authlink()
		}
	},
	
	join_cb_post_authlink : function() {
		var data = this.tempHoldings.data;
		
		if (this.optionalJoinParams.success_cb) {
			this.optionalJoinParams.success_cb(data);
		}
		if (this.optionalJoinParams.success_redirect) {
			document.location.href = this.optionalJoinParams.success_redirect;
		}		
	},
	
	//
	// helpers
	//
	
	combineTwoObjs : function(o1, o2) {
		
		var res = new Object();
		for (var key in o1) {
			res[key] = o1[key];
		}
		for (var key in o2) {
			res[key] = o2[key];
		}
		return res;			
	}
	
});
//----------------------------------------------------------------------------------------------------------------

 	var OkLoginJoin = new OKLJ(); 

//----------------------------------------------------------------------------------------------------------------

	
 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/Oryx/Sookie.js
AUTOCORE_SELF_CHECK.push("Oryx/Sookie.js");
/* 
 * 	Sookubit.js
 *  
 *  Manages the SOOKIE.sookubit object
 */

var Sookie = {
	
	url:'/sookie/remote_sookie.html',
	value:0,
	sookie:{},
	
	init:function(sookie)
	{
		if(sookie)
			this.sookie = sookie;
	},
	
	load:function()
	{
		var pass = this;
		new Ajax.Request(
			pass.url,
			{
				method:'get',
				parameters:{
					getall:1
				},
				onSuccess:function(response) {
					pass.sookie = response.responseText.evalJSON();
				}
			}
		);
	},
	
	get:function(n,o)
	{
		if(typeof this.sookie[n] == "undefined")
			return false;
		
		if(o)
			return this.sookie[n];
		else
			return this.sookie[n].value;
	},

	set:function(v,n,e)
	{
		var pass = this;
		
		params = {
			name:n,
			value:v
		}
		
		if(e) 
			params.expire = e;
		
		new Ajax.Request(
			pass.url,
			{
				method:'get',
				parameters:params,
				onSuccess:function() {
					pass.sookie[n] = v;
				}
			}
		);
	}
}

$sake = Sookie; 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/Oryx/Cookies.js
AUTOCORE_SELF_CHECK.push("Oryx/Cookies.js");
// 

function setOkCookie(name,value,expires) {
    setCookie(name,value,expires);
}

function deleteOkCookie(name) {
    deleteCookie(name);
}

// -----------------------------------------------------------------------------------

function getPageTopDomain() {
 var host = window.location.host;
 var parts = host.split('.');
 var res = parts[parts.length-2] + "." + parts[parts.length-1];
 if (res.indexOf(':') != -1) {
	 res = res.substr(0,res.indexOf(':'));
 }
 return res;
}

function secondsFromNow (sec) {
   res = new Date();
   res.setTime(new Date().getTime() + sec * 1000);
   return res;
}

// -----------------------------------------------------------------------------------

function setCookie(name,value,expires) {
  var curCookie = name + "=" + escape(value) +
      ((expires) ? "; expires=" + expires.toGMTString() : "") +
      "; path=/"+
      "; domain=" + getPageTopDomain();
  document.cookie = curCookie;
}
function deleteCookie(name) {
  if (getCookie(name)) {
    document.cookie = name + "=" +
    "; domain=" + getPageTopDomain() + 
    "; path=/"+
    "; expires=Thu, 01-Jan-70 00:00:01 GMT";
  }
}
function getCookie(name) {
	var dc = document.cookie;
  	var prefix = name + "=";
  	var begin = dc.indexOf("; " + prefix);
  	if (begin == -1) {
    	begin = dc.indexOf(prefix);
    	if (begin != 0) return null;
  	} 
	else
    	begin += 2;
  	var end = document.cookie.indexOf(";", begin);
  	if (end == -1)
    	end = dc.length;
  return unescape(dc.substring(begin + prefix.length, end));
}

 /* 
 */

function addNewCoresToCookie(file_nums, hash) {
	 var old_cookie = getCookie("core");
	 var old_cookie_parts = [];
	 var new_cookie = old_cookie;
	 if (old_cookie)
		old_cookie_parts = old_cookie.split(":");
	 if (old_cookie_parts.length == 0 || old_cookie_parts[0] != hash)
		old_cookie
	 if (! old_cookie || old_cookie.split(":")[0] != hash)
		new_cookie = hash;
	 var new_addition = file_nums.join(",");
	 for (var i = 1; i < old_cookie_parts.length && old_cookie_parts[0] == hash; i++) {
		 if (old_cookie_parts[i] == new_addition) {
			 return;
		 }
	 }
	 new_cookie += ":" + new_addition;
	 setCookie("core", new_cookie, secondsFromNow(86400 * 30));
}

// Backwards compatibility, but limit miniCookies to 3600 seconds.
 
function setMiniCookie(name,value) {
	NanoCookie.set(name,value,{ms:3600*1000});
}

function deleteMiniCookie(name) {
	NanoCookie.remove(name);
}

function getMiniCookie(name) {
	return NanoCookie.get(name);
}

// 
NanoCookie = {
	
	// 
	set : function(key,value,expires,test) {
		var cookies = this.deserialize();
		var d = new Date();
		var obj = new Object();
		obj["k"] = key;
		obj["v"] = value;
		if (expires.ms)
			obj["e"] = d.getTime() + parseInt(expires.ms);
		else if (expires.gmt)
			obj["e"] = expires.gmt;
		else
			obj["e"] = expires.date.getTime();
		if (isNaN(obj["e"]))
			obj["e"] = 0;
		var found = false;

		for (var i = 0; i < cookies.length; i++) {
			if (cookies[i]["k"] == key) {
				cookies[i] = obj;
				found = true;
			}
		}
		if (! found)
			cookies.push(obj);
		this.serializeAndStore(cookies);
	},
	get : function(key) {
		var cookies = this.deserialize();
		for (var i = 0; i < cookies.length; i++) {
			if (cookies[i]["k"] == key) {
				return cookies[i]["v"];
			}
		}
		return null;
	},	
	// 
	getAll : function(nano_cookie_str) {
		var cookies = this.deserialize(nano_cookie_str);
		var res = [];
		for (var i = 0; i < cookies.length; i++) {
			var obj = new Object();
			obj["key"] = cookies[i]["k"];
			obj["value"] = cookies[i]["v"];
			obj["expires"] = new Date(cookies[i]["e"]);
			res.push(obj);
		}
		return res;		
	},
	// 
	findRegExp : function(regexp) {
		var cookies = this.deserialize();
		var res = [];
		for (var i = 0; i < cookies.length; i++) {
			if (regexp.test(cookies[i]["k"])) {
				var obj = new Object();
				obj["key"] = cookies[i]["k"];
				obj["value"] = cookies[i]["v"];
				obj["expires"] = new Date(cookies[i]["e"]);
				res.push(obj);
			}
		}
		return res;
	},
	remove : function(key) {
		this.set(key,"",{ms:-1},true);		
	},
	removeAll : function() {
		deleteCookie("nano");
	},
	//
	// gets from cookie called "nano" ; alternatively you 
	// can pass it your own cookie and it'll deserialize that
	deserialize : function(nano_cookie_str) {

		var x = nano_cookie_str ? nano_cookie_str : getCookie("nano");
		
		var result = [];
		if (! x)
			return result;
		else {
			var individuals = x.split("|");
			for (var i = 0; i < individuals.length; i++) {
				var obj = new Object();
				var pairs = individuals[i].split(",");
				for (var j = 0; j < pairs.length; j++) {
					var pair = pairs[j].split("=");
					var val = unescape(pair[1]);
					var pval = parseInt(val);
					if (pval == val && ! isNaN(pval))
						val = pval;
					obj[pair[0]] = val;
				}
				result.push(obj);				
			}
			result.sort(this.compareDates);
			// 
			var d = new Date();
			var any_stripped = false;
			while(result.length > 0 && new Date(result[result.length - 1]["e"]) < d) {
				result.length--;
				any_stripped = true;
			}
			return result;
		}
	},
	// 
	serializeAndStore : function(cookies) {
		var res = "";
		for (var i = 0; i < cookies.length; i++) {
			res += (i == 0) ? "" : "|";
			res += "k=" + escape(cookies[i]["k"]);
			res += ",e=" + escape(cookies[i]["e"]);
			res += ",v=" + escape(cookies[i]["v"]);
		}
		setCookie("nano", res, secondsFromNow(3600*24*365));
	},
	compareDates : function(a,b) {
		if (a["e"] && b["e"])
			return ((a["e"] < b["e"]) ? 1 : ((a["e"] > b["e"]) ? -1 : 0));
	}
};
 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/psychologist_game.js
AUTOCORE_SELF_CHECK.push("psychologist_game.js");
// Yep, this is the psychgame javascript.  Woo!

// ---------- LANDING ---------- 

function usernamefocus () {
    if ($('username').value == "Username...") {
	$('username').value = '';
	$('username').setStyle ({ color:'#ff6136' });
    }
}
function usernameblur () {
    if ($('username').value == '') {
	$('username').value = "Username...";
	$('username').setStyle ({ color:'#999' });
    }
}
function passwordfocus () {
    $('passtext').hide ();
    $('password').show ();
    $('password').focus ();
}
function passwordblur () {
    if ($('password').value == '') {
	$('password').hide ();
	$('passtext').show ();
    }
}
function showlogin () {
    $('blackout').setStyle ({opacity:0.8}).show ();
    $('login').show ();
    return false;
}
function login () {
    OkLoginJoin.login ($F('username'), $F('password'), {success_cb:loginSuccess, failure_cb:loginFailure});
}
function loginSuccess (result) {
    document.location.href = "/the-psychologist-game?patient-signup=1";
}
function loginFailure (response) {
    var error;
    switch(response.status) {
    case "bad_pw": error = "Your password seems to be wrong."; break;
    case "user_youngster": error = "Whoa, you're too young for The Psychologist Game."; break;
    case "user_deleted": error = "Your account has been deleted."; break;
    case "user_error": error = "That username/e-mail has never joined."; break;
    case "vip": error = "You're blacklisted, fucker."; break;
    default: "WTF. Unknown error.";
    }
    $('loginfail').innerHTML = error;
    $('loginfail').show ();
}

// ---------- PATIENT SIGNUP ---------- 

// Walter method
function show_questions () {
    $("questionList").style.display = "block";
}
function hide_questions () {
    $("questionList").style.display = "none";
}
function save () {
    var saveplease;
    var confirmtext = "You have not agreed to the three privacy terms" +
	" listed above. You will not be able to play The Psychologist" +
	" Game as a patient. Continue anyway?";
    if ($F('check_1')!='1' || $F('check_2')!='1' || $F('check_3')!='1') {
	saveplease = confirm(confirmtext);
	if (saveplease == true) {
	    $('answer-form').submit ();
	}
    } else {
	$('answer-form').submit ();
    }
}

// ---------- PSYCHGAME!!!!! ---------- 

function review (side) {
    if ($('filereview' + side)) {
	$('filereview' + side).show ();
	$('blackout').show ();
	$('image' + side).addClassName ('expanded');
    } else {
	$('expand' + side).value = 1;
	$('gameform').submit ();
    }
}
function cancel_review (side) {
    $('blackout').toggle ();
    $('filereview' + side).toggle ();
    $('image' + side).removeClassName ('expanded');
}

function flipscore (newscore) {
    $('scorenum').dropOut ({duration:0.3, fps:60});
    setTimeout (function () {
	$('scorenum').innerHTML = newscore;
	$('scorenum').appear ({duration:0.5, fps:60});
    }, 500);
}
function answer (val) {
    $('choice').value = val;
    $('submitanswer').value = 1;
    $('gameform').submit ();
}
function review_text (el, hisher) {
    $(el).innerHTML = hisher;
    $(el).setStyle ({fontSize:'10px'});
}
function point_text (el) {
    $(el).innerHTML = "-3";
    $(el).setStyle ({fontSize:'22px'});
}
function correct (set, score) {
    Event.observe (window, 'load', function () {
	setTimeout ("$('correct').appear ({from:0.0, to:0.99, duration:0.25})", 300);
	setTimeout ("$('correct').fade ({from:0.99, to:0.0, duration:1.0})", 3000);

	$('num').fade ({duration:0.25});
	setTimeout ("$('num').innerHTML='"+set+"';$('num').appear ({duration:0.25})", 300);

	$('scorenum').fade ({duration:0.25, fps:60});
	setTimeout ("$('scorenum').innerHTML='"+score+"';$('scorenum').setStyle ({color:'#ed5a32'}).appear ({duration:0.25, fps:60})", 300);
	setTimeout ("$('scorenum').morph({color:'#ffffff'})", 3000);
    });
}

// ---------- admin functions ----------

function admin_search (text) {
    $$('div.statement-div').each (function (el) {
	var id = el.identify().sub('statement-div-', '');
	if ($('statement-a-' + id).innerHTML.toLowerCase().indexOf (text.toLowerCase()) == -1) {
	    el.hide ();
	} else {
	    el.show ();
	}
    });
}
function expand_all () {
    $$('div.statement-div').each (function (el) {
	var id = el.identify().sub('statement-div-', '');
	el.show ();
	$('statement-p-' + id).hide ();
	$('statement-form-' + id).show ();
    });
}
function collapse_all () {
    $$('div.statement-div').each (function (el) {
	var id = el.identify().sub('statement-div-', '');
	el.show ();
	$('statement-form-' + id).hide ();
	$('statement-p-' + id).show ();
    });
}

// ---------- includes ----------


// 

function killEnterAll(e) {
	var Ucode=e.keyCode? e.keyCode : e.charCode;
	if (Ucode == 13){}
	e = (e) ? e : ((window.event) ? window.event : "");
	if (e) {return !( e.keyCode==13 || e.which==13 );}	
}

var OKLJ = Class.create({

	inititalize : function() {
		
	},
	
	
	//
	//   optional inside params object:
	//   --------------------------------
	//   success_cb : a function to call after a successful login
	//   success_redirect : where to redirect browser after successful login
	//   failure_cb : a function to call after a failed login
	//   failure_redirect : where to redirect browser after failed login

	login : function(screenname, password, params) {
		var ajax_params = { "ajax" : 1, "username" : screenname, "password" : password, enable: params.enable ? 1 : 0};
		var req = new Ajax.Request("/login", {
			method: 'post', 
			parameters: ajax_params ,
			onSuccess: this.login_cb.bindAsEventListener(this, params), 
			onFailure: this.login_cb_failed.bindAsEventListener(this, params)});
		return req;
		
	},


	// This used to be fairly simple, but we also may get a paramter back from the login
	// service telling us we need to visit an authlink, in a hidden iframe. Can't use
	// AJAX for domain security reasons.  So we need to visit that before calling it a day. So this function
	// isn't the last one; it'll invoke login_cb_post_authlink when it's done

	login_cb : function(data, params) {
		var response = data.responseText.evalJSON();
		this.tempHoldings = {'data' : data, 'params' : params };
		if (response.hq_okc_authlink && response.hq_okc_authlink != "") {
			Element.insert(document.body, {  bottom: '<iframe style="display:none;" onload="OkLoginJoin.login_cb_post_authlink()" src="' + response.hq_okc_authlink + '" id="auth-ajax-login"></iframe>'  }  );			
			// doubly-set src for old IE bug where iframes added with src didn't work
			$('auth-ajax-login').src = response.hq_okc_authlink;
            setTimeout("OkLoginJoin.login_cb_post_authlink()", 1000);
		}
		else {
			this.login_cb_post_authlink();
		}		
	},
	
	login_cb_post_authlink : function() {
		
		var data = this.tempHoldings.data;
		var params = this.tempHoldings.params;
		var response = data.responseText.evalJSON();
		if (response.status == "success") {
			if (params.success_cb) {
				params.success_cb(response);
			}
			if (params.success_redirect) {
				document.location.href = params.success_redirect;
			}
		}
		else {
			if (params.failure_cb) {
				params.failure_cb(response);
			}
			if (params.failure_redirect) {
				document.location.href = params.failure_redirect;
			}			
		}
		
	},
	
	login_cb_failed : function(data, params) {
		alert("AJAX login Failed!");		
	},
	
	//
	//   optional inside params object:
	//   --------------------------------
	//   success_cb : a function to call after a successful logout
	//   success_redirect : where to redirect browser after successful logout

	logout : function(params) {
		var ajax_params = { "ajax" : 1 };
		var req = new Ajax.Request("/logout", {
			method: 'post',
			parameters: ajax_params ,
			onSuccess: this.logout_cb.bindAsEventListener(this, params), 
			onFailure: this.logout_cb_failed.bindAsEventListener(this, params)});
		return req;
	},

	logout_cb : function(data, params) {
		var response = data.responseText.evalJSON();
		if (response.status == "success") {
			if (params.success_cb) {
				params.success_cb(response);
			}
			if (params.success_redirect) {
				document.location.href = params.success_redirect;
			}
			Element.insert(document.body,
					{  bottom: '<iframe style="display:none;" src="http://www.okcupid.com/logout" id="auth-ajax-okc-logout"></iframe>'  }  );
		}
		else {
			alert("Logout somehow failed");
		}
	},
	
	logout_cb_failed : function(data, params) {
		alert("AJAX logout Failed!");		
	},
	

	//
	//   optional inside params object:
	//   --------------------------------
	//   success_cb : a function to call after a successful request (user exists, mail sent)
	//   success_redirect : where to redirect browser after successful request (user exists, mail sent)
	//   failure_cb : a function to call after a failed request
	//   failure_redirect : where to redirect browser after failed request

	lostPassword : function(screenname_or_email, params) {
		var ajax_params = { "ajax" : 1, "email" : screenname_or_email};
		var req = new Ajax.Request("/lostpassword", {
			method: 'post', 
			parameters: ajax_params ,
			onSuccess: this.lostPassword_cb.bindAsEventListener(this, params), 
			onFailure: this.lostPassword_cb_failed.bindAsEventListener(this, params)});
		return req;		
	},
	
	lostPassword_cb : function(data, params) {
		var response = data.responseText.evalJSON();
		if (response.status == "SUCCESS") {
			if (params.success_cb) {
				params.success_cb(response);
			}
			if (params.success_redirect) {
				document.location.href = params.success_redirect;
			}
		}
		else {
			if (params.failure_cb) {
				params.failure_cb(response);
			}
			if (params.failure_redirect) {
				document.location.href = params.failure_redirect;
			}			
		}
	},
	
	lostPassword_cb_failed : function(data, params) {
		alert("AJAX lostPassword Failed!");		
	},



	//
	// for signup, params state maintained in member, since we can't pass through cgi safely;
	// with other functions we use bindAsEventListener to pass to callback
	//
	optionalParams : { },

	//
	//   dest_div: where on the page they'll be doing the signup.
	//   signup_path : required, e.g. "/signup/paths/inga/1.html" -- path to first file in process
    //   cgi_extras : extra cgi parameters to pass to the signup service. ("cf", "gender", etc.)
	//     
	//   optional inside params object:
	//   --------------------------------
	//   success_cb : a function to call after a successful request (user exists, mail sent)
	//   success_redirect : where to redirect browser after successful request (user exists, mail sent)
	
	join : function(dest_div, signup_path, cgi_extras, params) {

			this.optionalJoinParams = params;
		
			var default_params = { "ajax" : 1, "reqs" : 0, "cbust" : Math.random() };
			var ajax_params = this.combineTwoObjs(default_params, cgi_extras);
			ajax_params["next_page"] = signup_path;
			var req = new Ajax.Updater(dest_div, "/signup", {
				method: 'post', 
				evalScripts: true,
				parameters: ajax_params });
	},

	//
	// Called by the HTML inserted via signup
	//
	// This used to be fairly simple, but we also may get a paramter back from join
	// service telling us we need to visit an authlink, in a hidden iframe. Can't use
	// AJAX for domain security reasons.  So we need to visit that before calling it a day. So this function
	// isn't the last one; it'll invoke login_cb_post_authlink when it's done

	join_cb : function (data) {
		this.tempHoldings = {'data' : data };
		if (data.hq_okc_authlink && data.hq_okc_authlink != "") {
			Element.insert(document.body, {  bottom: '<iframe style="display:none;" onload="OkLoginJoin.join_cb_post_authlink()" src="' + data.hq_okc_authlink + '" id="auth-ajax-join"></iframe>'  }  );
            setTimeout("OkLoginJoin.join_cb_post_authlink()", 1000);
		}
		else {
			this.join_cb_post_authlink()
		}
	},
	
	join_cb_post_authlink : function() {
		var data = this.tempHoldings.data;
		
		if (this.optionalJoinParams.success_cb) {
			this.optionalJoinParams.success_cb(data);
		}
		if (this.optionalJoinParams.success_redirect) {
			document.location.href = this.optionalJoinParams.success_redirect;
		}		
	},
	
	//
	// helpers
	//
	
	combineTwoObjs : function(o1, o2) {
		
		var res = new Object();
		for (var key in o1) {
			res[key] = o1[key];
		}
		for (var key in o2) {
			res[key] = o2[key];
		}
		return res;			
	}
	
});
//----------------------------------------------------------------------------------------------------------------

 	var OkLoginJoin = new OKLJ(); 

//----------------------------------------------------------------------------------------------------------------

	


 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/quizzy/bottom_bar.js
AUTOCORE_SELF_CHECK.push("quizzy/bottom_bar.js");
var TMP_SCREENNAME;

function bottomBarFlip() {
	var req = new Ajax.Request("/quizzy/bottom_bar_ajax.html",
	{
		method: "post",
		onSuccess: function(resp) {
			$('bottom-bar-bag').innerHTML = resp.responseText;
			var the_body = document.body;
			
			//if (ajs_hasClassName(the_body, 'logged-in')) {
			//if (document.body.className.indexOf('logged-in') != -1) {
			if ($(the_body).hasClassName("logged-in")) {
				$(the_body).removeClassName('logged-in');
				$(the_body).addClassName('logged-out');
			}
			else { 
				$(the_body).removeClassName('logged-out');
				$(the_body).addClassName('logged-in');
				fillBottomBarPic(TMP_SCREENNAME);
			}
		}
	} 
  );
}

function bottomBarRefresh() {
	$('bottom-bar-bag').style.display = "none";
	setTimeout("$('bottom-bar-bag').style.display = '';", 1);
}

function bottomBarLogout() {
	OkLoginJoin.logout({ 
		success_cb : bottomBarLogoutSuccess
	});	
}

function bottomBarLogin() {
	OkLoginJoin.login($F('bottom-bar-screenname'), $F('bottom-bar-password'), { success_cb : bottomBarLoginSuccess, failure_cb : bottomBarLogin_failure });	
}

function bottomBarLogoutSuccess() {
	var loc = window.location.href;
    if (loc.indexOf("/resultslist") != -1) {
        window.location.href = "/";
    }
    else if (loc.indexOf("write-a-test") != -1) {
        window.location.href = "/";
    }
    else {    
        reloadWithoutResubmitPost();
    }
}

function reloadWithoutResubmitPost() {
	var loc = window.location.href;
	if (loc.indexOf("?") == -1) {
		window.location.href = window.location.href + "?reload="+Math.round(Math.random()*1000000000);
	}
	else {
		window.location.href = window.location.href + "&reload="+Math.round(Math.random()*1000000000);
	}
}

function bottomBarLoginSuccess(result) {
    TMP_SCREENNAME = result.screenname;
    var loc = window.location.href;
    if (loc.indexOf("profile") != -1) {
        reloadWithoutResubmitPost();
    }
    else {
		// Don't call bottomBarFlip() here.  For some reason it flips twice in the IEs
		var req = new Ajax.Request("/quizzy/bottom_bar_ajax.html",
		{
			method: "post",
			onSuccess: function(resp) {
				$('bottom-bar-bag').innerHTML = resp.responseText;				
				if ($(document.body).hasClassName('logged-out')) {
					$(document.body).removeClassName('logged-out');
					$(document.body).addClassName('logged-in');
					}
				}
			} 
		);
    }
}

function bottomBarLogin_failure(response) {
	var res = "";
	switch(response.status) { 
	  case "bad_pw" : res += ""; toggleErrorClass("bottom-bar-password"); break;    
	  case "user_youngster" : res += ""; break;  
	  case "user_deleted" : res += ""; break;
	  case "user_error" : res += ""; toggleErrorClass("bottom-bar-screenname"); break;
	  case "vip" : res += ""; break; 
	  default : res += "";
	}
    $('bottom-bar-login-notices').innerHTML = res;
		$('bottom-bar-login-notices').show();
}

function toggleErrorClass(label) {
	var login_labels = $('login-form').getElementsByTagName('label');
	var login_inputs = $('login-form').getElementsByTagName('input');
	
	for(var i = 0; i < login_labels.length; i++) {
		if (login_labels[i].attributes['for'].value == label) {
			login_labels[i].className = 'error';
			login_inputs[i].className = 'error';
		}
		else {
			login_labels[i].className = "";
			login_inputs[i].className = "";
		}
	}
}


function toggleLogin() {
	login_form = $('login-form');
		
	if (login_form.visible()) {
		login_form.hide();
	}
	else {
		login_form.show();
		$('bottom-bar-screenname').focus();
	}
}

function fillBottomBarPic(screenname) {
   var pic = getCookie(screenname + "_pic");
   if (pic && pic != "none") {
	   		$("thumb-in-the-bottom").src = 'http://cdn.okcimg.com/php/load_okc_image.php/images/40x40/40x40/' + pic;
   }
   else if (! pic) {
	   TMP_SCREENNAME = screenname;
   		new Ajax.Request("/rjs/userinfo", 
			{
				parameters : {u : screenname, template : "userinfo/ajax.html", rnd : Math.random() },
		 		method : 'get',
				onSuccess : gotBottomBarUserInfo,
				onFailure: failedBottomBarUserInfo
			});
   }
}

function failedBottomBarUserInfo() {
	// let's not bother the service too much!
	setCookie(TMP_SCREENNAME + "_pic", "none", secondsFromNow(600));
}

function forgetBottomBarUserInfo(screenname) {
 	deleteCookie(screenname + "_pic");
}

function gotBottomBarUserInfo(data) {
    var res = data.responseText.evalJSON();
	var screenname = TMP_SCREENNAME;
    if (res.pics && res.pics.length > 0) {
		$("thumb-in-the-bottom").src = 'http://cdn.okcimg.com/php/load_okc_image.php/images/40x40/40x40/' + res.pics[0];
		setCookie(screenname + "_pic", res.pics[0], secondsFromNow(3600));
	}
	else {
		setCookie(screenname + "_pic", "none", secondsFromNow(3600));
	}
} 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/quizzy/xml.js
AUTOCORE_SELF_CHECK.push("quizzy/xml.js");
//
//
// Slightly modified (by CRC) code for parsing and generating XML from
// javascript objects
//
//-------------------------------------------------------------------------------------------------------
/*	This work is licensed under Creative Commons GNU LGPL License.
	License: http://creativecommons.org/licenses/LGPL/2.1/
*/
//-------------------------------------------------------------------------------------------------------

function parseXml(xml) {
   var dom = null;
   if (window.DOMParser) {
      try { 
         dom = (new DOMParser()).parseFromString(xml, "text/xml"); 
      } 
      catch (e) { dom = null; }
   }
   else if (window.ActiveXObject) {
      try {
         dom = new ActiveXObject('Microsoft.XMLDOM');
         dom.async = false;
         if (!dom.loadXML(xml)) // parse error ..
            window.alert(dom.parseError.reason + dom.parseError.srcText);
      } 
      catch (e) { dom = null; }
   }
   else
      alert("oops");
   return dom;
}

function json2xml(o, tab) {
   var toXml = function(v, name, ind) {
      var xml = "";
      if (v instanceof Array) {
         for (var i=0, n=v.length; i<n; i++)
            xml += toXml(v[i], name, ind);
      }
      else if (typeof(v) == "object") {
         var hasChild = false;
         xml += ind + "<" + name;
         for (var m in v) {
            if (m.charAt(0) == "@")
               xml += " " + m.substr(1) + "=\"" + v[m].toString() + "\"";
            else
               hasChild = true;
         }
         xml += hasChild ? ">\n" : "/>\n";
         if (hasChild) {
            for (var m in v) {
               if (m == "#text")
                  xml += ind + "\t" + v[m] + "\n";
               else if (m == "#cdata")
                  xml += ind + "\t" +  "<![CDATA[" + v[m] + "]]>\n";
               else if (m.charAt(0) != "@")
                  xml += toXml(v[m], m, ind+"\t");
            }
//            xml += (xml.charAt(xml.length-1)=="\n"?ind:"") + "</" + name + ">\n";
			  xml += ind + "</" + name + ">\n";
         }
      }
      else {
         xml += ind + "<" + name + ">" + v.toString() +  "</" + name + ">\n";
      }
      return xml;
   }, xml="";
   for (var m in o)
      xml += toXml(o[m], m, "");
   return tab ? xml.replace(/\t/g, tab) : xml.replace(/\t|\n/g, "");
}


function xml2json(xml, tab) {
   var X = {
      toObj: function(xml) {
         var o = {};
         if (xml.nodeType==1) {   // element node ..
            if (xml.attributes.length)   // element with attributes  ..
               for (var i=0; i<xml.attributes.length; i++)
                  o["@"+xml.attributes[i].nodeName] = (xml.attributes[i].nodeValue||"").toString();
            if (xml.firstChild) { // element has child nodes ..
               var textChild=0, cdataChild=0, hasElementChild=false;
               for (var n=xml.firstChild; n; n=n.nextSibling) {
                  if (n.nodeType==1) hasElementChild = true;
                  else if (n.nodeType==3 && n.nodeValue.match(/[^ \f\n\r\t\v]/)) textChild++; // non-whitespace text
                  else if (n.nodeType==4) cdataChild++; // cdata section node
               }
               if (hasElementChild) {
                  if (textChild < 2 && cdataChild < 2) { // structured element with evtl. a single text or/and cdata node ..
                     X.removeWhite(xml);
                     for (var n=xml.firstChild; n; n=n.nextSibling) {
                        if (n.nodeType == 3)  // text node
                           o["#text"] = X.escape(n.nodeValue);
                        else if (n.nodeType == 4)  // cdata node
                           o["#cdata"] = X.escape(n.nodeValue);
                        else if (o[n.nodeName]) {  // multiple occurence of element ..
                           if (o[n.nodeName] instanceof Array)
                              o[n.nodeName][o[n.nodeName].length] = X.toObj(n);
                           else
                              o[n.nodeName] = [o[n.nodeName], X.toObj(n)];
                        }
                        else  // first occurence of element..
                           o[n.nodeName] = X.toObj(n);
                     }
                  }
                  else { // mixed content
                     if (!xml.attributes.length)
                        o = X.escape(X.innerXml(xml));
                     else
                        o["#text"] = X.escape(X.innerXml(xml));
                  }
               }
               else if (textChild) { // pure text
                  if (!xml.attributes.length)
                     o = X.escape(X.innerXml(xml));
                  else
                     o["#text"] = X.escape(X.innerXml(xml));
               }
               else if (cdataChild) { // cdata
                  if (cdataChild > 1)
                     o = X.escape(X.innerXml(xml));
                  else
                     for (var n=xml.firstChild; n; n=n.nextSibling)
                        o["#cdata"] = X.escape(n.nodeValue);
               }
            }
            if (!xml.attributes.length && !xml.firstChild) o = null;
         }
         else if (xml.nodeType==9) { // document.node
            o = X.toObj(xml.documentElement);
         }
         else
            alert("unhandled node type: " + xml.nodeType);
         return o;
      },
      toJson: function(o, name, ind) {
         var json = name ? ("\""+name+"\"") : "";
         if (o instanceof Array) {
            for (var i=0,n=o.length; i<n; i++)
               o[i] = X.toJson(o[i], "", ind+"\t");
            json += (name?":[":"[") + (o.length > 1 ? ("\n"+ind+"\t"+o.join(",\n"+ind+"\t")+"\n"+ind) : o.join("")) + "]";
         }
         else if (o == null)
            json += (name&&":") + "null";
         else if (typeof(o) == "object") {
            var arr = [];
            for (var m in o)
               arr[arr.length] = X.toJson(o[m], m, ind+"\t");
            json += (name?":{":"{") + (arr.length > 1 ? ("\n"+ind+"\t"+arr.join(",\n"+ind+"\t")+"\n"+ind) : arr.join("")) + "}";
         }
         else if (typeof(o) == "string")
            json += (name&&":") + "\"" + o.toString() + "\"";
         else
            json += (name&&":") + o.toString();
         return json;
      },
      innerXml: function(node) {
         var s = ""
         if ("innerHTML" in node)
            s = node.innerHTML;
         else {
            var asXml = function(n) {
               var s = "";
               if (n.nodeType == 1) {
                  s += "<" + n.nodeName;
                  for (var i=0; i<n.attributes.length;i++)
                     s += " " + n.attributes[i].nodeName + "=\"" + (n.attributes[i].nodeValue||"").toString() + "\"";
                  if (n.firstChild) {
                     s += ">";
                     for (var c=n.firstChild; c; c=c.nextSibling)
                        s += asXml(c);
                     s += "</"+n.nodeName+">";
                  }
                  else
                     s += "/>";
               }
               else if (n.nodeType == 3)
                  s += n.nodeValue;
               else if (n.nodeType == 4)
                  s += "<![CDATA[" + n.nodeValue + "]]>";
               return s;
            };
            for (var c=node.firstChild; c; c=c.nextSibling)
               s += asXml(c);
         }
         return s;
      },
      escape: function(txt) {
         return txt.replace(/[\\]/g, "\\\\")
                   .replace(/[\"]/g, '\\"')
                   .replace(/[\n]/g, '\\n')
                   .replace(/[\r]/g, '\\r');
      },
      removeWhite: function(e) {
         e.normalize();
         for (var n = e.firstChild; n; ) {
            if (n.nodeType == 3) {  // text node
               if (!n.nodeValue.match(/[^ \f\n\r\t\v]/)) { // pure whitespace text node
                  var nxt = n.nextSibling;
                  e.removeChild(n);
                  n = nxt;
               }
               else
                  n = n.nextSibling;
            }
            else if (n.nodeType == 1) {  // element node
               X.removeWhite(n);
               n = n.nextSibling;
            }
            else                      // any other node
               n = n.nextSibling;
         }
         return e;
      }
   };
   if (xml.nodeType == 9) // document node
      xml = xml.documentElement;
   var json = X.toJson(X.toObj(X.removeWhite(xml)), xml.nodeName, "\t");
   return "{\n" + tab + (tab ? json.replace(/\t/g, tab) : json.replace(/\t|\n/g, "")) + "\n}";
}
 
AUTOCORE_SELF_CHECK.pop();
	
addNewCoresToCookie(["0", "1", "3", "4", "5", "6", "8", "24", "25", "28", "29", "46", "47", "49"], "f2d5639e"); 
