// 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/builder.js
AUTOCORE_SELF_CHECK.push("scriptaculous-1.8.1-for-autocore/builder.js");
// script.aculo.us builder.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)
//
// 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/

var Builder = {
  NODEMAP: {
    AREA: 'map',
    CAPTION: 'table',
    COL: 'table',
    COLGROUP: 'table',
    LEGEND: 'fieldset',
    OPTGROUP: 'select',
    OPTION: 'select',
    PARAM: 'object',
    TBODY: 'table',
    TD: 'table',
    TFOOT: 'table',
    TH: 'table',
    THEAD: 'table',
    TR: 'table'
  },
  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
  //       due to a Firefox bug
  node: function(elementName) {
    elementName = elementName.toUpperCase();
    
    // try innerHTML approach
    var parentTag = this.NODEMAP[elementName] || 'div';
    var parentElement = document.createElement(parentTag);
    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
    } catch(e) {}
    var element = parentElement.firstChild || null;
      
    // see if browser added wrapping tags
    if(element && (element.tagName.toUpperCase() != elementName))
      element = element.getElementsByTagName(elementName)[0];
    
    // fallback to createElement approach
    if(!element) element = document.createElement(elementName);
    
    // abort if nothing could be created
    if(!element) return;

    // attributes (or text)
    if(arguments[1])
      if(this._isStringOrNumber(arguments[1]) ||
        (arguments[1] instanceof Array) ||
        arguments[1].tagName) {
          this._children(element, arguments[1]);
        } else {
          var attrs = this._attributes(arguments[1]);
          if(attrs.length) {
            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
              parentElement.innerHTML = "<" +elementName + " " +
                attrs + "></" + elementName + ">";
            } catch(e) {}
            element = parentElement.firstChild || null;
            // workaround firefox 1.0.X bug
            if(!element) {
              element = document.createElement(elementName);
              for(attr in arguments[1]) 
                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
            }
            if(element.tagName.toUpperCase() != elementName)
              element = parentElement.getElementsByTagName(elementName)[0];
          }
        } 

    // text, or array of children
    if(arguments[2])
      this._children(element, arguments[2]);

     return element;
  },
  _text: function(text) {
     return document.createTextNode(text);
  },

  ATTR_MAP: {
    'className': 'class',
    'htmlFor': 'for'
  },

  _attributes: function(attributes) {
    var attrs = [];
    for(attribute in attributes)
      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
          '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
    return attrs.join(" ");
  },
  _children: function(element, children) {
    if(children.tagName) {
      element.appendChild(children);
      return;
    }
    if(typeof children=='object') { // array can hold nodes and text
      children.flatten().each( function(e) {
        if(typeof e=='object')
          element.appendChild(e)
        else
          if(Builder._isStringOrNumber(e))
            element.appendChild(Builder._text(e));
      });
    } else
      if(Builder._isStringOrNumber(children))
        element.appendChild(Builder._text(children));
  },
  _isStringOrNumber: function(param) {
    return(typeof param=='string' || typeof param=='number');
  },
  build: function(html) {
    var element = this.node('div');
    $(element).update(html.strip());
    return element.down();
  },
  dump: function(scope) { 
    if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope 
  
    var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
      "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
      "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
      "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
      "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
      "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
  
    tags.each( function(tag){ 
      scope[tag] = function() { 
        return Builder.node.apply(Builder, [tag].concat($A(arguments)));  
      } 
    });
  }
}
 
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/scriptaculous-1.8.1-for-autocore/dragdrop.js
AUTOCORE_SELF_CHECK.push("scriptaculous-1.8.1-for-autocore/dragdrop.js");
// script.aculo.us dragdrop.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)
//           (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
// 
// 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/

var Droppables = {
  drops: [],

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

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

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

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

    this.drops.push(options);
  },
  
  findDeepestChild: function(drops) {
    deepest = drops[0];
      
    for (i = 1; i < drops.length; ++i)
      if (Element.isParent(drops[i].element, deepest.element))
        deepest = drops[i];
    
    return deepest;
  },

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

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

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

  show: function(point, element) {
    if(!this.drops.length) return;
    var drop, affected = [];
    
    this.drops.each( function(drop) {
      if(Droppables.isAffected(point, element, drop))
        affected.push(drop);
    });
        
    if(affected.length>0)
      drop = Droppables.findDeepestChild(affected);

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

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

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

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

var Draggables = {
  drags: [],
  observers: [],
  
  register: function(draggable) {
    if(this.drags.length == 0) {
      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
      
      Event.observe(document, "mouseup", this.eventMouseUp);
      Event.observe(document, "mousemove", this.eventMouseMove);
      Event.observe(document, "keypress", this.eventKeypress);
    }
    this.drags.push(draggable);
  },
  
  unregister: function(draggable) {
    this.drags = this.drags.reject(function(d) { return d==draggable });
    if(this.drags.length == 0) {
      Event.stopObserving(document, "mouseup", this.eventMouseUp);
      Event.stopObserving(document, "mousemove", this.eventMouseMove);
      Event.stopObserving(document, "keypress", this.eventKeypress);
    }
  },
  
  activate: function(draggable) {
    if(draggable.options.delay) { 
      this._timeout = setTimeout(function() { 
        Draggables._timeout = null; 
        window.focus(); 
        Draggables.activeDraggable = draggable; 
      }.bind(this), draggable.options.delay); 
    } else {
      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
      this.activeDraggable = draggable;
    }
  },
  
  deactivate: function() {
    this.activeDraggable = null;
  },
  
  updateDrag: function(event) {
    if(!this.activeDraggable) return;
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    // Mozilla-based browsers fire successive mousemove events with
    // the same coordinates, prevent needless redrawing (moz bug?)
    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
    this._lastPointer = pointer;
    
    this.activeDraggable.updateDrag(event, pointer);
  },
  
  endDrag: function(event) {
    if(this._timeout) { 
      clearTimeout(this._timeout); 
      this._timeout = null; 
    }
    if(!this.activeDraggable) return;
    this._lastPointer = null;
    this.activeDraggable.endDrag(event);
    this.activeDraggable = null;
  },
  
  keyPress: function(event) {
    if(this.activeDraggable)
      this.activeDraggable.keyPress(event);
  },
  
  addObserver: function(observer) {
    this.observers.push(observer);
    this._cacheObserverCallbacks();
  },
  
  removeObserver: function(element) {  // element instead of observer fixes mem leaks
    this.observers = this.observers.reject( function(o) { return o.element==element });
    this._cacheObserverCallbacks();
  },
  
  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
    if(this[eventName+'Count'] > 0)
      this.observers.each( function(o) {
        if(o[eventName]) o[eventName](eventName, draggable, event);
      });
    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
  },
  
  _cacheObserverCallbacks: function() {
    ['onStart','onEnd','onDrag'].each( function(eventName) {
      Draggables[eventName+'Count'] = Draggables.observers.select(
        function(o) { return o[eventName]; }
      ).length;
    });
  }
}

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

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

    this.element = $(element);
    
    if(options.handle && Object.isString(options.handle))
      this.handle = this.element.down('.'+options.handle, 0);
    
    if(!this.handle) this.handle = $(options.handle);
    if(!this.handle) this.handle = this.element;
    
    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
      options.scroll = $(options.scroll);
      this._isScrollChild = Element.childOf(this.element, options.scroll);
    }

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

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

    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
    Event.observe(this.handle, "mousedown", this.eventMouseDown);
    
    Draggables.register(this);
  },
  
  destroy: function() {
    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
    Draggables.unregister(this);
  },
  
  currentDelta: function() {
    return([
      parseInt(Element.getStyle(this.element,'left') || '0'),
      parseInt(Element.getStyle(this.element,'top') || '0')]);
  },
  
  initDrag: function(event) {
    if(!Object.isUndefined(Draggable._dragging[this.element]) &&
      Draggable._dragging[this.element]) return;
    if(Event.isLeftClick(event)) {    
      // abort on form elements, fixes a Firefox issue
      var src = Event.element(event);
      if((tag_name = src.tagName.toUpperCase()) && (
        tag_name=='INPUT' ||
        tag_name=='SELECT' ||
        tag_name=='OPTION' ||
        tag_name=='BUTTON' ||
        tag_name=='TEXTAREA')) return;
        
      var pointer = [Event.pointerX(event), Event.pointerY(event)];
      var pos     = Position.cumulativeOffset(this.element);
      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
      
      Draggables.activate(this);
      Event.stop(event);
    }
  },
  
  startDrag: function(event) {
    this.dragging = true;
    if(!this.delta)
      this.delta = this.currentDelta();
    
    if(this.options.zindex) {
      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
      this.element.style.zIndex = this.options.zindex;
    }
    
    if(this.options.ghosting) {
      this._clone = this.element.cloneNode(true);
      this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
      if (!this.element._originallyAbsolute)
        Position.absolutize(this.element);
      this.element.parentNode.insertBefore(this._clone, this.element);
    }
    
    if(this.options.scroll) {
      if (this.options.scroll == window) {
        var where = this._getWindowScroll(this.options.scroll);
        this.originalScrollLeft = where.left;
        this.originalScrollTop = where.top;
      } else {
        this.originalScrollLeft = this.options.scroll.scrollLeft;
        this.originalScrollTop = this.options.scroll.scrollTop;
      }
    }
    
    Draggables.notify('onStart', this, event);
        
    if(this.options.starteffect) this.options.starteffect(this.element);
  },
  
  updateDrag: function(event, pointer) {
    if(!this.dragging) this.startDrag(event);
    
    if(!this.options.quiet){
      Position.prepare();
      Droppables.show(pointer, this.element);
    }
    
    Draggables.notify('onDrag', this, event);
    
    this.draw(pointer);
    if(this.options.change) this.options.change(this);
    
    if(this.options.scroll) {
      this.stopScrolling();
      
      var p;
      if (this.options.scroll == window) {
        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
      } else {
        p = Position.page(this.options.scroll);
        p[0] += this.options.scroll.scrollLeft + Position.deltaX;
        p[1] += this.options.scroll.scrollTop + Position.deltaY;
        p.push(p[0]+this.options.scroll.offsetWidth);
        p.push(p[1]+this.options.scroll.offsetHeight);
      }
      var speed = [0,0];
      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
      this.startScrolling(speed);
    }
    
    // fix AppleWebKit rendering
    if(Prototype.Browser.WebKit) window.scrollBy(0,0);
    
    Event.stop(event);
  },
  
  finishDrag: function(event, success) {
    this.dragging = false;
    
    if(this.options.quiet){
      Position.prepare();
      var pointer = [Event.pointerX(event), Event.pointerY(event)];
      Droppables.show(pointer, this.element);
    }

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

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

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

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

    if(this.options.endeffect) 
      this.options.endeffect(this.element);
      
    Draggables.deactivate(this);
    Droppables.reset();
  },
  
  keyPress: function(event) {
    if(event.keyCode!=Event.KEY_ESC) return;
    this.finishDrag(event, false);
    Event.stop(event);
  },
  
  endDrag: function(event) {
    if(!this.dragging) return;
    this.stopScrolling();
    this.finishDrag(event, true);
    Event.stop(event);
  },
  
  draw: function(point) {
    var pos = Position.cumulativeOffset(this.element);
    if(this.options.ghosting) {
      var r   = Position.realOffset(this.element);
      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
    }
    
    var d = this.currentDelta();
    pos[0] -= d[0]; pos[1] -= d[1];
    
    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
    }
    
    var p = [0,1].map(function(i){ 
      return (point[i]-pos[i]-this.offset[i]) 
    }.bind(this));
    
    if(this.options.snap) {
      if(Object.isFunction(this.options.snap)) {
        p = this.options.snap(p[0],p[1],this);
      } else {
      if(Object.isArray(this.options.snap)) {
        p = p.map( function(v, i) {
          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
      } else {
        p = p.map( function(v) {
          return (v/this.options.snap).round()*this.options.snap }.bind(this))
      }
    }}
    
    var style = this.element.style;
    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
      style.left = p[0] + "px";
    if((!this.options.constraint) || (this.options.constraint=='vertical'))
      style.top  = p[1] + "px";
    
    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
  },
  
  stopScrolling: function() {
    if(this.scrollInterval) {
      clearInterval(this.scrollInterval);
      this.scrollInterval = null;
      Draggables._lastScrollPointer = null;
    }
  },
  
  startScrolling: function(speed) {
    if(!(speed[0] || speed[1])) return;
    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
    this.lastScrolled = new Date();
    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
  },
  
  scroll: function() {
    var current = new Date();
    var delta = current - this.lastScrolled;
    this.lastScrolled = current;
    if(this.options.scroll == window) {
      with (this._getWindowScroll(this.options.scroll)) {
        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
          var d = delta / 1000;
          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
        }
      }
    } else {
      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
    }
    
    Position.prepare();
    Droppables.show(Draggables._lastPointer, this.element);
    Draggables.notify('onDrag', this);
    if (this._isScrollChild) {
      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
      if (Draggables._lastScrollPointer[0] < 0)
        Draggables._lastScrollPointer[0] = 0;
      if (Draggables._lastScrollPointer[1] < 0)
        Draggables._lastScrollPointer[1] = 0;
      this.draw(Draggables._lastScrollPointer);
    }
    
    if(this.options.change) this.options.change(this);
  },
  
  _getWindowScroll: function(w) {
    var T, L, W, H;
    with (w.document) {
      if (w.document.documentElement && documentElement.scrollTop) {
        T = documentElement.scrollTop;
        L = documentElement.scrollLeft;
      } else if (w.document.body) {
        T = body.scrollTop;
        L = body.scrollLeft;
      }
      if (w.innerWidth) {
        W = w.innerWidth;
        H = w.innerHeight;
      } else if (w.document.documentElement && documentElement.clientWidth) {
        W = documentElement.clientWidth;
        H = documentElement.clientHeight;
      } else {
        W = body.offsetWidth;
        H = body.offsetHeight
      }
    }
    return { top: T, left: L, width: W, height: H };
  }
});

Draggable._dragging = { };

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

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

var Sortable = {
  SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
  
  sortables: { },
  
  _findRootElement: function(element) {
    while (element.tagName.toUpperCase() != "BODY") {  
      if(element.id && Sortable.sortables[element.id]) return element;
      element = element.parentNode;
    }
  },

  options: function(element) {
    element = Sortable._findRootElement($(element));
    if(!element) return;
    return Sortable.sortables[element.id];
  },
  
  destroy: function(element){
    var s = Sortable.options(element);
    
    if(s) {
      Draggables.removeObserver(s.element);
      s.droppables.each(function(d){ Droppables.remove(d) });
      s.draggables.invoke('destroy');
      
      delete Sortable.sortables[s.element.id];
    }
  },

  create: function(element) {
    element = $(element);
    var options = Object.extend({ 
      element:     element,
      tag:         'li',       // assumes li children, override with tag: 'tagname'
      dropOnEmpty: false,
      tree:        false,
      treeTag:     'ul',
      overlap:     'vertical', // one of 'vertical', 'horizontal'
      constraint:  'vertical', // one of 'vertical', 'horizontal', false
      containment: element,    // also takes array of elements (or id's); or false
      handle:      false,      // or a CSS class
      only:        false,
      delay:       0,
      hoverclass:  null,
      ghosting:    false,
      quiet:       false, 
      scroll:      false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      format:      this.SERIALIZE_RULE,
      
      // these take arrays of elements or ids and can be 
      // used for better initialization performance
      elements:    false,
      handles:     false,
      
      onChange:    Prototype.emptyFunction,
      onUpdate:    Prototype.emptyFunction
    }, arguments[1] || { });

    // clear any old sortable with same element
    this.destroy(element);

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

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

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

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

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

    // build options for the droppables  
    var options_for_droppable = {
      overlap:     options.overlap,
      containment: options.containment,
      tree:        options.tree,
      hoverclass:  options.hoverclass,
      onHover:     Sortable.onHover
    }
    
    var options_for_tree = {
      onHover:      Sortable.onEmptyHover,
      overlap:      options.overlap,
      containment:  options.containment,
      hoverclass:   options.hoverclass
    }

    // fix for gecko engine
    Element.cleanWhitespace(element); 

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

    // drop on empty handling
    if(options.dropOnEmpty || options.tree) {
      Droppables.add(element, options_for_tree);
      options.droppables.push(element);
    }

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

    // keep reference
    this.sortables[element.id] = options;

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

  },

  // return all suitable-for-sortable elements in a guaranteed order
  findElements: function(element, options) {
    return Element.findChildren(
      element, options.only, options.tree ? true : false, options.tag);
  },
  
  findTreeElements: function(element, options) {
    return Element.findChildren(
      element, options.only, options.tree ? true : false, options.treeTag);
  },

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

    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
      return;
    } else if(overlap>0.5) {
      Sortable.mark(dropon, 'before');
      if(dropon.previousSibling != element) {
        var oldParentNode = element.parentNode;
        element.style.visibility = "hidden"; // fix gecko rendering
        dropon.parentNode.insertBefore(element, dropon);
        if(dropon.parentNode!=oldParentNode) 
          Sortable.options(oldParentNode).onChange(element);
        Sortable.options(dropon.parentNode).onChange(element);
      }
    } else {
      Sortable.mark(dropon, 'after');
      var nextElement = dropon.nextSibling || null;
      if(nextElement != element) {
        var oldParentNode = element.parentNode;
        element.style.visibility = "hidden"; // fix gecko rendering
        dropon.parentNode.insertBefore(element, nextElement);
        if(dropon.parentNode!=oldParentNode) 
          Sortable.options(oldParentNode).onChange(element);
        Sortable.options(dropon.parentNode).onChange(element);
      }
    }
  },
  
  onEmptyHover: function(element, dropon, overlap) {
    var oldParentNode = element.parentNode;
    var droponOptions = Sortable.options(dropon);
        
    if(!Element.isParent(dropon, element)) {
      var index;
      
      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
      var child = null;
            
      if(children) {
        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
        
        for (index = 0; index < children.length; index += 1) {
          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
            offset -= Element.offsetSize (children[index], droponOptions.overlap);
          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
            child = index + 1 < children.length ? children[index + 1] : null;
            break;
          } else {
            child = children[index];
            break;
          }
        }
      }
      
      dropon.insertBefore(element, child);
      
      Sortable.options(oldParentNode).onChange(element);
      droponOptions.onChange(element);
    }
  },

  unmark: function() {
    if(Sortable._marker) Sortable._marker.hide();
  },

  mark: function(dropon, position) {
    // mark on ghosting only
    var sortable = Sortable.options(dropon.parentNode);
    if(sortable && !sortable.ghosting) return; 

    if(!Sortable._marker) {
      Sortable._marker = 
        ($('dropmarker') || Element.extend(document.createElement('DIV'))).
          hide().addClassName('dropmarker').setStyle({position:'absolute'});
      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
    }    
    var offsets = Position.cumulativeOffset(dropon);
    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
    
    if(position=='after')
      if(sortable.overlap == 'horizontal') 
        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
      else
        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
    
    Sortable._marker.show();
  },
  
  _tree: function(element, options, parent) {
    var children = Sortable.findElements(element, options) || [];
  
    for (var i = 0; i < children.length; ++i) {
      var match = children[i].id.match(options.format);

      if (!match) continue;
      
      var child = {
        id: encodeURIComponent(match ? match[1] : null),
        element: element,
        parent: parent,
        children: [],
        position: parent.children.length,
        container: $(children[i]).down(options.treeTag)
      }
      
      /* Get the element containing the children and recurse over it */
      if (child.container)
        this._tree(child.container, options, child)
      
      parent.children.push (child);
    }

    return parent; 
  },

  tree: function(element) {
    element = $(element);
    var sortableOptions = this.options(element);
    var options = Object.extend({
      tag: sortableOptions.tag,
      treeTag: sortableOptions.treeTag,
      only: sortableOptions.only,
      name: element.id,
      format: sortableOptions.format
    }, arguments[1] || { });
    
    var root = {
      id: null,
      parent: null,
      children: [],
      container: element,
      position: 0
    }
    
    return Sortable._tree(element, options, root);
  },

  /* Construct a [i] index for a particular node */
  _constructIndex: function(node) {
    var index = '';
    do {
      if (node.id) index = '[' + node.position + ']' + index;
    } while ((node = node.parent) != null);
    return index;
  },

  sequence: function(element) {
    element = $(element);
    var options = Object.extend(this.options(element), arguments[1] || { });
    
    return $(this.findElements(element, options) || []).map( function(item) {
      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
    });
  },

  setSequence: function(element, new_sequence) {
    element = $(element);
    var options = Object.extend(this.options(element), arguments[2] || { });
    
    var nodeMap = { };
    this.findElements(element, options).each( function(n) {
        if (n.id.match(options.format))
            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
        n.parentNode.removeChild(n);
    });
   
    new_sequence.each(function(ident) {
      var n = nodeMap[ident];
      if (n) {
        n[1].appendChild(n[0]);
        delete nodeMap[ident];
      }
    });
  },
  
  serialize: function(element) {
    element = $(element);
    var options = Object.extend(Sortable.options(element), arguments[1] || { });
    var name = encodeURIComponent(
      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
    
    if (options.tree) {
      return Sortable.tree(element, arguments[1]).children.map( function (item) {
        return [name + Sortable._constructIndex(item) + "[id]=" + 
                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
      }).flatten().join('&');
    } else {
      return Sortable.sequence(element, arguments[1]).map( function(item) {
        return name + "[]=" + encodeURIComponent(item);
      }).join('&');
    }
  }
}

// Returns true if child is contained within element
Element.isParent = function(child, element) {
  if (!child.parentNode || child == element) return false;
  if (child.parentNode == element) return true;
  return Element.isParent(child.parentNode, element);
}

Element.findChildren = function(element, only, recursive, tagName) {   
  if(!element.hasChildNodes()) return null;
  tagName = tagName.toUpperCase();
  if(only) only = [only].flatten();
  var elements = [];
  $A(element.childNodes).each( function(e) {
    if(e.tagName && e.tagName.toUpperCase()==tagName &&
      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
        elements.push(e);
    if(recursive) {
      var grandchildren = Element.findChildren(e, only, recursive, tagName);
      if(grandchildren) elements.push(grandchildren);
    }
  });

  return (elements.length>0 ? elements.flatten() : []);
}

Element.offsetSize = function (element, type) {
  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
}
 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/scriptaculous-1.8.1-for-autocore/controls.js
AUTOCORE_SELF_CHECK.push("scriptaculous-1.8.1-for-autocore/controls.js");
// script.aculo.us controls.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)
//           (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
// Contributors:
//  Richard Livsey
//  Rahul Bhargava
//  Rob Wills
// 
// 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/

// Autocompleter.Base handles all the autocompletion functionality 
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
// Specific autocompleters need to provide, at the very least, 
// a getUpdatedChoices function that will be invoked every time
// the text inside the monitored textbox changes. This method 
// should get the text for which to provide autocompletion by
// invoking this.getToken(), NOT by directly accessing
// this.element.value. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoices.
//
// Tokenized incremental autocompletion is enabled automatically
// when an autocompleter is instantiated with the 'tokens' option
// in the options parameter, e.g.:
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
// a token array, e.g. { tokens: [',', '\n'] } which
// enables autocompletion on multiple tokens. This is most 
// useful when one of the tokens is \n (a newline), as it 
// allows smart autocompletion after linebreaks.


var Autocompleter = { }
Autocompleter.Base = Class.create({
  baseInitialize: function(element, update, options) {
    element          = $(element)
    this.element     = element; 
    this.update      = $(update);  
    this.hasFocus    = false; 
    this.changed     = false; 
    this.active      = false; 
    this.index       = 0;     
    this.entryCount  = 0;
    this.oldElementValue = this.element.value;

    if(this.setOptions)
      this.setOptions(options);
    else
      this.options = options || { };

    this.options.paramName    = this.options.paramName || this.element.name;
    this.options.tokens       = this.options.tokens || [];
    this.options.frequency    = this.options.frequency || 0.4;
    this.options.minChars     = this.options.minChars || 1;
    this.options.onShow       = this.options.onShow || 
      function(element, update){ 
        if(!update.style.position || update.style.position=='absolute') {
          update.style.position = 'absolute';
          Position.clone(element, update, {
            setHeight: false, 
            offsetTop: element.offsetHeight
          });
        }
        Effect.Appear(update,{duration:0.15});
      };
    this.options.onHide = this.options.onHide || 
      function(element, update){ new Effect.Fade(update,{duration:0.15}) };

    if(typeof(this.options.tokens) == 'string') 
      this.options.tokens = new Array(this.options.tokens);
    // Force carriage returns as token delimiters anyway
    if (!this.options.tokens.include('\n'))
      this.options.tokens.push('\n');

    this.observer = null;
    
    this.element.setAttribute('autocomplete','off');

    Element.hide(this.update);

    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
  },

  show: function() {
    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
    if(!this.iefix && 
      (Prototype.Browser.IE) &&
      (Element.getStyle(this.update, 'position')=='absolute')) {
      new Insertion.After(this.update, 
       '<iframe id="' + this.update.id + '_iefix" '+
       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
      this.iefix = $(this.update.id+'_iefix');
    }
    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
  },
  
  fixIEOverlapping: function() {
    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
    this.iefix.style.zIndex = 1;
    this.update.style.zIndex = 2;
    Element.show(this.iefix);
  },

  hide: function() {
    this.stopIndicator();
    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
    if(this.iefix) Element.hide(this.iefix);
  },

  startIndicator: function() {
    if(this.options.indicator) Element.show(this.options.indicator);
  },

  stopIndicator: function() {
    if(this.options.indicator) Element.hide(this.options.indicator);
  },

  onKeyPress: function(event) {
    if(this.active)
      switch(event.keyCode) {
       case Event.KEY_TAB:
       case Event.KEY_RETURN:
         this.selectEntry();
         Event.stop(event);
       case Event.KEY_ESC:
         this.hide();
         this.active = false;
         Event.stop(event);
         return;
       case Event.KEY_LEFT:
       case Event.KEY_RIGHT:
         return;
       case Event.KEY_UP:
         this.markPrevious();
         this.render();
         Event.stop(event);
         return;
       case Event.KEY_DOWN:
         this.markNext();
         this.render();
         Event.stop(event);
         return;
      }
     else 
       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;

    this.changed = true;
    this.hasFocus = true;

    if(this.observer) clearTimeout(this.observer);
      this.observer = 
        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
  },

  activate: function() {
    this.changed = false;
    this.hasFocus = true;
    this.getUpdatedChoices();
  },

  onHover: function(event) {
    var element = Event.findElement(event, 'LI');
    if(this.index != element.autocompleteIndex) 
    {
        this.index = element.autocompleteIndex;
        this.render();
    }
    Event.stop(event);
  },
  
  onClick: function(event) {
    var element = Event.findElement(event, 'LI');
    this.index = element.autocompleteIndex;
    this.selectEntry();
    this.hide();
  },
  
  onBlur: function(event) {
    // needed to make click events working
    setTimeout(this.hide.bind(this), 250);
    this.hasFocus = false;
    this.active = false;     
  }, 
  
  render: function() {
    if(this.entryCount > 0) {
      for (var i = 0; i < this.entryCount; i++)
        this.index==i ? 
          Element.addClassName(this.getEntry(i),"selected") : 
          Element.removeClassName(this.getEntry(i),"selected");
      if(this.hasFocus) { 
        this.show();
        this.active = true;
      }
    } else {
      this.active = false;
      this.hide();
    }
  },
  
  markPrevious: function() {
    if(this.index > 0) this.index--
      else this.index = this.entryCount-1;
    this.getEntry(this.index).scrollIntoView(true);
  },
  
  markNext: function() {
    if(this.index < this.entryCount-1) this.index++
      else this.index = 0;
    this.getEntry(this.index).scrollIntoView(false);
  },
  
  getEntry: function(index) {
    return this.update.firstChild.childNodes[index];
  },
  
  getCurrentEntry: function() {
    return this.getEntry(this.index);
  },
  
  selectEntry: function() {
    this.active = false;
    this.updateElement(this.getCurrentEntry());
  },

  updateElement: function(selectedElement) {
    if (this.options.updateElement) {
      this.options.updateElement(selectedElement);
      return;
    }
    var value = '';
    if (this.options.select) {
      var nodes = $(selectedElement).select('.' + this.options.select) || [];
      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
    } else
      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
    
    var bounds = this.getTokenBounds();
    if (bounds[0] != -1) {
      var newValue = this.element.value.substr(0, bounds[0]);
      var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
      if (whitespace)
        newValue += whitespace[0];
      this.element.value = newValue + value + this.element.value.substr(bounds[1]);
    } else {
      this.element.value = value;
    }
    this.oldElementValue = this.element.value;
    this.element.focus();
    
    if (this.options.afterUpdateElement)
      this.options.afterUpdateElement(this.element, selectedElement);
  },

  updateChoices: function(choices) {
    if(!this.changed && this.hasFocus) {
      this.update.innerHTML = choices;
      Element.cleanWhitespace(this.update);
      Element.cleanWhitespace(this.update.down());

      if(this.update.firstChild && this.update.down().childNodes) {
        this.entryCount = 
          this.update.down().childNodes.length;
        for (var i = 0; i < this.entryCount; i++) {
          var entry = this.getEntry(i);
          entry.autocompleteIndex = i;
          this.addObservers(entry);
        }
      } else { 
        this.entryCount = 0;
      }

      this.stopIndicator();
      this.index = 0;
      
      if(this.entryCount==1 && this.options.autoSelect) {
        this.selectEntry();
        this.hide();
      } else {
        this.render();
      }
    }
  },

  addObservers: function(element) {
    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
  },

  onObserverEvent: function() {
    this.changed = false;   
    this.tokenBounds = null;
    if(this.getToken().length>=this.options.minChars) {
      this.getUpdatedChoices();
    } else {
      this.active = false;
      this.hide();
    }
    this.oldElementValue = this.element.value;
  },

  getToken: function() {
    var bounds = this.getTokenBounds();
    return this.element.value.substring(bounds[0], bounds[1]).strip();
  },

  getTokenBounds: function() {
    if (null != this.tokenBounds) return this.tokenBounds;
    var value = this.element.value;
    if (value.strip().empty()) return [-1, 0];
    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
    var offset = (diff == this.oldElementValue.length ? 1 : 0);
    var prevTokenPos = -1, nextTokenPos = value.length;
    var tp;
    for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
      if (tp > prevTokenPos) prevTokenPos = tp;
      tp = value.indexOf(this.options.tokens[index], diff + offset);
      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
    }
    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
  }
});

Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
  var boundary = Math.min(newS.length, oldS.length);
  for (var index = 0; index < boundary; ++index)
    if (newS[index] != oldS[index])
      return index;
  return boundary;
};

Ajax.Autocompleter = Class.create(Autocompleter.Base, {
  initialize: function(element, update, url, options) {
    this.baseInitialize(element, update, options);
    this.options.asynchronous  = true;
    this.options.onComplete    = this.onComplete.bind(this);
    this.options.defaultParams = this.options.parameters || null;
    this.url                   = url;
  },

  getUpdatedChoices: function() {
    this.startIndicator();
    
    var entry = encodeURIComponent(this.options.paramName) + '=' + 
      encodeURIComponent(this.getToken());

    this.options.parameters = this.options.callback ?
      this.options.callback(this.element, entry) : entry;

    if(this.options.defaultParams) 
      this.options.parameters += '&' + this.options.defaultParams;
    
    new Ajax.Request(this.url, this.options);
  },

  onComplete: function(request) {
    this.updateChoices(request.responseText);
  }
});

// The local array autocompleter. Used when you'd prefer to
// inject an array of autocompletion options into the page, rather
// than sending out Ajax queries, which can be quite slow sometimes.
//
// The constructor takes four parameters. The first two are, as usual,
// the id of the monitored textbox, and id of the autocompletion menu.
// The third is the array you want to autocomplete from, and the fourth
// is the options block.
//
// Extra local autocompletion options:
// - choices - How many autocompletion choices to offer
//
// - partialSearch - If false, the autocompleter will match entered
//                    text only at the beginning of strings in the 
//                    autocomplete array. Defaults to true, which will
//                    match text at the beginning of any *word* in the
//                    strings in the autocomplete array. If you want to
//                    search anywhere in the string, additionally set
//                    the option fullSearch to true (default: off).
//
// - fullSsearch - Search anywhere in autocomplete array strings.
//
// - partialChars - How many characters to enter before triggering
//                   a partial match (unlike minChars, which defines
//                   how many characters are required to do any match
//                   at all). Defaults to 2.
//
// - ignoreCase - Whether to ignore case when autocompleting.
//                 Defaults to true.
//
// It's possible to pass in a custom function as the 'selector' 
// option, if you prefer to write your own autocompletion logic.
// In that case, the other options above will not apply unless
// you support them.

Autocompleter.Local = Class.create(Autocompleter.Base, {
  initialize: function(element, update, array, options) {
    this.baseInitialize(element, update, options);
    this.options.array = array;
  },

  getUpdatedChoices: function() {
    this.updateChoices(this.options.selector(this));
  },

  setOptions: function(options) {
    this.options = Object.extend({
      choices: 10,
      partialSearch: true,
      partialChars: 2,
      ignoreCase: true,
      fullSearch: false,
      selector: function(instance) {
        var ret       = []; // Beginning matches
        var partial   = []; // Inside matches
        var entry     = instance.getToken();
        var count     = 0;

        for (var i = 0; i < instance.options.array.length &&  
          ret.length < instance.options.choices ; i++) { 

          var elem = instance.options.array[i];
          var foundPos = instance.options.ignoreCase ? 
            elem.toLowerCase().indexOf(entry.toLowerCase()) : 
            elem.indexOf(entry);

          while (foundPos != -1) {
            if (foundPos == 0 && elem.length != entry.length) { 
              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
                elem.substr(entry.length) + "</li>");
              break;
            } else if (entry.length >= instance.options.partialChars && 
              instance.options.partialSearch && foundPos != -1) {
              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
                  foundPos + entry.length) + "</li>");
                break;
              }
            }

            foundPos = instance.options.ignoreCase ? 
              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
              elem.indexOf(entry, foundPos + 1);

          }
        }
        if (partial.length)
          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
        return "<ul>" + ret.join('') + "</ul>";
      }
    }, options || { });
  }
});

// AJAX in-place editor and collection editor
// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).

// Use this if you notice weird scrolling problems on some browsers,
// the DOM might be a bit confused when this gets called so do this
// waits 1 ms (with setTimeout) until it does the activation
Field.scrollFreeActivate = function(field) {
  setTimeout(function() {
    Field.activate(field);
  }, 1);
}

Ajax.InPlaceEditor = Class.create({
  initialize: function(element, url, options) {
    this.url = url;
    this.element = element = $(element);
    this.prepareOptions();
    this._controls = { };
    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
    Object.extend(this.options, options || { });
    if (!this.options.formId && this.element.id) {
      this.options.formId = this.element.id + '-inplaceeditor';
      if ($(this.options.formId))
        this.options.formId = '';
    }
    if (this.options.externalControl)
      this.options.externalControl = $(this.options.externalControl);
    if (!this.options.externalControl)
      this.options.externalControlOnly = false;
    this._originalBackground = this.element.getStyle('background-color') || 'transparent';
    this.element.title = this.options.clickToEditText;
    this._boundCancelHandler = this.handleFormCancellation.bind(this);
    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
    this._boundFailureHandler = this.handleAJAXFailure.bind(this);
    this._boundSubmitHandler = this.handleFormSubmission.bind(this);
    this._boundWrapperHandler = this.wrapUp.bind(this);
    this.registerListeners();
  },
  checkForEscapeOrReturn: function(e) {
    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
    if (Event.KEY_ESC == e.keyCode)
      this.handleFormCancellation(e);
    else if (Event.KEY_RETURN == e.keyCode)
      this.handleFormSubmission(e);
  },
  createControl: function(mode, handler, extraClasses) {
    var control = this.options[mode + 'Control'];
    var text = this.options[mode + 'Text'];
    if ('button' == control) {
      var btn = document.createElement('input');
      btn.type = 'submit';
      btn.value = text;
      btn.className = 'editor_' + mode + '_button';
      if ('cancel' == mode)
        btn.onclick = this._boundCancelHandler;
      this._form.appendChild(btn);
      this._controls[mode] = btn;
    } else if ('link' == control) {
      var link = document.createElement('a');
      link.href = '#';
      link.appendChild(document.createTextNode(text));
      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
      link.className = 'editor_' + mode + '_link';
      if (extraClasses)
        link.className += ' ' + extraClasses;
      this._form.appendChild(link);
      this._controls[mode] = link;
    }
  },
  createEditField: function() {
    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
    var fld;
    if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
      fld = document.createElement('input');
      fld.type = 'text';
      var size = this.options.size || this.options.cols || 0;
      if (0 < size) fld.size = size;
    } else {
      fld = document.createElement('textarea');
      fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
      fld.cols = this.options.cols || 40;
    }
    fld.name = this.options.paramName;
    fld.value = text; // No HTML breaks conversion anymore
    fld.className = 'editor_field';
    if (this.options.submitOnBlur)
      fld.onblur = this._boundSubmitHandler;
    this._controls.editor = fld;
    if (this.options.loadTextURL)
      this.loadExternalText();
    this._form.appendChild(this._controls.editor);
  },
  createForm: function() {
    var ipe = this;
    function addText(mode, condition) {
      var text = ipe.options['text' + mode + 'Controls'];
      if (!text || condition === false) return;
      ipe._form.appendChild(document.createTextNode(text));
    };
    this._form = $(document.createElement('form'));
    this._form.id = this.options.formId;
    this._form.addClassName(this.options.formClassName);
    this._form.onsubmit = this._boundSubmitHandler;
    this.createEditField();
    if ('textarea' == this._controls.editor.tagName.toLowerCase())
      this._form.appendChild(document.createElement('br'));
    if (this.options.onFormCustomization)
      this.options.onFormCustomization(this, this._form);
    addText('Before', this.options.okControl || this.options.cancelControl);
    this.createControl('ok', this._boundSubmitHandler);
    addText('Between', this.options.okControl && this.options.cancelControl);
    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
    addText('After', this.options.okControl || this.options.cancelControl);
  },
  destroy: function() {
    if (this._oldInnerHTML)
      this.element.innerHTML = this._oldInnerHTML;
    this.leaveEditMode();
    this.unregisterListeners();
  },
  enterEditMode: function(e) {
    if (this._saving || this._editing) return;
    this._editing = true;
    this.triggerCallback('onEnterEditMode');
    if (this.options.externalControl)
      this.options.externalControl.hide();
    this.element.hide();
    this.createForm();
    this.element.parentNode.insertBefore(this._form, this.element);
    if (!this.options.loadTextURL)
      this.postProcessEditField();
    if (e) Event.stop(e);
  },
  enterHover: function(e) {
    if (this.options.hoverClassName)
      this.element.addClassName(this.options.hoverClassName);
    if (this._saving) return;
    this.triggerCallback('onEnterHover');
  },
  getText: function() {
    return this.element.innerHTML;
  },
  handleAJAXFailure: function(transport) {
    this.triggerCallback('onFailure', transport);
    if (this._oldInnerHTML) {
      this.element.innerHTML = this._oldInnerHTML;
      this._oldInnerHTML = null;
    }
  },
  handleFormCancellation: function(e) {
    this.wrapUp();
    if (e) Event.stop(e);
  },
  handleFormSubmission: function(e) {
    var form = this._form;
    var value = $F(this._controls.editor);
    this.prepareSubmission();
    var params = this.options.callback(form, value) || '';
    if (Object.isString(params))
      params = params.toQueryParams();
    params.editorId = this.element.id;
    if (this.options.htmlResponse) {
      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
      Object.extend(options, {
        parameters: params,
        onComplete: this._boundWrapperHandler,
        onFailure: this._boundFailureHandler
      });
      new Ajax.Updater({ success: this.element }, this.url, options);
    } else {
      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
      Object.extend(options, {
        parameters: params,
        onComplete: this._boundWrapperHandler,
        onFailure: this._boundFailureHandler
      });
      new Ajax.Request(this.url, options);
    }
    if (e) Event.stop(e);
  },
  leaveEditMode: function() {
    this.element.removeClassName(this.options.savingClassName);
    this.removeForm();
    this.leaveHover();
    this.element.style.backgroundColor = this._originalBackground;
    this.element.show();
    if (this.options.externalControl)
      this.options.externalControl.show();
    this._saving = false;
    this._editing = false;
    this._oldInnerHTML = null;
    this.triggerCallback('onLeaveEditMode');
  },
  leaveHover: function(e) {
    if (this.options.hoverClassName)
      this.element.removeClassName(this.options.hoverClassName);
    if (this._saving) return;
    this.triggerCallback('onLeaveHover');
  },
  loadExternalText: function() {
    this._form.addClassName(this.options.loadingClassName);
    this._controls.editor.disabled = true;
    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
    Object.extend(options, {
      parameters: 'editorId=' + encodeURIComponent(this.element.id),
      onComplete: Prototype.emptyFunction,
      onSuccess: function(transport) {
        this._form.removeClassName(this.options.loadingClassName);
        var text = transport.responseText;
        if (this.options.stripLoadedTextTags)
          text = text.stripTags();
        this._controls.editor.value = text;
        this._controls.editor.disabled = false;
        this.postProcessEditField();
      }.bind(this),
      onFailure: this._boundFailureHandler
    });
    new Ajax.Request(this.options.loadTextURL, options);
  },
  postProcessEditField: function() {
    var fpc = this.options.fieldPostCreation;
    if (fpc)
      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
  },
  prepareOptions: function() {
    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
    [this._extraDefaultOptions].flatten().compact().each(function(defs) {
      Object.extend(this.options, defs);
    }.bind(this));
  },
  prepareSubmission: function() {
    this._saving = true;
    this.removeForm();
    this.leaveHover();
    this.showSaving();
  },
  registerListeners: function() {
    this._listeners = { };
    var listener;
    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
      listener = this[pair.value].bind(this);
      this._listeners[pair.key] = listener;
      if (!this.options.externalControlOnly)
        this.element.observe(pair.key, listener);
      if (this.options.externalControl)
        this.options.externalControl.observe(pair.key, listener);
    }.bind(this));
  },
  removeForm: function() {
    if (!this._form) return;
    this._form.remove();
    this._form = null;
    this._controls = { };
  },
  showSaving: function() {
    this._oldInnerHTML = this.element.innerHTML;
    this.element.innerHTML = this.options.savingText;
    this.element.addClassName(this.options.savingClassName);
    this.element.style.backgroundColor = this._originalBackground;
    this.element.show();
  },
  triggerCallback: function(cbName, arg) {
    if ('function' == typeof this.options[cbName]) {
      this.options[cbName](this, arg);
    }
  },
  unregisterListeners: function() {
    $H(this._listeners).each(function(pair) {
      if (!this.options.externalControlOnly)
        this.element.stopObserving(pair.key, pair.value);
      if (this.options.externalControl)
        this.options.externalControl.stopObserving(pair.key, pair.value);
    }.bind(this));
  },
  wrapUp: function(transport) {
    this.leaveEditMode();
    // Can't use triggerCallback due to backward compatibility: requires
    // binding + direct element
    this._boundComplete(transport, this.element);
  }
});

Object.extend(Ajax.InPlaceEditor.prototype, {
  dispose: Ajax.InPlaceEditor.prototype.destroy
});

Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
  initialize: function($super, element, url, options) {
    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
    $super(element, url, options);
  },

  createEditField: function() {
    var list = document.createElement('select');
    list.name = this.options.paramName;
    list.size = 1;
    this._controls.editor = list;
    this._collection = this.options.collection || [];
    if (this.options.loadCollectionURL)
      this.loadCollection();
    else
      this.checkForExternalText();
    this._form.appendChild(this._controls.editor);
  },

  loadCollection: function() {
    this._form.addClassName(this.options.loadingClassName);
    this.showLoadingText(this.options.loadingCollectionText);
    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
    Object.extend(options, {
      parameters: 'editorId=' + encodeURIComponent(this.element.id),
      onComplete: Prototype.emptyFunction,
      onSuccess: function(transport) {
        var js = transport.responseText.strip();
        if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
          throw 'Server returned an invalid collection representation.';
        this._collection = eval(js);
        this.checkForExternalText();
      }.bind(this),
      onFailure: this.onFailure
    });
    new Ajax.Request(this.options.loadCollectionURL, options);
  },

  showLoadingText: function(text) {
    this._controls.editor.disabled = true;
    var tempOption = this._controls.editor.firstChild;
    if (!tempOption) {
      tempOption = document.createElement('option');
      tempOption.value = '';
      this._controls.editor.appendChild(tempOption);
      tempOption.selected = true;
    }
    tempOption.update((text || '').stripScripts().stripTags());
  },

  checkForExternalText: function() {
    this._text = this.getText();
    if (this.options.loadTextURL)
      this.loadExternalText();
    else
      this.buildOptionList();
  },

  loadExternalText: function() {
    this.showLoadingText(this.options.loadingText);
    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
    Object.extend(options, {
      parameters: 'editorId=' + encodeURIComponent(this.element.id),
      onComplete: Prototype.emptyFunction,
      onSuccess: function(transport) {
        this._text = transport.responseText.strip();
        this.buildOptionList();
      }.bind(this),
      onFailure: this.onFailure
    });
    new Ajax.Request(this.options.loadTextURL, options);
  },

  buildOptionList: function() {
    this._form.removeClassName(this.options.loadingClassName);
    this._collection = this._collection.map(function(entry) {
      return 2 === entry.length ? entry : [entry, entry].flatten();
    });
    var marker = ('value' in this.options) ? this.options.value : this._text;
    var textFound = this._collection.any(function(entry) {
      return entry[0] == marker;
    }.bind(this));
    this._controls.editor.update('');
    var option;
    this._collection.each(function(entry, index) {
      option = document.createElement('option');
      option.value = entry[0];
      option.selected = textFound ? entry[0] == marker : 0 == index;
      option.appendChild(document.createTextNode(entry[1]));
      this._controls.editor.appendChild(option);
    }.bind(this));
    this._controls.editor.disabled = false;
    Field.scrollFreeActivate(this._controls.editor);
  }
});

//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
//**** This only  exists for a while,  in order to  let ****
//**** users adapt to  the new API.  Read up on the new ****
//**** API and convert your code to it ASAP!            ****

Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
  if (!options) return;
  function fallback(name, expr) {
    if (name in options || expr === undefined) return;
    options[name] = expr;
  };
  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
    options.cancelLink == options.cancelButton == false ? false : undefined)));
  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
    options.okLink == options.okButton == false ? false : undefined)));
  fallback('highlightColor', options.highlightcolor);
  fallback('highlightEndColor', options.highlightendcolor);
};

Object.extend(Ajax.InPlaceEditor, {
  DefaultOptions: {
    ajaxOptions: { },
    autoRows: 3,                                // Use when multi-line w/ rows == 1
    cancelControl: 'link',                      // 'link'|'button'|false
    cancelText: 'cancel',
    clickToEditText: 'Click to edit',
    externalControl: null,                      // id|elt
    externalControlOnly: false,
    fieldPostCreation: 'activate',              // 'activate'|'focus'|false
    formClassName: 'inplaceeditor-form',
    formId: null,                               // id|elt
    highlightColor: '#ffff99',
    highlightEndColor: '#ffffff',
    hoverClassName: '',
    htmlResponse: true,
    loadingClassName: 'inplaceeditor-loading',
    loadingText: 'Loading...',
    okControl: 'button',                        // 'link'|'button'|false
    okText: 'ok',
    paramName: 'value',
    rows: 1,                                    // If 1 and multi-line, uses autoRows
    savingClassName: 'inplaceeditor-saving',
    savingText: 'Saving...',
    size: 0,
    stripLoadedTextTags: false,
    submitOnBlur: false,
    textAfterControls: '',
    textBeforeControls: '',
    textBetweenControls: ''
  },
  DefaultCallbacks: {
    callback: function(form) {
      return Form.serialize(form);
    },
    onComplete: function(transport, element) {
      // For backward compatibility, this one is bound to the IPE, and passes
      // the element directly.  It was too often customized, so we don't break it.
      new Effect.Highlight(element, {
        startcolor: this.options.highlightColor, keepBackgroundImage: true });
    },
    onEnterEditMode: null,
    onEnterHover: function(ipe) {
      ipe.element.style.backgroundColor = ipe.options.highlightColor;
      if (ipe._effect)
        ipe._effect.cancel();
    },
    onFailure: function(transport, ipe) {
      alert('Error communication with the server: ' + transport.responseText.stripTags());
    },
    onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
    onLeaveEditMode: null,
    onLeaveHover: function(ipe) {
      ipe._effect = new Effect.Highlight(ipe.element, {
        startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
        restorecolor: ipe._originalBackground, keepBackgroundImage: true
      });
    }
  },
  Listeners: {
    click: 'enterEditMode',
    keydown: 'checkForEscapeOrReturn',
    mouseover: 'enterHover',
    mouseout: 'leaveHover'
  }
});

Ajax.InPlaceCollectionEditor.DefaultOptions = {
  loadingCollectionText: 'Loading options...'
};

// Delayed observer, like Form.Element.Observer, 
// but waits for delay after last key input
// Ideal for live-search fields

Form.Element.DelayedObserver = Class.create({
  initialize: function(element, delay, callback) {
    this.delay     = delay || 0.5;
    this.element   = $(element);
    this.callback  = callback;
    this.timer     = null;
    this.lastValue = $F(this.element); 
    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
  },
  delayedListener: function(event) {
    if(this.lastValue == $F(this.element)) return;
    if(this.timer) clearTimeout(this.timer);
    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
    this.lastValue = $F(this.element);
  },
  onTimerEvent: function() {
    this.timer = null;
    this.callback(this.element, $F(this.element));
  }
});
 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/bell.js
AUTOCORE_SELF_CHECK.push("bell.js");
//
// Chris Coyne 2003, 2004
//
// draws a bell curve over a range,
// including a dynamically colored underbar indicating
// match goodness
//
// mean = mean
// dev = standard deviation
// x2,x2 = range to display
// width, height = pixels of whole image
//
// COPYRIGHT -- OKCUPID! Not for public ripoff.
//

/*
Revamped to prepare data for google maps
*/

/*
Nothing here that couldn't be done in the backend at some future point
*/

function calc_it(x, mean, dev) {
	numerator = Math.exp(-(x-mean)*(x-mean)/(2*dev*dev));
	denominator = dev*Math.sqrt(2*Math.PI);
	return (numerator/denominator);
}
   
function generate_bell_data(mean, dev, x1, x2, width, height) {
    // Google parameters
    
    var height = 62,
        simpleEncoding = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
        cutoff1 = mean - dev*1,
        cutoff2 = mean + dev*1;
    
  	// cheat in rare case where bell is too thin to be seen
  	if (dev < (x2-x1)/width) dev = (x2-x1)/width;
	
	var res="",
		highest_in_range;
   
	// find the maximum value over the range first, so we can scale it
	for (i = 0; i < width; i++) {
	  	x = x1 + (x2-x1) * i/(width - 2);
	  	value = calc_it(x, mean, dev);
	  	if (i == 0 || value > highest_in_range) highest_in_range = value;
	}
	
	currentheight = 0;
  	x = 0;

	data = "";
    devi = "";

    ratio_calc = 100/width;

  	for (i = 0; i < width; i++) {
    	x = x1 + (x2-x1) * i/(width - 2);
		currentheight   = Math.round((height-2)*calc_it(x, mean, dev)/highest_in_range);
    	data            += simpleEncoding.charAt(currentheight);
        inrange         = Math.round(i*ratio_calc);
        if(inrange >= cutoff1 && inrange < cutoff2) 
            devi        += simpleEncoding.charAt(currentheight-1);
        else 
            devi        += simpleEncoding.charAt(0);
	}
 	return [data,devi];
}

function percent(ratio) {
    return(Math.round(ratio*1000)/10) + "%";
} 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/board.js
AUTOCORE_SELF_CHECK.push("board.js");

// -*- c -*-;
//------------------------------------------------------------------------
// $Id: $
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// "Blog this" functionality!
//------------------------------------------------------------------------



var BlogThisData = new Array();

// Now with trackback info!
// This was unavoidable; submittemplate
// refers to flat url location (e.g. "/shit.html")
// while resulttemplate is server by journal
// and is relative to okcontent
//
//

var PersistantBoard = {};

function showBlogThis(linkhtml, posttitle, quotedtext, uid, submittemplate, resulttemplate
    , trackback_username
    , trackback_boardid
    , trackback_url
    , trackback_uservar
    , trackback_title
    , opt_out
    , authcode) {

    var len = BlogThisData.length;
    BlogThisData[len] = posttitle;
    BlogThisData[len+1] = quotedtext;
    BlogThisData[len+2] = uid;
    BlogThisData[len+3] = submittemplate;
    BlogThisData[len+4] = resulttemplate;
    BlogThisData[len+5] = "BlogThisDiv_" + len;
    BlogThisData[len+6] = trackback_username;
    BlogThisData[len+7] = trackback_boardid;
    BlogThisData[len+8] = trackback_url;
    BlogThisData[len+9] = trackback_uservar;
    BlogThisData[len+10] = trackback_title;
    BlogThisData[len+11] = authcode;
    
    if(opt_out) {
        document.write('<a class="inner last" href="javascript:clickBlogThis('+ len  + ')" class="blog-this">'
         + linkhtml
         + '</a>');                   
        
    } else {
        document.write('<div id="BlogThisDiv_'+len+'" class="BlogThisDiv">'
         + '<a href="javascript:clickBlogThis('+ len  + ')" class="blog-this">'
         + linkhtml
         + '</a>'
         + '</div>');                   
    }

}

//
// Similar, but this version doesn't require a button, but rather just the text you want to edit in
//
function invokeBlogThis(posttitle, quotedtext, editdivid, uid, submittemplate, resulttemplate
    , trackback_username
    , trackback_boardid
    , trackback_url
    , trackback_uservar
    , trackback_title
	, authcode) {

    var len = BlogThisData.length;
    BlogThisData[len] = posttitle;
    BlogThisData[len+1] = quotedtext;
    BlogThisData[len+2] = uid;
    BlogThisData[len+3] = submittemplate;
    BlogThisData[len+4] = resulttemplate;
    BlogThisData[len+5] = editdivid;
    BlogThisData[len+6] = trackback_username;
    BlogThisData[len+7] = trackback_boardid;
    BlogThisData[len+8] = trackback_url;
    BlogThisData[len+9] = trackback_uservar;
    BlogThisData[len+10] = trackback_title;
	BlogThisData[len+11] = authcode;
    
    clickBlogThis(len);
    
}

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

function clickBlogThis(num)
{
    var params = ({
        "tuid" : BlogThisData[num+2],
        "quotedtext" :  BlogThisData[num+1],
        "posttitle" : BlogThisData[num],
        "rand" : Math.round(Math.random()*1000000),
        "num" : num });
		
    var ajax = new Ajax.Updater(BlogThisData[num+5], BlogThisData[num+3], {method: 'post', parameters: params} );
}

function submitBlogThis(num)
{
    var tbackheader = "";

    var params = ({
            "tuid" : BlogThisData[num+2],
            "title" : BlogThisData[num],
            "content" : tbackheader + "<blockquote>" + BlogThisData[num+1] + "</blockquote>" + $F("content_" + num),
            "commentsecurity" : 0,
            "commentapproval" : 0,
            "postsecurity" : 0,
            "rand" : Math.round(Math.random()*1000000),
            "formatting" :  $F("formatting_"+num),
            "add" : 1,
            "ajax" : 1,
            "resulttemplate" : BlogThisData[num+4],
			"authcode" : BlogThisData[num+11]
        });

    if (BlogThisData[num+6] != "")
        params["trackback_username"] = BlogThisData[num+6];
    if (BlogThisData[num+7] != "")
        params["trackback_boardid"] = BlogThisData[num+7];
    if (BlogThisData[num+8] != "")
        params["trackback_url"] = BlogThisData[num+8];
    if (BlogThisData[num+9] != "")
        params["trackback_uservar"] = BlogThisData[num+9];
    if (BlogThisData[num+10] != "")
        params["trackback_title"] = BlogThisData[num+10];    

    var ajax = new Ajax.Updater(BlogThisData[num+5], "/journal",
        {method: 'post',
         parameters: (params)});
}

//------------------------------------------------------------------------
// callback functions
//------------------------------------------------------------------------

function gotData(data) 
{
//	alert(data.responseText);



    data = eval('(' + stripBreaks(data.responseText) + ')');
	
	
	$('comment_add_' + data.boardid).style.display="none";
	
	$('comment_submit_message_' + data.boardid).style.display="none";

    if (data.error && data.error == "not logged in") {
        url = "/signup?p=" + escape("/board") + "&key1=redirect&val1=1&key2=add&val2=1&key3=bid&val3=" + data.bid + "&key4=url&val4=" + escape(data.board_url) + "&key5=boardname&val5=" + escape(data.boardname) + "&key6=text&val6=" + escape(data.comment) + "&key7=uservar&val7=" + escape(data.uservar);
        window.location.href = url;
    }
    else {
        

		
		if (data.has_pending_comments == 0)
		{
			alert("pending comments gone for " + data.boardid);
			if ($("pending_comments_"+data.boardid))
			{
				$("pending_comments_"+data.boardid).style.display = "none";
			}
		}
		


		var navstuff;
		if ($("comment_nav_" + data.boardid)) {

            $A($("comment_nav_" + data.boardid).getElementsByTagName("li")).each(function(obj){

                Element.removeClassName(obj,"active");

                if(typeof obj.stashit != "undefined") obj.innerHTML = obj.stashit;
			    if(obj.className == "st_" + starting) {
			        obj.className = obj.className + " active";
			        span = obj.getElementsByTagName("a")[0].innerHTML;
			        obj.stashit = obj.innerHTML;
			        obj.innerHTML = "<span>" + span + "</span>";
			    }

			});

            
            
			navstuff = ""
			    + $("comment_nav_" + data.boardid).getElementsByTagName("ul")[0].innerHTML 
    			+ "<li><a href='#nogo' id='close_" + data.boardid
    			+ "' onclick=\"$('comment_load_"+data.boardid+"').innerHTML = '';\""
    			+ ">Close</a></li>";

		} else if(data.num_comments != 0) {



			navstuff = "<li class='st_0 active'><span>1 - "+data.num_comments+"</span></li>"
			    + "<li><a href='#nogo' id='close_" + data.boardid
    			+ "' onclick=\"$('comment_load_"+data.boardid+"').innerHTML = '';\""
    			+ ">Close</a></li>";
			
			
		} else {
		    navstuff = "";
		}
		


		var top_nav = "<div class='comment_nav' id='comment_nav_top_" + data.boardid + "'><ul>" + navstuff + "</ul></div>";
		var bottom_nav = "<div class='comment_nav' id='comment_nav_" + data.boardid + "'><ul>" + navstuff + "</ul></div>";

		var url = "/journal?tuid=" + data.ownerid + "&pid=" + data.extra;

		var comment_added_str = "";
		if (data.added_comment != undefined)
		{
		    $('comment_add_link_' + data.boardid).innerHTML="<span class='holder'>Comment added.</span>";
		}

		var hide_top_nav = 0;
		if (data.num_comments != undefined)
		{
			if (data.num_comments == 0) { hide_top_nav = 1; }
		}
		// $("comment_load_" + data.boardid).style.display = "block";
		if (!hide_top_nav)
		{
			$("comment_load_" + data.boardid).innerHTML = top_nav + data.content + comment_added_str + bottom_nav;
		}
		else
		{
			$("comment_load_" + data.boardid).innerHTML = bottom_nav;
		}
		if($("loadup_link_"+data.boardid).stashit) $("loadup_link_"+data.boardid).innerHTML = $("loadup_link_"+data.boardid).stashit;
//		window.location.hash= '#' + data.boardid;
    }
}

function gotInterestingJournalData(data) {
    data = eval('(' + stripBreaks(data.responseText) + ')');
    if (data.error && data.error == "not logged in") {
        url = "/signup?p=" + escape("/board") + "&key1=redirect&val1=1&key2=add&val2=1&key3=bid&val3=" + data.bid + "&key4=url&val4=" + escape(data.board_url) + "&key5=boardname&val5=" + escape(data.boardname) + "&key6=text&val6=" + escape(data.comment) + "&key7=uservar&val7=" + escape(data.uservar);
        window.location.href = url;
    }
    else {
			$(data.boardid).innerHTML = data.content;
			if (window.postJournalLoad) {
				postJournalLoad();
			}
    }
}


function failed(err) {

}

function gotJournal(data) {
    data = eval('(' + stripBreaks(data.responseText) + ')');
    if (!data.empty) {
	$("jnl" + data.userid).innerHTML = data.content;
    }
}

function failedJournal(err) {
    // not sure what i can do here...
    // probably should display some error to the user
}

function gotCmtCount(data) {
    data = eval('(' + stripBreaks(data.responseText) + ')');
    var cmtstr = " Comments";
    if (data.count == 1 || data.count == "1") {
        cmtstr = " Comment";
    }
    var url = "/journal?tuid=" + data.ownerid + "&pid=" + data.extra;

	var count = data.count;
	var pending_count = data.pending_count;
	var page_size = 20;
	var pageLink = "";
	var pageLinkClose = "";
	var num_pages = Math.floor ((count + page_size - 1) / page_size);

	if (count > 0) {
		
		pageLink += "<a id='loadup_link_"+data.boardid+"' href=\"javascript:loadIDBoard('" +
			data.boardid + "','" + data.boardname + "','" + data.uservar +
			"','" + data.ownerid + "','" + data.userid + "','" + data.extra
			+ "', '" + 0 + "','" + page_size + "','" + url + "','')\">";
			
	    if (pending_count > 0)
    	{
    		pageLinkClose += "  (" + pending_count + " pending)";
    	}	
		
		pageLinkClose += "</a>";
		
	} else {
	    pageLink = "<span class='holder'>";
	    pageLinkClose = "</span>";
	}

	var content = "<div class='comment_count'>" + pageLink + count + cmtstr + pageLinkClose;

	
	


	content += "</div>";

	if (count > 0) {
		content += "<div class='comment_nav' id='comment_nav_" + data.boardid + "'><ul>";

		for (var i = 0; i < num_pages; ++i) {
		    start = i*20 + 1;
		    end = start + 19;
		    end = (end > count ? count : end);

            content += "<li class='st_"+(i*20)+"'><a id=\"comment_page_" + (i+1) + "_" + data.boardid + "\" href=\"javascript:loadIDBoard(" 
				+ "'" + data.boardid + "',"
				+ "'" + data.boardname + "',"
				+ "'" + data.uservar + "',"
				+ "'" + data.ownerid + "',"
				+ "'" + data.userid + "',"
				+ "'" + data.extra + "',"
				+ "'" + i * page_size + "',"
				+ "'" + page_size + "',"
				+ "'" + url + "','')"
				+"\">" + start +" - "+ end + "</a></li>";
			
		}

		content += "</ul></div>";
	}
    $(data.boardid).innerHTML = content;
}

function failedCmtCount(data) {
}

function gotActionPosts(data)
{
   	data = eval('(' + stripBreaks(data.responseText) + ')');
	$("action_posts").innerHTML = data.content;
}

function failedActionPosts(data)
{
}

//------------------------------------------------------------------------
// cache
//------------------------------------------------------------------------

function cacheBust() {
  return "&cacheBust=" + Math.round(Math.random()*100000000);
}

//------------------------------------------------------------------------
// general display functions
//------------------------------------------------------------------------

 function stripBreaks(str) {
   var res = str.replace(/\n/g," ");
   res = res.replace(/\n/g," ");
   return res;
 }

 function confirmPostDelete(postid) {
    if (confirm("Are you totally sure you want to delete this post?")) {
       window.location="/jpost?delete="+postid;
    }
 }

//------------------------------------------------------------------------
// utilty functions 
//------------------------------------------------------------------------

function newreq(params) {

    return new Ajax.Request("/board",
	                    {method: 'post', 
                             parameters: params, 
                             onSuccess: gotData, 
                             onFailure: failed});
}

function newjreq(params) {
//    alert(params);
    return new Ajax.Request("/journal",
	                    {method: 'post', 
                             parameters: params, 
                             onSuccess: gotJournal, 
                             onFailure: failedJournal});
}

function gettzo() {
    return "tzo=" + (new Date().getTimezoneOffset()/-60);
}

//------------------------------------------------------------------------
// action functions
//------------------------------------------------------------------------

function addComment(formid) {
    var myajax = newreq("add=1&start=" + starting + "&limit=20&" + Form.serialize(formid) + "&" + gettzo() + cacheBust());
}

function delComment(boardid) {
    var myajax = newreq("del=1&" + Form.serialize('cmtmodify_' + boardid) + "&" + gettzo() + cacheBust());
}

function delCommentInline(boardid, boardname, uservar, ownerid, userid, cid)
{
    var req = "del=1&bid=" + boardid + "&boardname=" + escape(boardname) + "&uservar=" 
		+ escape(uservar) + "&ownerid=" + escape(ownerid) + "&uid=" + userid 
		+ "&cid=" + escape(cid) + "&" + gettzo() + cacheBust();

    var myajax = newreq(req);
}

function approveCommentInline(boardid, boardname, uservar, ownerid, userid, cid)
{
    var req = "approve=1&bid=" + boardid + "&boardname=" + escape(boardname) + "&uservar=" 
		+ escape(uservar) + "&ownerid=" + escape(ownerid) + "&uid=" + userid 
		+ "&cid=" + escape(cid) + "&" + gettzo() + cacheBust();

    var myajax = newreq(req);
}

function boardActionInline(action, boardid, boardname, uservar, ownerid, userid, cid)
{
    if(!starting) starting = 0;
    var req = escape(action) + "=1&bid=" + boardid
		+ "&boardname=" + escape(boardname) 
		+ "&uservar=" + escape(uservar) 
		+ "&ownerid=" + escape(ownerid)
		+ "&uid=" + userid 
		+ "&cid=" + escape(cid)
		+ "&start=" + starting + "&limit=20"
		+ "&" + gettzo() + cacheBust();

    var myajax = newreq(req);
}

function gotUpdateResult(data)
{
   	data = eval('(' + stripBreaks(data.responseText) + ')');
	switch(data.action) 
	{
	    case "app": message     = "Comment approved!"; break;
	    case "del": message     = "Comment deleted"; break;
	    case "pend": message    = "Comment set to pending"; break;
	    case "delblock": message    = "Comment deleted, user blocked!"; break;
	    default: message        = "There was an error"; break;
	}
	$('actions_'+data.commentid).innerHTML = "<em class='comment_result'>"+message+"</em>";
}

function failedUpdateResult(err)
{
    alert(err);
}

function commentActionInline(action, postid, commentid, postownerid, commenterid)
{
    if(!starting) starting = 0;
    var req = "action=" + escape(action)
    + "&postid=" + escape(postid)
    + "&commentid=" + escape(commentid)
    + "&postownerid=" + escape(postownerid)
    + "&commenterid=" + escape(commenterid)

    // + "&ownerid=" + escape(ownerid)
    // + "&uid=" + userid 
    // + "&cid=" + escape(cid)
    // + "&start=" + starting + "&limit=20"
    // + "&" + gettzo() + cacheBust();
    var myajax = new Ajax.Request("/journal",
	                    {method: 'post', 
                             parameters: req, 
                             onSuccess: gotUpdateResult, 
                             onFailure: failedUpdateResult});
}

function delAndBlock(boardid) {
    var myajax = newreq("delblock=1&" + Form.serialize('cmtmodify_' + boardid) + "&" + gettzo() + cacheBust());
}

function loadBoard(boardid, boardname, userid) {
    var myajax = newreq("bid=" + boardid + "&uid=" + userid + "&boardname=" + escape(boardname) + "&" + gettzo() + cacheBust());
}

var starting = 0;

function loadIDBoard(boardid, boardname, uservar, ownerid, userid, postid, start, limit, s_url, cmtid) 
{
    starting = start;

    $("loadup_link_"+boardid).stashit = $("loadup_link_"+boardid).innerHTML;
    $("loadup_link_"+boardid).innerHTML = "Loading Comments";

    var req = "bid=" + boardid + "&boardname=" + escape(boardname) + "&uservar=" 
		+ escape(uservar) + "&ownerid=" + escape(ownerid) + "&uid=" + userid 
		+ "&extra=" + postid + "&start=" + start + "&limit=" + limit + "&cmtid=" 
        + cmtid + "&" + gettzo() + cacheBust();

    if (s_url != null && s_url.length > 0) {
        req += "&tpurl=" + escape(s_url);
    }

    var myajax = newreq(req);
}

function loadRecent(recentid, userid) 
{
    var myajax = newreq("recent=" + escape(recentid) + "&uid=" + userid + "&" + gettzo() + cacheBust());
}

function loadRelevant(relevantid, userid, includecomments) 
{
    var params;
    if (includecomments)
       params = "relevant=" + escape(relevantid) + "&userid=" + userid + "&" + gettzo() + "&comments=1&posts=1" + cacheBust();
    else
       params = "relevant=" + escape(relevantid) + "&userid=" + userid + "&" + gettzo() + "&posts=1" + cacheBust();       

    var myajax = new Ajax.Request("/relevant",
				  {method: 'get',
				   parameters: params,
				   onSuccess: gotData,
				   onFailure: failed});
}

function loadRelevantStrangers(relevantid, userid, gender, orientation) 
{
    var params = "relevant=" + escape(relevantid) 
             + "&userid=" + userid + "&g=" + gender + "&o=" 
	     + orientation +"&" + gettzo() + "&comments=1&posts=1" 
	     + cacheBust();
    var myajax = new Ajax.Request("/relevant",
				  {method: 'get',
				   parameters: params,
				   onSuccess: gotData,
				   onFailure: failed});
}

function loadInterestingJournalPosts(relevantid, userid, limit, start, maxperuser,
	timestamp_weight, match_weight, distance_weight, age_diff_weight, 
	gentation_weight, buddy_weight) 
{
    var params = "relevant=" + escape(relevantid) 
	     + "&userid=" + userid
	     + "&limit=" + limit
	     + "&start=" + start	
		 + "&maxperuser=" + maxperuser
	     + "&interesting=1"
	     + "&timestamp_weight="+timestamp_weight
	     + "&match_weight="+match_weight
	     + "&distance_weight="+distance_weight
		 + "&age_diff_weight="+age_diff_weight
	     + "&gentation_weight="+gentation_weight
         + "&buddy_weight="+buddy_weight
   		 + "&" + gettzo()
	     + cacheBust();
   var myajax = new Ajax.Request("/relevant",
				  {method: 'get',
				   parameters: params,
				   onSuccess: gotInterestingJournalData,
				   onFailure: failed});
}


function loadJournal(userid) {
    var myajax = newjreq("method=axml&tuid=" + userid + "&" + gettzo() + cacheBust());
}


// WARNING:  this should only be used for journal posts
//
function loadCommentCount(count_pending, boardid, boardname, uservar, ownerid, userid, postid, cmtid) {
	
	PersistantBoard[boardid] = {};
	
	PersistantBoard[boardid].countPending 	= count_pending;
	PersistantBoard[boardid].user_id		= userid;
	PersistantBoard[boardid].post_id 		= postid;
	PersistantBoard[boardid].cmt_id 		= cmtid;
	
	
    var params = "getcount=1&count_pending=" + count_pending + "&bid=" + boardid + "&boardname=" +
		escape(boardname) + "&uservar=" + escape(uservar) + "&ownerid=" +
		escape(ownerid) + "&uid=" + userid + "&extra=" + postid + "&" + 
		gettzo() + cacheBust();
    var myajax = new Ajax.Request("/board",
			          {method: 'get', 
					   parameters: params,
					   onSuccess: gotCmtCount, 
					   onFailure: failedCmtCount});
    return myajax;
}

function loadActionPosts(boardid, boardname, uservar, ownerid, userid, postid) {
    var params = "get_action_posts=1&bid=" + boardid + "&boardname=" +
		escape(boardname) + "&uservar=" + escape(uservar) + "&ownerid=" +
		escape(ownerid) + "&uid=" + userid + "&extra=" + postid + "&" + 
		gettzo() + cacheBust();
    var myajax = new Ajax.Request("/board",
			          {method: 'post', 
					   parameters: params,
					   onSuccess: gotActionPosts, 
					   onFailure: failedActionPosts});
    return myajax;
}


//------------------------------------------------------------------------
// show functions
//------------------------------------------------------------------------

function showDeleteForm2(boardid) {
    var mydiv = $("comments_modify_js_" + boardid);
    var x = '<input type=hidden name=url value="' +
		window.location.pathname + window.location.search + 
		'">' + '<input type=hidden name=loghash value="' +
		encodeURI(getCookie("data")) +
		'">' + '<input type=hidden name=uid value=' +
		CURRENTUSERID + '>';
    mydiv.innerHTML = x;
}

function toggleModify(boardid) {
    divdisp = $("comments_display_" + boardid);
    divmod = $("comments_modify_" + boardid);
    if (divdisp.style.display == "none") {
        divdisp.style.display = "block";
        divmod.style.display = "none";
    }
    else {
        divdisp.style.display = "none";
        divmod.style.display = "block";
        showDeleteForm2(boardid);
    }
}

//------------------------------------------------------------------------
// END
//------------------------------------------------------------------------
 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/diff_match_patch.js
AUTOCORE_SELF_CHECK.push("diff_match_patch.js");
function diff_match_patch(){this.Diff_Timeout=1.0;this.Diff_EditCost=4;this.Diff_DualThreshold=32;this.Match_Balance=0.5;this.Match_Threshold=0.5;this.Match_MinLength=100;this.Match_MaxLength=1000;this.Patch_Margin=4;function getMaxBits(){var maxbits=0;var oldi=1;var newi=2;while(oldi!=newi){maxbits++;oldi=newi;newi=newi<<1}return maxbits}this.Match_MaxBits=getMaxBits()}var DIFF_DELETE=-1;var DIFF_INSERT=1;var DIFF_EQUAL=0;diff_match_patch.prototype.diff_main=function(text1,text2,opt_checklines){if(text1==text2){return[[DIFF_EQUAL,text1]]}if(typeof opt_checklines=='undefined'){opt_checklines=true}var checklines=opt_checklines;var commonlength=this.diff_commonPrefix(text1,text2);var commonprefix=text1.substring(0,commonlength);text1=text1.substring(commonlength);text2=text2.substring(commonlength);commonlength=this.diff_commonSuffix(text1,text2);var commonsuffix=text1.substring(text1.length-commonlength);text1=text1.substring(0,text1.length-commonlength);text2=text2.substring(0,text2.length-commonlength);var diffs=this.diff_compute(text1,text2,checklines);if(commonprefix){diffs.unshift([DIFF_EQUAL,commonprefix])}if(commonsuffix){diffs.push([DIFF_EQUAL,commonsuffix])}this.diff_cleanupMerge(diffs);return diffs};diff_match_patch.prototype.diff_compute=function(text1,text2,checklines){var diffs;if(!text1){return[[DIFF_INSERT,text2]]}if(!text2){return[[DIFF_DELETE,text1]]}var longtext=text1.length>text2.length?text1:text2;var shorttext=text1.length>text2.length?text2:text1;var i=longtext.indexOf(shorttext);if(i!=-1){diffs=[[DIFF_INSERT,longtext.substring(0,i)],[DIFF_EQUAL,shorttext],[DIFF_INSERT,longtext.substring(i+shorttext.length)]];if(text1.length>text2.length){diffs[0][0]=diffs[2][0]=DIFF_DELETE}return diffs}longtext=shorttext=null;var hm=this.diff_halfMatch(text1,text2);if(hm){var text1_a=hm[0];var text1_b=hm[1];var text2_a=hm[2];var text2_b=hm[3];var mid_common=hm[4];var diffs_a=this.diff_main(text1_a,text2_a,checklines);var diffs_b=this.diff_main(text1_b,text2_b,checklines);return diffs_a.concat([[DIFF_EQUAL,mid_common]],diffs_b)}if(checklines&&text1.length+text2.length<250){checklines=false}var linearray;if(checklines){var a=this.diff_linesToChars(text1,text2);text1=a[0];text2=a[1];linearray=a[2]}diffs=this.diff_map(text1,text2);if(!diffs){diffs=[[DIFF_DELETE,text1],[DIFF_INSERT,text2]]}if(checklines){this.diff_charsToLines(diffs,linearray);this.diff_cleanupSemantic(diffs);diffs.push([DIFF_EQUAL,'']);var pointer=0;var count_delete=0;var count_insert=0;var text_delete='';var text_insert='';while(pointer<diffs.length){if(diffs[pointer][0]==DIFF_INSERT){count_insert++;text_insert+=diffs[pointer][1]}else if(diffs[pointer][0]==DIFF_DELETE){count_delete++;text_delete+=diffs[pointer][1]}else{if(count_delete>=1&&count_insert>=1){var a=this.diff_main(text_delete,text_insert,false);diffs.splice(pointer-count_delete-count_insert,count_delete+count_insert);pointer=pointer-count_delete-count_insert;for(var j=a.length-1;j>=0;j--){diffs.splice(pointer,0,a[j])}pointer=pointer+a.length}count_insert=0;count_delete=0;text_delete='';text_insert=''}pointer++}diffs.pop()}return diffs};diff_match_patch.prototype.diff_linesToChars=function(text1,text2){var linearray=[];var linehash={};linearray.push('');function diff_linesToCharsMunge(text){var chars='';while(text){var i=text.indexOf('\n');if(i==-1){i=text.length}var line=text.substring(0,i+1);text=text.substring(i+1);if(linehash.hasOwnProperty?linehash.hasOwnProperty(line):(linehash[line]!==undefined)){chars+=String.fromCharCode(linehash[line])}else{linearray.push(line);linehash[line]=linearray.length-1;chars+=String.fromCharCode(linearray.length-1)}}return chars}var chars1=diff_linesToCharsMunge(text1);var chars2=diff_linesToCharsMunge(text2);return[chars1,chars2,linearray]};diff_match_patch.prototype.diff_charsToLines=function(diffs,linearray){for(var x=0;x<diffs.length;x++){var chars=diffs[x][1];var text=[];for(var y=0;y<chars.length;y++){text.push(linearray[chars.charCodeAt(y)])}diffs[x][1]=text.join('')}};diff_match_patch.prototype.diff_map=function(text1,text2){var ms_end=(new Date()).getTime()+this.Diff_Timeout*1000;var max_d=text1.length+text2.length-1;var doubleEnd=this.Diff_DualThreshold*2<max_d;var v_map1=[];var v_map2=[];var v1={};var v2={};v1[1]=0;v2[1]=0;var x,y;var footstep;var footsteps={};var done=false;var hasOwnProperty=!!(footsteps.hasOwnProperty);var front=(text1.length+text2.length)%2;for(var d=0;d<max_d;d++){if(this.Diff_Timeout>0&&(new Date()).getTime()>ms_end){return null}v_map1[d]={};for(var k=-d;k<=d;k+=2){if(k==-d||k!=d&&v1[k-1]<v1[k+1]){x=v1[k+1]}else{x=v1[k-1]+1}y=x-k;if(doubleEnd){footstep=x+','+y;if(front&&(hasOwnProperty?footsteps.hasOwnProperty(footstep):(footsteps[footstep]!==undefined))){done=true}if(!front){footsteps[footstep]=d}}while(!done&&x<text1.length&&y<text2.length&&text1.charAt(x)==text2.charAt(y)){x++;y++;if(doubleEnd){footstep=x+','+y;if(front&&(hasOwnProperty?footsteps.hasOwnProperty(footstep):(footsteps[footstep]!==undefined))){done=true}if(!front){footsteps[footstep]=d}}}v1[k]=x;v_map1[d][x+','+y]=true;if(x==text1.length&&y==text2.length){return this.diff_path1(v_map1,text1,text2)}else if(done){v_map2=v_map2.slice(0,footsteps[footstep]+1);var a=this.diff_path1(v_map1,text1.substring(0,x),text2.substring(0,y));return a.concat(this.diff_path2(v_map2,text1.substring(x),text2.substring(y)))}}if(doubleEnd){v_map2[d]={};for(var k=-d;k<=d;k+=2){if(k==-d||k!=d&&v2[k-1]<v2[k+1]){x=v2[k+1]}else{x=v2[k-1]+1}y=x-k;footstep=(text1.length-x)+','+(text2.length-y);if(!front&&(hasOwnProperty?footsteps.hasOwnProperty(footstep):(footsteps[footstep]!==undefined))){done=true}if(front){footsteps[footstep]=d}while(!done&&x<text1.length&&y<text2.length&&text1.charAt(text1.length-x-1)==text2.charAt(text2.length-y-1)){x++;y++;footstep=(text1.length-x)+','+(text2.length-y);if(!front&&(hasOwnProperty?footsteps.hasOwnProperty(footstep):(footsteps[footstep]!==undefined))){done=true}if(front){footsteps[footstep]=d}}v2[k]=x;v_map2[d][x+','+y]=true;if(done){v_map1=v_map1.slice(0,footsteps[footstep]+1);var a=this.diff_path1(v_map1,text1.substring(0,text1.length-x),text2.substring(0,text2.length-y));return a.concat(this.diff_path2(v_map2,text1.substring(text1.length-x),text2.substring(text2.length-y)))}}}}return null};diff_match_patch.prototype.diff_path1=function(v_map,text1,text2){var path=[];var x=text1.length;var y=text2.length;var last_op=null;for(var d=v_map.length-2;d>=0;d--){while(1){if(v_map[d].hasOwnProperty?v_map[d].hasOwnProperty((x-1)+','+y):(v_map[d][(x-1)+','+y]!==undefined)){x--;if(last_op===DIFF_DELETE){path[0][1]=text1.charAt(x)+path[0][1]}else{path.unshift([DIFF_DELETE,text1.charAt(x)])}last_op=DIFF_DELETE;break}else if(v_map[d].hasOwnProperty?v_map[d].hasOwnProperty(x+','+(y-1)):(v_map[d][x+','+(y-1)]!==undefined)){y--;if(last_op===DIFF_INSERT){path[0][1]=text2.charAt(y)+path[0][1]}else{path.unshift([DIFF_INSERT,text2.charAt(y)])}last_op=DIFF_INSERT;break}else{x--;y--;if(last_op===DIFF_EQUAL){path[0][1]=text1.charAt(x)+path[0][1]}else{path.unshift([DIFF_EQUAL,text1.charAt(x)])}last_op=DIFF_EQUAL}}}return path};diff_match_patch.prototype.diff_path2=function(v_map,text1,text2){var path=[];var x=text1.length;var y=text2.length;var last_op=null;for(var d=v_map.length-2;d>=0;d--){while(1){if(v_map[d].hasOwnProperty?v_map[d].hasOwnProperty((x-1)+','+y):(v_map[d][(x-1)+','+y]!==undefined)){x--;if(last_op===DIFF_DELETE){path[path.length-1][1]+=text1.charAt(text1.length-x-1)}else{path.push([DIFF_DELETE,text1.charAt(text1.length-x-1)])}last_op=DIFF_DELETE;break}else if(v_map[d].hasOwnProperty?v_map[d].hasOwnProperty(x+','+(y-1)):(v_map[d][x+','+(y-1)]!==undefined)){y--;if(last_op===DIFF_INSERT){path[path.length-1][1]+=text2.charAt(text2.length-y-1)}else{path.push([DIFF_INSERT,text2.charAt(text2.length-y-1)])}last_op=DIFF_INSERT;break}else{x--;y--;if(last_op===DIFF_EQUAL){path[path.length-1][1]+=text1.charAt(text1.length-x-1)}else{path.push([DIFF_EQUAL,text1.charAt(text1.length-x-1)])}last_op=DIFF_EQUAL}}}return path};diff_match_patch.prototype.diff_commonPrefix=function(text1,text2){if(!text1||!text2||text1.charCodeAt(0)!==text2.charCodeAt(0)){return 0}var pointermin=0;var pointermax=Math.min(text1.length,text2.length);var pointermid=pointermax;var pointerstart=0;while(pointermin<pointermid){if(text1.substring(pointerstart,pointermid)==text2.substring(pointerstart,pointermid)){pointermin=pointermid;pointerstart=pointermin}else{pointermax=pointermid}pointermid=Math.floor((pointermax-pointermin)/2+pointermin)}return pointermid};diff_match_patch.prototype.diff_commonSuffix=function(text1,text2){if(!text1||!text2||text1.charCodeAt(text1.length-1)!==text2.charCodeAt(text2.length-1)){return 0}var pointermin=0;var pointermax=Math.min(text1.length,text2.length);var pointermid=pointermax;var pointerend=0;while(pointermin<pointermid){if(text1.substring(text1.length-pointermid,text1.length-pointerend)==text2.substring(text2.length-pointermid,text2.length-pointerend)){pointermin=pointermid;pointerend=pointermin}else{pointermax=pointermid}pointermid=Math.floor((pointermax-pointermin)/2+pointermin)}return pointermid};diff_match_patch.prototype.diff_halfMatch=function(text1,text2){var longtext=text1.length>text2.length?text1:text2;var shorttext=text1.length>text2.length?text2:text1;if(longtext.length<10||shorttext.length<1){return null}var dmp=this;function diff_halfMatchI(longtext,shorttext,i){var seed=longtext.substring(i,i+Math.floor(longtext.length/4));var j=-1;var best_common='';var best_longtext_a,best_longtext_b,best_shorttext_a,best_shorttext_b;while((j=shorttext.indexOf(seed,j+1))!=-1){var prefixLength=dmp.diff_commonPrefix(longtext.substring(i),shorttext.substring(j));var suffixLength=dmp.diff_commonSuffix(longtext.substring(0,i),shorttext.substring(0,j));if(best_common.length<suffixLength+prefixLength){best_common=shorttext.substring(j-suffixLength,j)+shorttext.substring(j,j+prefixLength);best_longtext_a=longtext.substring(0,i-suffixLength);best_longtext_b=longtext.substring(i+prefixLength);best_shorttext_a=shorttext.substring(0,j-suffixLength);best_shorttext_b=shorttext.substring(j+prefixLength)}}if(best_common.length>=longtext.length/2){return[best_longtext_a,best_longtext_b,best_shorttext_a,best_shorttext_b,best_common]}else{return null}}var hm1=diff_halfMatchI(longtext,shorttext,Math.ceil(longtext.length/4));var hm2=diff_halfMatchI(longtext,shorttext,Math.ceil(longtext.length/2));var hm;if(!hm1&&!hm2){return null}else if(!hm2){hm=hm1}else if(!hm1){hm=hm2}else{hm=hm1[4].length>hm2[4].length?hm1:hm2}var text1_a,text1_b,text2_a,text2_b;if(text1.length>text2.length){text1_a=hm[0];text1_b=hm[1];text2_a=hm[2];text2_b=hm[3]}else{text2_a=hm[0];text2_b=hm[1];text1_a=hm[2];text1_b=hm[3]}var mid_common=hm[4];return[text1_a,text1_b,text2_a,text2_b,mid_common]};diff_match_patch.prototype.diff_cleanupSemantic=function(diffs){var changes=false;var equalities=[];var lastequality=null;var pointer=0;var length_changes1=0;var length_changes2=0;while(pointer<diffs.length){if(diffs[pointer][0]==DIFF_EQUAL){equalities.push(pointer);length_changes1=length_changes2;length_changes2=0;lastequality=diffs[pointer][1]}else{length_changes2+=diffs[pointer][1].length;if(lastequality!==null&&(lastequality.length<=length_changes1)&&(lastequality.length<=length_changes2)){diffs.splice(equalities[equalities.length-1],0,[DIFF_DELETE,lastequality]);diffs[equalities[equalities.length-1]+1][0]=DIFF_INSERT;equalities.pop();equalities.pop();pointer=equalities.length?equalities[equalities.length-1]:-1;length_changes1=0;length_changes2=0;lastequality=null;changes=true}}pointer++}if(changes){this.diff_cleanupMerge(diffs)}this.diff_cleanupSemanticLossless(diffs)};diff_match_patch.prototype.diff_cleanupSemanticLossless=function(diffs){function diff_cleanupSemanticScore(one,two,three){var whitespace=/\s/;var score=0;if(one.charAt(one.length-1).match(whitespace)||two.charAt(0).match(whitespace)){score++}if(two.charAt(two.length-1).match(whitespace)||three.charAt(0).match(whitespace)){score++}return score}var pointer=1;while(pointer<diffs.length-1){if(diffs[pointer-1][0]==DIFF_EQUAL&&diffs[pointer+1][0]==DIFF_EQUAL){var equality1=diffs[pointer-1][1];var edit=diffs[pointer][1];var equality2=diffs[pointer+1][1];var commonOffset=this.diff_commonSuffix(equality1,edit);if(commonOffset){var commonString=edit.substring(edit.length-commonOffset);equality1=equality1.substring(0,equality1.length-commonOffset);edit=commonString+edit.substring(0,edit.length-commonOffset);equality2=commonString+equality2}var bestEquality1=equality1;var bestEdit=edit;var bestEquality2=equality2;var bestScore=diff_cleanupSemanticScore(equality1,edit,equality2);while(edit.charAt(0)===equality2.charAt(0)){equality1+=edit.charAt(0);edit=edit.substring(1)+equality2.charAt(0);equality2=equality2.substring(1);var score=diff_cleanupSemanticScore(equality1,edit,equality2);if(score>=bestScore){bestScore=score;bestEquality1=equality1;bestEdit=edit;bestEquality2=equality2}}if(diffs[pointer-1][1]!=bestEquality1){diffs[pointer-1][1]=bestEquality1;diffs[pointer][1]=bestEdit;diffs[pointer+1][1]=bestEquality2}}pointer++}};diff_match_patch.prototype.diff_cleanupEfficiency=function(diffs){var changes=false;var equalities=[];var lastequality='';var pointer=0;var pre_ins=false;var pre_del=false;var post_ins=false;var post_del=false;while(pointer<diffs.length){if(diffs[pointer][0]==DIFF_EQUAL){if(diffs[pointer][1].length<this.Diff_EditCost&&(post_ins||post_del)){equalities.push(pointer);pre_ins=post_ins;pre_del=post_del;lastequality=diffs[pointer][1]}else{equalities=[];lastequality=''}post_ins=post_del=false}else{if(diffs[pointer][0]==DIFF_DELETE){post_del=true}else{post_ins=true}if(lastequality&&((pre_ins&&pre_del&&post_ins&&post_del)||((lastequality.length<this.Diff_EditCost/2)&&(pre_ins+pre_del+post_ins+post_del)==3))){diffs.splice(equalities[equalities.length-1],0,[DIFF_DELETE,lastequality]);diffs[equalities[equalities.length-1]+1][0]=DIFF_INSERT;equalities.pop();lastequality='';if(pre_ins&&pre_del){post_ins=post_del=true;equalities=[]}else{equalities.pop();pointer=equalities.length?equalities[equalities.length-1]:-1;post_ins=post_del=false}changes=true}}pointer++}if(changes){this.diff_cleanupMerge(diffs)}};diff_match_patch.prototype.diff_cleanupMerge=function(diffs){diffs.push([DIFF_EQUAL,'']);var pointer=0;var count_delete=0;var count_insert=0;var text_delete='';var text_insert='';var commonlength;while(pointer<diffs.length){if(diffs[pointer][0]==DIFF_INSERT){count_insert++;text_insert+=diffs[pointer][1];pointer++}else if(diffs[pointer][0]==DIFF_DELETE){count_delete++;text_delete+=diffs[pointer][1];pointer++}else{if(count_delete!==0||count_insert!==0){if(count_delete!==0&&count_insert!==0){commonlength=this.diff_commonPrefix(text_insert,text_delete);if(commonlength!==0){if((pointer-count_delete-count_insert)>0&&diffs[pointer-count_delete-count_insert-1][0]==DIFF_EQUAL){diffs[pointer-count_delete-count_insert-1][1]+=text_insert.substring(0,commonlength)}else{diffs.splice(0,0,[DIFF_EQUAL,text_insert.substring(0,commonlength)]);pointer++}text_insert=text_insert.substring(commonlength);text_delete=text_delete.substring(commonlength)}commonlength=this.diff_commonSuffix(text_insert,text_delete);if(commonlength!==0){diffs[pointer][1]=text_insert.substring(text_insert.length-commonlength)+diffs[pointer][1];text_insert=text_insert.substring(0,text_insert.length-commonlength);text_delete=text_delete.substring(0,text_delete.length-commonlength)}}if(count_delete===0){diffs.splice(pointer-count_delete-count_insert,count_delete+count_insert,[DIFF_INSERT,text_insert])}else if(count_insert===0){diffs.splice(pointer-count_delete-count_insert,count_delete+count_insert,[DIFF_DELETE,text_delete])}else{diffs.splice(pointer-count_delete-count_insert,count_delete+count_insert,[DIFF_DELETE,text_delete],[DIFF_INSERT,text_insert])}pointer=pointer-count_delete-count_insert+(count_delete?1:0)+(count_insert?1:0)+1}else if(pointer!==0&&diffs[pointer-1][0]==DIFF_EQUAL){diffs[pointer-1][1]+=diffs[pointer][1];diffs.splice(pointer,1)}else{pointer++}count_insert=0;count_delete=0;text_delete='';text_insert=''}}if(diffs[diffs.length-1][1]===''){diffs.pop()}var changes=false;pointer=1;while(pointer<diffs.length-1){if(diffs[pointer-1][0]==DIFF_EQUAL&&diffs[pointer+1][0]==DIFF_EQUAL){if(diffs[pointer][1].substring(diffs[pointer][1].length-diffs[pointer-1][1].length)==diffs[pointer-1][1]){diffs[pointer][1]=diffs[pointer-1][1]+diffs[pointer][1].substring(0,diffs[pointer][1].length-diffs[pointer-1][1].length);diffs[pointer+1][1]=diffs[pointer-1][1]+diffs[pointer+1][1];diffs.splice(pointer-1,1);changes=true}else if(diffs[pointer][1].substring(0,diffs[pointer+1][1].length)==diffs[pointer+1][1]){diffs[pointer-1][1]+=diffs[pointer+1][1];diffs[pointer][1]=diffs[pointer][1].substring(diffs[pointer+1][1].length)+diffs[pointer+1][1];diffs.splice(pointer+1,1);changes=true}}pointer++}if(changes){this.diff_cleanupMerge(diffs)}};diff_match_patch.prototype.diff_addIndex=function(diffs){var i=0;for(var x=0;x<diffs.length;x++){diffs[x].push(i);if(diffs[x][0]!==DIFF_DELETE){i+=diffs[x][1].length}}};diff_match_patch.prototype.diff_xIndex=function(diffs,loc){var chars1=0;var chars2=0;var last_chars1=0;var last_chars2=0;var x;for(x=0;x<diffs.length;x++){if(diffs[x][0]!==DIFF_INSERT){chars1+=diffs[x][1].length}if(diffs[x][0]!==DIFF_DELETE){chars2+=diffs[x][1].length}if(chars1>loc){break}last_chars1=chars1;last_chars2=chars2}if(diffs.length!=x&&diffs[x][0]===DIFF_DELETE){return last_chars2}return last_chars2+(loc-last_chars1)};diff_match_patch.prototype.diff_prettyHtml=function(diffs){this.diff_addIndex(diffs);var html=[];for(var x=0;x<diffs.length;x++){var m=diffs[x][0];var t=diffs[x][1];var i=diffs[x][2];t=t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');t=t.replace(/\n/g,'&para;<BR>');if(m===DIFF_DELETE){html.push('<DEL STYLE="background:#FFE6E6;" TITLE="i=',i,'">',t,'</DEL>')}else if(m===DIFF_INSERT){html.push('<INS STYLE="background:#E6FFE6;" TITLE="i=',i,'">',t,'</INS>')}else{html.push('<SPAN TITLE="i=',i,'">',t,'</SPAN>')}}return html.join('')};diff_match_patch.prototype.diff_text1=function(diffs){var txt=[];for(var x=0;x<diffs.length;x++){if(diffs[x][0]!==DIFF_INSERT){txt.push(diffs[x][1])}}return txt.join('')};diff_match_patch.prototype.diff_text2=function(diffs){var txt=[];for(var x=0;x<diffs.length;x++){if(diffs[x][0]!==DIFF_DELETE){txt.push(diffs[x][1])}}return txt.join('')};diff_match_patch.prototype.diff_toDelta=function(diffs){var txt=[];for(var x=0;x<diffs.length;x++){switch(diffs[x][0]){case DIFF_DELETE:txt.push('-',diffs[x][1].length,'\t');break;case DIFF_EQUAL:txt.push('=',diffs[x][1].length,'\t');break;case DIFF_INSERT:txt.push('+',encodeURI(diffs[x][1]),'\t');break;default:alert('Invalid diff operation in diff_toDelta()')}}return txt.join('').replace(/%20/g,' ')};diff_match_patch.prototype.diff_fromDelta=function(text1,delta){var diffs=[];var pointer=0;var tokens=delta.split(/\t/g);for(var x=0;x<tokens.length;x++){var param=tokens[x].substring(1);switch(tokens[x].charAt(0)){case'-':case'=':var n=parseInt(param,10);if(isNaN(n)||n<0){alert('Invalid number in diff_fromDelta()')}else{var text=text1.substring(pointer,pointer+=n);if(tokens[x].charAt(0)=='='){diffs.push([DIFF_EQUAL,text])}else{diffs.push([DIFF_DELETE,text])}}break;case'+':diffs.push([DIFF_INSERT,decodeURI(param)]);break;default:if(tokens[x]){alert('Invalid diff operation in diff_fromDelta()')}}}if(pointer!=text1.length){alert('Text length mismatch in diff_fromDelta()')}return diffs};diff_match_patch.prototype.match_main=function(text,pattern,loc){loc=Math.max(0,Math.min(loc,text.length-pattern.length));if(text==pattern){return 0}else if(text.length===0){return null}else if(text.substring(loc,loc+pattern.length)==pattern){return loc}else{return this.match_bitap(text,pattern,loc)}};diff_match_patch.prototype.match_bitap=function(text,pattern,loc){if(pattern.length>this.Match_MaxBits){return alert('Pattern too long for this browser.')}var s=this.match_alphabet(pattern);var score_text_length=text.length;score_text_length=Math.max(score_text_length,this.Match_MinLength);score_text_length=Math.min(score_text_length,this.Match_MaxLength);var dmp=this;function match_bitapScore(e,x){var d=Math.abs(loc-x);return(e/pattern.length/dmp.Match_Balance)+(d/score_text_length/(1.0-dmp.Match_Balance))}var score_threshold=this.Match_Threshold;var best_loc=text.indexOf(pattern,loc);if(best_loc!=-1){score_threshold=Math.min(match_bitapScore(0,best_loc),score_threshold)}best_loc=text.lastIndexOf(pattern,loc+pattern.length);if(best_loc!=-1){score_threshold=Math.min(match_bitapScore(0,best_loc),score_threshold)}var matchmask=1<<(pattern.length-1);best_loc=null;var bin_min,bin_mid;var bin_max=Math.max(loc+loc,text.length);var last_rd;for(var d=0;d<pattern.length;d++){var rd=Array(text.length);bin_min=loc;bin_mid=bin_max;while(bin_min<bin_mid){if(match_bitapScore(d,bin_mid)<score_threshold){bin_min=bin_mid}else{bin_max=bin_mid}bin_mid=Math.floor((bin_max-bin_min)/2+bin_min)}bin_max=bin_mid;var start=Math.max(0,loc-(bin_mid-loc)-1);var finish=Math.min(text.length-1,pattern.length+bin_mid);if(text.charAt(finish)==pattern.charAt(pattern.length-1)){rd[finish]=(1<<(d+1))-1}else{rd[finish]=(1<<d)-1}for(var j=finish-1;j>=start;j--){if(d===0){rd[j]=((rd[j+1]<<1)|1)&s[text.charAt(j)]}else{rd[j]=((rd[j+1]<<1)|1)&s[text.charAt(j)]|((last_rd[j+1]<<1)|1)|((last_rd[j]<<1)|1)|last_rd[j+1]}if(rd[j]&matchmask){var score=match_bitapScore(d,j);if(score<=score_threshold){score_threshold=score;best_loc=j;if(j>loc){start=Math.max(0,loc-(j-loc))}else{break}}}}if(match_bitapScore(d+1,loc)>score_threshold){break}last_rd=rd}return best_loc};diff_match_patch.prototype.match_alphabet=function(pattern){var s=Object();for(var i=0;i<pattern.length;i++){s[pattern.charAt(i)]=0}for(var i=0;i<pattern.length;i++){s[pattern.charAt(i)]|=1<<(pattern.length-i-1)}return s};diff_match_patch.prototype.patch_addContext=function(patch,text){var pattern=text.substring(patch.start2,patch.start2+patch.length1);var padding=0;while(text.indexOf(pattern)!=text.lastIndexOf(pattern)&&pattern.length<this.Match_MaxBits-this.Patch_Margin-this.Patch_Margin){padding+=this.Patch_Margin;pattern=text.substring(patch.start2-padding,patch.start2+patch.length1+padding)}padding+=this.Patch_Margin;var prefix=text.substring(patch.start2-padding,patch.start2);if(prefix!==''){patch.diffs.unshift([DIFF_EQUAL,prefix])}var suffix=text.substring(patch.start2+patch.length1,patch.start2+patch.length1+padding);if(suffix!==''){patch.diffs.push([DIFF_EQUAL,suffix])}patch.start1-=prefix.length;patch.start2-=prefix.length;patch.length1+=prefix.length+suffix.length;patch.length2+=prefix.length+suffix.length};diff_match_patch.prototype.patch_make=function(a,b,c){var text1,text2,diffs;if(typeof b=='undefined'){diffs=a;text1=this.diff_text1(diffs);text2=''}else{text1=a;text2=b;if(typeof c!='undefined'){diffs=c}else{diffs=this.diff_main(text1,text2,true);if(diffs.length>2){this.diff_cleanupSemantic(diffs);this.diff_cleanupEfficiency(diffs)}}}if(diffs.length===0){return[]}var patches=[];var patch=new patch_obj();var char_count1=0;var char_count2=0;var prepatch_text=text1;var postpatch_text=text1;for(var x=0;x<diffs.length;x++){var diff_type=diffs[x][0];var diff_text=diffs[x][1];if(patch.diffs.length===0&&diff_type!==DIFF_EQUAL){patch.start1=char_count1;patch.start2=char_count2}if(diff_type===DIFF_INSERT){patch.diffs.push(diffs[x]);patch.length2+=diff_text.length;postpatch_text=postpatch_text.substring(0,char_count2)+diff_text+postpatch_text.substring(char_count2)}else if(diff_type===DIFF_DELETE){patch.length1+=diff_text.length;patch.diffs.push(diffs[x]);postpatch_text=postpatch_text.substring(0,char_count2)+postpatch_text.substring(char_count2+diff_text.length)}else if(diff_type===DIFF_EQUAL&&diff_text.length<=2*this.Patch_Margin&&patch.diffs.length!==0&&diffs.length!=x+1){patch.diffs.push(diffs[x]);patch.length1+=diff_text.length;patch.length2+=diff_text.length}if(diff_type===DIFF_EQUAL&&diff_text.length>=2*this.Patch_Margin){if(patch.diffs.length!==0){this.patch_addContext(patch,prepatch_text);patches.push(patch);patch=new patch_obj();prepatch_text=postpatch_text}}if(diff_type!==DIFF_INSERT){char_count1+=diff_text.length}if(diff_type!==DIFF_DELETE){char_count2+=diff_text.length}}if(patch.diffs.length!==0){this.patch_addContext(patch,prepatch_text);patches.push(patch)}return patches};diff_match_patch.prototype.patch_apply=function(patches,text){this.patch_splitMax(patches);var results=[];var delta=0;for(var x=0;x<patches.length;x++){var expected_loc=patches[x].start2+delta;var text1=this.diff_text1(patches[x].diffs);var start_loc=this.match_main(text,text1,expected_loc);if(start_loc===null){results.push(false)}else{results.push(true);delta=start_loc-expected_loc;var text2=text.substring(start_loc,start_loc+text1.length);if(text1==text2){text=text.substring(0,start_loc)+this.diff_text2(patches[x].diffs)+text.substring(start_loc+text1.length)}else{var diffs=this.diff_main(text1,text2,false);this.diff_cleanupSemanticLossless(diffs);var index1=0;var index2;for(var y=0;y<patches[x].diffs.length;y++){var mod=patches[x].diffs[y];if(mod[0]!==DIFF_EQUAL){index2=this.diff_xIndex(diffs,index1)}if(mod[0]===DIFF_INSERT){text=text.substring(0,start_loc+index2)+mod[1]+text.substring(start_loc+index2)}else if(mod[0]===DIFF_DELETE){text=text.substring(0,start_loc+index2)+text.substring(start_loc+this.diff_xIndex(diffs,index1+mod[1].length))}if(mod[0]!==DIFF_DELETE){index1+=mod[1].length}}}}}return[text,results]};diff_match_patch.prototype.patch_splitMax=function(patches){for(var x=0;x<patches.length;x++){if(patches[x].length1>this.Match_MaxBits){var bigpatch=patches[x];patches.splice(x,1);var patch_size=this.Match_MaxBits;var start1=bigpatch.start1;var start2=bigpatch.start2;var precontext='';while(bigpatch.diffs.length!==0){var patch=new patch_obj();var empty=true;patch.start1=start1-precontext.length;patch.start2=start2-precontext.length;if(precontext!==''){patch.length1=patch.length2=precontext.length;patch.diffs.push([DIFF_EQUAL,precontext])}while(bigpatch.diffs.length!==0&&patch.length1<patch_size-this.Patch_Margin){var diff_type=bigpatch.diffs[0][0];var diff_text=bigpatch.diffs[0][1];if(diff_type===DIFF_INSERT){patch.length2+=diff_text.length;start2+=diff_text.length;patch.diffs.push(bigpatch.diffs.shift());empty=false}else{diff_text=diff_text.substring(0,patch_size-patch.length1-this.Patch_Margin);patch.length1+=diff_text.length;start1+=diff_text.length;if(diff_type===DIFF_EQUAL){patch.length2+=diff_text.length;start2+=diff_text.length}else{empty=false}patch.diffs.push([diff_type,diff_text]);if(diff_text==bigpatch.diffs[0][1]){bigpatch.diffs.shift()}else{bigpatch.diffs[0][1]=bigpatch.diffs[0][1].substring(diff_text.length)}}}precontext=this.diff_text2(patch.diffs);precontext=precontext.substring(precontext.length-this.Patch_Margin);var postcontext=this.diff_text1(bigpatch.diffs).substring(0,this.Patch_Margin);if(postcontext!==''){patch.length1+=postcontext.length;patch.length2+=postcontext.length;if(patch.diffs.length!==0&&patch.diffs[patch.diffs.length-1][0]===DIFF_EQUAL){patch.diffs[patch.diffs.length-1][1]+=postcontext}else{patch.diffs.push([DIFF_EQUAL,postcontext])}}if(!empty){patches.splice(x++,0,patch)}}}}};diff_match_patch.prototype.patch_toText=function(patches){var text=[];for(var x=0;x<patches.length;x++){text.push(patches[x])}return text.join('')};diff_match_patch.prototype.patch_fromText=function(textline){var patches=[];var text=textline.split('\n');while(text.length!==0){var m=text[0].match(/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/);if(!m){return alert('Invalid patch string:\n'+text[0])}var patch=new patch_obj();patches.push(patch);patch.start1=parseInt(m[1],10);if(m[2]===''){patch.start1--;patch.length1=1}else if(m[2]=='0'){patch.length1=0}else{patch.start1--;patch.length1=parseInt(m[2],10)}patch.start2=parseInt(m[3],10);if(m[4]===''){patch.start2--;patch.length2=1}else if(m[4]=='0'){patch.length2=0}else{patch.start2--;patch.length2=parseInt(m[4],10)}text.shift();while(text.length!==0){var sign=text[0].charAt(0);var line=decodeURIComponent(text[0].substring(1));if(sign=='-'){patch.diffs.push([DIFF_DELETE,line])}else if(sign=='+'){patch.diffs.push([DIFF_INSERT,line])}else if(sign==' '){patch.diffs.push([DIFF_EQUAL,line])}else if(sign=='@'){break}else if(sign===''){}else{return alert('Invalid patch mode: "'+sign+'"\n'+line)}text.shift()}}return patches};function patch_obj(){this.diffs=[];this.start1=null;this.start2=null;this.length1=0;this.length2=0}patch_obj.prototype.toString=function(){var coords1,coords2;if(this.length1===0){coords1=this.start1+',0'}else if(this.length1==1){coords1=this.start1+1}else{coords1=(this.start1+1)+','+this.length1}if(this.length2===0){coords2=this.start2+',0'}else if(this.length2==1){coords2=this.start2+1}else{coords2=(this.start2+1)+','+this.length2}var txt=['@@ -',coords1,' +',coords2,' @@\n'];for(var x=0;x<this.diffs.length;x++){switch(this.diffs[x][0]){case DIFF_DELETE:txt.push('-');break;case DIFF_EQUAL:txt.push(' ');break;case DIFF_INSERT:txt.push('+');break;default:alert('Invalid diff operation in patch_obj.toString()')}txt.push(encodeURI(this.diffs[x][1]),'\n')}return txt.join('').replace(/%20/g,' ')}; 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/FacebookConnect.js
AUTOCORE_SELF_CHECK.push("FacebookConnect.js");
FacebookConnect = {
    key : "66620421d55cbafb1a0f8dbe12a5a592",
    receiver : "/fbconnect/xd_receiver.htm",
    inited : false,

    init : function(initDone) {
        if (this.inited) {
            return;
        }
        FB.init(this.key, this.receiver);
        FB.ensureInit(function() { FacebookConnect.inited=true; initDone(); });
    },

    notInited : function(funcName) {
        alert(funcName + " called before init in FacebookConnect.js");
    },

    // doneFn takes an int, which can be one of:
    // FB.ConnectState.connected
    // FB.ConnectState.userNotLoggedIn
    // FB.ConnectState.appNotAuthorized
    getConnStatus : function(doneFn) {
        if (!this.inited) {
            this.notInited("getConnStatus");
            return;
        }
        FB.Connect.get_status().waitUntilReady(doneFn);
    },

    afterFbLogin : function(isLoggedIn) {
        var loc = window.location.pathname;
        var newLoc = "/login?p=" + escape(loc);
        if (isLoggedIn) {
            newLoc += "&fb_connect=1";
        }
        window.location.href = newLoc;
    },

    getUserData : function(handler) {
        var uid = FB.Connect.get_loggedInUser();
        var api = FB.ApiClient(this.key);
        api.fql_query("SELECT first_name, last_name, pic_big, birthday_date, " +
                      "sex, meeting_sex, meeting_for, relationship_status, " +
                      "current_location, activities, interests, music, tv, " +
                      "movies, books, about_me, profile_blurb " +
                      "from user where uid=" + uid, handler);
    },

    requireSession : function(nextFunction) {
        return FB.Connect.requireSession(nextFunction);
    }

}
 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/md5.js
AUTOCORE_SELF_CHECK.push("md5.js");


// 


function get_md5_bit (in_str, which_bit) 
{
    while (which_bit > 127) {
	in_str = hex_md5(in_str);
	which_bit -= 128;
    }
    var hex = hex_md5(in_str);
    var c_pos = hex.length - 1 - Math.floor(which_bit / 4);
    var c = hex.charAt(c_pos);
    var base10 = parseInt("0x" + c, 16);
    var char_bit = which_bit % 4;
    while (char_bit > 0) {
	base10 = base10 >> 1;
	char_bit--;
    }
    return base10 % 2;
}
//
// returns an int in [0..15] for picking which 
// group a string (probably user) is in, in an experiment
//
function get_experiment_group(uid_str, experiment_str)
{
    var hex = hex_md5(uid_str + experiment_str);
    var last_char = hex.charAt(hex.length - 1);
    return parseInt("0x" + last_char, 16);
}

// 



/*
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 */

/*
 * Configurable variables. You may need to tweak these to be compatible with
 * the server-side, but the defaults work in most cases.
 */
var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */

/*
 * These are the functions you'll usually want to call
 * They take string arguments and return either hex or base-64 encoded strings
 */
function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }

/*
 * Perform a simple self-test to see if the VM is working
 */
function md5_vm_test()
{
  return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
}

/*
 * Calculate the MD5 of an array of little-endian words, and a bit length
 */
function core_md5(x, len)
{
  /* append padding */
  x[len >> 5] |= 0x80 << ((len) % 32);
  x[(((len + 64) >>> 9) << 4) + 14] = len;

  var a =  1732584193;
  var b = -271733879;
  var c = -1732584194;
  var d =  271733878;

  for(var i = 0; i < x.length; i += 16)
  {
    var olda = a;
    var oldb = b;
    var oldc = c;
    var oldd = d;

    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);

    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);

    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);

    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);

    a = safe_add(a, olda);
    b = safe_add(b, oldb);
    c = safe_add(c, oldc);
    d = safe_add(d, oldd);
  }
  return Array(a, b, c, d);

}

/*
 * These functions implement the four basic operations the algorithm uses.
 */
function md5_cmn(q, a, b, x, s, t)
{
  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}

/*
 * Calculate the HMAC-MD5, of a key and some data
 */
function core_hmac_md5(key, data)
{
  var bkey = str2binl(key);
  if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);

  var ipad = Array(16), opad = Array(16);
  for(var i = 0; i < 16; i++)
  {
    ipad[i] = bkey[i] ^ 0x36363636;
    opad[i] = bkey[i] ^ 0x5C5C5C5C;
  }

  var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
  return core_md5(opad.concat(hash), 512 + 128);
}

/*
 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
 * to work around bugs in some JS interpreters.
 */
function safe_add(x, y)
{
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return (msw << 16) | (lsw & 0xFFFF);
}

/*
 * Bitwise rotate a 32-bit number to the left.
 */
function bit_rol(num, cnt)
{
  return (num << cnt) | (num >>> (32 - cnt));
}

/*
 * Convert a string to an array of little-endian words
 * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
 */
function str2binl(str)
{
  var bin = Array();
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < str.length * chrsz; i += chrsz)
    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
  return bin;
}

/*
 * Convert an array of little-endian words to a string
 */
function binl2str(bin)
{
  var str = "";
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < bin.length * 32; i += chrsz)
    str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
  return str;
}

/*
 * Convert an array of little-endian words to a hex string.
 */
function binl2hex(binarray)
{
  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i++)
  {
    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
  }
  return str;
}

/*
 * Convert an array of little-endian words to a base-64 string
 */
function binl2b64(binarray)
{
  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i += 3)
  {
    var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
                | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
                |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
    for(var j = 0; j < 4; j++)
    {
      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
    }
  }
  return str;
}
 
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/Bitter.js
AUTOCORE_SELF_CHECK.push("Oryx/Bitter.js");
/* ----------------------------------------------------------------

OkCupid Bitter file

	- for some triple entendre 

This is a specialized file for setting the remote 64 bit settings
variable.

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

var Bitter = {
	
	//active:"" || false,
	
	bit:{},
	persist:NaN,
	funcs:new Array(),
	map:{},
	LOAD:function(cb)
	{			
		var pass = this;
		var fire = cb || function(){};
		
		new Ajax.Request(
			"/bitter_JSON.html",
			{
				method:"get",
				onSuccess:function(response) {
					pass.bit = response.responseText.evalJSON();
					fire();
				}
			}
		);
	},
	loadFromString:function(s)
	{
		for (var i = 0 ; i < s.length; i++) {
			this.bit["_" + i] = (s.charAt(i) == "1" ? 1 : 0);
		}
	},
	sanctify:function(bit) 
	{
		if(!bit && bit != 0) {
			bit = this.persist;
		}
		
		if(isNaN(bit)) {
			bit = this.map[bit];
		}
		
		formbit = "_" + bit;
		this.persist = [formbit,bit];
		return [formbit,bit];
	},
	get:function(bit) 
	{
		_bit = this.sanctify(bit);
		_val = this.bit[_bit[0]];
		if (typeof(_val) == "undefined") _val = 0;
		return _val;
	},
	set:function(value,bit) 
	{
		//if(!this.active) return;
		
		_bit = (bit ? this.sanctify(bit) : this.persist);
		
		value = value == 1 ? 1 : 0;
		
		this.bit[_bit[0]] = value;
		
		var passbit = _bit[1];
		var passval = value;
	    var cashbrk = Math.round(Math.random()*10000000);
	
		new Ajax.Request(
			"/settings", 
			{
				method: 'get',
				parameters: {
					update_ui_prefs:1,
					bit:passbit,
					val:passval,
					bst:cashbrk
				},
				onSuccess:function(response){var sacrifice = response.responseText;}
			}
		);
	},
	trigger:function(funcs,bit) 
	{
		_bit = this.sanctify(bit);
	
		this.funcs[_bit[0]] = funcs;
		var pass = this;
	
		if(funcs.on) 	document.observe("dom:loaded", function() {if(pass.bit[_bit[0]] == 1) pass.funcs[_bit[0]].on();});
		if(funcs.off) 	document.observe("dom:loaded", function() {if(pass.bit[_bit[0]] == 0) pass.funcs[_bit[0]].off();});
	},
	toggle:function(bit) 
	{
		_val = this.get(bit);
		this.set(++_val%2);
	}
};

var $bit = Bitter;
	
 
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/Oryx/Form.js
AUTOCORE_SELF_CHECK.push("Oryx/Form.js");
/* ----------------------------------------------------------------

Form.js

	- manages custom form manipulation


TODO:
	behavior variable may be unnecessary

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

var Frm = {
	
	last:false,
	locked:false,
	
	behaviors:{},
	multiv:{},
	multit:{},
	savelock:false,
	
	lock_auxiliary:function(id)
	{
		// Replace with functions to happen on lock
		return false;
	},
	
	unlock_auxiliary:function(id)
	{
		// Replace with functions to happen on unlock
		return false;
	},
	
	lock:function(id) {
		this.lock_auxiliary(id);
		this.locked = id;
	},
	unlock:function(id) {
		this.unlock_auxiliary(id);
		this.locked = false;
	},
	
	toggle:function(id,behavior,force)
	{			
		button = $(id + "_button") || false;
		if(!behavior || behavior == '') behavior = "toggle";
		if(behavior == "buff")
			this.buffed = false;
		
		
		if(this.last == "location_interface" && ($("nearme") && !$("nearme").checked) && $("anywhere") && !$("anywhere").checked) {
			check_empty();
			this.buffed = id;
			return;
		}
		
		else if(this.last && id != this.last && button) {
			$(this.last + "_button").removeClassName('active');
			$(this.last + "_drop").style.display = 'none';
			$(this.last).removeClassName("open");
		}
		if(button) this.last = id;
		if(!this.behaviors[id]) this.behaviors[id] = behavior;
		if(button) {
			if(!button.hasClassName("active")) {
				$(id).addClassName("open");
				$(button).addClassName("active");
				$(id + '_drop').style.display = "block";
			} else if (behavior == "toggle" || behavior == "adder") {
				$(id).removeClassName("open");
				$(button).removeClassName('active');
				$(id + '_drop').style.display = "none";
			}
		}
	},
	set_auxiliary:function()
	{
		// Set this if you have a local action that 
		// needs to take place during the set operation
		return false;
	},
	set:function(name,value,id,text,init,pattern)
	{
		if(!init) this.save_reset();
		
		if(!$(name)) return;

		$(name).value = value;
			
		if(!pattern)
			$(id + "_button_text").innerHTML = $(text).innerHTML;
		else {
			t = $(text).innerHTML;
			$(id + "_button_text").innerHTML = util.jogf(pattern,{s:t});
		}

		this.set_auxiliary(name,value,id,text,init,pattern);

		if(!init) this.toggle(id,this.behaviors[id]);
	},
	save_reset:function()
	{
		/* 
		This function is overwritten by match.js if it's included.
		Left in because it could have utility elsewhere.
		*/
		return false;
	},
	
	pro_show:function(from)
	{
		/* 
		This function is overwritten by match.js if it's included.
		Left in because it could have utility elsewhere.
		*/
		return false;
	},
	
	multi_set:function(name,id,point,count,pattern,width,default_text,init)
	{	
		
		c = $(id + "_check_" + point);
		
		if(c.checked != true || init) 
			c.checked = true;
		else
			c.checked = false;
		
		this.multiv[id] = [];
		this.multit[id] = [];
		
		items = $(id + "_drop").getElementsByTagName("li");
		
		for(iter=0;iter<items.length;iter++) {
			
			if(items[iter].className == "divvy") continue;
			var item = $(items[iter]).getElementsByTagName("input")[0];
			
			if(item.checked) {
				this.multiv[id][this.multiv[id].length] = item.value;
				this.multit[id][this.multit[id].length] = item.title;
			}
		}
		
		$(name).value = util.toMask(this.multiv[id]);
		
		if(this.multit[id].length != 0) {
			text = this.multit[id].join(", ");
			display = this.limit_text(pattern,text,width);
		} else {
			display = default_text;
		}
		$(id + "_button_text").innerHTML = display;
		
		if(!init) this.save_reset();
	},
	
	limit_text:function(pattern,text,width)
	{
		display = util.jogf(pattern,{s:text});
		
		flashback = pattern.substring(0,pattern.indexOf(" %s"));
		
		lim = Math.round(width/10);

		if(display.length > lim) display = display.substring(0,lim) + "...";
		
		display = display.substring(flashback.length,display.length);
		output = flashback + "<span>" + display + "</span>";
		
		return output;
	},
	
	// Special cases
	
	process_age:function(init)
	{
		minc = $("min_age").value;
		min = parseInt($("min_age").value.strip());
		max = parseInt($("max_age").value.strip());
		txt = $("ages_button_text");
		
		if(isNaN(min)) {
			min = false;
			$("min_age").value = "";
		}
		if(isNaN(max)) {
			max = false;
			$("max_age").value = "";
		}
		if(min < 18 && minc.length == 2) {
			min = 18;
			$("min_age").value = 18;
		}
		
		if(min && !max) {
			txt.innerHTML = "Ages " + min + " to 99" ;
		} else if(!min && max) {
			txt.innerHTML = "Ages 18 to " + max;
		} else if(min && max) {
			txt.innerHTML = "Ages " + min + " to " + max;
		} else {
			txt.innerHTML = "Age";
		}
		if(!init) this.save_reset();
	},
	
	process_keywords:function(v,init) 
	{
		if(v.strip().length != 0) {
			list = v.split(" ");
			text = list.join(", ");
			pattern = "Keywords: %s";
			width = 240;
			display = this.limit_text(pattern,text,width);
		} else {
 			display = "Keywords";
		}
		$("keywords_search_button_text").innerHTML = display;
		if(!init) this.save_reset();
	},
	
	process_height:function(init)
	{
		min_obj = $("height_min");
		max_obj = $("height_max");
		
		min = min_obj.value;
		max = max_obj.value;
		
		min_t = min_obj[min_obj.selectedIndex].innerHTML;
		max_t = max_obj[max_obj.selectedIndex].innerHTML;
		
		if(min > max && max != "0")
		{
			max_obj.selectedIndex = min_obj.selectedIndex;
		}
		
		if(min != "0" && max == "0") {
			display = "At least " + min_t;
		} else if (min == "0" && max != "0") {
			display = "Under " + max_t;
		} else if (min != "0" && max != "0") {
			display = "Between " + min_t + " and " + max_t;
		} else {
			display = "Height";
		}
		
		$("heights_button_text").innerHTML = display;
		
		if(!init) this.save_reset();
	},
	
	process_language:function(v,init)
	{
		objs = $("language");
		for(iter=0;iter<objs.length;++iter) {
			if(objs[iter].value==v && objs[iter].value != "") {
				desig = objs[iter].value;
				lang = objs[iter].innerHTML;
			} else if(objs[iter].value == "") {
				desig = "";
				lang = "...";
			}
		}
		
		if(lang) 
		{
			$("languages_button_text").innerHTML = "Speaks " + lang;
			$("MATCH_FILTER_LANGUAGES").value = desig;
			$("match_lang").checked = false;
		} else {
			$("languages_button_text").innerHTML = "Speaks ...";
		}
		if(!init) this.save_reset();
	},
	
	process_match_lang:function(checked,init)
	{
		console.log(init);
		
		if(!checked) {
			this.process_language($("language")[$("language").selectedIndex].value,init);
		} else {
			$("MATCH_FILTER_LANGUAGES").value = "0";
			$("languages_button_text").innerHTML = "Speaks my language(s)";
		}
		if(!init) this.save_reset();
	},
	
	// A bunch of location specific stuff

	timed:false,
	begin_check:function()
	{
		if(this.timed) clearTimeout(this.timed);
		this.timed = setTimeout("Frm.try_location()",1200);
	},
	
	try_location:function()
	{
		check_empty();
	},
	
	location_swaps:function(state)
	{
		switch(state)
		{
			case "nearme":
				$('radius').disabled = false;
			break;
			case "text":
				if($("nearme")) $("nearme").checked = false;
				$("anywhere").checked = false;
				$('radius').disabled = false;
			break;
			case "anywhere":
				$('radius').disabled = true;
			break;
		}
		this.save_reset();
	}
}

document.observe("click",function() {
	if(Frm.last && Frm.locked != Frm.last)
	{
		if(Frm.last != "location_interface") {
			$(Frm.last).removeClassName("open");
			$(Frm.last + "_button").removeClassName("active");
			$(Frm.last + "_drop").style.display = 'none';
		} else {
			check_empty();
		}
	}
	if($("searches_drop") && $("searches_drop").style.display=="block" && !Frm.savelock) {
		$("searches_drop").style.display="none";
	}
});
 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/Oryx/MatchCall.js
AUTOCORE_SELF_CHECK.push("Oryx/MatchCall.js");
var MatchCall = Class.create({
	initialize:function(args) {
		this.args = args;
		this.makecall();
	},
    //the strings and numbers here are pulled from match_prot.x; be sure to keep them up to date!
    //last updated: 9/15/08
    lookupSortString:function(name)
    {
        if(name == "match")
            return 0;
        if(name == "enemy")
            return 1;
        if(name == "random")
            return 2;
        if(name == "friend")
            return 3;
        if(name == "login")
            return 4;
        if(name == "join")
            return 6;
        if(name == "distance")
            return 7;
        if(name == "looks")
            return 8;
        if(name == "personality")
            return 9;
        if(name == "first_contact")
            return 10;
        if(name == "completeness")
            return 11;
        if(name == "rmatch")
            return 12;

        $log("unknown sort specified "+name, "error");
        return -1;
    },
    lookupFilterString:function(name)
    {
        if(name == "gentation")
            return 0;
        if(name == "require_photo")
            return 1;
        if(name == "age")
            return 2;
        if(name == "radius")
            return 3;
        if(name == "last_login")
            return 5;
        if(name == "join_date")
            return 6;
        if(name == "eligible")
            return 7;
        if(name == "religion")
            return 8;
        if(name == "ethnicity")
            return 9;
        if(name == "height")
            return 10;
        if(name == "smoking")
            return 11;
        if(name == "drinking")
            return 12;
        if(name == "drugs")
            return 13;
        if(name == "money")
            return 14;
        if(name == "jobtype")
            return 15;
        if(name == "dogs")
            return 16;
        if(name == "cats")
            return 17;
        if(name == "children")
            return 18;
        if(name == "education")
            return 19;
        if(name == "personality")
            return 20;
        if(name == "sign")
            return 21;
        if(name == "languages")
            return 22;
        if(name == "v_looks")
            return 23;
        if(name == "v_personality")
            return 25;
        if(name == "v_first_contact")
            return 27;
        if(name == "completeness")
            return 29;
        $log("unknown filter "+name, "error");
        return -1;
    },

    createParamString:function(obj) 
    {
        //see https://okoffice.okcupid.com/oktrac/wiki/MatchAjaxCalls for the idea behind what
        //goes in this object.  In a jiffy: immediate parameters "low", "count", "templateStyle",
        //and "destination"; and arrays "filters" and "sorts"

        //while a member of this object, nonetheless this is a handy general purpose function
        var cgi_param_str = "ajax=1&cacheBust=" + Math.round(Math.random()*1000000000000);
        if(obj.count)
        {
            cgi_param_str += "&count="+obj.count;
        }

        if(obj.locid)
        {
            cgi_param_str += "&locid="+obj.locid;
        }

        if(obj.low)
        {
            cgi_param_str += "&low="+obj.low;
        }

        if(obj.templateStyle)
        {
            cgi_param_str += "&template_style="+obj.templateStyle;
        }
        //using default prefs/ ignoring old prefs, unless specified
        if(obj.discard_prefs)
        {
            cgi_param_str += "&discard_prefs="+obj.discard_prefs;
        }
        else
        {
            cgi_param_str += "&discard_prefs=1";
        }
        if(obj.use_prefs)
        {
            cgi_param_str += "&use_prefs="+obj.use_prefs;
        }
        else
        {
            cgi_param_str += "&use_prefs=0";
        }
        
        //filters
        if(obj.filtersOverride && obj.filtersOverride.length) {
            cgi_param_str += obj.filtersOverride;
        } else if(obj.filters) {
            for(var i = 0; i < obj.filters.length; i++)
            {
                if(!obj.filters[i].name)
                {
                    $log("Missing filter name!", "error");
                    continue;
                }
                if(obj.filters[i].values.length == 0 ||
                   obj.filters[i].values.length > 2)
                {
                    $log("too many, or two few, values for filter", "error");
                    continue;
                }
                var filterNo = this.lookupFilterString(obj.filters[i].name);
                var part1 = "," + obj.filters[i].values[0];
                var part2 = "";
                if(obj.filters[i].values.length == 2)
                {
                    part2 += "," + obj.filters[i].values[1];
                }
                cgi_param_str += "&filter"+(i+1)+"="+filterNo+part1+part2;
            }
        }

        //sorts
        if(obj.sorts)
        {
            for(var i = 0; i < obj.sorts.length; i++)
            {
                if(!obj.sorts[i].name)
                {
                    $log("Missing sort name!", "error");
                    continue;
                }
                if(!obj.sorts[i].weight ||
                    obj.sorts[i].weight < 0 ||
                    obj.sorts[i].weight > 10000) 
                {
                    $log("sort missing weight, or invalid weight!", "error");
                    continue;
                }
                var sortNo = this.lookupSortString(obj.sorts[i].name);
                cgi_param_str += "&sort"+(i+1)+"="+sortNo+","+obj.sorts[i].weight;
                if(obj.sorts[i].reverse) //default is false, so we only need to override if true
                {
                    cgi_param_str += ",1"; //see match.T, line 1254 (as of this code revision)
                }
            }
        }
        return cgi_param_str;
    },
	makecall:function() {
        var params = this.createParamString(this.args);
		var success_function = this.doNothing;
        if(this.args.onSuccess)
        {
            success_function = this.args.onSuccess; //caller interrupts and does what he will...
        }
        else if(this.args.destination)
        {
            if(! this.args.templateStyle)
            {
                $log("destination specified, but templateStyle not - will be injecting raw JSON...", "error");
            }
            var pass = this;
            success_function = function(resp) { pass.setHTMLHelper(resp); };
        }

        new Ajax.Request(
			'/match',
			{
                method:'POST',
			    parameters:params,
			    onSuccess:success_function
            }
		);
	},
	setHTMLHelper:function(resp)
    {
		if (this.args.overwrite)
			$(this.args.destination).innerHTML = resp.transport.responseText;
		else
        	$(this.args.destination).innerHTML += resp.transport.responseText;
		resp = "";
    },
    doNothing:function(whatever){}
});
/*
Example setup

function output(response){
    $log(response.responseText);
}
var call_object = {
    count:13,
    destination: 'abc',
    templateStyle:'xyz',
    sorts: [{name:"match", weight:10}]
};
new AjaxMatchCall(call_object);

*/
 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/Oryx/StepAction.js
AUTOCORE_SELF_CHECK.push("Oryx/StepAction.js");
/* ----------------------------------------------------------------

StepAction.js

	- Left, Left, left ...

Allows for resetting evironments on action triggers.

TODO : Create abstract action tree implementation

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

var StepAction = Class.create({
	initialize:function(advancers,steps,pattern,init)
	{
	    this.steps = {_step:function(){},_clean:function(){}};
	     
		this.stage 		= -1;
		this.advancer 	= advancers;
		this.pattern	= pattern;
		this.steps		= Object.extend(this.steps,steps);
		
		var pass = this;
		
		KeyCombos[advancers.next] = function() {pass.advance();}
		KeyCombos[advancers.prev] = function() {pass.regress();}
		
		if(init) this.advance();
	},	
	advance:function(e)
	{
		if(++this.stage == this.pattern.length) this.stage = 0;
		this.steps._step();
		this.steps[this.pattern[this.stage]]();
		this.steps._clean();
	},
	regress:function(e)
	{
		if(--this.stage < 0) this.stage = this.pattern.length -1;
		this.steps._step();
		this.steps[this.pattern[this.stage]]();
		this.steps._clean();
	}
}); 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/Oryx/Stoplight.js
AUTOCORE_SELF_CHECK.push("Oryx/Stoplight.js");
CON_DATA = [];
function registerConData(data) {
    CON_DATA.push(data);
}
function promoteUncontacted(max) {
    var qualifiers = [];
    for (var i = 0; i < CON_DATA.length; i++) {
        if (CON_DATA[i].rnc7 == 0 && CON_DATA[i].first_contact_time == 0) {
            CON_DATA[i].ident = i; // this is a little hackneyed, but effecient until the service is properly implemented
            qualifiers.push(CON_DATA[i]);
        }
    }
    qualifiers.sort(uncontactedSort);
    for (var i = 0; i < max && i < qualifiers.length; i++) {
        $("uncontacted_notice_" + qualifiers[i].row_num).show();
        CON_DATA[qualifiers[i].ident].promoted = true;
    }
}

function originalColor(c) {
    var color = "yellow";
    
    if(c.rrp < 20 || c.rnc1 > 5 || c.rnc7 > 15) {
        color = "red";
    }
    if (c.rnc7 < 3 || (c.rnc7 < 10 && c.rrp >= 50)) {
        color = "green";
    }
    return color;
}

function chanceEstimateSafe(c) {
    var estimate = 
        2.961 * c.rrr28nim
        + 0.3001 * c.rnc28
        + 0.1209 * c.sg
        + 0.05571 * c.samv
        + 0.01313 * c.spl
        + 2.626e-5 * c.slpa
        + 1.714e-5 * c.mp
        - 0.006888 * c.ad
        - 0.1841
        - 0.2612 * c.rnc28nim;
    return estimate;
}


function safeColorChoice(estimate) {
    // red means < 36% chance of a response. It applies to 33% of pairs
    var color = "red";
    if (estimate > 1.58) {
        // a green means at least a 55% chance of a response, we're able to 
        // display it for 33% of sender-receiver pairs
        color = "green";
    } else if (estimate > 1.252) {
        // a yellow means between a 38% and 52% chance of a response, we're 
        // able to display it for 33% of sender-receiver pairs
        color = "yellow";
    }
    return color;
}

function newColorSafe(c) {
    var estimate = chanceEstimateSafe(c);
    
    return safeColorChoice(estimate);
}

function newEstimateWeighted(c) {
    var estimate = chanceEstimateSafe(c);
    // 12 messages a week should be as important as the red-green diff
    // if you get 5 messages a week, this shouldn't affect your score
    var modEstimate = estimate - (c.rnc7 - 5)/50.0;
    return modEstimate;
}

function newColorWeighted(c) {
    var estimate = newEstimateWeighted(c);
    return safeColorChoice(estimate);
}

function chanceEstimateBest(c) {
    var estimate = 
        2.963 * c.rrr28nim
        + 0.2986 * c.rnc28
        + 0.09111 * c.snc28nim
        + 0.05816 * c.samv
        + 0.008306
        + 2.257e-5 * c.slpa
        + 1.523e-5 * c.mp
        - 0.007745 * c.ad
        - 0.2003 * c.rnc28nim;
    return estimate;
}

function newColorBest(c) {
    var estimate = chanceEstimateBest(c);
    
    // red means < 39% chance of a response. It applies to 33% of pairs
    var color = "red";
    if (estimate > 1.58) {
        // a green means at least a 55% chance of a response, we're able to 
        // display it for 33% of sender-receiver pairs
        color = "green";
    } else if (estimate > 1.248) {
        // a yellow means between a 39% and 52% chance of a response, we're 
        // able to display it for 33% of sender-receiver pairs
        color = "yellow";
    }
    return color;
}

function chancePercStoplight(is_staff, override1, override2) {

    // override1 always puts you in stoplight experiment 1 
    if(typeof(override1) == 'undefined') {
        if (is_staff) {
            // always enabled for staff
            override1 = 1;
        } else {
            override1 = 0;
        }
    }
    // override2 chooses which section of experiment2 you're in
    if(typeof(override2) == 'undefined') {
        if (is_staff) {
            // turn on the "best" version for staff
            //override2 = 3;
            // let staff see whatever users see
            override2 = 0;
        } else {
            // set this to 1 to disable experiment 2 and give everyone the old
            // stoplight
            override2 = 0;
        }
    }

    if (get_experiment_group(CURRENTUSERID, "match-chance-perc-stoplight-1") <= 7 || override1) 
    {
        for (var i = 0; i < CON_DATA.length; i++) {
                       
            if(CON_DATA[i].promoted == true) continue;
            
            var c = CON_DATA[i];
            //var exp = get_experiment_group(CURRENTUSERID, "match-chance-perc-stoplight-3");
            var color;
            if (!override2) {
                color = originalColor(c);
            } else {
                if (override2 == 1) {
                    color = originalColor(c);
                } else if (override2 == 2) {
                    // override for people who haven't been contacted much this week
                    if (c.rnc7 < 2) {
                        color = "green";
                    } else {
                        color = newColorSafe(c);
                    }
                } else if (override2 == 3) {
                    color = newColorWeighted(c);
                } else {
                    // override for people who haven't been contacted much this week
                    if (c.rnc7 < 4) {
                        color = "green";
                    } else {
                        color = newColorSafe(c);
                    }
                }
            }

            if(c.xtra_function) {
                c.xtra_function();
            }

            wrap = $("stoplight_container_"+c.row_num);
            if(wrap) {
                wrap.show();
            }
            
            if(c.row_num) {
                tag = $("match-perc-stoplight-"+color+"-"+c.row_num);
            } else {
                tag = $("match-perc-stoplight-"+color);
            }

            if (tag) {
                tag.show();
            }
						
						hideMatchStoplight();
        }
    }
}

function hideMatchStoplight() {
	for (var i = 0; i < CON_DATA.length; i++) {
		if(CON_DATA[i].promoted == true) {
			tag = $("stoplight_container_"+CON_DATA[i].row_num);
			if (tag) { tag.hide(); }
		}
	}
}
 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/Oryx/TextDialogue.js
AUTOCORE_SELF_CHECK.push("Oryx/TextDialogue.js");
/* ----------------------------------------------------------------

OkCupid TextDialogues

	- Maintains some specified textbox features

This is a highly specialized file for the okcupid text boxes.

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

var TextDialogue = Class.create({
    initialize:function(obj,parameters) {
        this.o = $(obj);
        
        if(Object.isString(obj))    this.n = obj;
        else                        this.n = obj.id;
        
        if(!this.n || !this.o) return;
                
        new Draggable(this.o);

		if(Prototype.Browser.IE == false)
			this.o.style.position = "fixed";
        else {
			this.o.style.position = "absolute";
			var pass = this;
			Event.observe(window,"scroll",function(){
				// No need for prototype here; native methods used for performance
				pass.o.style.marginTop = document.documentElement.scrollTop + "px";
			});
		}

        var text = $(this.n + "_text");
        var acts = $(this.n + "_acts");
        var togg = $(this.n + "_togg");
        
        var actsTextDefault = acts.innerHTML;
        var toggTextDefault = togg.innerHTML;
        
        var altered = false;
        
        text.observe("keydown",function() {
            if(!altered) return;
            acts.innerHTML = actsTextDefault;
            togg.innerHTML = toggTextDefault;
            altered = false;
        });
        
        acts.href = "#nogo";
        acts.observe("click",function() {parameters.action(), altered = true;});
    }
});

/*
Current implementation is a little verbose; will make the class more robust if
needed.

EXAMPLE:

var messageDialogue = {
    action:function() {
		var params = {whatever:something}
        new Ajax.Request("/url",{
            parameters:params,
            onSuccess:function() {
                Do something
            }
        });
    }
}
document.observe("dom:loaded",function(){new TextDialogue("message", messageDialogue);});

*/ 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/Oryx/Utilities.Admin.js
AUTOCORE_SELF_CHECK.push("Oryx/Utilities.Admin.js");
var Admin = {
    blacklist:function(whom,id) {
        var comment = prompt("What reason are you blacklisting " + whom + " for?");
        if (comment != null)
        {
            window.location.href =
                "http://services.okcupid.com/admin/acctstatus" +
                    "?submit-update=1" + 
                    "&userid=" + id +
                    "&status=5" +
                    "&comment=" + comment;
        }
    }
}
 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/Oryx/Votes.js
AUTOCORE_SELF_CHECK.push("Oryx/Votes.js");
/* ----------------------------------------------------------------

Votes file

	- for star ratings and notes
	
Would like to clean up, but works fine for now

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

function processVoteNote(vote_or_note, vote_type, rating, tuid, similar_users, target_objectid, stopUnloadBlink, success_function) {
	var the_pad = $('note-pad');

	if (the_pad && the_pad.style.display != "none" || vote_or_note == "vote") {

		// target_objectid is 0 for profiles
		var the_url = "/vote_handler";
		var note_text = $('note-text');
		
		if (note_text) {
			note_text = note_text.childNodes[0].value;
		}

		var params = { 
			voterid: CURRENTUSERID, 
			target_userid: tuid,  
			type: vote_or_note
		};

		if (vote_or_note == "vote") {
			params["target_objectid"] = (target_objectid) ? target_objectid : "0"; 
			params["vote_type"] = vote_type;
			params["score"] = rating;
			
			if (rating == 0) {
				params["cant_tell"] = "1";
			}
		}
		else {
			params["note"] = note_text;
		}
		
		if ($('qm') != undefined)
		{
			new Ajax.Request(the_url, {
				parameters: params,
				onSuccess: function(){processedVoteNoteQMSuccess(vote_type, vote_or_note, tuid, rating);},
				onFailure: processedVoteNoteQMFailure
			});
		}
		else
		{
			new Ajax.Request(the_url, {
				parameters: params,
				onSuccess: function(data) {
					if (vote_or_note == "vote") {
						$('current-' + vote_type + '-' + tuid).style.width = rating * 20 + "%";
						
						if (rating == 0) {
							$(vote_type + '-rating').addClassName('faded');
						}
						$(vote_type + '-click-away-' + tuid).focus();
					}
					success_function;
				},
				onFailure: function(data) {
					var v_n = (vote_or_note == "vote") ? "vote" : "notes";
					alert("Your " + v_n + " couldn't be processed. Please try again later.");
				}
			});
		}
		
		if (similar_users && rating > 3) {
			new Ajax.Updater('rating_recommended', '/vote_similar', { 
				parameters: { 
					tuid: tuid
				},
				method: 'get'
			});
			$('personality-click-away-' + tuid).focus();
			//setTimeout("new Effect.BlindDown('rating_recommended');", 1000);
			setTimeout("$('rating_recommended').show();", 1000);
		}
	}
	
	if (vote_or_note == "note") {
		if(!stopUnloadBlink) the_pad.toggle();
	}

    
	if(typeof LOTCC != "undefined")
	    LOTCC.bcpw("act", "Vote");
}

function processedVoteNoteQMSuccess(vote_type, vote_or_note, tuid, rating)
{

	if (vote_or_note == "vote")
	{
		var is_safari = navigator.userAgent.search(/Safari/);
		
		if (rating == 0) {
			$(vote_type + '-rating').addClassName('faded');
		}
		
		$('current-' + vote_type + '-' + tuid).setStyle({
			width:(rating * 20 + "%"),
			height:'17px' // for IE6
		});

		if (is_safari == "-1") { /* because Safari pukes on this */
			$(vote_type + '-click-away-' + tuid).focus();
		}
	}
	
	if (vote_type == "personality")
	{
		document.quickmatchform.previous_personality.value = rating;
		document.quickmatchform.submit();
	}
}

function processedVoteNoteQMFailure(data)
{
	alert("Your vote couldn't be processed. Please try again later.");
}

function activateNoteAutosave(v_or_n, l_or_p, r, uid) {
	Event.observe(window, 'unload', function() {
		processVoteNote(v_or_n, l_or_p, r, uid, false, false, true);
	});
}

// preload note pad graphics
//
function preloadNotePad() {
	util.preloadImages(['http://cdn.okcimg.com/_img/layout2/buttons/note_pad_bg.gif',
				  'http://cdn.okcimg.com/_img/layout2/buttons/update_notes.gif']);
}

function closeRecommendations() {
  $('rating_recommended').style.display="none";
}
 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/quizzy_common.js
AUTOCORE_SELF_CHECK.push("quizzy_common.js");


 // 

var VIEWER_MATURITY = ''; // 

function updateMaturityChanger(select_id, dest_id) {

    var res = $F(select_id);
    setCookie('viewer_maturity',res, secondsFromNow(3600*24*31));
    if (res == "Adult") {
        $(dest_id).innerHTML = "Updating...and reloading! You dirty dog.";
    }
    else {
        $(dest_id).innerHTML = "Cleaning things up...and reloading!";
    }
    window.location.reload();

}

function showMaturityChanger(dest_id) {

    var vm = VIEWER_MATURITY;
    if (vm == '') {
        var vm = getCookie('viewer_maturity');
        if (! vm) {
            vm = 'Teen';
            setCookie('viewer_maturity', vm, secondsFromNow(3600*72));
            VIEWER_MATURITY = vm;
        }
        else {
            VIEWER_MATURITY = vm;
        }
    }
    var teen_sel = "";
    var adult_sel = "";
    if (vm == "Adult") {
        adult_sel=" selected=\"selected\" ";
    }
    else {
        teen_sel=" selected=\"selected\" ";
    }
    var res = "Some tests have adult content. What are your limits? ";
    var id="select-" + Math.round(Math.random()*1000000000);
    res += '<select id="' + id + '">'
        +  ' <option value="Teen" '+teen_sel+'>Teen and Family</option>'
        +  ' <option value="Adult" '+adult_sel+'>Adult</option>'
        +  '</select>'
        +  '<a href="javascript:updateMaturityChanger(\'' + id + '\',\'' + dest_id + '\');">Update</a>';
    
    $(dest_id).innerHTML = res;        

}

// 

function handleFlagResponse(response, container_id) {
	var success = response.responseXML.getElementsByTagName('success')[0].childNodes[0].nodeValue;
	
	if (success == '1') 
		$(container_id).innerHTML = "The test was successfully flagged.";
	else
		$(container_id).innerHTML = "There was a problem flagging the test. Please try again later.";
}

function flagTest(container_id, author_id, test_id) {											
	if (confirm('This test will be submitted for peer review as containing inappropriate material.')) {
		var url = "/flagmod";
		var params = {
			doFlag: "1",
			type: "11",
			owner_id: author_id,
			object_id: test_id,
			AJAX: "1"
		};
		
		new Ajax.Request(url, {
			parameters: params,
			onSuccess: handleFlagResponse.bindAsEventListener(this, container_id),
			onFailure: handleFlagResponse.bindAsEventListener(this, container_id)
		});
	}
} 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/quizzy/OkTestEditor.js
AUTOCORE_SELF_CHECK.push("quizzy/OkTestEditor.js");
//
//
// HelloOkHumorRainbowCupidQuizzy
// 2008
//
if ((typeof Prototype=='undefined')) {
	throw("OkTestEditor requires Prototype");
}
if ((typeof OkTest=='undefined')) {
	throw("OkTestEditor requires OkTest");
}
if ((typeof OkUploader=='undefined')) {
	throw("OkTestEditor requires OkUploader");
}
if ((typeof Scriptaculous=='undefined')) {
	throw("OkTestEditor requires Scriptaculiciousness");
}
if ((typeof parseXml=='undefined')) {
	throw("OkTestEditor requires xml.js");
}
if ((typeof Form.Element.Deserializers == 'undefined')) {
	throw("OkTestEditor requires deserialize.js");
}

//
// Utility functions
//
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 );}	
}

 //---------------------------------------------------------------------------------------------------------//
 // The meat and puhtaters
 //---------------------------------------------------------------------------------------------------------//


var OkTestEditor = Class.create({
	
	// list of other crap
	SampleVariableNames: ["Extroverted", "Friendly", "Tom_Cruise", "Evil", "Athletic", "Basketball Knowledge", "n00b-like", "lube-like", "Something" ],
	SampleResultTitles: ["The Lion Tamer", "The Snake Charmer", "The Smelly Kid", "Doctor Evil", "The Skydiver", "The Unforgettable Muffler", "A Great Skiier", "The Window Washer", "The Rockstar" ],
	AutosaveEvery: 90, // 
	EditorConfig: {},
	TestExtrinsicData: {},    // 
	IsRecoveredAutosaveNotYetSaved: false,
	zIndexIncrementer : 1000,
	openEditors : [],   // 
	hl_settings : { startcolor: '#FFFD13' },

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

	initialize: function() {
		this.ActionHistory = new Object();
		this.preloadImages();
		if (tinyMCE && MCE_CONFIG_QUESTION_TEXT) {
	 		tinyMCE.init(MCE_CONFIG_QUESTION_TEXT);
		}
	},
	
	preloadImages: function() {
		var paths = ["http://cdn.okcimg.com/_img/helloquizzy/buttons/animated_button.gif"];
		this.ImagePreloads = new Array();
		for (var i = 0; i < paths.length;i++) {
			this.ImagePreloads.push(new Image());
			this.ImagePreloads[this.ImagePreloads.length - 1].src= paths[i];
		}
	},

	//
	// if params includes a test_id member, it loads the XML file from create_websrv;
	// if it doesn't, we're creating a new test.
	//
	invoke: function(invoke_params, place_to_edit, create_websrv_url) {

		if ($(place_to_edit).tagName.toLowerCase() != "form") {
			throw("OkTest editor needs to be invoked into a form element, for proper data serialization");
		}

		this.IsRecoveredAutosaveNotYetSaved = false;
		this.CreateWebsrvUrl = create_websrv_url;

		

		this.InvokeParams = invoke_params;
		this.PlaceToEdit = place_to_edit;
		this.Uploaders = new Object(); // This is where we'll keep all our picture uploaders; indexed by id
		this.predrawLoad();
		var params = new Object();
		if (this.InvokeParams.test_id) {
			params.load_test_xml = 1;
			params.test_id = this.InvokeParams.test_id;
		}
		else if (this.InvokeParams.tranny_load_test) {
			params.load_test_xml = 1;
			params.tranny_load_test = 1;
			params.tranny_allowed = this.InvokeParams.tranny_allowed;
			params.tranny_test_url = this.InvokeParams.tranny_test_url;
		}
		else {

			params.create_new_test = 1;
			
			// We can pass new test XML requests an optional title, which get spit into the XML
			if (this.InvokeParams.test_title) {
				params.test_title = this.InvokeParams.test_title;
			}	

		}
		

		
		params.cache_bust = Math.round(Math.random()*1000000000);
		var req = new Ajax.Request(this.CreateWebsrvUrl,
															{method: 'post'
															,parameters: params
															,onSuccess: this.gotTestXMLData.bind(this)
															,onFailure: this.failedTestData.bind(this)});

	},
	
	translateTest : function() {
		this.invoke({tranny_load_test : 1, tranny_allowed : $F('translate-password'), tranny_test_url : $F('translate-url'), tranny_language : $F('translate-language') },
			this.PlaceToEdit,
			this.CreateWebsrvUrl);		
	},

	gotTestXMLData : function(data) {
		this.Test = new OkTest(data.responseText);
		if (this.InvokeParams.tranny_load_test) {
			this.changeTestForTranny();
		}
		this.getTestExtrinsicData(true);

	},

	getTestExtrinsicData : function(is_initial_load) {
		var req = new Ajax.Request(this.CreateWebsrvUrl,
										{method: 'post'
										,parameters: {get_test_extrinsic_data: 1, test_id: this.Test.Data.OkTest.TestId, cache_bust : Math.round(Math.random()*1000000000)}
										,onSuccess: this.gotTestExtrinsicData.bindAsEventListener(this, is_initial_load)
										,onFailure: this.failedExtrinsicTestData.bind(this)});

	},

	gotTestExtrinsicData : function(data, is_initial_load) {


		var dom = parseXml(data.responseText);
		var j = xml2json(dom, "   ");
		var obj = j.evalJSON();


		this.TestExtrinsicData = obj.ExtrinsicData;
		//alert(Math.round(Math.random()*1000000000)); 			//DEBUG_PETER

		if (is_initial_load) {

			// great; now get the list of legal categories
			var req = new Ajax.Request(this.CreateWebsrvUrl,
									   {method: 'get', //DEBUG_PETER : was post
									   parameters: { get_editor_config : 1, lang_code : this.Test.Data.OkTest.LanguageCode , cache_bust : Math.round(Math.random()*1000000000) },
									   onSuccess: this.gotEditorConfigData.bind(this),
									   onFailure: this.failedEditorConfigData.bind(this)});
		}
		else {	
			this.updateExtrinsicDataDisplay();
			this.updatePageLaunch();
			this.updatePageContest();
			this.updateMenuContestName();
			this.updatePageReview();	
			
		}

	},

	gotEditorConfigData : function (data) {
		var dom = parseXml(data.responseText);
		var j = xml2json(dom, "   ");
		var obj = j.evalJSON();
		this.EditorConfig = obj.EditorConfig;
		this.arrayPrepareEditorConfig();
		this.recordAction("save-event");
		this.recordAction("autosave-event");
		this.predrawAutosaveCheck();
		this.updateDebugBoxes();
	},

	updateDebugBoxes : function() {
		if ($('debug-area') && $('debug-area').style.display != "none") {
			$("debug1").value = this.Test.outputXML();
		}
	},

	failedTestData : function() { alert("Failed to get test data!"); },
	failedExtrinsicTestData : function() { alert("Failed to get test extrinsic data!"); },
	failedEditorConfigData : function() { alert("Failed to get editor config!"); },

 	saveWork : function() {
		this.closeAllEditors();
		var title = this.Test.Data.OkTest.Title["#cdata"];
		var title_simp = title.toLowerCase();
		title_simp = title_simp.replace(/ /g,"");
		if (title_simp.indexOf("unnamed") != -1) {
			scroll(0,0);
			alert("Please give your test an original name before saving. Click the title to edit it.");
		}
		else if (title_simp.length < 2) {
			scroll(0,0);
			alert("Please give your test a longer, more meaningful name! Click the title to edit it.");			
		}
		else if (this.saveInProcess) {
			// user is currently trying to save anyway...
		}
		else {
			this.saveInProcess = true;
			this.autoCreateThumbnailIfNone();
			$('save-button').src = "http://cdn.okcimg.com/_img/helloquizzy/buttons/animated_button.gif";
			$('save-link-text').innerHTML = "Saving...";

			var params = { save_test_xml : 1, test_id : this.Test.Data.OkTest.TestId,	xml : this.Test.outputXML()	};
			if (this.isSoftResetNeeded()) {
				this.noteSoftResetNotNeeded();
				params.resetStats = "1";
			}
			var req = new Ajax.Request(this.CreateWebsrvUrl, { method: 'post',
				parameters: params,
				onSuccess: this.successSaveWork.bind(this),
				onFailure: this.failedSaveWork.bind(this)
			});
		}
	},

	successSaveWork : function(data) {

		this.saveInProcess = false;
		var dom = parseXml(data.responseText);
		var j = xml2json(dom, "   ");
		var obj = j.evalJSON();
		if (obj.xml.LoginNeeded && obj.xml.LoginNeeded == "true") {
			this.showLoginJoinPage();
			this.updateSaveWork();
		}
        else if(obj.xml.ErrorCode && obj.xml.ErrorCode != "0")
        {
			alert("Error code: " + obj.xml.ErrorCode);
			if (obj.xml.ErrorMsg) {
				alert("Error message: " + obj.xml.ErrorMsg);
			}
			this.updateSaveWork();
        }
		else {
			$('save-button').src = "http://cdn.okcimg.com/_img/helloquizzy/buttons/disk.png";
			$('save-link-text').innerHTML = "Saved.";
			$('save-link-text').style.className = "";			
			this.IsRecoveredAutosaveNotYetSaved = false;
			this.recordAction("save-event");
			this.recordAction("autosave-event");
			this.updateSaveWork();
			// After we save it, certain things may change, like the preferred URL.
			this.getTestExtrinsicData(false);
		}
	},

	failedSaveWork : function(data) { 
		this.saveInProcess = false;
		alert("Save failed! Try again, and if this persists, please let us know immediately on the feedback page."); 
		this.updateSaveWork();
	},

	autosaveWork : function() {
		var req = new Ajax.Request(this.CreateWebsrvUrl, { method: 'post',
			parameters: { autosave_test_xml : 1, test_id : this.Test.Data.OkTest.TestId,	xml : this.Test.outputXML()	},
			onSuccess: this.successAuto.bind(this),
			onFailure: this.failedAuto.bind(this)
		});
	},

	successAuto : function(data) {
		this.recordAction("autosave-event");
		this.updateSaveWork();
	},

	failedAuto : function(data) { alert("Foobar! Fail saved!");},

	discardAutosave : function() {
		var req = new Ajax.Request(this.CreateWebsrvUrl, { method: 'post',
			parameters: { "discard_autosave" : 1, "test_id" : this.Test.Data.OkTest.TestId	},
			onSuccess: this.successDiscardAutosave.bind(this),
			onFailure: this.failedDiscardAutosave.bind(this)
		});
	},

	successDiscardAutosave : function(data) {
		this.draw();
		this.getTestExtrinsicData(false);
	},

	failedDiscardAutosave : function(data) { alert("Failed to discard autosave");},

	loadFromAutosave : function() {

		this.IsRecoveredAutosaveNotYetSaved = true;
		var req = new Ajax.Request(this.CreateWebsrvUrl,
												{method: 'post'
												,parameters: {"load_test_autosave_xml" : 1, "test_id" :  this.Test.Data.OkTest.TestId}
												,onSuccess: this.gotTestXMLData.bind(this)
												,onFailure: this.failedTestData.bind(this)});
	},



//--------------------------------------------------------------------------------------------------------------------------------
//
// D R A W I N G     F U N C T I O N S
//
//--------------------------------------------------------------------------------------------------------------------------------


	predrawAutosaveCheck: function() {
		var res = '';
		if (this.TestExtrinsicData.NewerAutosaveExists && this.TestExtrinsicData.NewerAutosaveExists == 'true' && ! this.IsRecoveredAutosaveNotYetSaved) {
			res += ' '
			        + '<div id="recover">'
			        + ' <h2>Recover Autosave?</h2>'
			        + ' <p class="desc">You closed your browser without saving your last edits, so we'
			        + '    auto-saved.  Do you want to load the auto-save?</p>'
			        + ' <p class="bacon"><a href="javascript:TestEditor.loadFromAutosave()">Yes, use the auto-save.</a></p>'
			        + ' <p class="turkey"><a href="javascript:TestEditor.discardAutosave()">No, use my last save. Trash the auto-save.</a></p>'
			        + '</div>'
			        + '';
			$(this.PlaceToEdit).innerHTML = res;
		}
		else {
			this.draw();
		}
	},

	predrawLoad: function () {
	        var res = '';
		res += ' '
		        + '<div id="loader">'
		        + ' <p>Loading...</p>'
		        + ' <p><img src="http://cdn.okcimg.com/_img/helloquizzy/icons/ajax-loader2.gif" /></p>'
		        + '</div>'
		        + '';
		$(this.PlaceToEdit).innerHTML = res;
	},

	draw: function() {
		
		var res = '';
		res += ' '
			+  '<div id="body-rock">'
			+    this.generateFixedShell()
			+  '</div>'
			+  '<div id="mobility">'
			+  ' <div class="test-page" id="test-page-basics">' + this.generatePageBasics() + '</div>'
			+  ' <div class="test-page" id="test-page-contents" style="display:none;">' + this.generatePageContents() + '</div>'
			+  ' <div class="test-page" id="test-page-scoring" style="display:none;">' + this.generatePageScoring() + '</div>'
			+  ' <div class="test-page" id="test-page-look" style="display:none;">' + this.generatePageLook() + '</div>'
			+  ' <div class="test-page" id="test-page-review" style="display:none;">' + this.generatePageReview() + '</div>'
		    +  ' <div class="test-page" id="test-page-launch" style="display:none;">' + this.generatePageLaunch() + '</div>'
			+  ' <div class="test-page" id="test-page-contest" style="display:none;">' + this.generatePageContest() + '</div>'
			+  ' <div class="test-page" id="test-page-notices" style="display:none;"' + this.generatePageNotices() + '</div>'
			+  ' <div style="clear:both;"><!-- clear --></div>'
			+  '</div>';

		$(this.PlaceToEdit).innerHTML = res;
		this.recordAction("edit-event");
		this.turnOnDragging();
	},
	
	generateFixedShell: function() {
		
		var res = ''
			+  ' <div id="url-bar">' + this.generateUrlBar() + '</div>'
			+  ' <div id="status-wrapper">'
			+  ' <div id="status-for-ems-left"></div>'
			+  ' <p id="test-status">' + this.generateTestStatusTab() + '</p>'
			+  ' <div id="status-for-ems-right"></div>'
			+  ' </div>'
			+  ' <div id="test-menu"><div id="for-ems-top-left" class="round"></div><div id="for-ems-top-right" class="round"></div>' 
			+      this.generateTestMenu() 
			+  ' </div>'
			+  this.generateSaveMenu();
		return res;
	},

	generateUrlBar : function() {
		var advicetext="Test it!";
		if (this.TestExtrinsicData.LiveStatus == "live") {
			advicetext="Spread it!";
		}

		var res = "";

		if (this.TestExtrinsicData.PreferredUrl) {		
			res +=  ' <h1><span>' + advicetext + '</span><a href="' 
				+ this.generatePreferredUrl(true) + '" id="preferred-url" target="_blank">' + this.generatePreferredUrl() 
				+ '</a></h1>';
		}
		else {
			res +=  ' <h1><span>&nbsp;</span></h1>';
		}

		return res;

	},
	updateUrlBar : function() {
		$('url-bar').innerHTML = this.generateUrlBar();
	},

	generateTestStatusTab : function() {
		var linkclass="paused";
		var linktext="Paused";
		var res = "";
		if (this.TestExtrinsicData.LiveStatus == "live") {
			linkclass="live";
			linktext="Alive";
		}
		res = 'Your test is <a href="javascript:TestEditor.showTestPage(\'launch\')" class="' + linkclass + '">' + linktext +'</a>.';
		return res;
		
	},
	
	updateTestStatusTab : function() {
		$('test-status').innerHTML = this.generateTestStatusTab();
	},
	
	updateFixedShell: function() {
		$('body-rock').innerHTML = this.generateFixedShell();
	},

	generatePreferredUrl: function(use_relative) {
		var url_root = "http://www.helloquizzy.com/tests/";
		
		if (use_relative) return "/tests/" + this.TestExtrinsicData.PreferredUrl;
		else return url_root + this.TestExtrinsicData.PreferredUrl;
	},

	generateTestMenu: function() {
		var contest_menu_display = "";
		var menu_launch_class="";
		if (! this.shouldShowContestMenu()) {
			contest_menu_display = "display:none;";
			menu_launch_class = "last";
		}
		var res = '<ul id="test-nav">'
			+ ' <li id="menu-basics" class="current"><a href="javascript:TestEditor.showTestPage(\'basics\')" accesskey="1">The Basics</a></li>'
			+ ' <li id="menu-contents"><a href="javascript:TestEditor.showTestPage(\'contents\')" accesskey="2">The Questions</a></li>'
			+ ' <li id="menu-scoring"><a href="javascript:TestEditor.showTestPage(\'scoring\')" accesskey="3">The Scores</a></li>'
			+ ' <li id="menu-look"><a href="javascript:TestEditor.showTestPage(\'look\')" accesskey="4">The Look</a></li>'
			+ ' <li id="menu-review"><a href="javascript:TestEditor.showTestPage(\'review\')" accesskey="5">The Advice</a></li>'
			+ ' <li class="' + menu_launch_class + '" id="menu-launch"><a href="javascript:TestEditor.showTestPage(\'launch\')" accesskey="6">The Launch</a></li>'
			+ ' <li class="last" id="menu-contest" style="' + contest_menu_display + '">' + this.generateMenuContestName() + '</li>'
			+ ' <li id="menu-notices" style="display:none;"><a href="javascript:TestEditor.showTestPage(\'notices\')">Notices</a></li>'
			+ '</ul>'
			+ '<div id="test-nav-cap"></div>';
		return res;
	},

	shouldShowContestMenu: function() {
		if (this.TestExtrinsicData.EnteredInContest.HasBeenAnEntry == "true"
			|| this.EditorConfig.UpcomingTestContests.Contest && this.EditorConfig.UpcomingTestContests.Contest.length > 0) {
				return true;
		}
		else {
			return false;
		}
	},

	generateMenuContestName: function() {
		var res = '<a href="javascript:TestEditor.showTestPage(\'contest\')" accesskey="7">';
		if (this.TestExtrinsicData.EnteredInContest.HasBeenAnEntry == "true") {
			res += 'Contest Details';
		}
		else {
			res += 'Enter a Contest';
		}
		res += '</a>';
		return res;
	},

	updateMenuContestName: function() {
		$('menu-contest').innerHTML = this.generateMenuContestName();
	},

	generateSaveMenu: function() {
		var res = ''
			+ '<p class="button">'
			+		'<img src="http://cdn.okcimg.com/_img/helloquizzy/buttons/disk.png" id="save-button" alt="Save button"/>'
			+ '  <span id="save-work-wrapper">' + this.generateSaveWork() + '</span>'
			+ '</p>'
			+ '<p id="time-spent-editing-wrapper">'
			+ '  Time well-spent: <strong id="time-spent-editing">' + this.generateTimeSpentEditing() + '</strong>';
		if (window.location.href.indexOf("translate") != -1) {
			res += '<br /><a href="javascript:TestEditor.showTrannyLoadTest()" style="color:#f00;">Translate a Test</a>'
			+ '<br /><a href="javascript:TestEditor.toggleDebugArea()">Toggle debug JSON/XML</a>';
		}			
			res += ''
			+ '</p>';
		return res;
	},

	generateSaveWork: function() {
		var res = "";
		var t1 = this.timeSinceLastAction("save-event");
		var t2 = this.timeSinceLastAction("edit-event");
		if (this.IsRecoveredAutosaveNotYetSaved || t1 > t2) {
			res += '<a href="javascript:TestEditor.saveWork()" class="unsaved" id="save-link-text">Save</a>';
		}
		else {
			res += '<a href="javascript:TestEditor.saveWork()" id="save-link-text">Saved.</a>';
		}
		return res;

	},

	generateTimeSpentEditing: function() {
		var d = new Date();
		var milliseconds = this.Test.Data.OkTest.TimeSpentEditing;
		return this.generateTimeLapseFromMilliseconds(milliseconds);

	},

	generateTimeLapseFromMilliseconds: function(ms) {
		var secs = ms / 1000;
		if (secs < 100) {
			return Math.round(secs) + " secs";
		}
		else if (secs < 7200) {
			return Math.round(secs / 6) / 10 + " mins";
		}
		else {
			return Math.round(secs / 360) / 10 + "hrs";
		}

	},

	//
	// Anythign that could come from extrinsic data
	// in a call for updates
	//
	updateExtrinsicDataDisplay: function() {
		this.updateTestStatusTab();
		this.updateUrlBar();
		if ($('test-launch-preferred-url')) {
			$('test-launch-preferred-url').innerHTML = this.generatePreferredUrl();
		}
	},

	updateTimeSpentEditing: function() {
		$('time-spent-editing').innerHTML = this.generateTimeSpentEditing();
	},

	updateSaveWork: function() {
		$('save-work-wrapper').innerHTML = this.generateSaveWork();
	},

	toggleDebugArea: function () {
		if  ($('debug-area').style.display == '') {
			$('debug-area').style.display = 'none';
		}
		else {
			$('debug-area').style.display = '';
		}
	},

	showTestPage: function (p) {
		this.closeAllEditors();

			var pages = new Array ("basics", "contents", "scoring", "review", "launch", "look", "notices", "contest");
			for (i = 0; i < pages.length; i++) {
				if (p == pages[i]) {
					$("test-page-" + pages[i]).style.display="";
					$("menu-" + pages[i]).addClassName("current");
					
					if (p == "contents") { bottomBarRefresh(); bruteForceRemoveDragging(); }
				}
				else {
					if ($("test-page-" + pages[i])) {
						$("test-page-" + pages[i]).style.display="none";
					}
					if ($("menu-" + pages[i])) {
						$("menu-" + pages[i]).removeClassName("current");
					}
				}
			}
			window.scrollTo(0,0);
	},


	generatePageLaunch: function() {

		var res = '';
		res += ''
		+ this.generateTestTitle()
		+ '  <div id="test-launch-status" class="container clearfix">'
		+     this.generateTestLaunchStatus()
		+ '  </div>';
		if (this.TestExtrinsicData.LiveStatus == "live") {

			res  += ''

			+ '  <div id="test-launch-url-to-spread" class="container clearfix">'
			+     this.generateTestLaunchUrl()
			+ '  </div>'
			+ '  <div id="test-launch-about-author" class="container clearfix">'
			+     this.generateTestLaunchAboutAuthor()
			+ '  </div>'
			+ '  <div id="test-launch-nuke-old-stats" class="container clearfix">'
			+     this.generateTestLaunchNukeOldStats()
			+ '  </div>'
			// + '  <div id="test-launch-invitations" class="container clearfix">'
			// 			+     this.generateTestLaunchInvitations()
			// 			+ '  </div>'

			+ '  <div id="test-launch-blog-html" class="container clearfix">'
			+     this.generateTestLaunchBlogHTML()
			// + '  </div>'
			// 			+ '  <div id="test-launch-delete-it" class="container clearfix">'
			// 			+     this.generateTestLaunchDeleteIt()
			+ '  </div>';

		}
		return res;

	},
	
	updatePageLaunch: function() {
		$('test-page-launch').innerHTML = this.generatePageLaunch();
	},
	
	updatePageScoring: function() {
		$('test-page-scoring').innerHTML = this.generatePageScoring();		
	},
	
	generatePageNotices: function() {
		var res = '';
		return res;		
	},
	
	postNotice: function(html) {
		$('test-page-notices').innerHTML = html;
		this.showTestPage('notices');
	},

	generateTestLaunchUrl: function() {
		var res = '';
		res += '<h3>Spread it!</h3>'
		+ '<div class="guts">'
		+ '<p style="color:#090;">'
			+  '      Congratulations on your launch. Now let the world know.'
			+  '      Seek out forums and the people who blog all about your test\'s topic and <em>send your quiz to them</em>. Odds are, they\'ll love posting about it and thank you. '
			+  '      Giving your test a push in the right community '
			+  '      can be the difference between 100 takers and 100,000 takers. So google is your friend. Research the communities and experts and let them all know.'
	    + '</p><p>'
		+ '<span id="test-launch-preferred-url" style="font-size:1.3em;color:#0A4CA2;">' + this.generatePreferredUrl() + '</span></p>'
		+ '</div>';
		return res;
	},


	generateTestLaunchStatus: function() {
		var res = '';

		if (this.TestExtrinsicData.ExtrinsicDataFound == "false") {
			res += '<h3>Save your test!</h3>'
				+ '<div class="guts">'
				+ ' <p class="auto">Before you can publish your test, make sure you '
				+ ' save it.  There\'s a cute button to the left that '
				+ ' manages that for you.</p>'
				+ '</div>';
		}
		else if (this.TestExtrinsicData.LiveStatus == "live") {
			res += '<h3>PAUSE IT</h3>'
			+ '		<div class="guts">'
			+ '     <p>Your test is <strong class="live">alive</strong> and anyone can take it. No one will be able to take your test when it is paused.</p>'
			+ '     <p class="button">'
			+ '			 <img src="http://cdn.okcimg.com/_img/helloquizzy/buttons/pause.png" alt="Pause It Button" />'
			+ '      <a href="javascript:TestEditor.updateTestLiveStatus(\'paused\')">PAUSE IT</a>'
			+ '     </p>'
			+ '		</div>';
		}
		else if (this.TestExtrinsicData.LiveStatus == "paused") {
			res += '<h3>UNPAUSE IT</h3>'
			+ '		<div class="guts">'
			+ '     <p>Your test is <b>paused</b> and no one except you can take it. When it\'s ready, unpause it.</p>'
			+ '     <p class="button">'
			+ '			 <img src="http://cdn.okcimg.com/_img/helloquizzy/buttons/play.png" alt="Launch the test button" />'
			+ '      <a href="javascript:TestEditor.updateTestLiveStatus(\'live\')" class="launch">UNPAUSE IT</a>'
			+ '     </p>'
			+ '     </div>';
		}
		else if (this.TestExtrinsicData.LiveStatus == "unfinished") {
			res += '<h3>LAUNCH IT</h3>'
			+ '		<div class="guts">'
			+ '     <p>Is <strong id="launch-title">' + this.Test.Data.OkTest.Title["#cdata"] + '</strong> ready for the big time? Are you ready for fame? Launch it and we\'ll start the promotion.</p>'
			+ '     <p class="button">'
			+ '			 <img src="http://cdn.okcimg.com/_img/helloquizzy/buttons/play.png" alt="Launch the test button" />'
			+ '      <a href="javascript:TestEditor.updateTestLiveStatus(\'live\')" class="launch">TAKE IT LIVE</a>'
			+ '     </p>'
			+ '     </div>';
		}
		else {
			res += '<h3>ERROR</h3><div>We\'re unsure what state your test is in.</div>';
		}

		return res;
	},



	generateTestLaunchAboutAuthor: function() {
		var res = "";
		var desc = "";
		var img_src = "";
		var img_w = "";
		var img_h = "";
		var has_img = false;
		var name = "";
		if (this.Test.Data.OkTest.AboutTheAuthor.Name) {
			name = this.Test.Data.OkTest.AboutTheAuthor.Name;
		}

		if (this.Test.Data.OkTest.AboutTheAuthor.Description && this.Test.Data.OkTest.AboutTheAuthor.Description["#cdata"]) {
			desc = this.Test.Data.OkTest.AboutTheAuthor.Description["#cdata"];
		}

		res += '<h3 class="fatty">About the Author</h3>'
			+ ' <div class="guts">'
			+ ' <p>Optional. If you fill in anything here, we\'ll generate a neat page all about you.</p>'
			+ ' <div id="about-the-author-pic-wrapper" class="clearfix">'
			+     this.generateAboutTheAuthorPic()
			+ ' </div>'
			+ ' <div id="about-the-author-pic-add-wrapper" class="clearfix">'
			+     this.generateAboutTheAuthorPicAddLink()
			+ ' </div>'
			+ ' <p class="about-title">'
			+ ' 	<label for="about-the-author-name">Your name (real, fake, whatever)</label> '
			+ ' 	<input type="text" value="' + name + '"id="about-the-author-name" onkeypress="return killEnterAll(event);" onchange="TestEditor.updateAboutTheAuthorName();" />'
			+ ' </p>'
			+ ' <p class="textarea">'
			+ '		<label for="about-the-author-description">About You</label>'
			+ ' 	<textarea id="about-the-author-description" onchange="TestEditor.updateAboutTheAuthorDescription();">' + desc + '</textarea>'
			+ ' </p>'
			+ '<div id="about-the-author-pic-upload-wrapper"></div>'
			+ ' </div>';

		return res;
	},

	generateAboutTheAuthorPic: function() {
		var res = '';
		if (this.Test.Data.OkTest.AboutTheAuthor.Image && this.Test.Data.OkTest.AboutTheAuthor.Image.Src) {
			img_src = this.Test.Data.OkTest.AboutTheAuthor.Image.Src;
			img_w = this.Test.Data.OkTest.AboutTheAuthor.Image.Width;
			img_h = this.Test.Data.OkTest.AboutTheAuthor.Image.Height;
			res += '<p class="image"><img src="' + img_src + '___110_110_110_110_28f10064_.jpg" /></p>'
			+ '     <p id="remove-about-image" class="button two-columns short"><img src="http://cdn.okcimg.com/_img/helloquizzy/buttons/delete_120.png" alt="Remove Image button" /><a href="javascript:TestEditor.updateAboutTheAuthorRemovePic();">Remove Pic</a></p>';
		}
		return res;
	},

	generateAboutTheAuthorPicAddLink: function() {
		var res = "";
		if (! this.Test.Data.OkTest.AboutTheAuthor.Image || ! this.Test.Data.OkTest.AboutTheAuthor.Image.Src) {
			res += '<p class="image"><img src="http://cdn.okcimg.com/_img/helloquizzy/user_defs/about_large.png" alt="Default User Image" /></p>'
					+ '<p id="add-about-image" class="button two-columns short">'
			 		+ '	<img src="http://cdn.okcimg.com/_img/helloquizzy/buttons/image_120_8.png" alt="Add Image button" />'
					+ '	<a href="javascript:TestEditor.updateAboutTheAuthorAddPic();">Add Your Pic</a>'
					+ '</p>';
		}
		return res;
	},


	generateTestLaunchNukeOldStats: function() {
		var res = "";


		res += '<h3>Old Stats</h3>'
			+ '<div class="guts">'
		  +  '<p>Imagine you just overhauled your test\'s variables, questions or scoring. Stats won\'t really make sense with the old scores.  Just reset them!  This will give your test a fresh start.</p>'
			+ '<p id="nuke-old-stats-link-wrapper" class="button short three-columns">'
			+ '	<img src="http://cdn.okcimg.com/_img/helloquizzy/buttons/trash_190.png" alt="Erase Stats button" />'
			+ ' <a href="javascript:TestEditor.nukeOldTestStats();" id="nuke-old-stats-link">Erase old stats</a>'
			+ '</p>'
			+ '<p id="nuke-old-stats-response-wrapper"></p>'
			+ '</div>';

		return res;
	},

	generateTestLaunchInvitations: function() {
		var res = "";
		return res;
	},

	generateTestLaunchBlogHTML: function() {
		var res = "";
		res += '<h3>Blog it!</h3>'
			+ ' <div class="guts">'
	    + ' 	<p>In addition to sending friends to the above address, you can make your test popular by mentioning it in forums and blogs.  How would you like to post it?</p>'
	    + ' 	<ul class="blog-it clearfix">'
			+ '  		<li><a href="javascript:TestEditor.generateAnnouncement(\'facebook\')" class="facebook"><span>facebook</span></a></li>'
			+ '  		<li><a href="javascript:TestEditor.generateAnnouncement(\'myspace\')" class="myspace"><span>myspace</span></a></li>'
			+ '  		<li><a href="javascript:TestEditor.generateAnnouncement(\'digg\')" class="digg"><span>digg</span></a></li>'
			+ '  		<li><a href="javascript:TestEditor.generateAnnouncement(\'blog\')" class="blog"><span>blog/LiveJournal</span></a></li>'
			+ '  		<li><a href="javascript:TestEditor.generateAnnouncement(\'html\')" class="html"><span>Plain&nbsp;HTML</span></a></li>'
			+ '  		<li><a href="javascript:TestEditor.generateAnnouncement(\'bb\')" class="bb"><span>BB&nbsp;Code</span></a></li>'
			+ ' 	</ul>'
			+ '		<div id="generate-announcement-area"></div>'
			+ ' </div>';
		
		return res;
	},

	generateTestLaunchDeleteIt: function() {
		var res = "";
		return res;
	},

	updatePageContest: function() {
		$('test-page-contest').innerHTML = this.generatePageContest();
	},

	generatePageContest: function() {

		var res = '';
		var contest = this.TestExtrinsicData.EnteredInContest;		

		res += '<div class="header">'
		    +  '	<h2>Test Contests</h2>'
		    +  '	<p>HelloQuizzy test contests are a lot of fun, and we pay with big prizes. '
		    +  ' 	if the test you are working on fits nicely into one of the following themes, '
		    +  ' 	enter the contest.  Prizes go to the #1 most popular and #1 highest rank (quality) '
		    +  ' 	test.</p>'
		    +  '</div>';
		
        var current_time = new Date().getTime() / 1000;

		// Case 0: they've never saved their test
		if (! this.TestExtrinsicData.PreferredUrl) {
			res += ''
			    + '<p class="fat-head pr-20 pl-20">Your test has never been saved. Please save it, using the button to the left. '
					+ 'Once your test is saved, we\'ll show you upcoming contests you can enter it in.'
 			    + '</p>';
		}
		// Case 1 : they were once in a contest but removed / left
		else if (contest.HasBeenAnEntry == "true" && contest.IsStillAnEntry == "false") {

			res += '<p>'
			    +  'Your test was entered in the contest <strong>'
			    +  contest.Contest.Theme["#cdata"]
			    +  '</strong> and then removed.'
			    +  'You\'re no longer eligible to enter this test in contests.'
			    + '</p>';
		}
		// Case 2 : they saw it through and their contest is over
		else if (contest.IsStillAnEntry == "true" && contest.IsContestOver == "true") {

			var won = false;
			res += '<div>'
			    +  '<p>Your test was in the contest <strong>'
			    +  contest.Contest.Theme["#cdata"]
			    + '</strong> and the contest is over.  Your results:</p>'
			    + '<ul class="rank-summary">'
			    + '  <li>Popularity Rank: <strong>#' + contest.PopularityRank + '</strong></li>'
			    + '  <li>Quality Rank: <strong>#' + contest.QualityRank + '</strong></li>'
                + '</ul>';
			if (contest.PopularityRank == "1") {
				won = true;
				res += "<p>You win: " + contest.Contest.PopularityPrize + "</p>";
			}
			if (contest.QualityRank == "1") {
				won = true;
				res += "<p>You win: " + contest.Contest.QualityPrize + "</p>";				
			} 
			if (won) {
				res += 'We mail prizes out automatically, so if you don\'t receive yours '
				    +  ' within 3 weeks of the end of the contest, please let us know. '
				    +  ' Congrats!';
			}			
			res += "</div>";
			res += this.generateContestUpdateForm(contest.Contest);
		}
		// Case 3 : their contest is currently running and their test is live
		else if (contest.IsStillAnEntry == "true" && 
                 this.TestExtrinsicData.LiveStatus == "live" &&
                 current_time > contest.Contest.EntryDeadline) 
        {
			res += '<div class="contest-entry clearfix">'
					+  '	<h3>You\'re Entered!</h3>'
			    +  '	<p>Yay! The <strong>'
			    +  		contest.Contest.Theme["#cdata"]
			    +  '	</strong> contest is currently running, and you are a contender.</p>'
			    +  '	<p>At last check:</p>'
			    +  '		<ul class="rank-summary clearfix">'
                +  '  		<li class="popularity">Popularity Rank: <strong>#' + (contest.PopularityRank != 0 ? contest.PopularityRank : "?") + '</strong></li>'
                +  '  		<li class="quality">Quality Rank: <strong>#' + (contest.QualityRank != 0 ? contest.QualityRank : "?") + '</strong></li>'
          +  '		</ul>'
			    +  ' 	<p class="clear">It is still early! You should <strong>promote &amp; improve</strong> your test as much as possible.</p>'
					+  		this.generateContestUpdateForm(contest.Contest)
			    +  '</div>';			
		}
		
		// Case 4 : their contest is currently running but their test is paused
		else if (contest.IsStillAnEntry == "true" && 
                 this.TestExtrinsicData.LiveStatus != "live" &&
                 current_time > contest.Contest.EntryDeadline) 
        {
			res += '<div class="contest-entry clearfix">'
					+  '	<h3>Contest Entered</h3>'
			    +  '	<p>You\'ve entered your test in the <strong>'
			    +  		contest.Contest.Theme["#cdata"]
			    +  '	</strong> contest.</p>'
				+  '	<p><b>You need to launch your test live to compete in this contest!</b>'
			    +			this.generateContestUpdateForm(contest.Contest)
			    +  '</div>';
		}

		// Case 5 : they've entered but their contest hasn't started scoring
		else if (contest.IsStillAnEntry == "true" && 
                 current_time < contest.Contest.EntryDeadline) {
			res += '<div class="contest-entry clearfix">'
					+  '	<h3>Contest Entered</h3>'
			    +  '	<p>You\'ve entered your test in the <strong>'
			    +  		contest.Contest.Theme["#cdata"]
			    +  '	</strong> contest.</p>'
					+  '	<p>The contest still has not officially begun, so now is the time'
			    +  '	to make lots and lots of improvements.  You should also make sure your test is live '
			    +  '	sometime before the contest begins.  We\'ll reset the stats right at contest launch time.</p>'
					+			this.generateContestUpdateForm(contest.Contest)
			    +  '</div>';
		}

		// Case 6 : they've yet to enter a contest
		else {
			res += this.generateContestSummaries();			
		}

		return res;

	},


	generateContestUpdateForm: function(contest) {
		var res= "";
		var addy = "";
		if (this.TestExtrinsicData.EnteredInContest.SendPrizeTo) {
			addy = this.TestExtrinsicData.EnteredInContest.SendPrizeTo["#cdata"];
		}
		res += '<h4>Entry Form</h4>'
		    + '<p>You\'re now entered! Good luck on the <strong>'
		    + 	contest.Theme["#cdata"]
		    + '</strong> contest.</p>  <p>Your only responsibility is '
		    + ' to keep this box updated with your mailing address, '
				+ ' exactly as you\'d want a shipping label printed for your prize.  We automate the process'
				+ ', so it\'s up to you to enter this <strong>before you win</strong>.</p>'
				+ '<p><label for="contest-address">Your FULL NAME AND Shipping Address</label></p>'
				+ '<p><textarea id="contest-address">' + addy + '</textarea></p>'
				+ '<p class="update-info"><a href="javascript:TestEditor.updateContestInfo()">Update Info</a></p>'
				+ '<h3 class="alt">Contest Details</h3>';
		res += this.generateContestDescription(contest);

		return res;
		
	},

	updateContestInfo : function() {
		var req = new Ajax.Request(this.CreateWebsrvUrl,
									{method: 'post'
									,parameters: {update_contestant_info: 1, 
												  address: $('contest-address').value }
									,onSuccess: this.successEnterTestContest.bind(this)
									,onFailure: this.failedEnterTestContest.bind(this)});
	},

	generateContestSummaries : function() {
		var contests = this.EditorConfig.UpcomingTestContests.Contest;
		var res = "";
		for (var i = 0; i < contests.length; i++) {
			res += this.generateContestDescription(contests[i], i);
			// pulled enter and cofirm
		}
		return res;			
	},
	
	tryToEnterTestContest : function(contestnum) {
		if (confirm("Are you sure this (" + contestnum + ") is the right contest? You are limited to one test per contest, and one contest per test.")) {
			this.enterTestContest(contestnum);
		}
	},
	
	enterTestContest : function(contestnum) {
		var req = new Ajax.Request(this.CreateWebsrvUrl,
									{method: 'post'
									,parameters: {enter_test_contest: 1, 
												  contest_id: this.EditorConfig.UpcomingTestContests.Contest[contestnum].Id, 
												  test_id: this.Test.Data.OkTest.TestId }
									,onSuccess: this.successEnterTestContest.bind(this)
									,onFailure: this.failedEnterTestContest.bind(this)});		
	},
	
	successEnterTestContest: function(data) {
		var dom = parseXml(data.responseText);
		var j = xml2json(dom, "   ");
		var res = j.evalJSON();
		if (res.Response && res.Response == "ALREADY_ENTERED") {
			alert("You already have a test entered in that contest! Good luck with it.");
		}
		else if (res.Response && res.Response == "TOO_OLD") {
			alert("Your test is a time-honored classic, and over 2 months old, so it's not eligible.");
		}
		this.saveWork();
	},

	failedEnterTestContest: function() { alert("AJAX call to enter contest failed!"); },

	generateContestDescription : function(contest, num) {
		var res = "";
		res += ''
		    + '<div class="contest clearfix">'
				+ '	<img src="' + contest.Image.Src + '" />'
				+ '	<h4><span>' + contest.Theme["#cdata"] + '</span></h4>'
				+ '	<div class="description">'
		    + 		contest.Description["#cdata"]
				+ '	</div>'
				+ '	<div class="deadline">'
				+ '		<h5>Deadline</h5>'
				+ '		<p>' + makeSmartDateString(new Date(parseInt(contest.EntryDeadline)*1000), HQ_CONTEST_FORMAT) + '</p>'
				+ ' </div>'
		    + '</div>';
		
		res += '<p class="enter-contest"><a href="javascript:TestEditor.toggleConfirm(\'' + contest.Id + '\');">Enter <span class="sarcasm">(click)</span></a></p>';
			
		res	+= '<div id="confirm-entry-' + contest.Id + '" class="confirm-entry" style="display: none;">'
				+ '	<p class="big">Are you sure this is the right contest?</p>'
				+	' <p class="little">Your limit: one test per contest, and one contest per test.</p>'
				+	' <p class="button">'
				+	'		<img src="http://cdn.okcimg.com/_img/helloquizzy/buttons/yes_button.png" alt="Enter contest image" />'
				+	'		<a href="javascript:TestEditor.enterTestContest(\'' + num + '\');">Yes, Enter!</a>'
				+	'	</p>'
				+	'	<p class="cancel"><a href="javascript:TestEditor.toggleConfirm(\'' + contest.Id + '\');">No,&nbsp;nevermind&nbsp;...</a></p>'
				+	'</div>';
		
		res += ''
		    + '<div class="goodies clearfix">'
		    + '	<h5>The Goodies <span>Winners announced ' 
				+ 		makeSmartDateString(new Date(parseInt(contest.WinnersDetermined)*1000), HQ_CONTEST_FORMAT) 
				+ '</span></h5>'
        + ' <p class="most-popular"><strong>Most popular</strong> wins: ' +  contest.PopularityPrize.Title  + '</p>'
        + ' <p class="highest-votes"><strong>Highest votes</strong> wins: ' +  contest.QualityPrize.Title  + '</p>'
				+ '</div>'
				+ '<div class="goodies-cap"></div>';

		return res;
		
	},
	
	toggleConfirm: function(id) {
		$('confirm-entry-' + id).toggle();
	},

	generatePageContents: function() {

		var res = "";
		var content_items = this.Test.Data.OkTest.Contents.ContentItem;
		// res += "<h2>The Questions</h2>";

		res += this.generateTestTitle();
		res += "<ul id=\"test-contents\">";
		
		res += this.drawOptionsBox(-1);
		
		for (var i = 0; i < content_items.length; i++) {
			res += this.generateContentItemLI(content_items[i]);
		}
		res += "</ul>";
		res += '<div class="contents-footer">'
		    +  '  This is your end of the test. Some quick tips: '
			+  '  <ul>'
			+  '    <li>1. To insert another question, use the "ACTIONS" button, visible when you mouse over an existing question.</li>'
			+  '    <li>2. If you end your test with a page break, you can control what the last button says.</li>'
			/*+  '    <li>3. You can toggle the Arrangement mode, which makes it easier to reorganize your questions, by holding down SHIFT.</li>'*/
			+  '  </ul>'
			+  '</div>';
		
		return res;
	},

  generateContentItemLI : function(item) {
	
			var res = "";
			// The underscore used in the following id is mandatory,
			// as that's how scriptaculous expects sortable id's to be named
			var added_classes = "";
			if (item["@Type"] == "PageBreak") {
				added_classes  = " pagebreak ";
			}
			res += '<li class="content-item container clearfix ' + added_classes + '" id="item_' + item["@Id"] + '">'
				+       this.drawOptionsBox(item["@Id"]) 
				+       this.generateContentItem(item)
				+ '    </li>';
			return res;
	
  },
	
	generateAnnouncement : function(service) {
		var lis = $('test-launch-blog-html').getElementsByClassName('blog-it')[0].getElementsByTagName('li');
		
		var urls = {
			facebook : 'http://www.helloquizzy.com/' + this.TestExtrinsicData.PreferredUrl,  
			myspace : '&lt;a href=&quot;http://www.helloquizzy.com/tests/'+this.TestExtrinsicData.PreferredUrl+'&quot;&gt;'+this.Test.Data.OkTest.Title['#cdata']+'&lt;/a&gt; at &lt;a href=&quot;http://www.helloquizzy.com/&quot;&gt;&lt;b style=&quot;color:#131313&quot;&gt;&lt;span style=&quot;color:#ac000c&quot;&gt;H&lt;/span&gt;ello&lt;span style=&quot;color:#ac000c&quot;&gt;Q&lt;/span&gt;uizzy&lt;/b&gt;&lt;/a&gt;',
			digg : 'http://www.helloquizzy.com/' + this.TestExtrinsicData.PreferredUrl,
			blog : '&lt;a href=&quot;http://www.helloquizzy.com/tests/'+this.TestExtrinsicData.PreferredUrl+'&quot;&gt;'+this.Test.Data.OkTest.Title['#cdata']+'&lt;/a&gt; at &lt;a href=&quot;http://www.helloquizzy.com/&quot;&gt;&lt;b style=&quot;color:#131313&quot;&gt;&lt;span style=&quot;color:#ac000c&quot;&gt;H&lt;/span&gt;ello&lt;span style=&quot;color:#ac000c&quot;&gt;Q&lt;/span&gt;uizzy&lt;/b&gt;&lt;/a&gt;',
			html : '&lt;a href=&quot;http://www.helloquizzy.com/tests/'+this.TestExtrinsicData.PreferredUrl+'&quot;&gt;'+this.Test.Data.OkTest.Title['#cdata']+'&lt;/a&gt; at &lt;a href=&quot;http://www.helloquizzy.com/&quot;&gt;&lt;b style=&quot;color:#131313&quot;&gt;&lt;span style=&quot;color:#ac000c&quot;&gt;H&lt;/span&gt;ello&lt;span style=&quot;color:#ac000c&quot;&gt;Q&lt;/span&gt;uizzy&lt;/b&gt;&lt;/a&gt;',
			bb : '[url=http://www.helloquizzy.com/tests/'+this.TestExtrinsicData.PreferredUrl+']'+this.Test.Data.OkTest.Title["#cdata"]+'[/url] at [url=http://www.helloquizzy.com][b][color=#ac000c]H[/color][color=#131313]ello[/color][color=#ac000c]Q[/color][color=#131313]uizzy[/color][/b][/url]'
		};
		
		var tips = {
			html : '<p><em>Use this general code for any service that accepts HTML</em></p>',
			bb   : '<p><em>Use this general code for any Bulletin Board that accepts <a href="http://en.wikipedia.org/wiki/BBCode" target="blank">BBCode</a></em></p>'
		};
		
		var addTip = (tips[service]) ? tips[service] : "";
		
		var res = "";
		res += '<p>Want your friends to take '
		    + ' <strong>' + this.Test.Data.OkTest.Title["#cdata"] + '</strong>?'
		    + ' <label for="blog-it-link">Just send this link:</label></p>'
	    	+ '<p><input id="blog-it-link" onkeypress="return killEnterAll(event);" type="text" readonly="readonly" onfocus="this.select();" value="'
			+ urls[service] + '" /></p>'
			+ addTip;
		$('generate-announcement-area').innerHTML = res;
					
		for (var i = 0; i < lis.length; i++) {
			var link = lis[i].getElementsByTagName('a')[0];
			
			/* works ... but needs to iterate over the other links too
			var link_class = link.attributes["class"].value.split(" ");
						
			if (link_class[0] == service) {				
				if (link_class[1] == "current") link_class.pop();
				else link_class.push("current");
			}

			link.attributes["class"].value = link_class.join(" ");*/
			
			if (link.className == service) {
			    $(link).addClassName('current');
			}
			else {
			    $(link).removeClassName('current');
			}
		}

	},

	addHTMLContentItem: function (previous_item_id, new_item) {
		// The underscore used in the following id is mandatory,
		// as that's how scriptaculous expects sortable id's to be named:
    var res = this.generateContentItemLI(new_item);
		if (previous_item_id == -1 ) {
			new Insertion.Top("test-contents", res);
		}
		else {
			new Insertion.After("item_" + previous_item_id, res);
		}
		this.turnOffDragging();
		this.turnOnDragging();
	},

	generateContentItem: function(item) {
		var res = "";
		res += '<div class="content-item-wrapper" id="content-item-wrapper-' + item["@Id"] + '">'
			+ '    <div class="handle" id="handle-'+ item["@Id"]+'" onmousedown="$(\'item_' + item["@Id"] + '\').addClassName(\'dragging\')" onmouseup="if(!bruteForceRemoveDragging_Disabled) $(\'item_' + item["@Id"] + '\').removeClassName(\'dragging\'); $(\'item_' + item["@Id"] + '\').style.left=\'auto\';">Move</div>'
			+ '    <div class="content-item-contents" id="content-item-contents-' + item["@Id"] + '">';
    debug_log(res);
		switch(item["@Type"]) {
			case "HTML Block" : res += this.drawHTMLBlock(item); break;
			case "QuestionRadio" : res += this.drawQuestionRadio(item);  break;
			case "QuestionSlider" : res += this.drawQuestionSlider(item);  break;
			case "PageBreak" : res += this.drawPageBreak(item); break;
			case "Image" : res += this.drawImage(item); break;
			case "YouTube" : res += this.drawYouTube(item); break;
			default: res += "<div>Uh oh! No idea how to draw a " + item["@Type"] + "</div>"; break;
		}
		res += '    </div>'
		     + '</div>';
		return res;
	},

	generatePageScoring: function() {
		var funcName = "generatePageScoring";
				
		debug_enter(funcName);
		var res = ""
			+ this.generateTestTitle()
			
			+ ' <div class="container clearfix">'
			+ '   <h3>Variables</h3>'
			+ ' 	<div class="guts">'
			+ '			<p>Use these variable ranges when creating your different result types.</p>'
			+ '			<span id="variable-summary">'
			+     	this.generateVariableSummary()
			+ '			</span>'
			+ '		</div>'
			+ ' </div>'
			
			+ ' <div id="scaling-summary" class="container clearfix">'
			+ '   <h3>Scaling Type</h3>'
			+ '		<div class="guts">'
			+ '			<p>Switch between raw scores (the ranges listed above) and percents.</p>'
			+ '			<span id="scaling-summary-details">'
			+     		this.generateScalingSummary()
			+ '			</span>'
			+ '		</div>'
			+ ' </div>'
			
			+ ' <div id="results-summary" class="clearfix">'
			+     this.generateResultsSummary()
			+ ' </div>';
		
		debug_exit(funcName);
		return res;
	},


	generatePageLook: function() {
		var funcName = "generatePageLook";
		debug_enter(funcName);
		var res = ''
			+ this.generateTestTitle()
			+	this.generateTemplateSummary();
			
			// + this.drawTemplateOptions();
			
		debug_exit(funcName);
		return res;
	},

	generateTemplateSummary: function() {
		var funcName = "generateTemplateSummary";
		debug_enter(funcName);
		var res = ''
			+ '<div id="template-list">'
			+ 	this.generateTemplateList()
			+ '</div>';
			
		debug_exit(funcName);
		return res;

		debug_exit(funcName);
	},
	
	drawTemplateOptions: function() {
		var res = ''
			+ '<div id="template-options-wrapper" class="container clearfix">'
			+ 	this.generateTemplateOptions()
			+ '	<img src="' + this.EditorConfig.ZeroImage + '" onload="TestEditor.autoFillTemplateOptions()" />'
			+ '</div>';
			
		return res;
	},

	updateTemplateSummary: function() {
		var funcName = "updateTemplateSummary";
		debug_enter(funcName);
		$('template-list').innerHTML = this.generateTemplateList();
		// $('template-options-wrapper').innerHTML = this.generateTemplateOptions();
		// this.autoFillTemplateOptions();
		debug_exit(funcName);
	},

	generateTemplateList: function() {
		var funcName = "generateTemplateList";
		debug_enter(funcName);
		var templates = this.EditorConfig.LegalLookTemplates.Template;
		var res = '';
		for (var i = 0; i < templates.length; i++) {
			if (this.Test.Data.OkTest.LookTemplate.Which == templates[i].Name) {
				res += '<div class="container clearfix selected">';
				$("p-create").addClassName(this.Test.Data.OkTest.LookTemplate.Which);						//PW_MOD
				
				if(this.Test.Data.OkTest.LookTemplate.Which == "Dark")
				{
					MCE_CONFIG_QUESTION_TEXT.content_css 	= "/quizzy/tinymce_questions_dark.css";
					MCE_CONFIG_HTML_SRC.content_css		 	= "/quizzy/tinymce_questions_dark.css";
					MCE_CONFIG_RESULT_DESCR.content_css		= "/quizzy/tinymce_questions_dark.css";
					MCE_CONFIG_ANSWER_TEXT.content_css		= "/quizzy/tinymce_answers_dark.css";
				}
			}
			else {
				res += '<div class="container clearfix">';
			}
			res+= '<h3>' + templates[i].Name + '</h3>'
					+ ' <div class="guts">'
					+ '		<img src="'+ templates[i].Image.Src +'" width="'
					+ 		templates[i].Image.Width + '" height="'
					+ 		templates[i].Image.Height + '"'
					+ ' 	onclick="TestEditor.chooseTemplate(\''+templates[i].Name+'\');" />'
					+ ' </div>'
					+ '</div>';
		}
		debug_exit(funcName);
		return res;
	},

	generateTemplateOptions: function() {
		var funcName = "generateTemplateOptions";
		debug_enter(funcName);
		var res = "";
		// retrieve an array of options
		var options = this.getCurrentTemplateOptions();
		if (options.length) {
			res += '<h3>Custom Options</h3>';
			res += '<div class="guts">';
			for (var i = 0; i < options.length; i++) {
				res += '<p class="template-options-item">'
					+ options[i].HTML["#cdata"]
					+ '</p>';
			}
			res += '</div>';
		}

		debug_exit(funcName);
		return res;
		debug_exit(funcName);
	},

	autoFillTemplateOptions: function() {
		var funcName = "autoFillTemplateOptions";
		debug_enter(funcName);
		var stores = this.getCurrentTemplateOptionStores();
		for (var i = 0; i < stores.length; i++) {
			if ($(stores[i].Id)) {
				var value = this.getLookTemplateOptionValue(stores[i].Id);
				if (value && value["#cdata"]) {
					this.autoFillFormValue(stores[i].Id, value["#cdata"]);
				}
				else if (value) {
					this.autoFillFormValue(stores[i].Id, "");
				}
			}
		}

		// And attach onchanges to each one
		for (var i = 0; i < stores.length; i++) {
			Event.observe(stores[i].Id, "change", this.changeTemplateOption.bindAsEventListener(this));
		}
		debug_exit(funcName);
	},

	autoFillFormValue: function(id, value) {
		Form.Element.Deserializers[$(id).tagName.toLowerCase()]($(id), value);
	},

	generateVariableSummary: function() {
		var funcName = "generateVariableSummary";
		debug_enter(funcName);
		var res = "";
		var test_vars = this.Test.Data.OkTest.Var;
		if (test_vars.length == 0) {
			res += 'Your test has 0 variables and can\'t measure anything.  <a href="javascript:TestEditor.showTestPage(\'basics\')">Edit test basics</a>';
		}
		else {
			res += "<ul>";
			for (var i = 0; i < test_vars.length; i++) {
				res += "<li><span class=\"middot\">&middot;</span> "
				    +  " <strong>" + test_vars[i].Name + "</strong> scores range from <strong>"
				    +    test_vars[i].MinValue + "</strong> to <strong>" + test_vars[i].MaxValue + ".</strong>"
				    +  "</li>";
			}
			res += "</ul>";
		}
		debug_exit(funcName);
		return res;
	},

	generateScalingSummary: function() {
		var funcName = "generateScalingSummary";
		debug_enter(funcName);
		var res = "";
		var scaling = this.getScalingType();

		if (scaling == "Percentage") {
			res += '<ul class="scaling-options">'
				+  '   <li><a href="javascript:TestEditor.updateScalingType(\'Raw\');" title="Use raw scores" class="raw">Use Raw Scores</a></li>'
				+  '   <li><a href="javascript:TestEditor.updateScalingType(\'Percentage\');" class="selected percent" title="You\'re using percentages">Using Percentages</a></li>'
				+  ' </ul>';
		}
		else if (scaling == "Raw") {
			res += '<ul class="scaling-options">'
				+  '   <li><a href="javascript:TestEditor.updateScalingType(\'Raw\');" class="selected raw" title="You\'re using raw scores">Using Raw Scores</a></li>'
				+  '   <li><a href="javascript:TestEditor.updateScalingType(\'Percentage\');" class="percent" title="Use percentages">Use Percentages</a></li>'
				+  ' </ul>';
		}
		else {
			res += "<p>Whoa, scaling type has gone crazy.</p>";
		}
		debug_exit(funcName);
		return res;
	},

  generateResultImage: function(id) {
		var funcName = "generateResultImage";
		debug_enter(funcName);

		var res = '';

		if (this.getResultById(id).Image && this.getResultById(id).Image.Src) {
			res += '<p class="image"><img class="result-image" src="' + this.getResultById(id).Image.Src + '___1_500_1_2000_7fa54554_.jpg" /></p>'
					+  '<p class="delete"><a class="result-image-del" class="result-image-del-' + id + '" href="javascript:TestEditor.deleteResultImage(' + id + ')">Delete Image</a></p>';
		}

		debug_exit(funcName);
		return res;
	},

  generateResultImageAddLink: function(id) {
		var funcName = "generateResultImage";
		debug_enter(funcName);

		var res = '';
		if (!this.getResultById(id).Image || !this.getResultById(id).Image.Src) {
			res = '<a class="add-image" id="result-image-add-' + id + '" href="javascript:TestEditor.addResultImage(' + id + ')">Add Image</a>';
		}

		debug_exit(funcName);
		return res;
	},

  generateResult: function(id) {
		var funcName = "generateResult";
		debug_enter(funcName);
		
		var pos = this.getResultItemPositionById(id);
		var num_results = this.Test.Data.OkTest.Results.Result.length;
		var res_list_len = this.getResultList().length;
		var thisResult = this.getResultById(id);
		
		
		var res = '';
		
		if (res_list_len != 1 || pos != num_results -1) {
			res += '<p class="delete-result">'
				+ '  <a href="javascript:TestEditor.deleteResult(' + id + ');">Delete</a>'
				+ '</p>';
		}
		
		res += '<div class="handle" id="handle-'+ id +'" style="display: none;" onmouseup="$(\'result-list-item_' + id + '\').style.left=\'auto\'">Move</div>'
				
 				+  '<div class="clearfix heading-requirements">'
				+  '	<div id="result-requirements-wrapper-' + id +'" class="req-res-wrap">'
				+  			this.generateRequirementList(id)
				+  '	</div>'
				+  '	<div id="result-summary-wrapper-' + id +'" class="res-sum-wrap">'
				+			this.writeRequirementSummary(id)
				+  '	</div>'
				+  '</div>';
		
		if (res_list_len != 1 || pos != num_results -1) {
			res += ''
				+ '<div class="clearfix">'
				+  '<h5 class="keep">Title</h5>'
				+  '<p class="result-title"><span id="result-title-' + id + '" onclick="TestEditor.clickResultTitle(\'' + id + '\')">' + thisResult.Title["#cdata"] + '</span></p>'
				+  '<p class="result-title-edit" id="result-title-edit-' + id + '" style="display: none;">'
				+  '  <input type="text" onkeypress="return killEnterAll(event);" class="result-title-text" id="result-title-text-' + id + '" value="' + thisResult.Title["#cdata"] + '" />'
				+  '  <span id="result-title-edit-advice-' + id + '" class="tip"><strong>This is their headline!</strong> Pick something powerful and suggestive.</span>'
				+  '  <a href="javascript:TestEditor.doneEditingResultTitle(' + id + ')" class="done">Done</a>'
				+  '</p>'
				+ '</div>'
				
				+ '<div class="clearfix">'
				+  '<h5>Subtitle</h5>';
		}
		else {
			res += ''
				+ '<div class="clearfix">'
			 	+ '<h5 class="keep">Title</h5>';
		}
		
			res += ''
				+  '<p class="result-subtitle"><span id="result-subtitle-' + id + '" onclick="TestEditor.clickResultSubtitle(\'' + id + '\')">' + thisResult.Subtitle["#cdata"] + '</span></p>'
				+  '<p class="result-subtitle-edit" id="result-subtitle-edit-' + id + '" style="display: none;">'
				+  '  <input type="text" onkeypress="return killEnterAll(event);" class="result-subtitle-text" id="result-subtitle-text-' + id + '" value="' + thisResult.Subtitle["#cdata"] + '" />'
				+  '  <span id="result-subtitle-edit-advice-' + id + '" class="tip"></span>'
				+  '  <a href="javascript:TestEditor.doneEditingResultSubtitle(' + id + ')" class="done">Done</a>'
				+  '</p>'
				+ '</div>'

				+ '<div class="clearfix">'
				+  '<h5>Description</h5>'
				+  '<div class="result-description"><span id="result-description-' + id + '" onclick="TestEditor.clickResultDescription(\'' + id + '\')">' + thisResult.Description["#cdata"] + '</span></div>'
				+  '<p class="result-description-edit" id="result-description-edit-' + id + '" style="display: none;">'
				+  '  <textarea class="result-description-textarea" id="result-description-textarea-' + id + '">' + thisResult.Description["#cdata"] + '</textarea>'
				+  '  <span id="result-description-edit-advice-' + id + '" class="tip"></span>'
				+  '  <a href="javascript:TestEditor.doneEditingResultDescription(' + id + ')" class="done">Done</a>'
				+  '</p>'
				+ '</div>'
				
				+ '<div class="clearfix">'
				+  '<h5>Image</h5>'
				+  '<div class="result-image-wrapper clearfix" id="result-image-wrapper-' + id + '">'
				+     this.generateResultImage(id)
				+  '</div>'

				+  '<p class="result-image-add-link-wrapper" id="result-image-add-link-wrapper-' + id + '">'
				+     this.generateResultImageAddLink(id)
				+  '</p>'
				+  '<div class="result-image-upload-wrapper" id="result-image-upload-wrapper-' + id + '"></div>'
				+ '</div>';

		debug_exit(funcName);
		return res;
	},

	addResultImage: function(id) {

		// Let the uploader do its trick
		var extras = {
            //            ShouldCommit: true,
            //            CommitStatus: '2',
			TempId : id,
	       	IFrameParams : 'width="590" height="75" scrolling="no" allowtransparency="true"',
	       	IFrameStyleTemplate : 'quizzy/uploader.css'
		};
		
		this.Uploaders[id] = getSimpleUploader('result-image-upload-wrapper-' + id
									  , this.addResultImage_cb.bindAsEventListener(this)
									  , extras);
	
  	},

	addResultImage_cb: function(up) {
		funcName = "addResultImage_cb";
		debug_enter(funcName);
		
		if (up && up.Status == "success") {
			var id = up.TempId;
			var result = this.getResultById(id);
			result.Image = new Object();
			result.Image.Picid = up.Picid;
			result.Image.Src = up.ResultUrl;
			result.Image.From = up.From;
			result.Image.OriginalSource = new Object();
			result.Image.OriginalSource["#cdata"] = up.OriginalSource;
			result.Image.Width = up.Width;
			result.Image.Height = up.Height;
			$('result-image-wrapper-'          + id).innerHTML = this.generateResultImage(id);
			$('result-image-add-link-wrapper-' + id).innerHTML = this.generateResultImageAddLink(id);
			setTimeout("$('result-image-upload-wrapper-' + " + id + ").innerHTML = ''", 100);
		}
		else {
			alert("Picture failed!");
		}
		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();
		debug_exit(funcName);
	},

    generateResultsSummary: function() {
		var funcName = "generateResultsSummary";
		debug_enter(funcName);
		var res           = '';
		var test_results  = this.getResultList();
		// alert("Generating results summary using : " + test_results);
		if (test_results.length == 0) {
			res += 'Your test has 0 results and can\'t give anyone any scores.';
		}
    else
    {
			var disabled_class = (this.getResultList().length == 1) ? "disabled" : "";
			
			res = ''
				+ '<div id="add-results" class="container clearfix">'
				+ '	<h3>Result Types</h3>'
				+ '	<div class="guts">'
				+ '  <p id="add-result"><a href="javascript:TestEditor.addResult(-1);" title="Add a new result type">Add a new result type</a></p>'
				+ '	 <p id="sorting-description" class="' + disabled_class + '">Sorting mode allows you to rearrange your result types because their order matters.</p>'
				+ '  <p id="sorting-toggle" class="' + disabled_class + '"><a href="javascript:TestEditor.toggleResultCollapse();" id="result-mode-toggle">Sorting</a> <span>Sorting mode is off</span>.</p>'
				+ '	</div>'
				+ '</div>';
			// var varcount = this.getTestVarNames().length;

			if (test_results.length == 1)
				res += '<ul id="result-list" class="result-list-singleres">';
			else
				res += '<ul id="result-list" class="result-list-multires">';

			// debug_log(Object.toJSON(test_results));
			
			for (var i = 0; i < test_results.length; i++) {
				var id = test_results[i]["@Id"];

				res += '<li class="result-list-item container clearfix" id="result-list-item_' + id + '">';
				res += this.generateResult(id);
				res += '</li>';

			}
			res += '</ul>';
    }
    // alert(res);
		debug_exit(funcName);
		return res;
  },

	generateRequirementList: function(result_id) {
		var funcName = "generateRequirementList";
		debug_enter(funcName);
		var res         = '';
		var ul_head 		= '<ul class="requirement-list" id="requirement-list-' + result_id + '">';
		var result      = this.getResultById(result_id);
		var pos         = this.getResultItemPositionById(result_id);
		var num_results = this.Test.Data.OkTest.Results.Result.length;


		if (!num_results || !result.Requirement) {
			res += '<h4>We\'re boned</h4>';
			return res;
		}

		if (num_results == 1) {
			res += '<h4>Give This Result</h4>';
			res += '<p class="note">... to everyone who takes your test.</p>';
		}
		else if (pos == num_results - 1) {
			res += '<h4>Lastly</h4>';
			res += '<p class="note">... give this result to everyone else.</p>';
		}
		else if (result.Requirement.length == 0) {
			res += (pos == 0) ? '<h4>Give This Result</h4>' : '<h4>Otherwise</h4>';
			res += ul_head;
			res += '<li><strong class="boom">You have to ';
			res += '  <a class="requirement-add-del" href="javascript:TestEditor.addRequirement(' + result_id + ');">add a requirement</a>';
			res += ' or this result will be ignored.</strong>';
			res += '</li>';
			res += '</ul>';
		}
		else {			
			res += (pos == 0) ? '<h4>Give This Result</h4>' : '<h4>Otherwise</h4>';
			
			if (pos != 0) {
				res += '<p class="note">If the user is not in an above category,</p>';
			}
			
			res += ul_head;
			
			for (var i = 0; i < result.Requirement.length; i++) {
				var is_last = (i == result.Requirement.length-1) ? true : false;
				
				if (pos == 0 && i == 0) {
					res += '<li class="requirement if" id="requirement-list-item-' + result.Requirement[i]["@Id"] + '">';
					res += '<span class="if">If</span>';
				}
				else {
					res += '<li class="requirement and" id="requirement-list-item-' + result.Requirement[i]["@Id"] + '">';
					res += '<span class="and">and</span>';
				}

				res += '<span id="requirement-contents-' + result.Requirement[i]["@Id"] + '">';
				res += this.generateRequirement(result, result.Requirement[i]["@Id"], is_last) + '</span></li>';
				
				if (is_last) {
					res += '<li><a href="javascript:TestEditor.addRequirement(\'' + result_id + '\', \'' + result.Requirement[i]["@Id"] + '\');" title="add another requirement" class="add-req">add another requirement</a></li>';
				}
			}
			
			res += '</ul>';
		}

		// debug_log("Returning requirement list");
		debug_exit(funcName);
		return res;
	},


	generateTargetVariablePicker: function(result_id, requirement_id, selected, disallowed) {
		var funcName = "generateTargetVariablePicker";
		debug_enter(funcName);
		var varnames = this.getTestVarNames();
		var res = '';
		res += '<select id="requirement-target-variable-picker-' + requirement_id + '" class="variable" onChange="javascript:TestEditor.updateRequirementTargetVariable(' + result_id + ', ' + requirement_id + ');">';
		for (var i = 0; i < varnames.length; i++) {
			var isSelected  = ((selected == varnames[i])    ? "selected"  : "");
			var isAllowed	  = ((disallowed == varnames[i])  ? false	      : true);
			if (isAllowed)
				res += '<option value="' + varnames[i] + '" ' + isSelected + '>' + varnames[i] + '</option>';
		}
		res += '</select>';
		debug_exit(funcName);
		return res;
	},

	generateSubjectVariablePicker: function(result_id, requirement_id, selected) {
		var funcName = "generateSubjectVariablePicker";
		debug_enter(funcName);
		var varnames = this.getTestVarNames();
		var res = '';
		res += '<select id="requirement-subject-variable-picker-' + requirement_id + '" onChange="javascript:TestEditor.updateRequirementSubjectVariable(' + result_id + ', ' + requirement_id + ');" class="variable first">';
		for (var i = 0; i < varnames.length; i++) {
			var isSelected = ((selected == varnames[i]) ? "selected" : "");
			res += '<option value="' + varnames[i] + '" ' + isSelected + '>' + varnames[i] + '</option>';
		}
		res += '</select>';
		debug_exit(funcName);
		return res;
	},

	generateRequirementTypePicker: function(result_id, requirement_id, selected) {
		var funcName = "generateRequirementTypePicker";
		debug_enter(funcName);
		var res = '';
		var type = LEGAL_REQUIREMENT_TYPES; // Comes from OkTestHelpers.js

		res += '<select id="requirement-type-picker-' + requirement_id + '" onChange="javascript:TestEditor.updateRequirementType(' + result_id + ', ' + requirement_id + ');" class="inequality">';

		for (var i = 0; i < type.length; i++) {
			var isSelected = ((selected == type[i].name) ? "selected" : "");
			res += '  <option value="' + type[i].name + '" ' + isSelected + '>' + type[i].description +'</option>';
		}

		res += '</select>';

		debug_exit(funcName);
		return res;
	},

	generateRequirement: function(result, requirement_id) {
		var funcName = "generateRequirement";
		debug_enter(funcName);
		var requirement = this.getObjByGenericId(requirement_id, result.Requirement);
		var percentage_display = (this.Test.Data.OkTest.VarScaling.Type == "Raw") ? "none" : "";
		
		var res = '';
		res += this.generateSubjectVariablePicker(result["@Id"], requirement_id, requirement.Var, '');
		res += ' <span class="is">is</span> ';
		res += this.generateRequirementTypePicker(result["@Id"], requirement_id, requirement["@Type"]);
		res += ' ';
		if      (requirement["@Type"] == 'GreaterThanValue' ||
				     requirement["@Type"] == 'LessThanValue') {
			res += '<input id="requirement-value-' + requirement_id + '" type="text" onkeypress="return killEnterAll(event);" value="' + requirement.Value + '" onChange="javascript:TestEditor.updateRequirementValue(' + result["@Id"] + ', ' + requirement_id + ');"/><span class="percentage" style="display: ' + percentage_display + '">%</span>';
		}
		else if (requirement["@Type"] == 'GreaterThanVar' ||
				     requirement["@Type"] == 'LessThanVar') {
			res += this.generateTargetVariablePicker(result["@Id"], requirement_id, requirement.Target, requirement.Var);
		}
		
		res += '<span class="remove-req"><a href="javascript:TestEditor.deleteRequirement(' + result["@Id"] + ', ' + requirement_id + ');" title="remove this requirement">remove</a></span>';

		debug_exit(funcName);
		return res;
	},

	enableMultipleResultMode: function() {
		var funcName = "generateRequirement";
		debug_enter(funcName);
		
		this.enableSorting();

		$("result-list").removeClassName("result-list-singleres");
		$("result-list").addClassName("result-list-multires");

		debug_exit(funcName);
	},

	enableSingleResultMode: function() {
		var funcName = "generateRequirement";
		debug_enter(funcName);

		this.disableSorting();
		
		$("result-list").addClassName("result-list-singleres");
		$("result-list").removeClassName("result-list-multires");

		debug_exit(funcName);
	},

	toggleResultCollapse: function() {
		var res_list = $("result-list");
		var isCollapsed = res_list.hasClassName("result-list-collapsed");
		var handles = res_list.getElementsByClassName('handle');
		var res_list_lis = res_list.getElementsByClassName('result-list-item');
		var res_titles = res_list.getElementsByClassName('result-title');
		
		// clear backgrounds from Scriptaculous highlight
		for (var i = 0; i < res_list_lis.length; i++) {
			res_list_lis[i].style.background = "";
		}
		
		if(isCollapsed) {
			// toggle the description and button
			$('sorting-toggle').removeClassName('on');
			this.enableSorting();
			$('sorting-toggle').getElementsByTagName('span')[0].innerHTML = "Sorting mode is off";
						
			// toggle the list
			$("result-list").removeClassName("result-list-collapsed");
			$("result-list").addClassName("result-list-full");
			
			// add the onclick back
			for (var i = 0; i < res_titles.length; i++) {
				res_titles[i].getElementsByTagName('span')[0].onclick = function() { TestEditor.clickResultTitle(this.id.split('-')[2]); };
			}
			
			Sortable.destroy("result-list");
			
			for (var i = 0; i < handles.length; i++) {
				handles[i].hide();
			}
		}
		else {
			// toggle the description and button
			$('sorting-toggle').addClassName('on');
			this.enableSorting();
			$('sorting-toggle').getElementsByTagName('span')[0].innerHTML = "Sorting mode is on";
			
			//generate summaries
			for (var i = 0; i < res_list_lis.length; i++) {
				$("result-summary-wrapper-" + res_list_lis[i].id.split('_')[1]).innerHTML = this.writeRequirementSummary(res_list_lis[i].id.split('_')[1]);
			}
			
			// toggle the list
			$("result-list").removeClassName("result-list-full");
			$("result-list").addClassName("result-list-collapsed");
			
			// remove the onclick
			for (var i = 0; i < res_titles.length; i++) {
				res_titles[i].getElementsByTagName('span')[0].onclick = null;
			}
									
			Sortable.create("result-list", {tag:"li", handle: "handle", constraint: "vertical", scroll: window, ghosting:false, onUpdate: this.resortResultList.bind(this)});
			
			for (var i = 0; i < handles.length; i++) {
				handles[i].show();
			}
		}
	},
	
	disableSorting: function() {
		$('sorting-description').addClassName('disabled');
		$('sorting-toggle').addClassName('disabled');
	},
	
	enableSorting: function() {
		$('sorting-description').removeClassName('disabled');
		$('sorting-toggle').removeClassName('disabled');
	},

	updateVariableSummary: function() {
		$("variable-summary").innerHTML = this.generateVariableSummary();
	},

	updateScalingSummary: function() {
		$("scaling-summary-details").innerHTML = this.generateScalingSummary();
	},

	updateVariableListing: function() {
		$("variable-listing").innerHTML = this.generateVariableListing(this.getTestVarNames());
	},


	
	generateTestTitle: function() {
		var title = this.Test.Data.OkTest.Title["#cdata"];
		
		var res = ''
			+ '	<h2><label for="test-title">You are editing:</label>'
			+ '		<input type="text" class="test-title" name="test-title" onkeypress="return killEnterAll(event);" value="' + title + '" onChange="TestEditor.updateTestTitle(this.value)" />'
			+ '	</h2>'
			
			+ '	<p class="test-name-warning-region" style="display:none;">'
			+ '		<strong>Tip!</strong> We recommend including <strong>"Quiz"</strong> '
			+ '		or <strong>"Test"</strong> in your test\'s name. Your call, though.'
			+ ' <a href="javascript:TestEditor.hideTestTitleTip();" class="ignore">ignore</a>'
			+ '	</p>';
		
		return res;
	},

	generatePageReview: function() {
		var res = this.generateTestTitle();
		
/*		res += '<div class="container clearfix">';
		res += '  <h3>Review Time</h3>';
		res += '  <div class="guts">';
		
		if (this.TestExtrinsicData.PreferredUrl && this.TestExtrinsicDataPreferredUrl != "") {
			res += "<p>Soon we'll add more testing options here, but in the meantime, you can <strong>test "
			    + " your test simply by visiting the URL of it</strong> and taking it yourself.  As the author,"
			    + " you can take the test even before you've taken it live.</p>";
				
		}
		else {
			res += "<p>You haven't saved your test yet!  Save it and then you can start playing.</p>";
		}
		
		res += '  </div>';
		res += '</div>';
	*/	
  		res += '<div class="container clearfix">';
		res += '  <h3>Some Advice</h3>'
		        +  '  <div class="guts">'
			+  '    <p><em>At HelloQuizzy, we have noticed some trends among the most popular, highest rated tests.  This advice'
			+  '     is yours to ignore, but we recommend reading the checklist.</em></p>'
		        +  '  </div>'
		        +  ' </div>'

		        +  '<div class="container clearfix">'
		        +  '  <h3 class="num">1</h3>'
		        +  '  <div class="guts">'
		        +  '    <p class="advice-heading">Keep your answers short</p>'
		        +  '    <p>Test takers are like drunk children.  They need simple pleasure or they\'ll pass out.'
		        +  '       "True/false," "Yes/No/Maybe,"&mdash;these are understandable by everyone.  Consider'
		        +  '       the following scenario question:</p>'
		        +  '    <div class="example-question bad">'
		        +  '      <h4>Bad Question: Reader might fall asleep</h4>'
		        +  '      <p>You\'ve just cooked a really tasty meal for your significant other, and it took you'
		        +  '         4 hours to prepare all the courses.  He or she shows up at home, throws down the'
		        +  '         briefcase, and heads out to join friends.  Without eating, and with no apology.  Do'
		        +  '         you...</p>'
		        +  '      <ul>'
		        +  '        <li>(a) Follow them out the door, with a pot of food, and dump it on them.  Hey, they'
		        +  '            can either eat it or wear it.</li>'
		        +  '        <li>(b) Try to be understanding; friendship is important and we didn\t plan the dinner'
		        +  '            ahead of time</li>'
		        +  '      </ul>'
		        +  '    </div>'
		        +  '    <p>Your test will never be popular like this.  Be snappy!</p>'
		        +  '    <div class="example-question">'
		        +  '      <h4>Good question</h4>'
		        +  '      <p>Scenario: you surprise your lover with a 7-course home-cooked meal, but (s)he wants'
		        +  '         to skip out to see friends.  Is that cool?</p>'
		        +  '      <ul>'
		        +  '        <li>(a) Sure</li>'
		        +  '        <li>(b) Not really</li>'
		        +  '        <li>(c) No</li>'
		        +  '      </ul>'
		        +  '    </div>'
		        +  '    <p>Quick tip: answer options should never wrap lines.</p>'
		        +  '  </div>'
		        +  '</div>'
		
		        +  '<div class="container clearfix">'
		        +  '  <h3 class="num">2</h3>'
		        +  '  <div class="guts">'
		        +  '    <p class="advice-heading">Offer complete answer options</p>'
		        +  '    <p>Test takers are also like robots.  Their heads explode when they run out of options.'
		        +  '       For example:</p>'
		        +  '    <div class="example-question bad">'
		        +  '      <h4>Bad question: Not enough answer options</h4>'
		        +  '      <p>You catch your roommate and your boyfriend together.  What do you do?</p>'
		        +  '      <ul>'
		        +  '        <li>(a) I fight him to the death.</li>'
		        +  '        <li>(b) I fight her to the death.</li>'
		        +  '      </ul>'
		        +  '    </div>'
		        +  '    <p>Sayonara, test taker.  Robot-man needs more options.  What if he would just roll away?'
		        +  '       What if he would confront them with words and data?  Be general, but all-encompassing:</p>'
		        +  '    <div class="example-question">'
		        +  '      <p>...what do you do?</p>'
		        +  '      <ul>'
		        +  '        <li>(a) Confront</li>'
		        +  '        <li>(b) Move on</li>'
		        +  '        <li>(c) Other / I don\'t know</li>'
		        +  '      </ul>'
		        +  '    </div>'
		        +  '  </div>'
		        +  '</div>'

		        +  '<div class="container clearfix">'
		        +  '  <h3 class="num">3</h3>'
		        +  '  <div class="guts">'
		        +  '    <p class="advice-heading">Measure something meaningful</p>'
		        +  '    <p>Congratulations on your funny test.  To make it go all viral, give your readers something'
		        +  '       accurate to compare.  A purity percentage, a colorful personality type, a political party,'
		        +  '       X% of your friends HATE you: these are powerful, telling titles and people will want to'
		        +  '       share.  On the other end, "Great job!  You got 35 lollipop points!" at the end of a test'
		        +  '       is not so helpful.  Give people a meaningful result.'
		        +  '  </div>'
		        +  '</div>'

		        +  '<div class="container clearfix">'
		        +  '  <h3 class="num">4</h3>'
		        +  '  <div class="guts">'
		        +  '    <p class="advice-heading">Make sure everyone doesn\'t get the same result</p>'
		        +  '    <p>The launch of your test is just the beginning.  You should watch your stats (accessible'
		        +  '       from the "Edit your tests" page), and you should adjust scoring or results rules to make'
		        +  '       sure people are getting a variety of results.  If you write a Which-Superhero-Are-You-Test'
		        +  '       that has 8 results, make sure people are scoring all 8 results.  If you write a Purity'
		        +  '       test, make sure some people score high and some low.</p>'
		        +  '  </div>'
		        +  '</div>'

		        +  '<div class="container clearfix">'
		        +  '  <h3 class="num">5</h3>'
		        +  '  <div class="guts">'
		        +  '    <p class="advice-heading">Include nice pictures and check your spelling and grammar</p>'
		        +  '    <p>The more professional your test, the more it will spread around.  Grammar and spelling'
		        +  '       nazis are teh sux0rz, but they are still part of your audience.  Accommodate those with'
		        +  '       higher standards, without pissing off those without.</p>'
		        +  '  </div>'
		        +  '</div>'

		        +  '<div class="container clearfix">'
		        +  '  <h3 class="num">6</h3>'
		        +  '  <div class="guts">'
		        +  '    <p class="advice-heading">When your test is polished, make it famous!</p>'
		        +  '    <p>Sometimes a test just needs some activation energy.  For example, if you write a quiz'
		        +  '       about topic X, seek out forums on X.  Email your friends.  Find people who blog all about'
		        +  '       X and send your quiz to them.  Odds are, they\'ll love posting about it and thank you.'
		        +  '       Giving your test a push in the right community can be the difference between 100 takers'
		        +  '       and 100,000 takers.  So Google is your friend.  Research the communities and experts and'
		        +  '       let them all know.'
		        +  '  </div>'
		        +  '</div>'

		        +  '';

  		res += '<div class="container clearfix">';
		res += '  <h3>Last...</h3>'
		        +  '  <div class="guts">'
		        +  '   <p>'
		        +  '    <em>We will add more advice here soon.  Thanks for reading... -HelloQuizzy/OkCupid Staff</em>'			
		        +  '   </p>'
		        +  '  </div>'
		        +  '</div>';
		return res;
	},
	
	updatePageReview: function() {
		$('test-page-review').innerHTML = this.generatePageReview();
	},

	generatePageBasics: function() {
		var category = this.Test.Data.OkTest.Category;
		var subcategory = this.Test.Data.OkTest.Subcategory;
		var rating = this.Test.Data.OkTest.Rating;
		var tags = this.getTestTags();
		var varnames = this.getTestVarNames();

		var res = ''
			+ this.generateTestTitle()

			+ '	<div class="container clearfix">'
			+ '		<h3>Description</h3>'
			+ '		<div class="guts">'
			+ '			<p>Write a sentence that summarizes your test, for our catalogue.'
			+ '			</p>'
			+           this.generateDescription()
			+ '		</div>'
			+ '	</div>'
			
			+ '	<div class="container clearfix">'
			+ '		<h3><label for="test-rating">Maturity rating</label></h3>'
			+ '		<div class="guts">'
			+				this.generateTestRatingPulldown(rating)
			+ '		</div>'
			+ '	</div>'
			
			+ '	<div class="test-big-input container clearfix">'
			+ '		<h3><label for="test-category">Category</label></h3>'
			+ '		<div class="guts">'
			+ '			<p>If possible, choose a category and subcategory that fit your test.</p>'
			+ '			<p><span id="test-category-item">'
			+					this.generateTestCategoryPulldown(category)
			+ '			</span>'
			+ '			<span id="test-subcategory-item">'
			+ 				this.generateTestSubcategoryPulldown(category, subcategory)
			+ '			</span></p>'
			+ '		</div>'
			+ '	</div>'
			+ '	<div class="container clearfix">'
			+ '		<h3>Variables</h3>'
			+ '		<div class="guts">'
			+ '			<p>Simple tests that measure one thing about you (e.g. "purity") require one variable. Tests that '
			+ '        explore multiple parts of your personality, like, say "knowledge" and "potential" require '
			+ '        multiple.'
			+ '			</p>'
			+ '			<ul id="variable-listing">'
			+					this.generateVariableListing(varnames)
			+ '			</ul>'
			+ '		</div>'
			+ '	</div>'
			+ '	<div class="container clearfix last">'
			+ '		<h3>Thumbnail</h3>'
			+ '		<div class="guts">'
			+ '			<p>And (optional) upload a picture that will go next to your test name.'
			+ '			</p>'
			+ '         <div id="thumbnail-container">'
			+            this.generateThumbnail()
			+ '         </div>'
			+ '		</div>'
			+ '	</div>';

		return res;
	},

	generateDescription: function() {
		var res = "";
		res += 
		  '   <div id="description-edit" style="display: none;">'
        + '     <textarea id="description-edit-textarea">' + this.Test.Data.OkTest.Description["#cdata"] + '</textarea>'
		+ '     <p class="done"><a href="javascript:TestEditor.doneEditingDescription()">Done</a></p>'
        + '   </div>'
        + '   <div id="description-content" onclick="TestEditor.clickDescription()">'
        + '     ' + this.Test.Data.OkTest.Description["#cdata"]
        + '   </div>';
		return res;
	},
	
	updateThumbnail: function() {
		$('thumbnail-container').innerHTML = this.generateThumbnail();
	},
	
	generateThumbnail: function() {
		var res = "";
		if (this.Test.Data.OkTest.Thumb.Image && this.Test.Data.OkTest.Thumb.Image.Src) {
			res += "<p>We'll resize and use this image as your test's thumbnail:"
			    +  "</p>"
				+  '<img src="' + this.Test.Data.OkTest.Thumb.Image.Src + '___55_55_55_55_879b4d6d_.jpg" width="55" height="55" alt="Default Test Image" />'
				+  '<p><a href="javascript:TestEditor.removeThumbnail()" title="remove / change image" class="delete">remove / change</a></p>';
		}
		else {
			res += '<p><a href="javascript:TestEditor.addThumbnail()" title="add a thumbnail" class="add">add a thumbnail</a>'
			    + '<div id="thumb-upload-area"></div>'
			    + '</p>';
		}
		return res;		
	},

	generateTestCategoryPulldown: function(category) {

		var cats = this.getLegalTestCategoryNames();
		var res = '<select name="test-category" id="test-category" onchange="TestEditor.updateTestCategory()" class="three-columns mr-20">';
		res += '<option value="">- Choose -</option>';
		for (var i = 0; i < cats.length; i++) {
			if (cats[i] == category) {
				res += '<option value="' + cats[i] + '" selected="selected">' + cats[i] + '</option>';
			}
			else {
				res += '<option value="' + cats[i] + '">' + cats[i] + '</option>';
			}
		}
		res += '</select>';
		return res;
	},

	generateTestSubcategoryPulldown: function(category, subcategory) {
		var funcName = "generateTestSubcategoryPulldown";
		debug_enter(funcName);

		var subcats = this.getLegalTestSubcategoryNames(category);
        debug_log(subcats);
		var res = ' <span class="one-column pr-20">under</span> <select name="test-subcategory" id="test-subcategory" onchange="TestEditor.updateTestSubcategory()" class="three-columns">';
		res += '<option value="">- Choose -</option>';
		for (var i = 0; i < subcats.length; i++) {
			if (subcats[i] == subcategory) {
				res += '<option value="' + subcats[i] + '" selected="selected">' + subcats[i] + '</option>';
			}
			else {
				res += '<option value="' + subcats[i] + '">' + subcats[i] + '</option>';
			}
		}
		res += '</select>';
		if (subcats.length > 0) {
            debug_exit(funcName);
			return res;
		}
		else {
            debug_exit(funcName);
			return "";
		}
	},

	generateVariableListing: function(varnames) {
		var res = '';
		for (var i = 0; i < varnames.length; i++) {
				res += '<li class="variable clearfix"><label for="variable-' + i + '" class="one-column">Variable Name</label> '
					+ '     <input type="text" onkeypress="return killEnterAll(event);" name="variable-' + i + '" id="variable-' + i + '" value="'+varnames[i]+'" '
					+ '            onChange="javascript:TestEditor.renameVariable('+i+');" />';
				if (varnames.length != 0) {
					res += '<a href="javascript:TestEditor.dropVariable('+i+')" class="remove">remove</a>';
				}
				res += '</li>';
		}
		res += '<li><a href="javascript:TestEditor.addVariable()" class="add">add a variable</a></li>';
		return res;
	},

	generateTestRatingCommentary: function(rating) {
		if (rating == "Family")
			return 'Clean and pure for grandmas and babies.';
		else if (rating == "Teen")
			return 'May contain dirty language, near nudity.';
		else
			return "Appropriate for adults only. No hard-core. Think \"R\" rated.";
	},

	generateTestRatingPulldown: function(rating) {
		var res = '<select name="test-rating" id="test-rating" onchange="TestEditor.updateTestRating()" class="two-columns mr-20">';
		(rating == "Family") ? res += '<option value="Family" selected="selected">Family</option>' : res += '<option value="Family">Family</option>';
		(rating == "Teen") ? res += '<option value="Teen" selected="selected">Teen</option>' : res += '<option value="Teen">Teen</option>';
		(rating == "Adult") ? res += '<option value="Adult" selected="selected">Adult</option>' : res += '<option value="Adult">Adult</option>';
		res += '</select> <span id="rating-summary-area">' + this.generateTestRatingCommentary(rating) + '</span>';
		return res;
	},

	//
	// previous_item_id = -1 if this is the first item;
	// otherwise it's the id of the previous item, not the index, which of course can change
	//
	drawOptionsBox: function(previous_item_id) {
		var res = ''
		+ ' <p id="insertion-div-' + previous_item_id + '">'
		+ '		<a href="javascript:TestEditor.toggleOptionsBox(' + previous_item_id + ');" class="options" id="insertion-button-' + previous_item_id + '">'
		+ '			Actions'
		+ '		</a>'
		+ ' </p>'
		+ '<div id="insertion-expanded-div-' + previous_item_id + '" style="display: none;"></div>'
		+ '<div style="display:none;" class="insertion-expanded-div" id="insertion-expanded-div-' + previous_item_id + '"></div>';
		 return res;
	},

	unexpandOptionsBox: function (previous_item_id) {
		//$("insertion-div-" + previous_item_id).style.display="";
		$("insertion-expanded-div-" + previous_item_id).style.display="none";
	},

	//
	// Show the user addition options
	//	
	toggleOptionsBox: function(previous_item_id) {
		var opts = $("insertion-expanded-div-" + previous_item_id);
		var num_items = this.Test.Data.OkTest.Contents.ContentItem.length;

		if (opts.style.display == "") {
			opts.style.display="none";
		}

		else {
					opts.style.zIndex = ++this.zIndexIncrementer;
					opts.parentNode.style.zIndex = ++this.zIndexIncrementer;
				opts.style.display="";
				 var res = ''
					+  '<ul class="options-list">'
					+ ' <li><a href="javascript:TestEditor.actOnOptionsBox(\'' + previous_item_id + '\',\'QuestionRadio\');" class="first">add multiple choice question</a></li>'
					+ ' <li><a href="javascript:TestEditor.actOnOptionsBox(\'' + previous_item_id + '\',\'HTML Block\');">add Text or HTML</a></li>'
					+ ' <li><a href="javascript:TestEditor.actOnOptionsBox(\'' + previous_item_id + '\',\'PageBreak\');">add page break</a></li>'
					+ ' <li><a href="javascript:TestEditor.actOnOptionsBox(\'' + previous_item_id + '\',\'Image-step-1\');">add picture or photo</a></li>'
					+ ' <li><a href="javascript:TestEditor.actOnOptionsBox(\'' + previous_item_id + '\',\'YouTube-step-1\');">add YouTube video</a></li>';
			
					if (num_items > 1) {
					 	res += '<li><a class="delete" id="delete-item-button-' + previous_item_id + '" href="javascript:TestEditor.deleteAboveItem(\''+previous_item_id+'\')">'
				 		+ '   Delete this item'
				 		+ '  </a></li>';
				 }
					res += ' <li><a href="javascript:TestEditor.actOnOptionsBox(\'' + previous_item_id + '\',\'Nevermind\');" class="cancel last">Cancel</a></li>'
					+ '</ul>';
			
					$("insertion-expanded-div-" + previous_item_id).innerHTML = res;
		}
	},

	//
	// Perform addition or ask supplemental questions
	//
	actOnOptionsBox: function(previous_item_id, what) {
		var insertion = "ERROR. Couldn't generate " + what;
		var id = Math.round(Math.random() * 1000000000);
		if (what == "QuestionRadio") {
			var new_q  = this.addNewQuestionRadio(previous_item_id);
			this.addHTMLContentItem(previous_item_id, new_q);
			this.unexpandOptionsBox(previous_item_id);
			new Effect.Highlight("item_" + new_q["@Id"], this.hl_settings);
		}
		else if (what == "PageBreak") {
			var new_pb = this.addNewPageBreak(previous_item_id);
			this.addHTMLContentItem(previous_item_id, new_pb);
			this.unexpandOptionsBox(previous_item_id);
			new Effect.Highlight("item_" + new_pb["@Id"], this.hl_settings);
		}
		else if (what == "Image-step-1") {
			var res = ''
				+ '<div class="uploader-wrap clearfix" id="uploader-wrap-' + previous_item_id + '">'
				+ '	<div id="uploader-dest-' + previous_item_id + '" class="uploader"></div>'
				+ '	<a href="javascript:TestEditor.removeOptionsInsertion(\'uploader-wrap-' + previous_item_id + '\');" class="cancel">Cancel</a>'
				+ '</div>';

			// insert the new div
			// hide the options menu
			$('item_' + previous_item_id).insert(res, { position: "bottom" });
			$("insertion-expanded-div-" + previous_item_id).hide();
			
			// Let the uploader do its trick
			var extras = {
                //                ShouldCommit: true,
                //                CommitStatus: '2',
				TempId : previous_item_id,     
				IFrameParams : 'width="590" height="75" scrolling="no" allowtransparency="true"' ,   
				IFrameStyleTemplate : 'quizzy/uploader.css'
			};
			
			this.Uploaders[previous_item_id] = getSimpleUploader('uploader-dest-' + previous_item_id
										  , this.actOnOptionsBoxImage_cb.bindAsEventListener(this)
										  , extras);

		}

		else if (what == "YouTube-step-1") {
			//$("insertion-expanded-div-" + previous_item_id).innerHTML =
			var res = ''
			+ '<div class="youtube-wrap clearfix" id="youtube-wrap-' + previous_item_id + '">'
			+ '		<p class="cancel"><a href="javascript:TestEditor.removeOptionsInsertion(\'youtube-wrap-' + previous_item_id + '\');" class="cancel">Cancel</a></p>'
			+ '		<p><label for="youtube-textarea-' + previous_item_id + '">What\'s your video\'s embed code?</label></p>'
			+ '		<p class="quiet">Insert the "embed code" from YouTube. This is wicked cool, but if you save your test and the video disappears,'
			+ '     send us feedback with the embed code you used, so we can update our whitelist.</p>'
			+ '		<p><textarea wrap="virtual" id="youtube-textarea-' + previous_item_id + '" class="short"></textarea></p>'
			+ '		<p class="done"><a href="javascript:TestEditor.actOnOptionsBox(\'' + previous_item_id + '\',\'YouTube-step-2\');">Done</a></p>'
			+ '</div>';
				
			// should make this a function ... repeated from above
			// insert the new div
			// hide the options menu
			$('item_' + previous_item_id).insert(res, { position: "bottom" });
			$("insertion-expanded-div-" + previous_item_id).hide();
		}
		else if (what == "YouTube-step-2") {
			var new_yt = this.addNewYouTube(previous_item_id);
			this.addHTMLContentItem(previous_item_id, new_yt);
			this.removeOptionsInsertion('youtube-wrap-' + previous_item_id);
			new Effect.Highlight('item_' + new_yt["@Id"], this.hl_settings);
		}
    else if (what == "HTML Block") {
      var new_html = this.addNewHTMLBlock(previous_item_id);
      this.addHTMLContentItem(previous_item_id, new_html);
	  	var default_text = "Edit your <strong>text</strong> or <strong>HTML</strong> here.";
      $('html-block-textarea-' + new_html["@Id"]).innerHTML  = default_text;
      $('html-block-content-' + new_html["@Id"]).innerHTML  = default_text;

			new Effect.Highlight('item_' + new_html["@Id"], this.hl_settings);

      this.unexpandOptionsBox(previous_item_id);
    }
		else {  // "Nevermind"
			this.unexpandOptionsBox(previous_item_id);
		}
	},
	
	removeOptionsInsertion: function(item_id) {
		$(item_id).remove();
	},
	
	actOnOptionsBoxImage_cb : function(up) {
		
		var previous_item_id = up.TempId;

		if (up && up.Status == "success") {
			var new_im = this.addNewImage(previous_item_id
										  	 , up.Picid
											 , up.ResultUrl
											 , up.From
											 , up.OriginalSource
											 , up.Width
											 , up.Height );

			this.addHTMLContentItem(previous_item_id, new_im);
			
			this.removeOptionsInsertion('uploader-wrap-' + previous_item_id);
			new Effect.Highlight('item_' + new_im["@Id"], this.hl_settings);
		}
		else {
			alert("Picture failure!");
		}
		this.unexpandOptionsBox(previous_item_id);
		
	},

	drawHTMLBlock: function(item) { 
		var id = item["@Id"];
		res = '<div class="html-block-wrap" id="html-block-wrap-' + id + '">'
        + '   <div class="html-block-edit" id="html-block-edit-' + id + '" style="display: none;">'
        + '     <textarea class="html-block-textarea" id="html-block-textarea-' + id + '">' + item["#cdata"] + '</textarea>'
		+ '     <p class="done"><a href="javascript:TestEditor.doneEditingHTMLBlock(\'' + id + '\')">Done</a></p>'
        + '   </div>'
        + '   <div class="html-block-content" id="html-block-content-' + id + '" onclick="TestEditor.clickHTMLBlock(\'' + id + '\')">'
        + '     ' + item["#cdata"]
        + '   </div>'
        + '</div>'
        + '';
    return res;
	},

	drawImage: function(item) {
		var res = '';
		if (item.Picid) {
		   res = '<div class="image"><img src="' + item.Src + '___1_500_1_2000_7fa54554_.jpg" /></div>';
		}
		// don't resize old imported images 
		else {
		   res = '<div class="image"><img src="' + item.Src + '" /></div>';			
		}
		return res;
	},

	drawYouTube: function(item) {
		var res = '<div class="YouTube">' + item.Src["#cdata"] + '</div>';
		return res;
	},

	getScoringSummary: function(question_item, answer_num) {
		var res = '';
		var num_effects_found = 0;
		var scoring = question_item.Answer[answer_num].AnswerScoring;
		if (scoring) {
			for (var i = 0; i < scoring.length; i++) {
				if (scoring[i].Effect != 0) {
					num_effects_found++;
					res += '<li class="scoring-summary" id="scoring-summary-'+question_item["@Id"]+'-'+answer_num+'"><span class="middot">&middot;</span> ';
					res += scoring[i].VarAffected + "&nbsp; ";
					if (scoring[i].Effect >= 0) {
						res += "+";
					}
					res += scoring[i].Effect
						+  '';
					res += "</li>";
				}
			}
		}
		if (num_effects_found == 0) {
			res += '<li><span class="middot">&middot;</span> no effect</li>';
		}
		
		return res;
	},


	maybeShowCustomVar: function(varname, varnum, qid, ans) {
		if ($F('answer-effect-select-' + qid + '-' + ans + '-' + varnum) == "other") {
			$('answer-effect-custom-' + qid + '-' + ans + '-' + varnum).style.display = "";
		}
		else {
			$('answer-effect-custom-' + qid + '-' + ans + '-' + varnum).style.display = "none";			
		}
		
	},

	showSelectBox: function(varname, varnum, qid, ans, presetvalue) {
		var res = '<select id="answer-effect-select-' + qid + '-' + ans + '-' + varnum + '" onchange="TestEditor.maybeShowCustomVar(\''+varname+'\',\''+varnum+'\',\''+qid+'\',\''+ans+'\')">';
		var found = false;
		
		for (var i = 5; i >= -5; i--) {
			var display = i;
			if (i >= 0) {
				display = "+" + i;
			}
			if (presetvalue != i) {
				res += '<option value="'+i+'">' + display + '</option>';
			}
			else {
				found = true;
				res += '<option value="'+i+'" selected="1">' + display + '</option>';
			}					
		}
		if (! found) {
			res += '<option value="other" selected="1">other</option>';
		}
		else {
			res += '<option value="other">other</option>';
		}
		res += '</select>';
		if (! found) {
			res += '<input type="text" onkeypress="return killEnterAll(event);" id="answer-effect-custom-' + qid + '-' + ans + '-' + varnum + '" value="' + presetvalue + '" />';
		}
		else {
			res += '<input type="text" onkeypress="return killEnterAll(event);" id="answer-effect-custom-' + qid + '-' + ans + '-' + varnum + '" value="' + presetvalue + '" style="display:none;"/>';			
		}
		return res;
	},

	updateRequirementTargetVariable : function(result_id, requirement_id) {
		var funcName = "updateRequirementTargetVariable";
		debug_enter(funcName);

		var result = this.getResultById(result_id);
		var requirement = this.getObjByGenericId(requirement_id, result.Requirement);
		var newValue = $('requirement-target-variable-picker-' + requirement_id).value;

		requirement.Target = newValue;

		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();
		debug_exit(funcName);
	},

	updateRequirementSubjectVariable: function(result_id, requirement_id) {
		/*
		** 1. Update DOM
		** 2. Redraw target variable picker
		*/

		var funcName = "updateRequirementSubjectVariable";
		debug_enter(funcName);

		var result = this.getResultById(result_id);
		var requirement = this.getObjByGenericId(requirement_id, result.Requirement);

		var newValue = $('requirement-subject-variable-picker-' + requirement_id).value;

		requirement.Var = newValue;

		debug_log("Redrawing target variable picker");

		/*
		** If we're clobbering this requirement's target variable, then
		** clobber it in the DOM also
		*/
		if (requirement.Var == requirement.Target) {
			var testVars = this.getTestVarNames();
			for (var i = 0; i < testVars.length; i++)
				if (testVars[i] != requirement.Var) {
					requirement.Target = testVars[i];
					break;
				}
		}

		$('requirement-contents-' + requirement_id).innerHTML = this.generateRequirement(result, requirement_id);

		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();
		debug_exit(funcName);
	},

	updateRequirementValue: function(result_id, requirement_id) {
		var funcName = "updateRequirementType";
		debug_enter(funcName);

		var result = this.getResultById(result_id);
		var requirement = this.getObjByGenericId(requirement_id, result.Requirement);

		var newValue = $('requirement-value-' + requirement_id).value;

		requirement.Value = newValue;


		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();
		debug_exit(funcName);
	},

	updateRequirementType: function(result_id, requirement_id) {
		/*
		** 1. Update DOM
		** 2. Redraw requirement
		*/

		var funcName = "updateRequirementType";
		debug_enter(funcName);

		/*
		** 1. Update the DOM
		**   If the type changes, we need to re-set the values to something
		** sensible. This will keep the DOM sane. It wouldn't be good if we
		** ended up with requirement parameters that didn't agree with its
		** type (like having a Target when the type is GreaterThanValue).
		*/
		var result = this.getResultById(result_id);
		var requirement = this.getObjByGenericId(requirement_id, result.Requirement);

		var newValue = $('requirement-type-picker-' + requirement_id).value;

		if (requirement.Type != newValue)
		{
			/*
			** Make some changes
			*/
			if  (newValue == "GreaterThanValue" || newValue == "LessThanValue") {
				requirement.Target = null;
				requirement.Value = 0;
			}
			else if (newValue == "GreaterThanVar" || newValue == "LessThanVar") {

				if (requirement.Var == requirement.Target || ! requirement.Target) {
										
					var testVars = this.getTestVarNames();
					for (var i = 0; i < testVars.length; i++)
						if (testVars[i] != requirement.Var) {
							requirement.Target = testVars[i];
							break;
						}
				}

				requirement.Value = null;
			}
			else if (newValue == "GreatestVar" || newValue == "LeastVar") {
				requirement.Target = null;
				requirement.Value = null;
			}
		}

		requirement["@Type"] = newValue;

		$('requirement-contents-' + requirement_id).innerHTML = this.generateRequirement(result, requirement_id);

		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();
		debug_exit(funcName);
	},

	updateScoringDisplay: function(qid, ans) {
		$("scoring-summary-wrap-" + qid + "-" + ans).innerHTML = this.getScoringSummary(this.getObjByContentId(qid),ans);
		this.updateScoringModifierDiv(qid, ans);
	},

	updateScoringModifierDiv: function(qid,ans) {
		var res = "";
		var testvars = this.getTestVarNames();
		for (var i = 0; i < testvars.length; i++) {
			res += "<li class=\"modifier-pair clearfix\">"
					+	 "  <span class=\"modifier-varname\">&middot; " + testvars[i] + ": </span>"
					+  "  <span class=\"modifier-selection\">" + this.showSelectBox(testvars[i], i, qid,ans, this.getAnswerEffectOnVar(testvars[i], qid, ans)) + "</span>"
					+  "</li>";
		}
		$("scoring-modifier-div-" + qid + "-" + ans).innerHTML = res;
	},

	drawAnswerRadio: function(item, ans) {

		var id = item["@Id"];
		var res = ''
			+	'	<li><span class="middot">&middot;</span> '
			
			+ '	<div id="answer-wrap-' + id + '-' + ans + '" class="answer-wrap clearfix">'
			+ '   <span id="answer-text-' + id + '-' + ans + '" class="answer-text" onclick="TestEditor.clickAnswerText(\'' + id + '\',' + ans + ')" >'
			+	    item.Answer[ans].AnswerText["#cdata"]
			+ '   </span>'
			
			+ '		<span id="answer-left-indent-'+id+'-'+ans+'">'
			+ '     <a href="javascript:TestEditor.deleteQuestionAnswer(\''+id+'\',\''+ans+'\')" '
			+ '      class="remove" id="delete-answer-button-'+id+'-'+ans+'">remove</a> '
			+ '   </span>'
			+ '		<ul class="scoring-summary" id="scoring-summary-wrap-' + id + '-' + ans + '">'
			+				this.getScoringSummary(item,ans)
			+ '		</ul>'
			+ ' </div>'
			
			+ ' <div id="answer-text-edit-wrap-' + id + '-' + ans + '" class="answer-text-edit-wrap clearfix" style="display:none;"> '
			+ '   <textarea id="answer-text-edit-region-' + id + '-' + ans + '">' + item.Answer[ans].AnswerText["#cdata"] + '</textarea>'
			+ '   <ul id="scoring-modifier-div-' + id + '-' + ans + '" class="scoring-modifier"><!-- will be filled dynamically --></ul>'
			+ '	  <p class="done"><a href="javascript:TestEditor.doneEditingAnswer(\'' + id + '\',' + ans + ')">Done</a></p> '
			+ '	</div>'
			
			+ ' </li>';
			
		return res;

	},

	drawQuestionRadio: function(item) {
		var id = item["@Id"];
		var res = "";
		var answers = "";

		var answer_array = item.Answer;
		for (var i = 0; i < answer_array.length; i++) {
			answers += this.drawAnswerRadio(item, i);
		}
		res = '<div id="question-wrap-' + id + '" class="question-wrap">'
			+ '  <div id="question-text-edit-wrap-' + id + '" style="display:none;" class="question-text-edit-wrap">'
			+ '    <textarea id="question-text-edit-region-' + id + '" class="question-text-edit-region">' + item.QuestionText["#cdata"] + '</textarea>'
			+ '    <p class="done"><a href="javascript:TestEditor.doneEditingQuestion(\'' + id + '\')">Done</a></p>'
			+ '  </div>'
			+ '  <h4 class="question-text" id="question-text-' + id + '" '
			+ '      onclick="TestEditor.clickQuestionText(\'' + id + '\')" >'
			+ '			<span>' + item.QuestionText["#cdata"] + '</span>'
			+ '  </h4> '
			+ '  <ul class="answers">'
			+     answers
			+ '  </ul>'
			+ '  <p id="add-answer-wrap-'+id+'" class="add-answer-wrap">'
			+ '    <a class="add-answer" id="add-answer-button-'+id+'" '
			+ '       href="javascript:TestEditor.addARadioAnswer(\'' + item["@Id"] + '\')">Add an answer</a>'
			+ '  </p>'
			+ '</div>';
		return res;
	},

	drawQuestionSlider:function(item) {
		return '<div class="QuestionSlider">Slider' + item["@Id"] + '</div>';
	},

	drawPageBreak: function(item) {
		var button_text = item.SubmitButtonText["#cdata"];			
		var res = "";
		res += '<div class="page-break-wrap" id="page-break-wrap-' + item["@Id"] + '">'
			+ '   <p id="page-break-' + item["@Id"] + '" class="page-break">'
			+ '     <strong>Page Break</strong> (<span>Submit Button Text: '
			+ '     <a href="javascript:TestEditor.changePageBreakButton(\'' + item["@Id"] + '\')" id="page-break-button-text-'
			+       	item["@Id"] + '" class="page-break-button-text">'
			+ 				item.SubmitButtonText["#cdata"] + '</a></span>'
			+           '<span class="page-break-click-advice">&larr; click to edit</span> )'
			+ '   </p>'
			+ '   <p id="page-break-edit-' + item["@Id"] + '" class="seven-columns" style="display:none;">'
			+ '     Change button text to '
			+ '     <input type="text" onkeypress="return killEnterAll(event);" id="page-break-edit-text-' + item["@Id"] + '" class="two-columns" value="' + item.SubmitButtonText["#cdata"] + '" />'
			+ '     <a href="javascript:TestEditor.updatePageBreakButton(\'' + item["@Id"] + '\')" class="done">Done</a>'
			+ '   </p>'
			+ '  </div>';
		return res;
	},


	//--------------------------------------------------------------------------------------------------------------------------------
	//
	// L O G G I N G   I N   A N D  J O I N I N G   F U N C T I O N S
	//
	//--------------------------------------------------------------------------------------------------------------------------------

	
	showLoginJoinPage : function() {
		var res = '<div class="signp-login">'
				+ ' <h2>Ready to save?</h2>'
				+  '<a href="javascript:TestEditor.showLoginOnlyPage()">Log in to a HelloQuizzy account</a>'
				+  '<br /><a href="javascript:TestEditor.showLoginOnlyPage()">Log in to an OkCupid account</a> (that works too)'
				+ '<h2>Otherwise...</h2>'
				+ ' <p>Joining HelloQuizzy takes 60 seconds, and it is free.</p>'
		        + ' <div id="join-experience-wrapper"></div>'
				+ '</div>';
		
		this.postNotice(res);
		OkLoginJoin.join(
					"join-experience-wrapper",
					"/signup/paths/quizzy_standard_ajax/1.html",
					{"cf" : "hq-inline", use_existing_form : this.PlaceToEdit},  
					{success_cb : TestEditor.tryToJoinSuccess.bindAsEventListener(this)}
			);		
	},
		
	showLoginOnlyPage : function() {
		var res = '<div class="signp-login">'
			+ '	<h2>Ready To Save?</h2>'
			+ ' <fieldset class="sign-in clearfix">'
			+ ' 	<legend>Sign In!</legend>'
			+ ' 	<p id="login-notices" style="display: none;"></p>'
			+ '		<div class="left">'
			+ '			<p class="label"><label for="testeditor-screenname">Screen Name</label></p>'
			+ '			<p class="input"><input type="text" onkeypress="return killEnterAll(event);" id="testeditor-screenname" name="testeditor-screenname" /></p>'
			+ '		</div>'
			+ '		<div class="right">'
			+ '			<p class="label"><label for="testeditor-password">Password <span class="help-text">(<a href="javascript:TestEditor.showLostPasswordForm()">forget your password?</a>)</span></label></p>'
			+ ' 		<p class="input"><input type="password" onkeypress="return killEnterAll(event);" id="testeditor-password" name="testeditor-password" /></p>'
			+ '		</div>'
			+ ' 	<p class="submit left"><a href="javascript:TestEditor.tryToLogin()">Log in!</a></p>'
			+ '		<p class="join last right">Don\'t have an account? <a href="javascript:TestEditor.showLoginJoinPage()">Sign up</a>!</p>'
			+ ' </fieldset>'
			+ ' </div>';

		this.postNotice(res);
	},

	showLostPasswordForm : function() {
		var res = '<div class="signp-login">'
			+ ' <h2>Ready To Save?</h2>'
			+ ' <fieldset class="password-request">'
			+ ' 	<legend>Password Request</legend>'
			+ '		<p id="login-notices" style="display: none;"></p>'
			+ '		<p class="label"><label for="testeditor-screenname">Enter you <strong>Screen Name</strong> or <strong>Email Address</strong></label></p>'
			+ '		<p class="input"><input type="text" onkeypress="return killEnterAll(event);" id="testeditor-screenname" name="testeditor-screenname" /></p>'
			+ '		<p class="submit left"><a href="javascript:TestEditor.tryToRequestPassword()">Request It</a></p>'
			+ '		<p class="join last right">Don\'t have an account? <a href="javascript:TestEditor.showLoginJoinPage()">Sign up</a>!</p>'
			+ ' </fieldset>'
			+ ' </div>';

		this.postNotice(res);
	},

	tryToJoinSuccess : function() {
		this.saveWork();
		this.postSuccessfulLoginNotice("you");
		bottomBarFlip();
	},
	
	tryToLogin : function() {
		OkLoginJoin.login($F('testeditor-screenname'),$F('testeditor-password'), 
				{ success_cb : TestEditor.tryToLoginSuccess.bindAsEventListener(this), 
				  failure_cb : TestEditor.tryToLoginFailure.bindAsEventListener(this) }  );			
	},
	
	tryToLoginSuccess : function(response) {
		this.saveWork();
		this.postSuccessfulLoginNotice(response.screenname);
		bottomBarFlip();
	},
	
	postSuccessfulLoginNotice: function(screenname) {
		this.postNotice('<p class="success"><span>Hey, ' 
				+ screenname
				+ ', you\'re now logged in. And your work is saved.</p>'
				+ '	<div class="container clearfix">'
				+ '		<h3>What now?</h3>'
				+ '		<div class="guts">'
				+ '      Use the tabs at the left to navigate. Also, your test now has a permanent address, at the top of this page.'
				+ '      As author, you can click it to preview your test, even before launch.'
				+ '     </div>'
				+ ' </div>');		
	},

	tryToLoginFailure : function(response) {
			
		var res = "";
		switch(response.status) {
			case "bad_pw" : res += "Your password seems to be wrong"; break;
			case "user_youngster" : res += "Whoa, you're too young for OkCupid"; break;
			case "user_deleted" : res += "Your account has been deleted."; break;
			case "user_error" : res += "That username/e-mail has never joined."; break;
			case "vip" : res += "You're blacklisted, fucker."; break;
			default : res += "WTF. Unknown status.";
		}
	    $('login-notices').innerHTML =  res;
			$('login-notices').className = "error";
			$('login-notices').style.display = "";
	},
	
	tryToRequestPassword : function() {		
		OkLoginJoin.lostPassword($F('testeditor-screenname'), 
				{ success_cb : TestEditor.tryToRequestPasswordSuccess.bindAsEventListener(this), 
				  failure_cb: TestEditor.tryToRequestPasswordFailure.bindAsEventListener(this)
				 } );
		
	},
	
	tryToRequestPasswordSuccess : function(response) {
		this.showLoginOnlyPage();
		$('login-notices').innerHTML = "Sent! Expect an e-mail from us within a minute.";
		$('login-notices').className = "success";
		$('login-notices').style.display = "";
	},

	tryToRequestPasswordFailure : function(response) {
		
		var res = "";
		switch(response.status) {
			case "" : res += "Please enter your e-mail address or OkCupid screenname."; break;
			case "NOT_FOUND" : res += "We couldn't find that user! Maybe you should join?"; break;
			case "ERROR_INVALID_USER" : res += "Invalid user! Maybe you should join?"; break;
			case "ENTER_INFO" : res += "Sorry, but you can only request your password " + response.maxrequests + " in a " + response.timeout + "-hour period. Try later!";
			default: res += "Unknown error! (" + responst.status + ")";
		}
		$('login-notices').innerHTML = res;
		$('login-notices').className = "error";
		$('login-notices').style.display = "";
	},
	
	showTrannyLoadTest : function() {
		
		var result = "<h1>Translate a test</h1>"
		 + "<p>To translate a test, you'll need some basic info.</p>"
		 + '<table>'
		 + '<tr style="padding:5px;margin:5px;">'
		 + '  <td valign="top">Test URL:</td>'
		 + '  <td valign="top"><input type="text" style="width:200px;" id="translate-url" /><br /><span style="font-size:0.8em;">e.g. http://www.helloquizzy.com/tests/the-beatles-test</span></td>'
		 + '</tr>'
		 + '<tr style="padding:5px;margin:5px;">'
		 + '  <td valign="top">New Language</td>'
		 + '  <td valign="top"><select id="translate-language">'
		 + '   <option value="es">Spanish</option>'
		 + '   <option value="he">Hebrew</option>'
		 + '   <option value="pt">Portuguese</option>'
		 + '   <option value="nl">Dutch</option>'
		 + '   <option value="de">German</option>'
		 + '   <option value="tr">Turkish</option>'
		 + '   <option value="pl">Polish</option>'
		 + '   <option value="it">Italian</option>'
		 + '   <option value="ja">Japanese</option>'
		 + '   <option value="ru">Russian</option>'
		 + '   <option value="fr">French</option>'
		 + '  </select></td>'
		 + '</tr>'
		 + '<tr style="padding:5px;margin:5px;">'
		 + '  <td valign="top">Password</td>'
		 + '  <td valign="top"><input type="text" style="width:200px;" id="translate-password" /><br /><span style="font-size:0.8em;">ask the Google Group if you don\'t know</span></td>'
		 + '</tr>'
		 + '<tr style="padding:5px;margin:5px;">'
		 + '  <td colspan="2"><a href="javascript:TestEditor.translateTest()">Load the test for translation</a></td>'
		 + '</tr>'
		 + '</table>';
		
		this.postNotice(result);
	},
	
	//--------------------------------------------------------------------------------------------------------------------------------
	//
	// M O D I F I C A T I O N   F U N C T I O N S
	//
	//--------------------------------------------------------------------------------------------------------------------------------

	randomId: function() {
		return Math.round(Math.random() * 1000000000);
	},


	//
	// We've loaded someone else's test. Gotta change info
	// for the translator
	changeTestForTranny : function() {
		this.Test.Data.OkTest.TestId = this.randomId();
		this.Test.Data.OkTest.LanguageCode = this.InvokeParams.tranny_language;
	},

	addNewQuestionRadio: function(previous_item_id) {
		var new_q = new Object();
		new_q["@Id"] = this.randomId();
		new_q["@Type"] = "QuestionRadio";
		new_q.Mandatory = "true";
		new_q.QuestionText = new Object();
		new_q.QuestionText["#cdata"] = "Click here to edit your new question. Ok?";
		new_q.Answer = new Array();

		new_q.Answer[0] = new Object();
		new_q.Answer[0].AnswerText = new Object();
		new_q.Answer[0].AnswerText["#cdata"] = "Answer 1 - click to change";
		new_q.Answer[0].AnswerScoring = new Array();

		new_q.Answer[1] = new Object();
		new_q.Answer[1].AnswerText = new Object();
		new_q.Answer[1].AnswerText["#cdata"] = "Answer 2 - click to change";
		new_q.Answer[1].AnswerScoring = new Array();

		this.insertItem(previous_item_id, new_q);

		this.noticeTimeSpentEditing();
		return new_q;
	},

	addNewImage: function(previous_item_id, picid, src, from, original_src, width, height) {
		var new_im = new Object();
		new_im["@Id"] = this.randomId();
		new_im["@Type"] = "Image";
		new_im.Src = src;
		new_im.Picid = picid;
		new_im.From = from;
		if (original_src && original_src != "") {
			new_im.OriginalSrc = new Object();
			new_im.OriginalSrc["#cdata"] = original_src;
		}
		new_im.Width = width;
		new_im.Height = height;

		this.insertItem(previous_item_id, new_im);

		this.noticeTimeSpentEditing();
		return new_im;

	},

	addNewHTMLBlock: function(previous_item_id) {
		var new_html        = new Object();
		new_html["@Id"]     = this.randomId();
		new_html["@Type"]	  = "HTML Block";

		this.insertItem(previous_item_id, new_html);

		return new_html;

	},

	addNewPageBreak: function(previous_item_id) {
		var new_pb = new Object();
		new_pb["@Id"] = this.randomId();
		new_pb["@Type"] = "PageBreak";
		new_pb["SubmitButtonText"] = new Object();
		new_pb.SubmitButtonText["#cdata"] = "Next!";

		this.insertItem(previous_item_id, new_pb);
		

		this.noticeTimeSpentEditing();
		return new_pb;
	},

	addNewYouTube: function(previous_item_id) {
		var new_yt = new Object();
		new_yt["@Id"] = this.randomId();
		new_yt["@Type"] = "YouTube";
		new_yt["Src"] = new Object();
		new_yt.Src["#cdata"] = $("youtube-textarea-" + previous_item_id).value;

		this.insertItem(previous_item_id, new_yt);

		this.noticeTimeSpentEditing();
		return new_yt;
	},

	insertItem: function(previous_item_id, new_item) {
		var pos = this.getContentItemPositionById(previous_item_id);
		if (pos == -1) {
			this.Test.Data.OkTest.Contents.ContentItem.splice(0,0, new_item);
		}
		else {
			this.Test.Data.OkTest.Contents.ContentItem.splice(pos + 1, 0, new_item);
		}

		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();
	},

	deleteAboveItem: function(previous_item_id) {
		var pos = this.getContentItemPositionById(previous_item_id);
		var num_items = this.Test.Data.OkTest.Contents.ContentItem.length;
		
		if (pos == -1) {
			alert("trying to delete non-element. oops");
		}
		else if (pos == 0 && num_items == 1) { 
			// prevents the last element in a test from being deleted
			alert("Sorry, you can not delete the last item in a test.");
			return;
		}
		else {
			this.Test.Data.OkTest.Contents.ContentItem.splice(pos, 1);
		}
		$("test-contents").removeChild($("item_" + previous_item_id));

		this.updateVariableMinAndMaxes();	
		this.updateVariableListing();
		this.updateVariableSummary();
		this.updatePageScoring();
		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();

	},

	addVariable: function() {

		this.noteSoftResetNeeded();

		var varnames = this.getTestVarNames();
		var newvarname = "";
		for (var i = 0; newvarname == "" && i < this.SampleVariableNames.length; i++) {
			var found = false;
			for (var j = 0; j < varnames.length && found == false; j++) {
				if (varnames[j] == this.SampleVariableNames[i]) {
					found = true;
				}
			}
			if (found == false) {
				newvarname = this.SampleVariableNames[i];
			}
		}
		if (newvarname == "") {
			newvarname =  this.SampleVariableNames[this.SampleVariableNames.length - 1] + "_" + Math.round(Math.random() * 1000000000);
		}
		var all_vars = this.Test.Data.OkTest.Var;
		var new_var = new Object();
		new_var.Name = newvarname;
		all_vars[all_vars.length] = new_var;


    /*
    ** Update the scoring type
    */
    var varCount = this.Test.Data.OkTest.Var.length;
    if      (varCount == 1)
    {
      this.updateScalingType("Raw");
    }
    else if (varCount >  1)
    {
      this.updateScalingType("Percentage");
    }

    /*
    ** Redraw everything
    */
	
		this.updateVariableMinAndMaxes();
		this.updateVariableListing();
		this.updateVariableSummary();
		this.updatePageScoring();
		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();

	},

	dropVariable: function(varnum) {

		this.noteSoftResetNeeded();

		var varnames = this.getTestVarNames();
		var all_vars = this.Test.Data.OkTest.Var;
		var oldvarname = varnames[varnum];

		// drop from the list of vars
		all_vars.splice(varnum, 1);
		this.Test.Data.OkTest.Var = all_vars;

		// Remove from any references in scoring and test
		var contents = this.Test.Data.OkTest.Contents.ContentItem;
		for (var i = 0; i < contents.length; i++) {
			// Sliders have a VarAffected at top level
			if (contents[i].AnswerScoring
						 && contents[i].AnswerScoring.VarAffected
						 && contents[i].AnswerScoring.VarAffected == oldvarname) {
				contents[i].AnswerScoring.VarAffected = null;
			}
			// Regular questions have VarAffected per answer
			var answers = contents[i].Answer;
			if (answers) {
				for (var j = 0; j < answers.length; j++) {
					var answerscoring = answers[j].AnswerScoring;
					for (var k = 0; k < answerscoring.length; k++) {
						if (answerscoring[k].VarAffected == oldvarname) {
							answerscoring.splice(k,1);
							answers[j].AnswerScoring = answerscoring;
							this.updateScoringDisplay(contents[i]["@Id"], j);
						}
					}
				}
			}
		}
		
		// Delete any scoring rules using these vars
		
		var results = this.Test.Data.OkTest.Results.Result;
		for (var i = 0; i < results.length; i++) {
			var requirement = results[i].Requirement;
			if (requirement && requirement.length) {
				for (var j = 0; j < requirement.length; j++) {
					if ((requirement[j].Var && requirement[j].Var == oldvarname)
					|| (requirement[j].Target && requirement[j].Target == oldvarname)) {
						requirement.splice(j,1);
						j--;
					}
				}
			}
		}

    /*
    ** Update the scoring type
    */
    var varCount = this.Test.Data.OkTest.Var.length;
    if      (varCount == 1)
    {
      this.updateScalingType("Raw");
    }
    else if (varCount >  1)
    {
      this.updateScalingType("Percentage");
    }

    /*
    ** Redraw everything
    */
		this.updateVariableMinAndMaxes();	
		this.updateVariableListing();
		this.updateVariableSummary();
		this.updatePageScoring();
		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();

	},

	renameVariable: function(varnum) {

		this.noteSoftResetNeeded();

		var varnames = this.getTestVarNames();
		var oldvarname = varnames[varnum];
		var newvarname = $F('variable-'+varnum);
		var rxx = /^[0-9a-zA-Z_\-]+$/;

		if (newvarname == "") {
			alert ("Your variable can't have an empty name! Please try again!");
			$('variable-'+varnum).value = oldvarname;
			return;
		}
		else if (! newvarname.match(rxx)) {
			alert ("Be clean...Your variables should have just letters and numbers in their names.");
			$('variable-'+varnum).value = oldvarname;
			return;
		}
		else if (newvarname == oldvarname) {
			return;
		}
		else {
			for (var i = 0; i < varnames.length; i++) {
				if (i != varnum && varnames[i] == newvarname) {
					alert("You already have a variable named " + newvarname + ". Please try again!");
					$('variable-'+varnum).value = oldvarname;
					return;
				}
			}
		}
		
		// Update the variable entry
		var all_vars = this.Test.Data.OkTest.Var;
		all_vars[varnum].Name = newvarname;
		var contents = this.Test.Data.OkTest.Contents.ContentItem;
		// Update references in the test
		for (var i = 0; i < contents.length; i++) {
			// Sliders have a VarAffected at top level
			if (contents[i].AnswerScoring
						 && contents[i].AnswerScoring.VarAffected
						 && contents[i].AnswerScoring.VarAffected == oldvarname) {
				contents[i].AnswerScoring.VarAffected = newvarname;
			}
			// Regular questions have VarAffected per answer
			var answers = contents[i].Answer;
			if (answers) {
				for (var j = 0; j < answers.length; j++) {
					var answerscoring = answers[j].AnswerScoring;
					for (var k = 0; k < answerscoring.length; k++) {
						if (answerscoring[k].VarAffected == oldvarname) {
							answerscoring[k].VarAffected = newvarname;
							this.updateScoringDisplay(contents[i]["@Id"], j);
						}
					}
				}
			}
		}
		// Update scoring rules
		var results = this.Test.Data.OkTest.Results.Result;
		for (var i = 0; i < results.length; i++) {
			var requirement = results[i].Requirement;
			if (requirement && requirement.length) {
				for (var j = 0; j < requirement.length; j++) {
					if (requirement[j].Var && requirement[j].Var == oldvarname) {
						requirement[j].Var = newvarname;
					}
					if (requirement[j].Target && requirement[j].Target == oldvarname) {
						requirement[j].Target = newvarname;
					}
				}
			}
		}

		// Update scoring templates
		var results = this.Test.Data.OkTest.Results.Result;
		var findexp = new RegExp(oldvarname, "gi");
		var replacestr = newvarname;

		for (var i = 0; i < results.length; i++) {
			results[i].Subtitle["#cdata"] = results[i].Subtitle["#cdata"].replace(findexp, replacestr);
			results[i].Description["#cdata"] = results[i].Description["#cdata"].replace(findexp, replacestr);
		}

		// Redraw the scoring page
		this.updatePageScoring();
		
		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();


	},

	changePageBreakButton: function(id) {
		$("page-break-" + id).style.display = "none";
		$("page-break-edit-" + id).style.display = "block";
		this.noticeTimeSpentEditing();
	},

	updatePageBreakButton: function(id) {
		var pb = this.getObjByContentId(id);
		var new_text = ($("page-break-edit-text-" + id).value.strip() != "" ? $("page-break-edit-text-" + id).value : "Next"); /* PW_DEV */
		$("page-break-button-text-" + id).innerHTML = new_text;
		$("page-break-" + id).style.display = "";
		$("page-break-edit-" + id).style.display = "none";
		pb.SubmitButtonText["#cdata"] = new_text;

		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();

	},

	deleteQuestionAnswer: function(qid,ans) {

		this.closeAllEditors();
		var q = this.getObjByContentId(qid);
		var newanswers = q.Answer;
		newanswers.splice(ans, 1);
		q.Answer = newanswers;
		$('content-item-contents-' + qid).innerHTML = this.drawQuestionRadio(q);

		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();


	},

	addARadioAnswer: function(qid) {
		this.closeAllEditors();
		var q = this.getObjByContentId(qid);
		var newanswer = new Object();
		newanswer.AnswerText = new Object();
		newanswer.AnswerText["#cdata"] = "Click to Edit";
		newanswer.AnswerScoring = new Array();
		var newanswers = q.Answer;
		newanswers.push(newanswer);
		q.Answer = newanswers;
		$('content-item-contents-' + qid).innerHTML = this.drawQuestionRadio(q);
		this.updateVariableSummary();

		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();

	},

	chooseTemplate: function(name) {
		if (name != this.Test.Data.OkTest.LookTemplate.Which) {
			// Switch editor preview colors			//PW_MOD

			if(name == "Dark")
			{
				MCE_CONFIG_QUESTION_TEXT.content_css 	= "/quizzy/tinymce_questions_dark.css";
				MCE_CONFIG_HTML_SRC.content_css		 	= "/quizzy/tinymce_questions_dark.css";
				MCE_CONFIG_RESULT_DESCR.content_css		= "/quizzy/tinymce_questions_dark.css";
				MCE_CONFIG_ANSWER_TEXT.content_css		= "/quizzy/tinymce_answers_dark.css";
			} else {
				MCE_CONFIG_QUESTION_TEXT.content_css 	= "/quizzy/tinymce_questions.css";
				MCE_CONFIG_HTML_SRC.content_css		 	= "/quizzy/tinymce_questions.css";
				MCE_CONFIG_RESULT_DESCR.content_css		= "/quizzy/tinymce_questions.css";
				MCE_CONFIG_ANSWER_TEXT.content_css		= "/quizzy/tinymce_answers.css";
			}

			$("p-create").removeClassName(this.Test.Data.OkTest.LookTemplate.Which);										
			$("p-create").addClassName(name);

			this.Test.Data.OkTest.LookTemplate.Which = name;
			this.updateTemplateSummary();
			this.noticeTimeSpentEditing();
			this.updateDebugBoxes();
		}
	},

	changeTemplateOption: function() {
		var stores = this.getCurrentTemplateOptionStores();
		for (var i = 0; i < stores.length; i++) {
			if ($(stores[i].Id)) {
				if (! this.Test.Data.OkTest.LookTemplate.Option) {
					this.Test.Data.OkTest.LookTemplate.Option = new Array();
				}
				var found = false;
				for (var j = 0; j < this.Test.Data.OkTest.LookTemplate.Option.length; j++) {
					if (stores[i].Id == this.Test.Data.OkTest.LookTemplate.Option[j].Id) {
						this.Test.Data.OkTest.LookTemplate.Option[j].Value = new Object();
						this.Test.Data.OkTest.LookTemplate.Option[j].Value["#cdata"] = $F(stores[i].Id);
						found = true;
					}
				}
				if (! found) {
					this.Test.Data.OkTest.LookTemplate.Option.push( { "Id" : stores[i].Id, "Value" : {"#cdata" : $F(stores[i].Id)} } );
				}
			}
		}
		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();
	},

	deleteResultImage: function(id) {
		funcName = "deleteResultImage";
		debug_enter(funcName);

		var result = this.getResultById(id);
		result.Image = null;
		$('result-image-wrapper-'          + id).innerHTML = this.generateResultImage(id);
		$('result-image-add-link-wrapper-' + id).innerHTML = this.generateResultImageAddLink(id);
		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();

		debug_exit(funcName);
	},


	deleteResult: function(result_id) {
		this.closeAllEditors();
		funcName = "deleteResult";
		debug_enter(funcName);

		/*
		** Take it out of the DOM
		*/
		var result_list = this.getResultList();
		var index = this.getResultItemPositionById(result_id);
		result_list.splice(index, 1);

		/*
		** Kill it from the HTML
		*/
		$('result-list').removeChild($("result-list-item_" + result_id));

		/*
		** If this was the last item, update the new last item
		*/
		if (index == 0)
			$('result-list-item_' + result_list[0]["@Id"]).innerHTML = this.generateResult(result_list[0]["@Id"]);
		/*
		** If this was the first item, update the new first item
		*/
		if (index == result_list.length)
			$('result-list-item_' + result_list[result_list.length - 1]["@Id"]).innerHTML = this.generateResult(result_list[result_list.length - 1]["@Id"]);

		/*
		** Update result type
		*/
    var result_count = result_list.length;
    if      (result_count == 1)
    {
			/*
			** Update results to use subtitle instead of title
			*/
			this.enableSingleResultMode();
    }
    else if (result_count >  1)
    {
			/*
			** Update results to use title instead of subtitle
			*/
			this.enableMultipleResultMode();
    }

		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();

		debug_exit(funcName);
	},

	addResult: function(previous_result_id) {
		this.closeAllEditors();
		funcName = "addResult";
		debug_enter(funcName);

		var index = this.getResultItemPositionById(previous_result_id);
		var result_list = this.getResultList();

		var newResult                   = new Object();

		newResult["@Id"]                = this.randomId();
		newResult.Requirement			= new Array();
		newResult.Title                 = new Object();
		newResult.Title["#cdata"]       = this.SampleResultTitles[this.getResultList().length % this.SampleResultTitles.length];
		var sampleVariable              = this.SampleVariableNames[Math.round(Math.random() * this.SampleVariableNames.length)];
		newResult.Subtitle              = new Object();

		/*
		** The subtitle depends on the scaling method
		*/
		var scaling = this.getScalingType();
		var tempSubtitle = '';
		var varnames = this.getTestVarNames();
		
		var show_percent = "";
		if (scaling == "Percentage") {
			show_percent = "%";
		}
		for (var i = 0; i < varnames.length; i++) {
			if (i != varnames.length - 1) {
				tempSubtitle += " $(" + varnames[i] + ")" + show_percent + " " + varnames[i];
				if (i < varnames.length - 2) {
					tempSubtitle += ", ";
				}
				else {
					tempSubtitle +=" and ";
				}
			}
			else {
				tempSubtitle += " $(" + varnames[i] + ")" + show_percent + " " + varnames[i] + "!";
			}
		}

		newResult.Subtitle["#cdata"] = tempSubtitle;
		newResult.Description           = new Object();
		var samplevar = "Pure";
		if (varnames.length > 0) {
			samplevar = varnames[0];
		}
		newResult.Description["#cdata"] = ""
			+  "<strong>TODO:</strong> Edit this and write something informative. This is what the test taker "
			+  " takes away from the test.  Tell "
			+  " the taker what his/her score means and why they got this category. "
			+  " Note: in the description or title of a test result, you can refer "
			+  " to someone's score on a variable by wrapping the variable name in $(). "
			+  " For example, you could say here, <em>Hey! You scored " + tempSubtitle + " Brilliant!</em>";

		this.getResultList().splice(index + 1, 0, newResult);

		var newResultHTML = '<li class="container clearfix result-list-item" id="result-list-item_' + newResult["@Id"] + '">' + this.generateResult(newResult["@Id"]) + '</li>';
		if (previous_result_id == -1) {
			new Insertion.Top('result-list', newResultHTML);
			/*
			** If this was the first item, update the new first item
			*/
			$('result-list-item_' + result_list[1]["@Id"]).innerHTML = this.generateResult(result_list[1]["@Id"]);
		}
		else {
			new Insertion.After('result-list-item_' + previous_result_id, newResultHTML);
			// debug_log('Adding after the ' + this.getResultItemPositionById(previous_result_id) + 'th (out of ' + this.getResultList().length + ')');
			if (this.getResultItemPositionById(previous_result_id) == (this.getResultList().length - 2)) {
				// debug_log("Adding after last item");
				/*
				** If this was the last item, update the new last item
				*/
				$('result-list-item_' + previous_result_id).innerHTML = this.generateResult(previous_result_id);
			}
		}
		
		new Effect.Highlight('result-list-item_' + newResult["@Id"], this.hl_settings);

		/*
		** Update result type
		*/
    var result_count = result_list.length;
    if      (result_count == 1)
    {
			/*
			** Update results to use subtitle instead of title
			*/
			this.enableSingleResultMode();
    }
    else if (result_count >  1)
    {
			/*
			** Update results to use title instead of subtitle
			*/
			this.enableMultipleResultMode();
    }

		this.toggleResultCollapse();
		this.toggleResultCollapse();

		this.noticeTimeSpentEditing();

		this.updateDebugBoxes();

		debug_exit(funcName);
	},

	deleteRequirement: function(result_id, requirement_id) {
		funcName = "deleteRequirement";
		debug_enter(funcName);

		var result = this.getResultById(result_id);
		var requirement_pos_id = this.getGenericItemPositionById(requirement_id, result.Requirement);

		result.Requirement.splice(requirement_pos_id, 1);

		$('result-requirements-wrapper-' + result_id).innerHTML = this.generateRequirementList(result_id, requirement_id);

		this.noticeTimeSpentEditing();

		this.updateDebugBoxes();

		debug_exit(funcName);
	},

	addRequirement: function(result_id, requirement_id) {
		funcName = "addRequirement";
		debug_enter(funcName);

		var requirement = new Object();
		requirement["@Id"] = this.randomId();
		requirement.Var = this.getTestVarNames()[0];
		requirement["@Type"] = LEGAL_REQUIREMENT_TYPES[0].name;
		requirement.Value = 0;

		var result = this.getResultById(result_id);
		var requirement_pos_id = this.getGenericItemPositionById(requirement_id, result.Requirement);

		result.Requirement.splice(requirement_pos_id + 1, 0, requirement);

		arrayPrepare(result, "Requirement");

		$('result-requirements-wrapper-' + result_id).innerHTML = this.generateRequirementList(result_id, requirement_id);

		this.noticeTimeSpentEditing();

		this.updateDebugBoxes();

		debug_exit(funcName);
	},
	
	writeRequirementSummary: function(id) {
		var res_list = this.getResultList();
		var pos = this.getResultItemPositionById(id);
		var num_results = this.Test.Data.OkTest.Results.Result.length;
		var summary = '';
		
		for (var i = 0; i < res_list.length; i++) {
			if (id == res_list[i]["@Id"] && pos != num_results - 1) {
				summary += '<p class="summary">';
				summary += '<span style="display: none;">(VERBOSE: </span>';
			
				var req_list = res_list[i].Requirement;
						
				for (var j = 0; j < req_list.length; j++) {
					summary += (j == 0) ? ((pos == 0) ? 'If ' : 'and ') : ', and ';
					summary += '<strong>' + req_list[j].Var + '</strong> is ';
				
					switch(req_list[j]["@Type"]) {
						case "GreaterThanValue" : summary += 'greater than <strong>' + req_list[j].Value + '</strong>'; break;
						case "GreaterThanVar" : summary += 'greater than <strong>' + req_list[j].Var + '</strong>'; break;
						case "GreatestVar" : summary += '<strong>the greatest variable</strong>'; break;
						case "LessThanValue" : summary += 'less than <strong>' + req_list[j].Value + '</strong>'; break;
						case "LessThanVar" : summary += 'less than <strong>' + req_list[j].Var + '</strong>'; break;
						case "LeastVar" : summary += '<strong>the least variable</strong>'; break;
						default : summary += 'FUBAR';
					}
										
					summary += (j == res_list.length-1) ? '.' : '';
				}
				
				summary += (req_list.length == 0) ? '<strong class="boom">You must add a requirement or this result will be ignored.</strong>' : '';
				
				summary += '<span style="display: none;">)</span>';
				summary += '</p>';
			}
		}
		
		return summary;
	},

	updateTestRating: function() {
		this.Test.Data.OkTest.Rating = $F('test-rating');
		$("rating-summary-area").innerHTML = this.generateTestRatingCommentary($F('test-rating'));

		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();

	},

	isTestTitledWell: function(title) {
		var t = title.toLowerCase();
		if (t.indexOf("test") == -1 && t.indexOf("quiz") == -1) {
			return false;
		}
		return true;
	},

	updateTestTitle: function(title) {
		var all_title_inputs = document.getElementsByClassName('test-title');
		var all_test_tips = document.getElementsByClassName('test-name-warning-region');
		
		// update the title inputs on all pages
		for (var i = 0; i < all_title_inputs.length; i++) {
			all_title_inputs[i].value = title;
		}
		
		this.Test.Data.OkTest.Title["#cdata"] = title;
		if (! this.isTestTitledWell(title)) {
			for (var i = 0; i < all_test_tips.length; i++) {
				all_test_tips[i].style.display = "";
			}
		}
		else {
			for (var i = 0; i < all_test_tips.length; i++) {
				all_test_tips[i].style.display = "none";
			}
		}
		if ($("launch-title")) {
			$("launch-title").innerHTML = title;
		}
		
		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();

	},
	
	hideTestTitleTip: function() {
		var all_test_tips = document.getElementsByClassName('test-name-warning-region');
		
		for (var i = 0; i < all_test_tips.length; i++) {
			all_test_tips[i].style.display = "none";
		}
	},

	updateTestCategory: function() {
		this.Test.Data.OkTest.Category = $F('test-category');
		$('test-subcategory-item').innerHTML = this.generateTestSubcategoryPulldown(this.Test.Data.OkTest.Category, this.Test.Data.OkTest.Subcategory);
		if ($('test-subcategory')) {
			this.Test.Data.OkTest.Subcategory = $F('test-subcategory');
		}
		else {
			this.Test.Data.OkTest.Subcategory = "";
		}

		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();

	},

	updateTestSubcategory: function() {
		this.Test.Data.OkTest.Subcategory = $F('test-subcategory');

		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();

	},

	updateScalingType: function(new_type) {
		var funcName = "updateScalingType";
		var percentages = document.getElementsByClassName('percentage');
		debug_enter(funcName);
		var old_type = this.Test.Data.OkTest.VarScaling.Type;

		if (old_type == new_type) {
			debug_exit(funcName);
			return;
		}

		debug_log("Updating scaling type to " + new_type + " from " + old_type);

		var result_list = this.getResultList();
		if (new_type == "Percentage" && old_type == "Raw") {
			for (var i = 0; i < result_list.length; i++) {
				var requirement_list = result_list[i].Requirement;
				for (var j = 0; j < requirement_list.length; j++) {
					if (requirement_list[j]["@Type"] == "GreaterThanValue" || requirement_list[j]["@Type"] == "LessThanValue") {
						var varsub_j = this.getVariableRawScoreRange(requirement_list[j].Var);
						// Avoid possible divide by zero if axis isn't scored on any Q's.
						if (varsub_j.high > varsub_j.low) {
							requirement_list[j].Value = Math.round(100 * ((requirement_list[j].Value - varsub_j.low) / (varsub_j.high - varsub_j.low)));
						}
						else {
							requirement_list[j].Value = 0;
						}
						if ($('requirement-contents-' + requirement_list[j]["@Id"])) {
							$('requirement-contents-' + requirement_list[j]["@Id"]).innerHTML = this.generateRequirement(result_list[i], requirement_list[j]["@Id"]);
						}
					}
				}
			}
			var percentages = $('test-page-scoring').getElementsByClassName('percentage');
			for (var i = 0; i < percentages.length; i++) {
				percentages[i].style.display = "";
			}			
		}
		else if (new_type == "Raw" && old_type == "Percentage") {
			for (var i = 0; i < result_list.length; i++) {
				debug_log("Result #" + i);
				debug_log(Object.toJSON(result_list[i]));
				var requirement_list = result_list[i].Requirement;
				for (var j = 0; j < requirement_list.length; j++) {
					debug_log("-- Requirement #" + j);
					if (requirement_list[j]["@Type"] == "GreaterThanValue" || requirement_list[j]["@Type"] == "LessThanValue") {
						var varsub_j = this.getVariableRawScoreRange(requirement_list[j].Var);
						requirement_list[j].Value = Math.round(((requirement_list[j].Value / 100) * (varsub_j.high - varsub_j.low)) + varsub_j.low);
						if ($('requirement-contents-' + requirement_list[j]["@Id"])) {
							$('requirement-contents-' + requirement_list[j]["@Id"]).innerHTML = this.generateRequirement(result_list[i], requirement_list[j]["@Id"]);
						}
					}
				}
			}
			var percentages = $('test-page-scoring').getElementsByClassName('percentage');
			for (var i = 0; i < percentages.length; i++) {
				percentages[i].style.display = "none";
			}	
		}
		else {
			debug_log("This is not a sane state!");
			/* WTF */
		}

		this.Test.Data.OkTest.VarScaling.Type = new_type;

		this.updateScalingSummary();

		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();

		debug_exit(funcName);
	},

	updateTestLiveStatus: function(new_status) {
		var req = new Ajax.Request(this.CreateWebsrvUrl,
										{method: 'post'
										,parameters: {set_test_live_status: 1, test_id: this.Test.Data.OkTest.TestId, status: new_status}
										,onSuccess: this.successUpdateTestLiveStatus.bind(this)
										,onFailure: this.failedUpdateTestLiveStatus.bind(this)});


	},

	successUpdateTestLiveStatus: function(data) {
		var dom = parseXml(data.responseText);
		var j = xml2json(dom, "   ");
		var res = j.evalJSON();
		if (! res || ! res.xml || ! res.xml.Result || res.xml.Result != "true") {
			alert ("Failed to update test_status");
		}
		else {
			this.TestExtrinsicData.LiveStatus = res.xml.NewStatus;
			this.updatePageLaunch();
		}
		this.updateExtrinsicDataDisplay();
		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();
	},

	failedUpdateTestLiveStatus: function(data) {
		alert("Failed to update test status");
	},

	updateAboutTheAuthorName: function(data) {
		this.Test.Data.OkTest.AboutTheAuthor.Name = $F('about-the-author-name');
		if (this.Test.Data.OkTest.AboutTheAuthor.Description["#cdata"] == "") {
			$('about-the-author-description').value = this.Test.Data.OkTest.AboutTheAuthor.Name
			+ " lives and breathes in Irkutsk, Russia, where he toils in a barrel factory by day. "
			+ " At night he composes HelloQuizzy tests and plays with his two cats, Irma and Michael Jackson."
			+ " He's regionally ranked at Battleship, and oh! He just sank yours.";
		}
		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();
	},

	updateAboutTheAuthorRemovePic: function(data) {
		this.Test.Data.OkTest.AboutTheAuthor.Image = new Object();
		$('about-the-author-pic-wrapper').innerHTML = this.generateAboutTheAuthorPic();
		$('about-the-author-pic-add-wrapper').innerHTML = this.generateAboutTheAuthorPicAddLink();
		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();
	},

	updateAboutTheAuthorDescription: function(data) {
		this.Test.Data.OkTest.AboutTheAuthor.Description["#cdata"] = $F('about-the-author-description');
		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();
	},

	updateAboutTheAuthorAddPic: function(data) {
		this.Test.Data.OkTest.AboutTheAuthor.Image = new Object();
		// Let the uploader do its trick
		var extras = {
            //            ShouldCommit: true,
            //          CommitStatus: '2',
			TempId : "about-the-author",
	       	IFrameParams : 'width="590" height="75" scrolling="no" allowtransparency="true"' ,   
	       	IFrameStyleTemplate : 'quizzy/uploader.css'
		};
		
		this.Uploaders["about-the-author"] = getSimpleUploader('about-the-author-pic-upload-wrapper'
									  , this.updateAboutTheAuthorAddPic_cb.bindAsEventListener(this)
									  , extras);
	},

	updateAboutTheAuthorAddPic_cb: function(up) {
		if (up && up.Status == "success") {
			var id = up.TempId;
			var result = this.getResultById(id);
			this.Test.Data.OkTest.AboutTheAuthor.Image = new Object();
			this.Test.Data.OkTest.AboutTheAuthor.Image.Src = up.ResultUrl;
			this.Test.Data.OkTest.AboutTheAuthor.Image.Picid = up.Picid;
			this.Test.Data.OkTest.AboutTheAuthor.Image.From = up.From;
			this.Test.Data.OkTest.AboutTheAuthor.Image.OriginalSource = new Object();
			this.Test.Data.OkTest.AboutTheAuthor.Image.OriginalSource["#cdata"] = up.OriginalSource;
			this.Test.Data.OkTest.AboutTheAuthor.Image.Width = up.Width;
			this.Test.Data.OkTest.AboutTheAuthor.Image.Height = up.Height;
			$('about-the-author-pic-wrapper').innerHTML = this.generateAboutTheAuthorPic();
			$('about-the-author-pic-add-wrapper').innerHTML = this.generateAboutTheAuthorPicAddLink();
			setTimeout("$('about-the-author-pic-upload-wrapper').innerHTML = ''", 100);
		}
		else {
			alert("Picture failed!");
		}		
		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();
	},

	nukeOldTestStats: function() {

			var req = new Ajax.Request(this.CreateWebsrvUrl,
											{method: 'get'
											,parameters: {nuke_old_stats: 1, test_id: this.Test.Data.OkTest.TestId}
											,onSuccess: this.successNukeOldStats.bind(this)
											,onFailure: this.failedNukeOldStats.bind(this)});
	},

	successNukeOldStats: function(data) {
		$('nuke-old-stats-response-wrapper').innerHTML += ' <i>Nuked!</i>';
		$('nuke-old-stats-link').innerHTML = 'Nuke them again';
	},

	failedNukeOldStats: function() { alert("Failed to nuke old stats"); },

	// When the user has dragged around the content items, we look at the ordering of the li's
	// and then update our own ordering of the object to match

	resortResultList: function() {
		// alert("Resorting result list");
		this.resortGenericData("result-list", this.Test.Data.OkTest.Results, "Result");
	  var results = this.getResultList();
		for (var i = 0; i < results.length; i++) {
			$("result-requirements-wrapper-" + results[i]["@Id"]).innerHTML = this.generateRequirementList(results[i]["@Id"]);
			$("result-summary-wrapper-" + results[i]["@Id"]).innerHTML = this.writeRequirementSummary(results[i]["@Id"]);
		}
	},

	resortTestData: function() {
		// brute force removal of dragging class name
		bruteForceRemoveDragging();
		this.resortGenericData("test-contents", this.Test.Data.OkTest.Contents, "ContentItem");
	},

	resortGenericData: function(html_container, dom_parent, array_name) {
		var res = this.getHTMLOrdering(html_container);
		// Rebuild the contents; this is n^2 at the moment. TODO: speed it up.
		var newcontents = new Array();
		var oldcontents = dom_parent[array_name];
		for (i = 0; i < res.length; i++) {
			for (j = 0; j < oldcontents.length; j++) {
				if (res[i] == oldcontents[j]["@Id"]) {
					newcontents.push(oldcontents[j]);
				}
			}
		}
		dom_parent[array_name] = newcontents;

		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();

	},

	//
	// gets an array of the current HTML ordering of item id numbers
	//
	getHTMLOrdering: function(elements) {
		var res = Sortable.serialize(elements).split(elements + "[]=");
		for (i = 0; i < res.length; i++) {
			res[i] = res[i].replace("&","");
			res[i] = res[i].replace(",","");
		}
		res = res.splice(1, res.length - 1);
		return res;
	},

	clickAnswerText: function(qid,ans) {
		this.noteOpenEditor({'type' : "AnswerText", 'qid' : qid, 'ans' : ans});			
 		$("answer-text-edit-wrap-" + qid + "-" + ans).style.display="";
		$("scoring-modifier-div-" + qid + "-" + ans).style.display="";
		this.updateScoringModifierDiv(qid,ans);
 		$("answer-wrap-" + qid + "-" + ans).style.display="none";
		tinyMCE.settings = MCE_CONFIG_ANSWER_TEXT;																
 		if (tinyMCE.getInstanceById("answer-text-edit-region-" + qid + "-" + ans) == null) {
    		tinyMCE.execCommand('mceAddControl', false, "answer-text-edit-region-" + qid + "-" + ans);
			this.mceSelectAll();
 		}
	},

	doneEditingAnswer: function(qid,ans) {
		this.noteCloseEditor({'type' : "AnswerText", 'qid' : qid, 'ans' : ans});				
		if (tinyMCE.getInstanceById("answer-text-edit-region-" + qid + "-" + ans) != null) {
			tinyMCE.execCommand('mceRemoveControl', false, "answer-text-edit-region-" + qid + "-" + ans);
		}
 		$("answer-text-edit-wrap-" + qid + "-" + ans).style.display="none";
 		$("answer-wrap-" + qid + "-" + ans).style.display="";
		
		/*PW_DEV*/
		if($("answer-text-edit-region-" + qid + "-" + ans).value.stripTags() != "") {
			$("answer-text-" + qid + "-" + ans).innerHTML = $("answer-text-edit-region-" + qid + "-" + ans).value;
		} else {
			$("answer-text-" + qid + "-" + ans).innerHTML = "Click to change";
		}
		/*/PW_DEV*/
		
		var q = this.getObjByContentId(qid);
		q.Answer[ans].AnswerText["#cdata"] = $("answer-text-edit-region-" + qid + "-" + ans).value;

		// Figure out the scoring changes
		var testvars = this.getTestVarNames();
		for (var i = 0; i < testvars.length; i++) {
			if ($("answer-effect-select-" + qid + "-" + ans + "-" + i)) {
				var newscore = $F("answer-effect-select-" + qid + "-" + ans + "-" + i);
				if (newscore == "other") {
					newscore = this.cleanIntInput($F('answer-effect-custom-' + qid + '-' + ans + '-' + i));
				}
				this.updateScoringEffect(q,ans,testvars[i],newscore);
			}
			$('scoring-summary-wrap-' + qid + '-' + ans).innerHTML = this.getScoringSummary(q,ans);
		}
		this.updateVariableMinAndMaxes();		
		this.updateVariableSummary();
		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();

	},
	
	updateVariableMinAndMaxes: function() {
		var test_vars = this.Test.Data.OkTest.Var;
		for (var i = 0; i < test_vars.length; i++) {
			var range = this.getVariableRawScoreRange(test_vars[i].Name);
			test_vars[i].MaxValue = range.high;
			test_vars[i].MinValue = range.low;
		}
	},

	updateScoringEffect: function(qobj,ans,varname,effect) {
		var found = false;
		var scoring = qobj.Answer[ans].AnswerScoring;
		if (! scoring) {
			qobj.Answer[ans].AnswerScoring = new Array();
			scoring = qobj.Answer[ans].AnswerScoring;
		}
		for (var i = 0; i < scoring.length; i++) {
			if (scoring[i].VarAffected == varname) {
				scoring[i].Effect = effect;
				found = true;
			}
		}
		if (! found) {
			var neweffect = new Object();
			neweffect.VarAffected = varname;
			neweffect.Effect = effect;
			scoring.push(neweffect);
			qobj.Answer[ans].AnswerScoring = scoring;
		}
		this.noticeTimeSpentEditing();
	},

	clickResultSubtitle: function(rid) {
		var funcName = "clickResultSubtitle";
		debug_enter(funcName);

 		$("result-subtitle-" + rid).style.display="none";
		$("result-subtitle-edit-" + rid).style.display="";
		this.showVarTemplatingAdvice("result-subtitle-edit-advice-" + rid);
		

		debug_exit(funcName);
	},

	doneEditingResultSubtitle: function(rid) {
		var funcName = "doneEditingResultSubtitle";
		debug_enter(funcName);

    /*
    ** Update UI
    */

 		$("result-subtitle-edit-" + rid).style.display  = "none";
 		$("result-subtitle-"      + rid).style.display  = "block"; 	/*PW_DEV*/
 		$("result-subtitle-"      + rid).style.height  = "1.4em";	/*PW_DEV*/

		/*PW_DEV*/
		if($F("result-subtitle-text-" + rid) != "") {
			$("result-subtitle-"      + rid).innerHTML      = $F("result-subtitle-text-" + rid);
		} else {
			$("result-subtitle-"      + rid).innerHTML      = "Click to change";
		}
		/*/PW_DEV*/

    /*
    ** Update XML
    */
		var result_subtitle = this.getResultById(rid).Subtitle;
		result_subtitle["#cdata"] = $F("result-subtitle-text-" + rid);

		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();

		debug_exit(funcName);
	},

	showVarTemplatingAdvice : function(dest) {
		var testvars = this.getTestVarNames();
		var samplename = "Laziness";
		var scoretype = "points";
		if (testvars.length != 0) {
			samplename = testvars[0];
		}
		if ( this.getScalingType() == "Percentage" ) {
			scoretype = "percent";
		}
				
		$(dest).innerHTML = "<strong>Tip!</strong> You can refer to the taker's score on a given "
		 	+ " variable by wrapping it in $(&thinsp;).  For example: <em>You scored $(" + samplename + ") "
		    + scoretype + " on " + samplename + ". Well done!</em>";
	},

	clickResultTitle: function(rid) {
		var funcName = "clickResultTitle";
		debug_enter(funcName);

 		$("result-title-" + rid).style.display="none";
		$("result-title-edit-" + rid).style.display="";

		debug_exit(funcName);
	},

	doneEditingResultTitle: function(rid) {
		var funcName = "doneEditingResultTitle";
		debug_enter(funcName);

    /*
    ** Update UI
    */

 		$("result-title-edit-" + rid).style.display  = "none";
 		$("result-title-"      + rid).style.display  = "";

		/*PW_DEV*/
		if($F("result-title-text-" + rid) != "") {
			$("result-title-"      + rid).innerHTML      = $F("result-title-text-" + rid);
		} else {
			$("result-title-"      + rid).innerHTML      = "Click to change";
		}
		/*/PW_DEV*/

    /*
    ** Update XML
    */
		var result_title = this.getResultById(rid).Title;
		result_title["#cdata"] = $F("result-title-text-" + rid);

		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();

		debug_exit(funcName);
	},

	clickResultDescription: function(rid) {
		this.noteOpenEditor({'type' : "ResultDescription", 'rid' : rid});	
		var funcName = "clickResultDescription";
		debug_enter(funcName);

 		$("result-description-" + rid).style.display="none";
		$("result-description-edit-" + rid).style.display="";
		this.showVarTemplatingAdvice("result-description-edit-advice-" + rid);
		
 		tinyMCE.settings = MCE_CONFIG_RESULT_DESCR;
		if (tinyMCE.getInstanceById("result-description-textarea-" + rid) == null) {
			debug_log("Exec'ing command...");
    		tinyMCE.execCommand('mceAddControl', false, "result-description-textarea-" + rid);
			this.mceSelectAll();
 		}

		debug_exit(funcName);
	},

    //
    // Allows the user to stop using TinyMCE
    //

    htmlToggleResultDescription : function(rid, is_desc) {
            tinyMCE.settings = (is_desc) ? MCE_CONFIG_RESULT_DESCR : MCE_CONFIG_HTML_SRC;
            if (tinyMCE.getInstanceById("result-description-textarea-" + rid) != null) {
                tinyMCE.execCommand('mceRemoveControl', false, "result-description-textarea-" + rid);
            }
            else {
                tinyMCE.execCommand('mceAddControl', false, "result-description-textarea-" + rid);
                this.mceSelectAll();
            }
        },
    
	doneEditingResultDescription: function(rid) {		
		this.noteCloseEditor({'type' : "ResultDescription", 'rid' : rid});	

		var funcName = "doneEditingResultDescription";
		debug_enter(funcName);

        this.htmlToggleResultDescription(rid, true);
        $("result-description-edit-" + rid).style.display="none";
        $("result-description-" + rid).style.display="";
			
        if($F("result-description-textarea-" + rid).stripTags() != "") {
            $("result-description-" + rid).innerHTML = $F("result-description-textarea-" + rid);
        } else {
            $("result-description-" + rid).innerHTML = "Click to edit";
        }
        
        var result_description = this.getResultById(rid).Description;
        result_description["#cdata"] = $F("result-description-textarea-" + rid);
        this.noticeTimeSpentEditing();
        this.updateDebugBoxes();		

		debug_exit(funcName);
	},


	// The test itself's thumbnail
	removeThumbnail: function() {
		this.Test.Data.OkTest.Thumb = new Object();
		this.updateThumbnail();
		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();
	},

	// called by save
	autoCreateThumbnailIfNone: function() {
		if (! this.Test.Data.OkTest.Thumb.Image || ! this.Test.Data.OkTest.Thumb.Image.Src) {
			var img_obj = this.getFirstImageInTest();
			if (img_obj) {
				this.Test.Data.OkTest.Thumb.Image = new Object();
				if (img_obj.Src) { this.Test.Data.OkTest.Thumb.Image.Src = img_obj.Src; }
				if (img_obj.Picid) { this.Test.Data.OkTest.Thumb.Image.Picid = img_obj.Picid; }
				if (img_obj.From) { this.Test.Data.OkTest.Thumb.Image.From = img_obj.From; }
				if (img_obj.OriginalSource && img_obj.OriginalSource["#cdata"]) { 
					this.Test.Data.OkTest.Thumb.Image.OriginalSource = new Object();
					this.Test.Data.OkTest.Thumb.Image.OriginalSource["#cdata"] = img_obj.OriginalSource["#cdata"]; 
				}
				if (img_obj.Width) { this.Test.Data.OkTest.Thumb.Image.Width = img_obj.Width; }
				if (img_obj.Height) { this.Test.Data.OkTest.Thumb.Image.Height = img_obj.Height; }				
				this.updateThumbnail();		
			}
		}
	},


	addThumbnail: function(data) {
		this.Test.Data.OkTest.Thumb.Image = new Object();
		var extras = {
            //          ShouldCommit: true,
            //          CommitStatus: '2',
			TempId : 'test-thumb',
	       	IFrameParams : 'width="590" height="75" scrolling="no" allowtransparency="true"' ,   
	       	IFrameStyleTemplate : 'quizzy/uploader.css'
		};
		
		this.Uploaders["test-thumb"] = getSimpleUploader('thumb-upload-area'
									  , this.addThumbnail_cb.bindAsEventListener(this)
									  , extras);
	},

	addThumbnail_cb: function(up) {
		if (up && up.Status == "success") {
			var id = up.TempId;
			this.Test.Data.OkTest.Thumb.Image = new Object();
			this.Test.Data.OkTest.Thumb.Image.Src = up.ResultUrl;
			this.Test.Data.OkTest.Thumb.Image.Picid = up.Picid;
			this.Test.Data.OkTest.Thumb.Image.From = up.From;
			this.Test.Data.OkTest.Thumb.Image.OriginalSource = new Object();
			this.Test.Data.OkTest.Thumb.Image.OriginalSource["#cdata"] = up.OriginalSource;
			this.Test.Data.OkTest.Thumb.Image.Width = up.Width;
			this.Test.Data.OkTest.Thumb.Image.Height = up.Height;
			this.updateThumbnail();
		}
		else {
			alert("Picture failed!");
		}		
		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();
	},

	// The test's description
	clickDescription: function() {
		this.noteOpenEditor({type : "Description"});
		$('description-edit').style.display="";
		$('description-content').style.display="none";
 		tinyMCE.settings = MCE_CONFIG_DESCRIPTION;
		if (tinyMCE.getInstanceById('description-edit-textarea') == null) {
    		tinyMCE.execCommand('mceAddControl', false, 'description-edit-textarea');
			this.mceSelectAll();
 		}				
	},
	
	doneEditingDescription: function() {
		this.noteCloseEditor({type : "Description"});
		$('description-edit').style.display="none";
		$('description-content').style.display="";
		if (tinyMCE.getInstanceById('description-edit-textarea') != null) {
			tinyMCE.execCommand('mceRemoveControl', false, 'description-edit-textarea');
		}
		var res = $F('description-edit-textarea');
		if (res.length > 200) {
			res = res.substr(0,200) + "...";
		}
		this.Test.Data.OkTest.Description["#cdata"] = res;
		$('description-content').innerHTML = res;
		this.noticeTimeSpentEditing();
		this.updateDebugBoxes();
	},

	clickHTMLBlock: function(hid) {
		this.noteOpenEditor({'type' : "HTMLBlock", 'hid' : hid});
 		$("html-block-edit-" + hid).style.display="";
		$("html-block-content-" + hid).style.display="none";
 		tinyMCE.settings = MCE_CONFIG_HTML_SRC;
		if (tinyMCE.getInstanceById("html-block-textarea-" + hid) == null) {
    		tinyMCE.execCommand('mceAddControl', false, "html-block-textarea-" + hid);
			this.mceSelectAll();
 		}
	},


	//
	// Allows the user to stop using TinyMCE
	//
	htmlToggleHTMLBlock : function(hid) {
		tinyMCE.settings = MCE_CONFIG_HTML_SRC;
		if (tinyMCE.getInstanceById("html-block-textarea-" + hid) != null) {
			tinyMCE.execCommand('mceRemoveControl', false, "html-block-textarea-" + hid);
		}
		else {
    		tinyMCE.execCommand('mceAddControl', false, "html-block-textarea-" + hid);			
			this.mceSelectAll();
		}		
	},

	doneEditingHTMLBlock: function(hid) {

		this.noteCloseEditor({'type' : "HTMLBlock", 'hid' : hid});

        this.htmlToggleHTMLBlock(hid);
        $("html-block-edit-"    + hid).style.display  = "none";
        $("html-block-content-" + hid).style.display  = "";
        
        if($("html-block-textarea-" + hid).value.stripTags() != "") {
            $("html-block-content-" + hid).innerHTML      = $("html-block-textarea-" + hid).value;
        } else {
            $("html-block-content-" + hid).innerHTML      = "Click to change";
        }
			
        var html_block = this.getObjByContentId(hid);
        html_block["#cdata"] = $("html-block-textarea-" + hid).value;

        this.noticeTimeSpentEditing();
        this.updateDebugBoxes();

	},

	//
	// Allows the user to stop using TinyMCE
	//
	htmlToggleQuestion : function(qid) {
		tinyMCE.settings = MCE_CONFIG_QUESTION_TEXT;
		if (tinyMCE.getInstanceById("question-text-edit-region-" + qid) != null) {
			tinyMCE.execCommand('mceRemoveControl', false, "question-text-edit-region-" + qid);
		}
		else {
    		tinyMCE.execCommand('mceAddControl', false, "question-text-edit-region-" + qid);			
			this.mceSelectAll();
		}		
	},

	clickQuestionText: function(qid) {

		this.closeAllEditors();
		this.noteOpenEditor({'type' : "QuestionText", 'qid' : qid});		
 		$("question-text-edit-wrap-" + qid).style.display="";
		$("question-text-" + qid).style.display="none";
		this.htmlToggleQuestion(qid);
	},

	mceSelectAll: function() {
		// this delay is necessary since tinyMCE has no callback methods I know of
		//setTimeout("tinyMCE.execCommand('selectall')",250);
		//
		// THIS FUNCTION DOESN'T SEEM TO WORK RIGHT; THE GOAL WAS TO HIGHLIGHT WHATEVER
		// THEY JUST CLICKED, bUT I'M PRETTY SURE IT'S THE SOURCE OF THE BUG THAT MAKES
		// IT SO YOU CAN'T DELETE ANYTHING UNTIL YOU'VE TYPED SOMETHING IN A BOX. SUCKY.
		//
		
	},

	doneEditingQuestion: function(qid) {

		this.noteCloseEditor({'type' : "QuestionText", 'qid' : qid});

        this.htmlToggleQuestion(qid);
        $("question-text-edit-wrap-" + qid).style.display="none";
        $("question-text-" + qid).style.display="";
        
        if($("question-text-edit-region-" + qid).value.stripTags() != "") {
            $("question-text-" + qid).innerHTML = $("question-text-edit-region-" + qid).value;
        } else {
            $("question-text-" + qid).innerHTML = "Click to change";
        }
	
        var q = this.getObjByContentId(qid);
        q["QuestionText"]["#cdata"] = $("question-text-edit-region-" + qid).value;
        this.noticeTimeSpentEditing();
        this.updateDebugBoxes();
	},

	noteSoftResetNeeded: function() {
		this.Test.Data.OkTest.SoftResetNeeded = "true";		
	},
	
	noteSoftResetNotNeeded: function() {
		this.Test.Data.OkTest.SoftResetNeeded = "false";				
	},
	
	isSoftResetNeeded: function() {
		if (this.Test.Data.OkTest.SoftResetNeeded && this.Test.Data.OkTest.SoftResetNeeded == "true") {
			return true;
		}		
		else {
			return false;
		}
	},



//--------------------------------------------------------------------------------------------------------------------------------
//
// L O O K U P   &   H E L P E R    F U N C T I O N S
//
//--------------------------------------------------------------------------------------------------------------------------------


	turnOnDragging: function() {
		Sortable.create("test-contents", {tag:"li", handle: "handle", constraint: "vertical", hoverclass: "hover-over", scroll: window, ghosting:false, onUpdate: this.resortTestData.bind(this)});
		
		/* Drag sort method */
		/* PW_DEV */



/*  */

	},

	turnOffDragging: function() {
		Sortable.destroy("test-contents");
	},


	getResultList: function() {
		return this.Test.Data.OkTest.Results.Result;
	},

	getScalingType: function() {
    debug_log("VarScaling : " + this.Test.Data.OkTest.VarScaling.Type);
		return this.Test.Data.OkTest.VarScaling.Type;
	},


	getLookTemplateOptionValue: function(id) {
		var res = null;
		var options = this.Test.Data.OkTest.LookTemplate.Option;
		if (options) {
			for (var i = 0; i < options.length; i++) {
				if (options[i].Id == id) {
					return options[i].Value;
				}
			}
		}
		return res;
	},

	// The returns a subset of the this.EditorConfig.LegalLookTemplateOptions.Option array,
	// based on which template the user currently has selected
	getCurrentTemplateOptions: function() {
		var funcName = "getCurrentTemplateOptions";
		debug_enter(funcName);
		var res = new Array();
		var template = this.getTemplateByName(this.Test.Data.OkTest.LookTemplate.Which);
		debug_log(Object.toJSON(template));
		for (var i = 0; i < template.ShowOption.length; i++) {
			var option = this.getTemplateOptionByName(template.ShowOption[i]);
			if (option) {
				res.push(option);
			}
		}
		debug_exit(funcName);
		return res;
	},

	// Based on currently selected template,
	// returns a bunch of store objects from EditorConfig, each of which
	// contains Id
	getCurrentTemplateOptionStores: function() {
		var res = new Array();
		var template = this.getTemplateByName(this.Test.Data.OkTest.LookTemplate.Which);
		for (var i = 0; i < template.ShowOption.length; i++) {
			var option = this.getTemplateOptionByName(template.ShowOption[i]);
			if (option) {
				for (var j = 0; j < option.Store.length; j++) {
					res.push(option.Store[j]);
				}
			}
		}
		return res;
	},

	getTemplateByName: function(name) {
		var funcName = "getTemplateByName";
		debug_enter(funcName);
		for (var i = 0; i < this.EditorConfig.LegalLookTemplates.Template.length; i++) {
			if (this.EditorConfig.LegalLookTemplates.Template[i].Name == name) {
				debug_exit(funcName);
				return this.EditorConfig.LegalLookTemplates.Template[i];
			}
		}
		debug_exit(funcName);
		return this.EditorConfig.LegalLookTemplates.Template[0];
	},
	getTemplateOptionByName: function(name) {
		for (var i = 0; i < this.EditorConfig.LegalLookTemplateOptions.Option.length; i++) {
			if (this.EditorConfig.LegalLookTemplateOptions.Option[i].Name == name) {
				return this.EditorConfig.LegalLookTemplateOptions.Option[i];
			}
		}
		return null;
	},

	arrayPrepareEditorConfig: function() {
			
		//EditorConfig.LegalTestCategories is set to "Failure" in opera			//DEBUG_PETER
		//if(Prototype.Browser.Opera == true) this.EditorConfig.LegalTestCategories = false;
		
            if (! this.EditorConfig.LegalTestCategories) {  
				this.EditorConfig.LegalTestCategories = new Object();
                this.EditorConfig.LegalTestCategories.Category = new Array();
            }
            if (! this.EditorConfig.LegalTestCategories.Category) {
                this.EditorConfig.LegalTestCategories.Category = new Array();
            }
		
		//data is wrong but not empty, so everything fails
		
		//alert(this.EditorConfig.LegalTestCategories);
		arrayPrepare(this.EditorConfig.LegalTestCategories, "Category");

		//alert("Error");
		
		//Failure point
		
		for (var i = 0; i < this.EditorConfig.LegalTestCategories.length; i++) {
			if (! this.EditorConfig.LegalTestCategories.Category[i].Subcategory)	{
				this.EditorConfig.LegalTestCategories.Category[i].Subcategory = new Array();
			}
			arrayPrepare(this.EditorConfig.LegalTestCategories.Category[i], "Subcategory");
		}
		
		//alert("End Error");
		
		arrayPrepare(this.EditorConfig.LegalLookTemplates, "Template");
		for (var i = 0; i < this.EditorConfig.LegalLookTemplates.Template.length; i++) {
			if (! this.EditorConfig.LegalLookTemplates.Template[i].ShowOption) {
				this.EditorConfig.LegalLookTemplates.Template[i].ShowOption = new Array();
			}
			arrayPrepare(this.EditorConfig.LegalLookTemplates.Template[i], "ShowOption");
		}

		arrayPrepare(this.EditorConfig.LegalLookTemplateOptions, "Option");

		for (var i = 0; i < this.EditorConfig.LegalLookTemplateOptions.Option.length; i++) {
			if (! this.EditorConfig.LegalLookTemplateOptions.Option[i].Store) {
				this.EditorConfig.LegalLookTemplateOptions.Option[i].Store = new Array();
			}
			arrayPrepare(this.EditorConfig.LegalLookTemplateOptions.Option[i], "Store");
		}

		if (! this.EditorConfig.UpcomingTestContests
			|| this.EditorConfig.UpcomingTestContests == "true" 
			|| ! this.EditorConfig.UpcomingTestContests.Contest 
			|| this.EditorConfig.UpcomingTestContests.Contest.length == 0) {
			this.EditorConfig.UpcomingTestContests = new Object();
			this.EditorConfig.UpcomingTestContests.Contest = new Array();
		}

		arrayPrepare(this.EditorConfig.UpcomingTestContests, "Contest");
	},


	//
	// Finds the first image in the test (excluding thumbnail and excluding our samples) but including
	// image questions and results images. To be used initally for auto thumbnail generation
	//
	
	getFirstImageInTest : function() {
		var items = this.Test.Data.OkTest.Contents.ContentItem;
		for (var i = 0; i < items.length; i++) {
			if (items[i]["@Type"] == "Image" && items[i].From != "SAMPLE") {
				return items[i];
			}
		}
		var items = this.Test.Data.OkTest.Results.Result;
		for (var i = 0; i < items.length; i++) {
			if (items[i].Image) {
				return items[i].Image;
			}
		}
		return false;
	},

	//
	// For a given variable, finds the raw score range possible
	// in the test.
	//
	getVariableRawScoreRange: function(varname) {
		var res = new Object();
		res.low = 0;
		res.high = 0;

		var items = this.Test.Data.OkTest.Contents.ContentItem;
		for (var i = 0; i < items.length; i++) {
			var local_low = 0;
			var local_high = 0;
			if (items[i]["@Type"] == "QuestionRadio" && items[i].Answer && items[i].Answer.length) {
				for (var j = 0; j < items[i].Answer.length; j++) {
					var effect = this.getAnswerEffectOnVar(varname, items[i], j);
					if (j == 0) {
						local_low = effect;
						local_high = effect;
					}
					else {
						if (effect < local_low) {
							local_low = effect;
						}
						if (effect > local_high) {
							local_high = effect;
						}
					}
				}
			}
			res.low += local_low;
			res.high += local_high;
		}
		return res;
	},

	getTestVarNames: function() {
		var funcName = "getTestVarNames";
		debug_enter(funcName);
		var res = new Array();
		var the_vars = this.Test.Data.OkTest.Var;
		for (var i = 0; i < the_vars.length; i++) {
			res.push(the_vars[i].Name);
		}
		debug_exit(funcName);
		return res;
	},

	getTestTags: function() {
		var res = new Array();
		var the_tags = this.Test.Data.OkTest.Tags.Tag;
		for (var i = 0; i < the_tags.length; i++) {
			res.push(the_tags[i]["#cdata"]);
		}
		return res;
	},

	getLegalTestCategoryNames: function() {
		var res = new Array();
		for (var i = 0; i < this.EditorConfig.LegalTestCategories.Category.length; i++) {
			res.push(this.EditorConfig.LegalTestCategories.Category[i].Name["#cdata"]);
		}
		return res;
	},

	getLegalTestSubcategoryNames: function(category_name) {
		var funcName = "getLegalTestSubcategoryNames";
		debug_enter(funcName);
		var res = new Array();
        debug_log(Object.toJSON(this.EditorConfig.LegalTestCategories));
		for (var i = 0; i < this.EditorConfig.LegalTestCategories.Category.length; i++) {
			if (category_name == this.EditorConfig.LegalTestCategories.Category[i].Name["#cdata"]) {
				if (this.EditorConfig.LegalTestCategories.Category[i].Subcategory) {
					for (var j = 0; j < this.EditorConfig.LegalTestCategories.Category[i].Subcategory.length; j++) {
						res.push(this.EditorConfig.LegalTestCategories.Category[i].Subcategory[j]["#cdata"]);
					}
				}
                debug_exit(funcName);
                return res;
			}
		}
		debug_exit(funcName);
		return res;
	},

    // cleans plus sign out and maps strings to ints
	cleanIntInput : function(text) {
		if (text.length > 0 && text.charAt(0) == "+") {
			text = text.substr(1);
		}
		var res = parseInt(text);
		if (isNaN(res)) {
			return 0;
		}
		else {
			return res;
		}
	},

	getAnswerEffectOnVar: function(varname, q_or_qid, ans) {
		var res = 0;
		var q;
		if (q_or_qid && q_or_qid.Answer) {
			q = q_or_qid;
		}
		else {
			q = this.getObjByContentId(q_or_qid);
		}
		var scoring = q.Answer[ans].AnswerScoring;
		if (scoring) {
			for (var i = 0; i < scoring.length; i++) {
				if (scoring[i].VarAffected == varname) {
					var e = parseInt(scoring[i].Effect);
					if (! isNaN(e)) {
						res += e;
					}
				}
			}
		}
		return res;
	},

	outputTestXML: function() {
		return this.Test.outputXML();
	},

	outputTestOriginalXML: function() {
		return this.Test.outputOriginalXML();
	},

	outputTestJSON: function() {
		return this.Test.outputJSON();
	},

	getGenericItemPositionById: function(id, list) {
		for (i = 0; i < list.length; i++) {
			if (id == list[i]["@Id"]) {
				return i;
			}
		}
		return -1;
	},

	getContentItemPositionById: function(id) {
		return this.getGenericItemPositionById(id, this.Test.Data.OkTest.Contents.ContentItem);
	},

	getResultItemPositionById: function(id) {
		return this.getGenericItemPositionById(id, this.Test.Data.OkTest.Results.Result);
	},

	getObjByContentId: function(id) {
			return this.getObjByGenericId(id, this.Test.Data.OkTest.Contents.ContentItem);
	},

	getResultById: function(id) {
		return this.getObjByGenericId(id, this.Test.Data.OkTest.Results.Result);
	},

	getObjByGenericId: function(id, list) {
		var res = this.getGenericItemPositionById(id, list);
		if (res == -1) {
			return null;
		}
		else {
			return list[res];
		}
	},

	//
	// For timing total time spent editing the test
	// Needs to be called by most user-run functions,
	// instead of in some kind of interval, for performance reasons.
	noticeTimeSpentEditing : function() {
		var d = new Date();
		var t = this.timeSinceLastAction("edit-event");
		if (t > 1800000) {
			t = 1800000;
		}
		this.Test.Data.OkTest.TimeSpentEditing = parseInt(this.Test.Data.OkTest.TimeSpentEditing) + t;
		this.Test.Data.OkTest.LastEditTime = Math.round(d.getTime()/1000);
		this.recordAction("edit-event");
		this.updateTimeSpentEditing();
		this.updateSaveWork();
		if (this.timeSinceLastAction("autosave-event") > this.AutosaveEvery * 1000) {
			this.autosaveWork();
		}
	},

	//
	// You can make up an action type and use the next to functions
	// to report and see how long it's been since that's been done.
	// Autosaves might be something they're used for.
	//
	timeSinceLastAction: function(actiontext) {
		var d = new Date();
		if	(this.ActionHistory[actiontext]) {
			return (d.getTime() - this.ActionHistory[actiontext]);
		}
		else {
			return d.getTime();
		}
	},

	recordAction: function(actiontext) {
		var d = new Date();
		this.ActionHistory[actiontext] = d.getTime();
	},
	
	noteOpenEditor : function (obj) {
		this.openEditors.push(obj);	
	},
	
	noteCloseEditor : function(obj) {
		var j = Object.toJSON(obj);
		for (var i = 0; i < this.openEditors.length; i++) {
			if (Object.toJSON(this.openEditors[i]) == j) {
				this.openEditors.splice(i, 1);
			}
		}
	},
	
	closeAllEditors : function() {
		while (this.openEditors.length > 0) {
			var obj = this.openEditors[0];
			this.openEditors.splice(0,1);
			if (obj.type == "AnswerText") {
				this.doneEditingAnswer(obj.qid, obj.ans);
			}
			else if (obj.type == "Description") {
				this.doneEditingDescription();
			}
			else if (obj.type == "QuestionText") {
				this.doneEditingQuestion(obj.qid);
			}
			else if (obj.type == "HTMLBlock") {
				this.doneEditingHTMLBlock(obj.hid);
			}
			else if (obj.type == "ResultDescription") {
				this.doneEditingResultDescription(obj.rid);
			}
		}		
	}

});
//----------------------------------------------------------------------------------------------------------------

 	var TestEditor = new OkTestEditor();

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

 
AUTOCORE_SELF_CHECK.pop();
	
// File: /okcontent/js/service/qreview.js
AUTOCORE_SELF_CHECK.push("service/qreview.js");
// qreview.js


function adjustboxesifnecessary() {
	var numControls = document.questionform.length;
	var shouldDisable = false;
    var element = null;

    if ($('irrev').checked) {
    	shouldDisable = true;     
    }  
    
	for (var aa = 0; aa < numControls; aa++) {
    	element = document.questionform[aa];
        if ((element.type == "checkbox") && (element.name == "matchanswers")) {
			if (element.disabled != shouldDisable)
	        	element.disabled = shouldDisable;
            if (shouldDisable) { 
            	element.checked = !shouldDisable;
            }
        }
    }
      	
	setTimeout('adjustboxesifnecessary()',100);

}

//
// For qreview
//
function toggleNav(show_link)
{
     var rx_legend = /Legend/; 
	var li_inner = show_link.innerHTML;
	
	if (li_inner.search(rx_legend) == -1)
		$('question-types').toggle();
	else
		$('question-legend').toggle();

}

function toggleForm() {
	$('extreme_form').toggle();
}
 
AUTOCORE_SELF_CHECK.pop();
	
addNewCoresToCookie(["0", "1", "3", "4", "5", "6", "7", "8", "9", "10", "13", "14", "17", "18", "23", "24", "25", "27", "28", "29", "33", "35", "38", "39", "40", "42", "43", "50", "54", "76"], "fff5892c"); 
