// redirect if we're not using a secure connection (in case the user forgot to specify https as the protocol)
// this can also be used to force a particular subdomain, in this case secure

	function require_SSL() {
		//if (document.URL.indexOf("https://secure.") == -1) { 
		//	document.write("<p class=subheading>Redirecting to a secure port...</p>");
		//	location = "https://secure." + document.URL.substr(document.URL.indexOf("contentthatworks.com"), document.URL.length);
		//}
	}


// redirect if we no longer need a secure connection

	function avoid_SSL() {
		//if (document.URL.indexOf("https://") != -1) { 
		//	document.write("<p class=subheading>Redirecting to a standard port...</p>")
		//	// use the same subdomain we're on right now; just change the protocol
		//	location = "http://www." + document.URL.substr(document.URL.indexOf("contentthatworks.com"), document.URL.length);
		//}
	}


// load additional style sheets for some platforms; these will override any attributes already set

	function get_CSS_tweaks() {
		tags = "";
		if ((navigator.appVersion.indexOf("Win") != -1)&&(navigator.appVersion.indexOf("MSIE") != -1)) {
			tags += "<link href=\"../shared/styles_IE_tweaks.css\" rel=\"stylesheet\" type=\"text/css\">\n";
		}
		return tags;
	}


// get a CSS class identifier based on its name

	function get_class(className) {
		for (s=0; s<document.styleSheets.length; s++) {
			if (document.styleSheets[s].rules) {
				for (r=0; r<document.styleSheets[s].rules.length; r++) {
					if (document.styleSheets[s].rules[r].selectorText == "." + className) {
						return document.styleSheets[s].rules[r];
					}
				}
			} else if(document.styleSheets[s].cssRules) {
				for (r=0; r<document.styleSheets[s].cssRules.length; r++) {
					if (document.styleSheets[s].cssRules[r].selectorText == "." + className) {
						return document.styleSheets[s].cssRules[r];
					}
				}
			}
		}
		return null;
	}


// construct Flash object and embed tags
// FlashVars should be a string of name/value pairs separated by ampersands

	function get_flash_tags(filename, width, height, bgcolor, flashvars) {
		name = filename;
		if (name.indexOf("/")) { name = name.substring(name.lastIndexOf("/") + 1); }
		if (name.indexOf(".")) { name = name.substring(0, name.indexOf(".")); }
		
		output = '<OBJECT classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"';
		output += ' codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0"';
		output += ' WIDTH="' + String(width) + '" HEIGHT="' + String(height) + '" id="' + name + '" name="' + name + '" ALIGN="">';
		output += '<PARAM NAME=movie VALUE="' + filename + '"> <PARAM NAME=quality VALUE=high> <PARAM NAME=bgcolor VALUE=' + bgcolor + '> <EMBED src="' + filename + '" quality=high bgcolor=' + bgcolor + ' WIDTH="' + String(width) + '" HEIGHT="' + String(height) + '" NAME="' + name + '" ALIGN=""';
		output += ' TYPE="application/x-shockwave-flash" PLUGINSPAGE="http://www.macromedia.com/go/getflashplayer" swLiveConnect="true"';
		output += ' FlashVars="' + flashvars + '"></EMBED>';
		output += '<PARAM NAME="FlashVars" VALUE="' + flashvars + '">';
		output += '</OBJECT>';
		return output;
	}


// set a cookie
	
	// 02/15/03 - initial development
	// 05/14/04 - expanded to allow saving multiple values within a single cookie

	function set_cookie(name, key, value) {

		// specify a default cookie name prefix for this project, to group cookies together
		name_prefix = "ctw_";
		value_exists = 0;
		
		// the following cookie parameters are optional ... change values to null if you don't need them
		hours = 43800;
		path = "/";						
		domain = null;
		
		cookie_value = get_cookie(name, "all"); // get the entire cookie string so we can update the values
		key_value_array = cookie_value.split("&"); // split the cookie string into an array of key=value strings
		
		// search through the list of key=value strings for the presence of the specified key
		for (key_value=0; key_value<key_value_array.length; key_value++) {
			if (key_value_array[key_value].indexOf(key + "=") == 0) {
			
				// when we find it, overwrite the value of that array element
				key_value_pair = key_value_array[key_value].split("=");
				key_value_key = key_value_pair[0];
				key_value_array[key_value] = key_value_key + "=" + escape(value);
				
				// then combine the key=value strings back into a single string
				cookie_value = key_value_array.join('&');
				value_exists = 1; // flag this key as already being in the cookie string so that you don't add it to the cookie string again
				break;

			}
		}
		
		// if the cookie string doesn't already contain this key, add the key=value string
		if (value_exists == 0) {
			ampersand = (cookie_value != "") ? "&" : "" ;
			cookie_value += ampersand + key + "=" + escape(value);
		}
		
		expiration_date = (hours) ? new Date((new Date()).getTime() + hours*3600000).toGMTString() : false ;
		cookie_string = name_prefix + name + "=" + escape(cookie_value) + ((expiration_date)?(';expires=' +expiration_date):'') + ((path)?(';path='+path):'') + ((domain)?(';domain='+domain):'');
		document.cookie = cookie_string;
	
	}


// get values from a cookie

	// 02/15/03 - initial development
	// 05/14/04 - expanded to allow saving multiple values within a single cookie

	function get_cookie(name, key) {
	
		name_prefix = "ctw_"; // specify a default cookie name prefix for this project, to group cookies together
		output_value = "";
		
		if (document.cookie != "") {
			cookie_array = document.cookie.split("; ");

			// search for the cookie with this name
			for (cookie=0; cookie < cookie_array.length; cookie++) {
				if (cookie_array[cookie].indexOf(name_prefix + name + "=") == 0) {

					cookie_pair = cookie_array[cookie].split("=");
					cookie_value = unescape(cookie_pair[1]); // the value is the list of key=value strings that we saved into this cookie
	
					if (key == "all") {
						output_value = cookie_value;
						break;
					} else {
						key_value_array = cookie_value.split("&");
					
						// search through the list of key=value strings for the value of the requested key
						for (key_value=0; key_value<key_value_array.length; key_value++) {
							if (key_value_array[key_value].indexOf(key + "=") == 0) {
							
								key_value_pair = key_value_array[key_value].split("=");
								output_value = unescape(key_value_pair[1]);
								break;
										
							}	
						}
						
					}
					
				}
			}
			
		}
		
		return output_value;
		
	}
	
	
// provide buttons for saving and loading form values with a cookie
// this is primarily used to streamline development and testing, but it could have real-world applications as well

	// 03/01/05 - initial development
	// 07/15/05 - added length check to checkbox and radio button groups to avoid an error when those groups only contain one element

	function show_form_cookie_buttons(form_name) {
		output = "";
		output += "<a href=\"JavaScript:form_cookie_save('" + form_name + "')\">Save form values</a><br>";
		output += "<a href=\"JavaScript:form_cookie_load('" + form_name + "')\">Load form values</a>";
		return output;
	}

	function form_cookie_save(form_name) {
		last_name = "";
		eval("this_form = document." + form_name);
		for (n=0; n<this_form.length; n++) {
			value = "";
			if (this_form[n].type != "hidden") { // don't save hidden values
				if (this_form[n].name != last_name) { // don't process the checkbox and radio groups more than once
					if ((this_form[n].type == "text")||(this_form[n].type == "textarea")||(this_form[n].type == "password")) {
						value = this_form[n].value;
					} else if (this_form[n].type == "select-one") {
						value = this_form[n].selectedIndex;
					} else if (this_form[n].type == "select-multiple") {
						values = new Array();
						for (nn=0; nn<this_form[n].options.length; nn++) {
							if (this_form[n].options[nn].selected) {
								values[values.length] = nn;
							}
						}
						value = values.join(",");
					} else if (this_form[n].type == "checkbox") {
						eval("this_group = this_form." + this_form[n].name);
						if (this_group.length > 0) {
							values = new Array();
							for (nn=0; nn<this_group.length; nn++) {
								if (this_group[nn].checked) {
									values[values.length] = nn;
								}
							}
							value = values.join(",");
						} else {
							value = "0";
						}
					} else if (this_form[n].type == "radio") {
						eval("this_group = this_form." + this_form[n].name);
						if (this_group.length > 0) {
							for (nn=0; nn<this_group.length; nn++) {
								if (this_group[nn].checked) {
									value = nn;
									break;
								}
							}
						} else {
							value = "0";
						}
					}
					//alert(this_form[n].name + " = " + value);
					set_cookie(form_name, this_form[n].name, value);
					last_name = this_form[n].name;
				}
			}
		}
	}
	
	function form_cookie_load(form_name) {
		last_name = "";
		eval("this_form = document." + form_name);
		for (n=0; n<this_form.length; n++) {
			value = "";
			if (this_form[n].type != "hidden") { // don't load hidden values
				if (this_form[n].name != last_name) { // don't process the checkbox and radio groups more than once
					value = get_cookie(form_name, this_form[n].name, value);
					//alert(this_form[n].name + " = " + value);
					if ((this_form[n].type == "text")||(this_form[n].type == "textarea")||(this_form[n].type == "password")) {
						this_form[n].value = value;
					} else if (this_form[n].type == "select-one") {
						this_form[n].selectedIndex = value;
					} else if (this_form[n].type == "select-multiple") {
						values = value.split(",");
						for (nn=0; nn<values.length; nn++) {
							this_form[n].options[values[nn]].selected = true;
						}
					} else if (this_form[n].type == "checkbox") {
						eval("this_group = this_form." + this_form[n].name);
						if (this_group.length > 0) {
							values = value.split(",");
							for (nn=0; nn<values.length; nn++) {
								this_group[values[nn]].checked = true;
							}
						} else {
							if (value === "0") {
								this_group.checked = true;
							}
						}
					} else if (this_form[n].type == "radio") {
						eval("this_group = this_form." + this_form[n].name);
						if (this_group.length > 0) {
							this_group[value].checked = true;
						} else {
							if (value === "0") {
								this_group.checked = true;
							}
						}
					}
					last_name = this_form[n].name;
				}
			}
		}
	}


// ajax functions
// 2009-01-30 - added support for synchronous connections (by setting asynchronous to false)
// 2009-04-16 - tweaked handling of synchronous connections for Firefox

	function ajax_initialize() {
		ajax = "";
		if (typeof(XMLHttpRequest) != "undefined") {
			ajax = new XMLHttpRequest();
		} else {
			// need to check for additional versions here?
			ajax = new ActiveXObject("Microsoft.XMLHTTP");
		}
		return ajax;
	}
	
	function ajax_request(URL, success_code, failure_code, method, mode, data, wait) {
		// method: GET or POST
		// mode: text or XML
		// data: query string
		// wait: 1=asynchronous, 0=synchronous
	
		// in IE, we have to initialize before every request or subsequent request don't call the monitor
		ajax_initialize();
		
		// set some defaults
		if (!failure_code) { failure_code = "alert('There was a problem with the AJAX request.');"; }
		if (!method) { method = "GET"; }
		if (!mode) { mode = "text"; }
		asynchronous = (wait) ? false : true ;
		
		// if we're using the GET method but passing in data, add it to the URL
		if ((method == "GET")&&(data)) {
			delimiter = (URL.indexOf("?") != -1) ? "&" : "?" ;
			URL += delimiter + data;
		}
		
		// pass this function's arguments through so the monitor function can see them
		success_code_passthrough = success_code;
		failure_code_passthrough = failure_code;
		mode_passthrough = mode;
		
		// perform the request
		if (asynchronous) { // listen for a response action
			ajax.onreadystatechange = ajax_monitor;
		}
		ajax.open(method, URL, asynchronous);
		if (method == "POST") {
			ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        	ajax.send(data);
		} else {
        	ajax.send(null);
        }
		if (!asynchronous) { // just run the response action now; Firefox doesn't change the ready state
			ajax_monitor();
		}
	}
	
	function ajax_monitor() {
		if (ajax.readyState == 4) {
			if (ajax.getResponseHeader("Ajax-Redirect")) {
				top.location = "index.php?page=" + ajax.getResponseHeader("Ajax-Redirect");
			} else {
				if (ajax.status == 200) {
					if (mode_passthrough == "XML") {
						ajax_response = ajax.responseXML;
					} else {
						ajax_response = ajax.responseText;
					}
					eval(success_code_passthrough);
				} else {
					ajax_response = ajax.statusText;
					eval(failure_code_passthrough);
				}
			}
		}
	}
	
	function ajax_serialize(input, encode) {
		// convert an object into a string of name/value pairs for including in an HTTP request
		// we need to encode POST values, but not GET values
		output = new Array();
		for (key in input) {
			value = input[key];
			if (encode) {
				value = encodeURIComponent(value);
			}
			output[output.length] = key + "=" + value;
		}
		output = output.join("&");
		return output;
	}
	
	function ajax_unserialize(input, decode) {
		// convert a string of name/value pairs into an object for retrieving from an HTTP response
		input = input.split("&");
		output = new Object();
		for (n=0; n < input.length; n++) {
			pair = input[n].split("=");
			key = pair[0];
			value = pair[1];
			if (decode) {
				value = decodeURIComponent(value);
			}
			output[key] = value;
		}
		return output;
	}
	

// load a related menu when the publication menu changes

	function load_publication_submenu(field, publication, medium, multiple, selected, required, wait) {
		request_URL = "/shared/load_publication_submenu.php?field=" + field;
		request_URL += "&publication=" + publication;
		request_URL += "&medium=" + medium;
		request_URL += "&multiple=" + multiple;
		request_URL += "&selected=" + selected;
		success_code = "display_publication_submenu('" + field + "', '" + required + "', ajax_response)";
		ajax_request(request_URL, success_code, "", "GET", "text", "", wait);
	}

	function display_publication_submenu(field, required, ajax_response) {
		document.getElementById(field + "_menu").innerHTML = ajax_response;
		
		required = (parseInt(required));
		if ((required)&&(isset("validate_settings"))) {
			menu_displayed = (ajax_response.indexOf("disabled") == -1);
			menu_validated = get_position(field, validate_settings);
			if ((menu_displayed)&&(menu_validated == -1)) {
				// if the menu is not disabled, and it's not already there, add it to the validation array
				validate_settings.splice(0, 0, field, ucfirst(field), "not_blank_menu");
			} else if ((!menu_displayed)&&(menu_validated != -1)) {
				// if the menu is disabled, and it is already there, remove it from the validation array
				validate_settings.splice(menu_validated, 3);
			}
		}
	}
	

// grab all name/value pairs from the query string or a provided string and create JavaScript variables from them

	function get_vars(string) {
	
		if (!string) {
			string = location.search;
		}
		
		pairs = string.split("&");
		
		for (n=0; n < pairs.length; n++) {
			pair = pairs[n].split("=");
			key = pair[0];
			value = pair[1];
			eval(key + " = '" + value + "'");
		}

	}
	

// trim whitespace from before and after a string

	function trim(string) {
		string = String(string);
		//string = string.replace(/^\s*(.*?)\s*$/, "$1"); // this single line should work instead of the following two lines, but the ? to make the middle part less greedy throws an error in Mac IE, where that modifier is apparently not supported
		string = string.replace(/^\s*/, "");
		string = string.replace(/\s*$/, "");
		return string;
	}
	
	
// return a value up to the delimiter, or the original value if the delimiter isn't there
// - this is used most often when parsing an argstring to set button states

	function crop(value, delimiter) {
		result = (value.indexOf(delimiter) != -1) ? value.substring(0, value.indexOf(delimiter)) : value ;
		return result;
	}
	

// open a popup window, specifying source, width, and height, and optionally a name; if no name, choose a random one

	function popup(source, width, height, window_name) {
		if (! window_name) { 
			now = new Date();
			window_name = now.getTime();
		} else {
			window_name = window_name.replace(/ /g, "_");
		}
		popup_window = window.open(source, window_name, "width=" + String(width) + ",height=" + String(height) + ",location=no,menubar=no,directories=no,toolbar=no,scrollbars=yes,resizable=yes,status=yes");
		popup_window.focus();
	}


// show an email link on a page in a way that spam harvesters can't see

	function show_email(user, domain, tld) {
		document.write("<a href='mailto:" + user + "@" + domain + "." + tld + "'>" + user + "@" + domain + "." + tld + "<\/a>");
	}


// write a random element from the specified array

	function write_random(array_name) {
		which = (Math.round(Math.random() * (arrayName.length - 1)));
		document.write(array_name[which]);
	}


// the simplest possible rollover function

	function swap(name, state) {
		eval('document.images.' + name + '.src = ' + name + '_' + String(state) + '.src');
	}
	

// write a block of code that preloads a list of graphics
// - this version makes on and off states and is most often used for button rollovers

// names - a comma-delimited list of button names (e.g. "home,about,contact")
// path - the path to the graphics; defaults to "../graphics" if not set (e.g. "../graphics/menus/home")
// extension - the file extension of the graphics files; defaults to "gif" if not set (e.g. "jpg")

	function preload_buttons(names, path, extension) {
		names = names.split(",");
		path = (path) ? path : "../graphics" ;
		extension = (extension) ? extension : "gif" ;
		for (n=0; n < names.length; n++) {
			this_name = names[n];
			this_path = path + "/" + this_name;
			eval(this_name + "_0 = new Image()");
			eval(this_name + "_0.src = '" + this_path + "_0." + extension + "'");
			eval(this_name + "_1 = new Image()");
			eval(this_name + "_1.src = '" + this_path + "_1." + extension + "'");
		}
	}
	

// write a block of code that preloads a list of graphics
// - this version just makes an on state and is most often used for tips associated with button rollovers

// names - a comma-delimited list of button names (e.g. "home,about,contact")
// path - the path to the graphics; defaults to "../graphics" if not set (e.g. "../graphics/menus/home")
// extension - the file extension of the graphics files; defaults to "gif" if not set (e.g. "jpg")

	function preload_tips(names, path, extension) {
		names = names.split(",");
		path = (path) ? path : "../graphics" ;
		extension = (extension) ? extension : "gif" ;
		for (n=0; n < names.length; n++) {
			this_name = names[n];
			this_path = path + "/" + this_name;
			eval(this_name + " = new Image()");
			eval(this_name + ".src = '" + this_path + "." + extension + "'");
		}
	}
	

// write a block of code that preloads a list of graphics
// - this version uses the same source file for each instance and is most often used for markers associated with button rollovers

// names - a comma-delimited list of instance names (e.g. "home_marker,about_marker,contact_marker")
// marker_name - the base name of the marker graphic
// path - the path to the graphics; defaults to "../graphics" if not set (e.g. "../graphics/menus/home")
// extension - the file extension of the graphics files; defaults to "gif" if not set (e.g. "jpg")

	function preload_markers(names, marker_name, path, extension) {
		names = names.split(",");
		path = (path) ? path : "../graphics" ;
		extension = (extension) ? extension : "gif" ;
		for (n=0; n < names.length; n++) {
			this_name = names[n];
			this_path = path + "/" + marker_name;
			eval(this_name + "_0 = new Image()");
			eval(this_name + "_0.src = '" + this_path + "_0." + extension + "'");
			eval(this_name + "_1 = new Image()");
			eval(this_name + "_1.src = '" + this_path + "_1." + extension + "'");
		}
	}
	

// leave this here to catch calls to the original preload script

	function make_preloads(names, path) {
		preload_buttons(names, path);
	}
	

// figure out whether a variable has been set or not without generating an undefined error if it hasn't

	function isset(variable) {
		eval("result = (typeof(" + variable + ") != 'undefined')");
		return result;
	}
	

// capitalize the first letter of a string, and make the others lowercase

	function ucfirst(str) {
		str = str.charAt(0).toUpperCase() + str.substr(1).toLowerCase();
		return str;
	}
	

// returns the index of an array element, something that ought to be built into JavaScript but isn't!
// returns -1 if not present

	function get_position(string, array) {
		for (n=0; n < array.length; n++) {
			if (array[n] == string) {
				return n;
				break;
			}
		}
		return -1;
	}


// sets a menu to a given text or value

	function set_menu(form_name, field_name, text_or_value, key) {
		eval("these_options = document." + form_name + "." + field_name + ".options");
		for (n=0; n < these_options.length; n++) {
			eval("this_text_or_value = these_options[n]." + text_or_value);
			if (this_text_or_value == key) {
				eval("document." + form_name + "." + field_name + ".selectedIndex = " + n);
				break;
			}
		}
		return "";
	}


// set date menus to a new SQL-standard date
// leave date blank to select today's date
// set to -1 to clear the menu

	function select_date(form_and_menu, new_date) {
		if (new_date != "-1") {
			if (new_date == "") {
				date = new Date();
				
				day = date.getDate();
				month = date.getMonth() + 1;
				year = date.getFullYear();
				
			} else {
				date = new_date;
				
				year = date.substring(0, date.indexOf("-"));
				month = date.substring(date.indexOf("-") + 1, date.lastIndexOf("-"));
				day = date.substring(date.lastIndexOf("-") + 1);
				
			}
					
			eval("document." + form_and_menu + "_month.selectedIndex = month");
			eval("document." + form_and_menu + "_day.selectedIndex = day");
			eval("document." + form_and_menu + "_year.selectedIndex = year - document." + form_and_menu + "_year.options[1].value + 1");
		} else {
			eval("document." + form_and_menu + "_month.selectedIndex = 0");
			eval("document." + form_and_menu + "_day.selectedIndex = 0");
			eval("document." + form_and_menu + "_year.selectedIndex = 0");
		}
	}


// set time menus to a new SQL-standard time
// leave time blank to select the current time

	function select_time(form_and_menu, new_time) {
		if (new_time != "-1") {
			if (new_time == "") {
				time = new Date();
				
				hours = time.getHours();
				minutes = time.getMinutes();
				seconds = time.getSeconds();
				
			} else {
				time = new_time;
				
				hours = time.substring(0, time.indexOf(":"));
				minutes = time.substring(time.indexOf(":") + 1, time.lastIndexOf(":"));
				seconds = time.substring(time.lastIndexOf(":") + 1);
				
			}
					
			if (hours >= 12) {
				ampm_index = 2;
				if (hours > 12) {
					hours = hours - 12;
				}
			} else {
				ampm_index = 1;
				if (hours == 0) {
					hours = 12;
				}
			}

			eval("document." + form_and_menu + "_hours.selectedIndex = hours");
			eval("document." + form_and_menu + "_minutes.selectedIndex = minutes + 1");
			eval("document." + form_and_menu + "_seconds.selectedIndex = seconds + 1");
			eval("document." + form_and_menu + "_ampm.selectedIndex = " + ampm_index);
		}
	}


// loop through a set of checkboxes and build a comma-delimited list of selected values

	function get_checkbox(form_name, field_name) {
		eval("these_elements = document." + form_name + ".elements['" + field_name + "']");
		values = new Array();
		if (these_elements.length > 0) {
			for (var i=0; i<these_elements.length; i++) {
				if (these_elements[i].checked) {
					values[values.length] = these_elements[i].value;
				}
			}
		} else {
			if (these_elements.checked) {
				values[values.length] = these_elements.value;
			}
		}
		values = values.join(",");
		return values;
	}


// loop through a set of radio buttons and find the selected value

	function get_radio(form_name, field_name) {
		eval("these_elements = document." + form_name + ".elements['" + field_name + "']");
		value = "";
		if (these_elements.length > 0) {
			for (var i=0; i<these_elements.length; i++) {
				if (these_elements[i].checked) {
					value = these_elements[i].value;
					break;
				}
			}
		} else {
			if (these_elements.checked) {
				value = these_elements.value;
			}
		}
		return value;
	}


// get the selected text or value for a menu
// selectedIndex doesn't access all selected options in a multiple-select menu, so those aren't supported yet

	function get_menu(form_name, field_name, text_or_value) {
		option_selected = document.forms[form_name].elements[field_name].options[document.forms[form_name].elements[field_name].selectedIndex];
		value = (text_or_value == "text") ? option_selected.text : option_selected.value ;
		return value;
	}


// jumble up some text for safer transfer in places where cookies or PHP encryption can't go

	// 08-27-04 - we're no longer removing and restoring spaces; that should be the job of the code before and after calling this function, since it is not always desirable

	function pseudo_crypt(input, direction, key) { 
		output = "";
		palette = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+&=:/"; // any other characters will be untouched
		key = "everygoodboydeservesfudge"; // can only contain members of palette
		key_position = 0;
		for (n=0; n < input.length; n++) {
			input_char = palette.indexOf(input.charAt(n));
			if (input_char != -1) {
				key_char = key.charAt(key_position);
				offset = palette.indexOf(key_char);
				if (direction == 1) {
					offset = ((input_char + offset) > (palette.length - 1)) ? offset - palette.length : offset ;
					output += palette.charAt(input_char + offset);
				} else {
					offset = ((input_char - offset) < 0) ? offset - palette.length : offset ;
					output += palette.charAt(input_char - offset);
				}
				key_position++;
				key_position = (key_position > key.length) ? 0 : key_position ;
			} else {
				output += input.charAt(n);
			}
		}
		return output;
	}
	
