// 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>");
	},
	
	/* 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();	
	},

    // 
    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; }
			}
		);
	},
	
	isTextDirty:function(text)
	{
		var strict = ["bdsm", "blowjob", "bondage", "boobs", "cunnilingus", "fellatio", "footjob", "foreplay", "fuck", "kinky", "masochist", "masturbation", "milf", "nipple", "oral", "penis", "pussiology", "pussy", "sex-toy", "slut", "submissive"],
			loose = ["ass", "clit", "cock", "cum", "cunt", "dick", "lick", "porn", "sex", "shit", "suck", "tits", "voyeur"],
			pattern = "\\b%word\\b",
			bad = false;
			
		for (iter=0;iter<strict.length;++iter)
			if(text.toLowerCase().indexOf(strict[iter]) != -1)
				bad = true;
		
		for (iter=0;iter<loose.length;++iter) {
			check = new RegExp(this.jogf(pattern,{word:loose[iter]}),"i");
			if(text.match(check))
				bad = true;
		}
		
		if(bad) {
			util.updateStats("text was dirty", 1, "counter", "2993sKmO5RUm7OVDFffg2egDYgM=");
			return true;
		}
		
		return false;
	},
	polygonBoundary:function(bounds,point)
	{
		var polyX=bounds[0],
			polyY=bounds[1],
			j=polyX.length-1,
			odd=false,
			x=point[0],
			y=point[1];

		for (i=0;i<polyX.length;i++) {
			if(polyY[i]<y && polyY[j]>=y
			|| polyY[j]<y && polyY[i]>=y) {
				if (polyX[i]+(y-polyY[i])/(polyY[j]-polyY[i])*(polyX[j]-polyX[i])<x) {
					odd=!odd;
				}
			}
			j=i;
		}
		return odd;
	}
};
/*  */


/* 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 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 ($('header_login')) 
			util.fillInTheP();

		util.adjustMCHeight();
	}
);

util.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/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',
	staff:false,
	set:function(n,v,e)
	{
		try {
			validate = Object.toJSON(v);
			validate.evalJSON();
		} catch(e) {
			alert("Not a valid JSON object!");
			return;
		}
		var pass = this;
		
		params = {
			name:n,
			value:Object.toJSON(v)
		}
		if(e)
			params.expire = e;
			
		if(this.staff)
			params.staff = 1;
		
		new Ajax.Request(
			pass.url,
			{
				method:'get',
				parameters:params,
				onSuccess:function() {
					pass.sookie[n] = v;
				}
			}
		);
	}
} 
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/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/quizzy/xml.js
AUTOCORE_SELF_CHECK.push("quizzy/xml.js");
//
//
// Slightly modified (by CRC) code for parsing and generating XML from
// javascript objects
//
//-------------------------------------------------------------------------------------------------------
/*	This work is licensed under Creative Commons GNU LGPL License.
	License: http://creativecommons.org/licenses/LGPL/2.1/
*/
//-------------------------------------------------------------------------------------------------------

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

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


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