<!--
// PLEASE NOTE that this version is more recent than the incorrectly
// numbered v6.1, dated 2003.11.17. From now on, version numbers will
// follow those of Hot Potatoes.
/* hot-potatoes.js (v6.0.4.0 - 2005.02.18)
 * =======================================
 * by Gordon Bateson, February 2003
 * Copyright (c) 2003 Gordon Bateson. All Rights Reserved.
 *
 * You are hereby granted a royalty free license to use or modify this
 * software provided that this copyright notice appears on all copies.
 *
 * This software is provided "AS IS" without a warranty of any kind.
 *
 * Documentation and downloads may be available from:
 * http://www.kanazawa-gu.ac.jp/~gordon/research/hot-potatoes/
 */
// This JavaScript library modifies the SendResults and StartUp functions
// used by hotpot v5 and v6, so that more (or less!) details about the
// student can be input, and more details of a quiz's questions and answers
// can be submitted to the server when the quiz is finished
// If the arrays below (Login, DB, JBC, ...) are set BEFORE calling this
// script, they will NOT be overwritten. Any array that is not set, will
// use the defaults below. This is useful if you want to use different
// settings for different quizzes.
// ************
//  Login Screen
// ************
if (window.Login==null) {
	Login = new Array();
	Login[0] = true;	// Show prompt for user name
				// This can also be a string of user names ...
				// Login[0] = "Guest,Peter,Paul,Mary,Webmaster";
				// or an array of user names (and on-screen texts) (and passwords) ...
				// Login[0] = new Array("Guest", "001,Peter,xxxx", "002,Paul,yyyy", "003,Mary,zzzz", "Webmaster");
				// and can also be  written as ...
				// Login[0] = new Array(
				//	new Array("Guest"),
				//	new Array("001", "Peter", "xxxx"),
				//	new Array("002", "Paul", "yyyy"),
				//	new Array("003", "Mary", "zzzz"),
				//	new Array("Webmaster")
				// );
	Login[1] = true;	// Show prompt for student's UserID
				// If there is no password prompt (i.e. Logon[3] is false), this value
				// will be checked against the password information, if any, in Login[0]
	Login[2] = false;	// Show prompt for student's email
	Login[3] = false;	// Show prompt for quiz password, and check this value against
				// the password information, if any, in Login[0]
				// This can also be a string required to start the quiz ...
				// Login[3] = "password";
	Login[4] = true;	// Show prompt for the cookie expiry date
				// If false, cookies expire at the end of the current session
	Login[5] = "guest,webmaster"
				// guest user names (case insensitive) ...
				// Login[5] = "guest,webmaster";
				// These users do NOT need to fill in other login fields
				// and their quiz results are NOT added to the database
	// the Login prompts and error messages
	// are defined in the MSG array (see below)
}
// *********
//  Database (for use with BFormMail)
// *********
if (window.DB==null) {
	DB = new Array();
	DB[0] = true; // append form fields to database on server
			// If you are NOT using BFormMail's database feature,
			// set DB[0]=false, and you can then safely ignore DB[1 to 5]
	DB[1] = "/home/gordon/public_html/cgi/hot-potatoes-data";
			// append_db folder path (no trailing slash)
			// Can be either an absolute path  e.g. "/home/gordon/public_html/cgi/hot-potatoes-data"
			// or a relative (to CGI bin) path  e.g. "hot-potatoes-data"
	DB[2] = "hot-potatoes";
			// append_db file name (no extension)
			// If left blank, the quiz file name, without extension, will be used
			// i.e. each quiz will have its results stored in a different file.
			// If filled in, this file will store the results for ALL quizzes.
			// Database files and folders must be set up BEFORE running the quiz
			// must have appropriate access privileges (on Unix, use "chmod 666").
	DB[3] = ""; // append_db extension (if left blank, ".txt" will be used)
	DB[4] = ""; // db_fields (if left blank, ALL quiz fields will be sent)
	DB[5] = ""; // db_delimiter (if left blank, tab will be used)
	DB[6] = "REMOTE_ADDR,HTTP_USER_AGENT";
			// env_report ('REMOTE_ADDR','HTTP_USER_AGENT' and a few others)
	// for a complete description of these fields are, see ...
	// http://www.infosheet.com/stuff/BFormMail.readme
	// Switches DB[7] and DB[8] force the settings in the ResultForm
	// In v5 and v6 quizzes, these settings wil be override those in the original quiz
	// If the quiz results are to be sent to an LMS (via the "store" form)
	// then switches DB[7] and DB[8] are not used
	DB[7] = '';	// URL of form processing script
			// e.g. http://www.kanazawa-gu.ac.jp/~gordon/cgi/bformmail.cgi
	DB[8] = '';	// email address to which results should be sent
			// e.g. gordon@kanazawa-gu.ac.jp
}
// By default the quiz's question's scores will be returned.
// If you want more detailed information, set the flags below:
// ********
//  JBC
// ********
if (window.JBC==null) {
	JBC = new Array();
	JBC[0] = true;	// show separator line between answers on email
	JBC[1] = true;	// show number of attempts to answer question
	JBC[2] = true;	// show question texts
	JBC[3] = true;	// show right answer(s)
	JBC[4] = true;	// show wrong answer(s)
	JBC[5] = true;	// show ignored answer(s)
	JBC[6] = false;	// show answer as text (false) or number (true)
}
// JBC quizzes use the global variables 'I' and 'Status'
// I : an array of JBC_QUESTIONs (one for each question)
// JBC_QUESTION :
//	[0] : question text
//	[1] : array of JBC_ANSWERs (one for each answer)
//	[2] : single/multi flag
//		0 : single answer (using 'button')
//		1 : multiple answers (using 'checkbox')
// JBC_ANSWER :
//	[0] : answer text
//	[1] : answer feedback
//	[2] : correct answer flag
//		0 : this is NOT the correct answer
//		1 : this is the correct answer
// Status : an array of JBC_QUESTION_STATUSes
// JBC_QUESTION_STATUS:
//	[0] : correctly answered yet flag
//		0 : this question has NOT been correctly answered
//		1 : this question has been correctly answered
//	[1] : array of JBC_ANSWER_STATUSes (one for each answer)
//		'0' : initial value
//		'R' : single answer question was answered 'R'ight
//		'W' : single answer question was answered 'W'rong
//		'C' : multiple answer question's checkbox was 'C'hecked
//		'U' : multiple answer question's checkbox was 'U'nchecked
//	[2] : number of times this question has been wrongly answered
//	[3] : score (out of 1) for this question (maybe undefined on HP<5.5)
//		0 : not correct yet
//		0<[3]<1 : correct but only after [2] wrong attempts
//		1 : correct first time (bravo!)
//	N.B. score = (numberOfAnswers - numberofWrongTries) / numberOfAnswers
// ********
//  JCloze
// ********
if (window.JCloze==null) {
	JCloze = new Array();
	JCloze[0] = true;	// show separator line between answers on email
	JCloze[1] = true;	// show student's correct answer
	JCloze[2] = true;	// show other correct answer(s), if any
	JCloze[3] = true;	// show wrong answer(s), if any (NOT available for v5)
	JCloze[4] = false;	// show number of hints + checks (legacy field, replaced by [7]+[9])
	JCloze[5] = false;	// show if clue was asked for or not (legacy field, replaced by [8])
	JCloze[6] = true;	// show clue
	JCloze[7] = true;	// show number of hints (=next letter requests)
	JCloze[8] = true;	// show number of clues
	JCloze[9] = true;	// show number of checks
}
// JCloze quizzes use the global variables 'I' and 'State'
// I : array of JCLOZE_ANSWERs
// JCLOZE_ANSWER :
//	[0] : (unused)
//	[1] : array of JCLOZE_ANSWER_TEXTs
//	[2] : clue for this answer
// JCLOZE_ANSWER_TEXT :
//	[0] : array (seems unnecessary, just the text would be enough?)
//		[0] : text of possible answer
// State : array of JCLOZE_ANSWER_STATEs
// JCLOZE_ANSWER_STATE (v5) :
//	[0] : clue asked for or not
//	[1] : number of hints (show next letter) and penalties ('check' an incorrect answer)
//	[2] : length of answer matched
//	[3] : score for this item
//	[4] : already answered correctly
//	[5] : answer entered in text box (right or not)
// JCLOZE_ANSWER_STATE (v6)
//	this.ClueGiven = false;
//	this.HintsAndChecks = 0;
//	this.MatchedAnswerLength = 0;
//	this.ItemScore = 0;
//	this.AnsweredCorrectly = false;
//	this.Guesses = new Array(); last guess is correct answer
// ********
//  JCross
// ********
if (window.JCross==null) {
	JCross = new Array();
	JCross[0] = true;	// show separator line between answers on email
	JCross[1] = true;	// show number of penalties (hints or checks before complete)
	JCross[2] = true;	// show number of letters
	JCross[3] = true;	// show correct answers
	JCross[4] = true;	// show clues
	JCross[5] = true;	// show wrong answers
	JCross[6] = true;	// show if clue was asked for or not
	JCross[7] = true;	// show number of hints (=next letter requests)
	JCross[8] = true;	// show number of checks
	// there are no "ignored" answers for JCross quizzes
}
// JCross quizzes use the following global variables:
// 	L : letters (of correct answers)
// 	C : clue numbers (CL in v6)
// 	G : guesses
// 'L', 'C' ('CL') and 'G' are all 2-dimensional arrays (rows x cols)
//
// v5 quizzes additionally use the following single-dimension arrays
// 	A : clues for across (horizontal) words
// 	D : clues for down (vertical) words
// N.B. form is only sent when all answers are correct so
// you can't find out what 'wrong' answers were entered
// ********
//  JMatch
// ********
if (window.JMatch==null) {
	JMatch = new Array();
	JMatch[0] = true;	// show separator line between answers on email
	JMatch[1] = false;	// show number of penalties (= total number of checks)
	JMatch[2] = true;	// show LHS texts (the question)
	JMatch[3] = true;	// show correct answers
	JMatch[4] = true;	// show wrong answers
	JMatch[5] = true;	// show checks (per match) [empty or unchanged RHS are not counted]
	// JMatch has no "clue" or "hint" buttons
	// there cannot be any "ignored" answers
}
// v5 JMatch quizzes use the global variables 'I' and 'Status' (and 'RItems')
// v6 JMatch quizzes use only 'Status'
// v6+ JMatch quizzes use 'F' and 'D' (see below)
// I : an array of JMATCH_PAIRs (one for each pair)
// JMATCH_PAIR :
//	[0] : LHS text
//	[1] : RHS text
//	[2] : fixed (=not jumbled) flag
//		0 : not fixed
//		1 : fixed
//	[3] : index in drop down list selection
// Status : an array of JMATCH_PAIR_STATUSes
// JMATCH_PAIR_STATUS:
//	[0] : correctly matched yet flag
//		0 : this pair has NOT been correctly matched
//		1 : this pair has been correctly matched
//	[1] : number of times this item has been wrongly matched
// 	v6 quizzes only
//	[2] : id of original SELECT element containing possible matches
//	Note that after matching, this SELECT is removed, so don't try looking for it :-)
// v6+ JMatch quizzes use the global variables 'F' and 'D'
// F : array of JMATCH_FIXED_ITEMs
// JMATCH_FIXED_ITEM:
//	[0] : text
//	[1] : tag
// D : array of JMATCH_DRAGGABLE_ITEMs
// JMATCH_DRAGGABLE_ITEM
//	[0] : text
//	[1] : tag of the F item to which it SHOULD be dragged
//	[2] : tag of the F item to which it was dragged (initally 0)
// N.B. form is only sent when all answers are correct so
// you can't find out what 'wrong' answers were entered
// ********
//  JMix
// ********
if (window.JMix==null) {
	JMix = new Array();
	JMix[0] = true;		// show separator line between answers on email
	JMix[1] = false;	// show number of wrong guesses (replaced by JMix[5])
	JMix[2] = true;		// show right answer
	JMix[3] = true;		// show wrong answer, if any
	JMix[4] = false;	// show answer as text (false) or number (true)
	JMix[5] = true;		// show number of checks
	JMix[6] = true;		// show number of hints (=show next word)
}
// JMix quizzes use the global variables
// 'Segments', 'GuessSequence' and 'Penalties'
// Segments : array of JMix_QUESTIONs
// JMix_QUESTION:
//	[0] : text
//	[1] : order in sequence
//	[2] : used flag
// GuessSequence : array of 'order in sequence' numbers
// Penalties : number of incorrect guesses
// ********
//  JQuiz
// ********
if (window.JQuiz==null) {
	JQuiz = new Array();
	JQuiz[0] = true;	// show separator line between answers on email
	JQuiz[1] = true;	// show question text
	JQuiz[2] = true;	// show student's correct answer(s)
	JQuiz[3] = false;	// show wrong and ignored answer(s) (legacy field superceded by [8] & [9])
	JQuiz[4] = true;	// show number of hints requested
	JQuiz[5] = false;	// show number of checks of incorrect answers (legacy field superceded by [12])
	// HP6 v6 quizzes only
	JQuiz[6] = false;	// show answer value (false) or A,B,C... index (true)
	JQuiz[7] = false;	// show all students answers
	JQuiz[8] = true;	// show student's wrong answers
	JQuiz[9] = true;	// show ignored answers (not relevant for multi-select questions)
	JQuiz[10] = true;	// show score weightings
	JQuiz[11] = true;	// show question type
	JQuiz[12] = true;	// show number of checks (if true, then JQuiz[5] of will be ignored)
	JQuiz[13] = true;	// show number of times ShowAnswer button was pressed (usually 0 or 1)
}
// v5 JQuiz quizzes use the global variables 'I' and 'Status'
// I : array of JQUIZ_ANSWERs
// JQUIZ_ANSWER :
//	[0] : question text
//	[1] : array of JQUIZ_ANSWER_TEXTs (one for each answer)
// JQUIZ_ANSWER_TEXT :
//	[0] : array (seems unnecessary, just the text would be enough?)
//		[0] : text of possible answer
// Status : array of JQUIZ_ANSWER_STATEs
// JQUIZ_ANSWER_STATE :
//	[0] : question done or not
//	[1] : number of wrong checks
//	[2] : number of hints asked for
//	[3] : student's answer
//	[4] : score for this question
// v6 JQuiz quizzes use the global variables 'I' and 'State'
// I : array of JQUIZ_QUESTIONs
// JQUIZ_QUESTION :
//	[0] : weighting
//	[1] : ?? (always set to '')
//	[2] : question type
//		'0'=multiple-choice, '1'=short-answer, '2'=hybrid, '3'=multi-select
//	[3] : array of JQUIZ_ANSWERSs (one for each possible answer)
// JQUIZ_ANSWER :
//	[0] : answer value
//	[1] : feedback text
//	[2] : correct answer flag (1=a correct answer, 0=a wrong answer)
//	[3] : weighted score (as percentage) if correct
//	[4] : flag (usually set to 1, but for hybrid answers that are not
//		to be included in multiple choice options, it is set to 0)
// State : array of JQUIZ_QUESTION_STATEs
// JQUIZ_QUESTION_STATE :
//	[0] : score (-1 shows not done yet)
//	[1] : array showing on which number try each JQUIZ_ANSWER was selected
//	[2] : number of attempts at this question
//	[3] : total of weighted scores of correct answers that were selected
//		i.e. each time a correct answer is selected,
//		its JQUIZ_ANSWER[3] weighting is added to this total
//		so when all the correct answers have been selected, this will be 100
//	[4] : penalties incurred for hints (score is set to zero if >= 1)
//	[5] : 	- for multiple choice, short-answer and hybrid questions, this is a
//		comma-delimited list showing order in which answers were chosen
//		- for multi-select fields, this is bar-delimted ('|') list of settings
//		showing whether each checkbox was selected ('Y') on not ('N') when the
//		'Check' button was clicked. The final item in the list will be the
//		settings for the correct answer.
// N.B. JBC, JMatch(v5) and JQuiz(v5) all use global variables 'I' and 'Status'
//	JBC : I[0].length==3 && !window.RItems
// 	JQuiz(v5) : I[0].length==2
//	JMatch(v5) : I[0].length==4 && window.RItems
// N.B. JCloze(v5+6) and JQuiz(v6) both use global variables 'I' and 'State'
//	JCloze (v5) : I[0].length==3 && State[0].Guesses==null
//	JCloze (v6) : I[0].length==3 && State[0].Guesses!=null
// 	JQuiz  (v6) : I[0].length==4
// **********
//  Rhubarb
// **********
if (window.Rhubarb==null) {
	Rhubarb = new Array();
	Rhubarb[0] = true;  // show correct words (so far)
	Rhubarb[1] = true;  // show correct words as count (true) or list (false)
	Rhubarb[2] = true;  // show wrong words
	Rhubarb[3] = false; // show wrong words as count (true) or list (false)
	Rhubarb[4] = false; // show ignored words (not implemented yet)
	Rhubarb[5] = true;  // show hints
}
// **********
//  Sequitur
// **********
if (window.Sequitur==null) {
	Sequitur = new Array();
	Sequitur[0] = true;  // show count of correct button clicks
	Sequitur[1] = true;  // show count of wrong button clicks
}
// **********
//  Messages
// **********
if (window.MSG==null) {
	MSG = new Array();
	// Login prompts
	MSG[0] = 'Name';
	MSG[1] = 'ID';
	MSG[2] = 'Email';
	MSG[3] = 'Password';
	MSG[4] = 'Cookies';
	// Login buttons
	MSG[5] = 'Start the Quiz';
	MSG[6] = 'Cancel';
	// Cookie menu options (only used if Login[4] is true)
	MSG[7] = 'keep for this session only';
	MSG[8] = 'keep for one day';
	MSG[9] = 'keep for one month';
	MSG[10] = 'do NOT keep cookies';
	// Login error messages
	MSG[11] = 'Sorry, you were unable to login. Please try again later.';
	MSG[12] = 'Please fill in all the information.';
	MSG[13] = 'Incorrect Password. Please try again.';
	MSG[14] = 'Incorrect ID. Please try again.';
	MSG[15] = 'Email address does not appear to be valid.';
	// day and month names (used in Start_Time and End_Time)
	MSG[16] = new Array('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
	MSG[17] = new Array('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
	// enable popups
	MSG[18] = 'Please enable pop-up windows on your browser.';
	// browser specific instuctions on how to enable popup windows
	var n = navigator;
	var s = n.userAgent.toLowerCase();
	if (n.appName=='Netscape' && s.indexOf('gecko')>=0) {
		// Netscape 6 and 7
		MSG[18] += '\n\n' + 'Edit->Preferences, ' + (s.indexOf('mac')>=0 ? 'Advanced->Scripts & Plugins' : 'Privacy & Security->Popup Window Controls');
	} else if (s.indexOf('safari')>=0) {
		// Safari
		MSG[18] += '\n\n' + 'on Safari menu, uncheck "Block Pop-Up Windows"';
	} else if (s.indexOf('firebird')>=0) {
		// Firebird
		MSG[18] += '\n\n' + 'Preferences->Web Features, uncheck "Block Pop-Up Windows"';
	} else if (s.indexOf('msie 6')>=0) {
		// IE 6 (WinXP.SP2)
		MSG[18] += '\n\n' + 'Tools->Pop-up Blocker->Turn Off Pop-up Blocker';
	}
}
//if (window.FEEDBACK==null) {
//	FEEDBACK = new Array();
//	FEEDBACK[0] = ''; // url of feedback page/script
//	FEEDBACK[1] = ''; // array of array('teachername', 'value');
//	FEEDBACK[2] = ''; // 'student name' [formmail only]
//	FEEDBACK[3] = ''; // 'email@somewhere.com>' [formmail only]
//	FEEDBACK[4] = ''; // window width
//	FEEDBACK[5] = ''; // window height
//	FEEDBACK[6] = ''; // 'Send a message to teacher' [prompt/button text]
//	FEEDBACK[7] = ''; // 'Title'
//	FEEDBACK[8] = ''; // 'Teacher'
//	FEEDBACK[8] = ''; // 'Message'
//	FEEDBACK[10] = ''; // 'Close this window' [formmail only]
//}
// **********
//  HP array
// **********
HP = new Array();
for (var i=0; i<=8; i++) {
	HP[i] = new Array();
}
// indexes for the HP array (makes the code further down easier to read)
_score   = 0;
_weight  = 1;
_correct = 2;
_wrong   = 3;
_unused  = 4;
_hints   = 5;
_clues   = 6;
_checks  = 7;
_guesses = 8;
// *************
//  Server Fields
// *************
if (window.ServerFields==null) {
	ServerFields = new Array();
	// these fields will be added to the ResultForm and submitted to the CGI script on the server.
	// 'Sort', 'return_link_title', 'return_link_url' and 'print_blank_fields' are useful for formmail
	// override the HP setting of sort fields (forces ALL fields to be displayed)
	ServerFields[0] = new Array('sort', '');
	// add link to close pop-up results window
	ServerFields[1] = new Array('return_link_title', 'Close this window');
	ServerFields[2] = new Array('return_link_url', 'javascript:self.close()');
	// make sure zero values are printed
	ServerFields[3] = new Array('print_blank_fields', 'yes');
	// you can also set other fields for your customized CGI script
	// e.g. adding a server defined start time (instead of a client defined start time)
	// ServerFields[4] = new Array('serverStartTime', '<?php echo date("Y-m-d H:i:s") ?>');
}
// *********************
//      Login screen
//  (not required by LMS)
// *********************
function QuizLogin(LoginPrompt) {
	if (!is_LMS() && (Login[0] || Login[1] || Login[2] || Login[3])) {
		var html = ''
			+ '<html>'
			+ '<head></head>'
			+ '<body bgColor="#cccccc" onLoad="opener.setFocus(self)">'
			+ '<form onSubmit="'
			+ 	'self.ok=true;'
			+ 	'self.expiry=null;'
		;
		if (Login[4]) { // cookie expiry
			html += "opener.checkOK(self,'CookieExpiry');";
		}
		if (Login[0]) { // user name
			html += "opener.checkOK(self,'UserName');";
		}
		if (Login[1]) { // user ID
			html += "opener.checkOK(self,'UserID');";
		}
		if (Login[2]) { // user email
			html += "opener.checkOK(self,'UserEmail');";
		}
		if (Login[3]) { // quiz password
			html += "opener.checkOK(self,'Password');";
		}
		html += 	 'if(ok){'
			+	 	'opener.StartQuiz();'
			+	 	'self.close();'
			+	  '}else{'
			+	 	'if(isNaN(self.tries))self.tries=0;'
			+	 	'self.tries++;'
			+	 	'if(self.tries<3){'
			+			'opener.setFocus(self);'
			+	 	'}else{'
			+	 		"alert(opener.MSG[11]);"
			+	 		'opener.goBack();'
			+	 		'self.close();'
			+	 	'}'
			+	  '}'
			+	  'return false;'
			+ '">'
		;
		html += '<table>'
			+ 	'<caption>' + LoginPrompt + '</caption>';
		;
		if (Login[0]) { // user name
			var v = getCookie(self, 'UserName');
			html += '<tr>'
				+	'<th align=right nowrap>' + MSG[0] + ' :</th>'
				+	'<td>'
			;
			if (typeof(Login[0])=='boolean') { // text box
				html += '<input type=text name=UserName value="' + v + '">';
			} else { // drop down menu of names
				// pattern to match commas and white space
				var comma = (window.RegExp) ? new RegExp('\\s*,\\s*') : ',';
				// convert list of names to array, if necessary
				if (typeof(Login[0])=='string') {
					Login[0] = Login[0].split(comma);
				}
				html += '<select name=UserName size=1>'
					+ '<option value=""></option>'
				;
				for(var i=0; i<Login[0].length; i++) {
					// convert name details to array if nececount_cary
					if (typeof(Login[0][i])=='string') {
						Login[0][i] = Login[0][i].split(comma);
					}
					html += makeOption(Login[0][i][0], v, Login[0][i][1]);
				}
				html += '</select>';
			}
			html += 	'</td>'
				+ '</tr>'
			;
		}
		if (Login[1]) { // user ID
			var v = getCookie(self, 'UserID');
			html += '<tr><th align=right nowrap>' + MSG[1] + ' :</th><td><input type=text name=UserID value="' + v + '"></td></tr>';
		}
		if (Login[2]) { // user email
			var v = getCookie(self, 'UserEmail');
			html += '<tr><th align=right nowrap>' + MSG[2] +' :</th><td><input type=text name=UserEmail value="' + v + '"></td></tr>';
		}
		if (Login[3]) { // quiz password
			var v = getCookie(self, 'Password');
			html += '<tr><th align=right nowrap>' + MSG[3] + ' :</th><td><input type=password name=Password value="' + v + '"></td></tr>';
		}
		if (Login[4]) { // cookie lifespan
			var v = getCookie(self, 'CookieExpiry');
			html += '<tr>'
				+ 	'<th align=right nowrap>' + MSG[4] + ' :</th>'
				+ 	'<td>'
				+		'<select name="CookieExpiry" size=1>'
				+ 			makeOption('session', v, MSG[7])
				+ 			makeOption('day', v, MSG[8])
				+ 			makeOption('month', v, MSG[9])
				+ 			makeOption('never', v, MSG[10])
				+ 		'</select>'
				+ 	'</td>'
				+ '</tr>'
			;
		}
		html += 	'<tr>'
			+		'<th>&nbsp;</th>'
			+		'<td nowrap>'
			+			'<input type=submit value="' + MSG[5] + '"> '
			+ 			'<input type=button value="' + MSG[6] + '" onClick="opener.goBack();self.close();">'
			+		'</td>'
			+	'</tr>'
			+ '</table></form></body></html>'
		;
		// set height of Login Window
		var m = navigator.userAgent.indexOf('Mac')>=0;
		var h = (m ? 80 : 100);
		for (var i=0; i<5; i++) h += (Login[i] ? (m ? 20 : 25) : 0);
		// open up a new window
		if (!openWindow('', '', (m ? 320 : 300), h, 'RESIZABLE', html)) {
			alert(MSG[18]); // unable to open popup window
		}
	} else { // no Login required
		window.UserName = window.UserID = window.UserEmail = window.Password = '';
		window.StartQuiz();
	}
	return true;
}
function makeOption(value, v, txt) {
	return '<option value="' + value + '"' + (value==v ? ' SELECTED' : '') + '>' + (txt ? txt : value) + '</option>';
}
function setFocus(w) {
	w.focus(); // bring window to the front
	var obj = w.document.forms[0].elements;
	for(var i=0; i<obj.length; i++) {
		var v = getValue(w, i);
		if (v=='' || obj[i].type=='submit') {
			obj[i].focus();
			break;
		}
	}
}
function checkOK(w, n){
	var v = getValue(w, n, true);
	if (v || (n!='UserName' && isGuest())) {
		if (n=='CookieExpiry') setCookieExpiry(w, v);
		setCookie(self, n, v, w.expiry);
		if (n!='CookieExpiry') eval('self.' + n + '=v');
	} else {
		if (w.ok) alert(MSG[12]);
		w.ok = false;
	}
}
function getValue(w, n, flag) {
	var obj = w.document.forms[0].elements[n];
	var TYPE = obj.type.toUpperCase(); // required for ns4 (win)
	if (obj.options && TYPE.indexOf('SELECT')>=0){
		var v = obj.options[obj.selectedIndex].value;
	} else {
		var v = obj.value;
	}
	if (flag) {
		var msg = '';
		if (n=='Password' || (n=='UserID' && !Login[3])) {
			var pwd = getPassword(w);
			if (pwd && v!=pwd) msg = MSG[n=='Password' ? 13 : 14];
		}
		if (n=='UserEmail' && window.RegExp) {
			var r = '(\\w|-)+';
			r = r + '(\\.' + r + ')';
			r = new RegExp('^(' + r + '*)@(' + r + '+)$');
			if (v.match(r)==null) msg = MSG[15];
		}
		if (msg) {
			obj.value = v = '';
			if (w.ok) alert(msg);
			w.ok = false;
		}
	}
	return v;
}
function getPassword(w) {
	var pwd = '';
	if (Login[3] && typeof(Login[3])=='string') {
		pwd = Login[3];
	} else if ((Login[3] || Login[1]) && typeof(Login[0])=='object') {
		var username = getValue(w, 'UserName');
		for(var i=0; i<Login[0].length; i++) {
			if (username==Login[0][i][0]) {
				pwd = Login[0][i][2];
				break;
			}
		}
	}
	return pwd;
}
function setCookieExpiry(w, v) {
	if (v=='never'){
		w.expiry = new Date('Thu, 01-Jan-70 00:00:01 GMT');
	} else if (v=='day' || v=='month') {
		var ms = (v=='month' ? 31 : 1) * 60 * 60 * 24 * 1000;
		w.expiry = new Date((new Date()).getTime() + ms);
	}
}
function setCookie(w, name, value, expires, path, domain, secure) {
	if (name) w.document.cookie = ''
		+ 'HP_' + name + "=" + escape(value)
		+ (expires ? "; expires=" + expires.toGMTString() : "")
		+ (path ? "; path=" + path : "")
		+ (domain ? "; domain=" + domain : "")
		+ (secure ? "; secure" : "")
	;
}
function getCookie(w, n) {
	var c = w.document.cookie;
	var i = c.indexOf('HP_' + n + '=');
	var j = (i<0) ? -1 : c.indexOf(';', (i += n.length + 4));
	return (i<0) ? '' : unescape(c.substring(i, ((j<0) ? c.length : j)));
}
function goBack(w) {
	if (w==null) w = self; // default
	if (w.history.length) w.history.back();
}
function openWindow(url, name, width, height, attributes, html) {
	// set height, width and attributes
	if (window.screen && width && height) {
		var W = screen.availWidth;
		var H = screen.availHeight;
		width = Math.min(width, W);
		height = Math.min(height, H);
		attributes = ''
			+ (attributes ? (attributes+',') : '')
			+ 'WIDTH='+width+',HEIGHT='+height
		;
	}
	// create global hpWindows object, if necessary
	if (!window.hpWindows) window.hpWindows = new Array();
	// initialize window object
	var w = null;
	// has a window with this name been opened before?
	if (name && hpWindows[name]) {
		// http://www.webreference.com/js/tutorial1/exist.html
		if (hpWindows[name].open && !hpWindows[name].closed) {
			w = hpWindows[name];
			w.focus();
		} else {
			hpWindows[name] = null;
		}
	}
	// check window is not already open
	if (w==null) {
		// workaround for "Access is denied" errors in IE when offline
		// based on an idea seen at http://www.devshed.com/Client_Side/JavaScript/Mini_FAQ
		var ie_offline = (document.all && location.protocol=='file:');
		// try and open the new window
		w = window.open((ie_offline ? '' : url), name, attributes);
		// check window opened OK (user may have prevented popups)
		if (w) {
			// center the window
			if (window.screen && width && height) {
				w.moveTo((W-width)/2, (H-height)/2);
			}
			// add content, if required
			if (html) {
				with (w.document) {
					clear();
					open();
					write(html);
					close();
				}
			} else if (url && ie_offline) {
				w.location = url;
			}
			if (name) hpWindows[name] = w;
		}
	}
	return w;
}
// *********************
//  Send results by email
//  (not required by LMS)
// *********************
function SendAllResults(Score) {
	// check this quiz is not generated by a LMS
	if (!is_LMS()) {
		// add flat file database details to the results form
		AddDatabaseDetailsToResultForm();
		// add student details to the results form
		AddStudentDetailsToResultForm();
		// add question details to the results form
		AddQuestionDetailsToResultForm();
		// add server fields, if any, to results form
		AddServerFieldsToResultForm();
		// change "method" of form, because "get" only allows 512 byts of data
		ResultForm = replaceLast('method="get"', 'method="post"', ResultForm);
		// create results window and form
		var w = openWindow('', '', 500, 400, 'RESIZABLE,SCROLLBARS,LOCATION', ResultForm);
		// check window opened OK (user may have prevented popups)
		if (w) {
			// get shortcut to form object
			var form = w.document.forms[0];
			// update some important field values
			form.Score.value = Score + '%';
			form.realname.value = UserName;
			form.Start_Time.value = getTime(Start_Time);
			form.End_Time.value = getTime();
			// force email subject and Exercise title
			form.subject.value = document.title;
			form. Exercise.value = document.title;
			// update DB fields, if required
			if (DB[0] && !isGuest()) set_db_fields(form);
			if (DB[7]) form.action = DB[7];
			if (DB[8]) form.recipient.value = DB[8];
			// if this is a Netscape browser, check if the referer will be set OK
			if (navigator.appName=='Netscape' && (location.protocol=='file:' || navigator.userAgent.indexOf('Netscape6')>=0)) {
				// ns4 and ns7 set referer to 'file:// ...' when running a quiz offline
				// ns6.2 (at least) always sets referer to 'about:blank'
				// Netscape's setting of referer can cause BFormMail
				// to reject the form, so encode the form data as a URL
				var url = form.action;
				var obj = form.elements;
				for (var i=0; i<obj.length; i++) {
					var  v = escape(obj[i].value);
					v = v.replace((new RegExp('\\+', 'g')), '%2B');
					url += (i==0 ? '?' : '&') + obj[i].name + '=' + v;
				}
				w.location.href = url;
			} else { // browser can POST form ok
				form.submit();
			}
		} else { // unable to open popup window
			alert(MSG[18]);
		}
	} // end if LMS
}
function isGuest() {
	// check username is not a "guest" user
	var flag = false;
	var n = getCookie(self, 'UserName').toLowerCase();
	if (n) {
		// convert list of user names to array, if necessary
		if(typeof(Login[5])=='string') {
			Login[5] = Login[5].split(',');
		}
		for(var i=0; i<Login[5].length; i++) {
			if (n==Login[5][i].toLowerCase()) {
				flag = true;
				break;
			}
		}
	}
	return flag;
}
function set_db_fields(form) {
	// update list of DB fields, if required
	if (DB[4]=='' && window.RegExp) {
		// add administration fields
		var db_fields = ''
			+ 'subject,realname'
			+ (Login[1] ? ',ID' : '')
			+ (Login[2] ? ',email' : '')
			+ (Login[3] ? ',password' : '')
			+ ',Score,Start_Time,End_Time'
		;
		// add answer fields (except separators)
		var r = new RegExp('^[^_]+_q\\d\\d_\\w+$');
		for(var i=0; i<form.elements.length; i++) {
			var n = form.elements[i].name;
			if (r.test(n)) db_fields += ',' + n;
		}
		form.db_fields.value = db_fields;
	}
	// make sure delimiter is set (NS6+ requires this be set here, not any earlier)
	form.db_delimiter.value = (DB[5] ? DB[5] : '\t');
}
function AddStudentDetailsToResultForm() {
	var sDetails = '';
	if (Login[0]) { // user name
		// use 'realname' instead of a separate 'Name' field
		// sDetails += hpHiddenField('Name', window.UserName);
	}
	if (Login[1]) { // user ID
		sDetails += hpHiddenField('ID', window.UserID);
	}
	if (Login[2]) { // user email
		sDetails += hpHiddenField('email', window.UserEmail);
	}
	if (sDetails && window.RegExp) {
		// insert sDetails before '<input...Score...></input>'
		var r = new RegExp('<input[^>]*Score[^>]*><\\/input>', 'i');
		var m = r.exec(ResultForm);
		if (m) {
			ResultForm = ResultForm.replace(m[0], sDetails + m[0] + makeSeparator('Time_'));
			sDetails = '';
		}
	}
	if (Login[3]) { // quiz password
		sDetails += hpHiddenField('Password', window.Password);
		ResultForm = replaceLast('</form>', sDetails + '</form>', ResultForm);
	}
}
function AddQuestionDetailsToResultForm() {
	var qDetails = GetQuestionDetails();
	if (qDetails) {
		// insert qDetails before the final </form> tag in the ResultForm
		ResultForm = replaceLast('</form>', qDetails + '</form>', ResultForm);
	}
}
function AddDatabaseDetailsToResultForm() {
	if (window.DB && DB[0] && !isGuest()) {
		var dbDetails = '';
		var folder = DB[1];
		if (folder && folder.charAt(folder.length-1)!='/') folder += '/';
		var file = DB[2];
		if (file=='') {
			file = location.href;
			file= file.substring(file.lastIndexOf('/')+1);
			var i = file.indexOf('?');
			if (i >= 0) file = file.substring(0, i);
			var i = file.lastIndexOf('.');
			if (i >= 0) file = file.substring(0, i);
		}
		var ext = (DB[3] ? DB[3] : 'txt');
		if (ext.charAt(0)!='.') ext = '.' + ext;
		dbDetails += hpHiddenField('append_db', folder + file + ext);
		dbDetails += hpHiddenField('db_fields', DB[4]);
		dbDetails += hpHiddenField('db_delimiter', ''); // NS6+ requires this be set later
		if (DB[6]) dbDetails += hpHiddenField('env_report', DB[6]);
		// insert dbDetails before the final </form> tag in the ResultForm
		ResultForm = replaceLast('</form>', dbDetails + '</form>', ResultForm);
	}
}
function AddServerFieldsToResultForm() {
	if (window.ServerFields) {
		var s = ''; // input tags for s(erver fields)
		for (var i=0; i<ServerFields.length; i++) {
			if (ServerFields[i][0] && window.RegExp) {
				// remove previous field value, if any
				var r = new RegExp('<input[^>]*name\\s*=\\s*["\']\\s*' + ServerFields[i][0] + '[^>]*>(\\s*<\\/input>)?', 'i');
				if (r.test(ResultForm)) {
					ResultForm = ResultForm.replace(r, '');
				}
			}
			if (ServerFields[i][1]) {
				s += hpHiddenField(ServerFields[i][0], ServerFields[i][1]);
			}
		} // end for
		if (s) ResultForm = replaceLast('</form>', s + '</form>', ResultForm);
	}
}
function replaceLast(a, b, c) {
	// replace last occurrence of 'a' in 'c' with 'b'
	var l = a.length;
	var i = c.lastIndexOf(a);
	return (i<0 || l==0) ? c : (c.substring(0, i) + b + c.substring(i+l));
}
// *************************
//  Extract question details
// *************************
function GetQuestionDetails() {
	var hp = hpVersion();
	var t = hpQuizType();
	var v = hpQuizVersion();
	return	(t==1) ? GetJbcQuestionDetails(hp, v) :
		(t==2) ? GetJClozeQuestionDetails(hp, v) :
		(t==3) ? GetJCrossQuestionDetails(hp, v) :
		(t==4) ? GetJMatchQuestionDetails(hp, v) :
		(t==5) ? GetJMixQuestionDetails(hp, v) :
		(t==6) ? GetJQuizQuestionDetails(hp, v) :
		(t==7) ? GetRhubarbDetails(hp, v) :
		(t==8) ? GetSequiturDetails(hp, v) : '';
}
function GetJbcQuestionDetails(hp, v) {
	qDetails = '';
	// check the quiz version
	if (hp==5 || hp==6) {
		// get question details
		for(var q=0; q<I.length; q++) {
			// initialize strings to hold answer details
			var aDetails = new Array();
			aDetails[0] = new Array(); // right
			aDetails[1] = new Array(); // wrong
			aDetails[2] = new Array(); // ignored
			// get answer details
			for(var a=0; a<I[q][1].length; a++) {
				var i = (Status[q][1][a]=='R') ? 0 : (Status[q][1][a]=='0') ? 2 : 1;
				aDetails[i][aDetails[i].length] = (JBC[6] ? a : I[q][1][a][0]);
			}
			// format 'Q' (a padded, two-digit version of 'q')
			var Q = getQ('JBC', q);
			// add separator, if required
			if (JBC[0]) qDetails += makeSeparator(Q);
			if (JBC[1]) { // number of attempts to answer question
				qDetails += hpHiddenField(Q+'attempts', Status[q][2] + (Status[q][0]==1 ? 1 : 0));
			}
			if (JBC[2]) { // question text
				qDetails += hpHiddenField(Q+'text', I[q][0]);
			}
			if (JBC[3] && (DB[0] || aDetails[0].length>0)) { // right
				qDetails += hpHiddenField(Q+'right', aDetails[0]);
			}
			if (JBC[4] && (DB[0] || aDetails[1].length>0)) { // wrong
				qDetails += hpHiddenField(Q+'wrong', aDetails[1]);
			}
			if (JBC[5] && (DB[0] || aDetails[2].length>0)) { // ignored
				qDetails += hpHiddenField(Q+'ignored', aDetails[2]);
			}
			// calculate score for this question, if required (for HP version < 5.5)
			if (isNaN(Status[q][3])) {
				var a1 = Status[q][1].length; // answers
				var a2 = Status[q][2]; // attempts
				Status[q][3] =  (a1<1 || a1<(a2-1)) ? 0 : ((a1 - (a2-1)) / a1);
			}
			// add 'score' for this question
			qDetails += hpHiddenField(Q+'score', Math.floor(Status[q][3]*100)+'%');
		} // end for
	}
	return qDetails;
}
function GetJClozeQuestionDetails(hp, v) {
	var qDetails = '';
	// check the quiz version
	if (hp==5 || hp==6) {
		var r = hpRottmeier();
		if (parseInt(r)==2) { // Rottmeier Find-It 3a+3b
			qDetails += hpHiddenField('JCloze_penalties', window.TotWrongChoices);
		}
		// get details for each question
		var q_max = (r==0) ? State.length :  GapList.length; // could use I.length for both
		for (var q=0; q<q_max; q++) {
			// format 'Q' (a padded, two-digit version of 'q')
			var Q = getQ('JCloze', q);
			// add separator, if required
			if (JCloze[0]) qDetails += makeSeparator(Q);
			// score (as %)
			var x = (hp==5) ? State[q][3] : (r==0) ? State[q].ItemScore : GapList[q][1].Score;
			qDetails += hpHiddenField(Q+'score', Math.floor(x*100)+'%');
			var correct = (HP[_correct][q] ? HP[_correct][q] : '');
			if (JCloze[1]) { // student's correct answer
				qDetails += hpHiddenField(Q+'correct', correct);
			}
			if (JCloze[2]) { // ignored answers
				var x = new Array();
				if (r!=2.1) { // exclude Find-It 3a
					for (var i=0, ii=0; i<I[q][1].length; i++) {
						var s = I[q][1][i][0];
                        if (typeof(s)=='string' && s!='') {
                            if (s.toUpperCase() == correct.toUpperCase()) {
                                var is_ignored = false;
                            } else {
                                // DropDown 2.4
                                var is_ignored = true;
                                var iii_max = HP[_wrong][q] ? HP[_wrong][q].length : 0;
                                for (var iii=0; iii<iii_max; iii++) {
                                    if (s.toUpperCase() == HP[_wrong][q][iii].toUpperCase()) {
                                        var is_ignored = false;
                                    }
                                }
                            }
    						if (is_ignored) {
    							x[ii++] = s;
    						}
                        }
					}
				}
				qDetails += hpHiddenField(Q+'ignored', x);
			}
			if (JCloze[3]) {
				var x = (HP[_wrong][q] ? HP[_wrong][q] : '');
				qDetails += hpHiddenField(Q+'wrong', x);
			}
			if (JCloze[4]) { // number of penalties (Hints + Checks)
				var x = (hp==5) ? State[q][1] : (r==0) ? State[q].HintsAndChecks : (r==1) ?  GapList[q][1].NumOfTrials : (r==2.2) ?  GapList[q][1].HintsAndChecks : 0;
				qDetails += hpHiddenField(Q+'penalties', x);
			}
			if (JCloze[5]) { // clue shown?
				var x = (hp==5) ? State[q][0] : (r==0) ? State[q].ClueGiven: (r==1) ? GapList[q][1].ClueAskedFor : false;
				qDetails += hpHiddenField(Q+'clue_shown', (x ? 'YES' : 'NO'));
			}
			if (JCloze[6]) { // clue text
				qDetails += hpHiddenField(Q+'clue_text', I[q][2]);
			}
			if (JCloze[7]) { // number of hints
				var x = (HP[_hints][q] ? HP[_hints][q] : 0);
				qDetails += hpHiddenField(Q+'hints', x);
			}
			if (JCloze[8]) { // number of clues
				var x = HP[_clues][q] ? HP[_clues][q] : 0;
				qDetails += hpHiddenField(Q+'clues', x);
			}
			if (JCloze[9]) { // number of checks (including the final one for the correct answer)
				var x = (HP[_checks][q] ? HP[_checks][q] : 0);
				qDetails += hpHiddenField(Q+'checks', x);
			}
		} // end for
	}
	return qDetails;
}
function GetJCrossQuestionDetails(hp, v) {
	var qDetails = '';
	// check the quiz version
	if (hp==5 || hp==6) {
		// inialize letter count
		var letters = 0;
		// get details for each question
		for (var row=0; row<L.length; row++) {
			for (var col=0; col<L[row].length; col++) {
				// increment letter count, if required
				if (L[row][col]) letters++;
				// show answers and clues, if required
				var q = (hp==5) ? C[row][col] : CL[row][col];
				if (q) {
					for (var i=0; i<2; i++) { // 0==across, 1==down
						var AD = (i==0) ? 'A' : 'D';
						var acrossdown = (i==0) ? 'across' : 'down';

						var clue = (hp==5) ? eval(AD+'['+q+']') : GetJCrossClue('Clue_'+AD+'_'+q);
						if (clue) {
							// format 'Q' (a padded, two-digit version of 'q')
							var Q = getQ('JCross', q) + acrossdown + '_'; // e.g. JCross_01_across_

							if (JCross[0]) {
								qDetails += makeSeparator(Q);
							}
							if (JCross[5]) {
								var x = (HP[_correct][AD] && HP[_correct][AD][q]) ? HP[_correct][AD][q] : '';
								qDetails += hpHiddenField(Q+'correct', x);
							}
							if (JCross[4]) qDetails += hpHiddenField(Q+'clue', clue);
							if (JCross[5]) {
								var x = (HP[_wrong][AD] && HP[_wrong][AD][q]) ? HP[_wrong][AD][q] : '';
								qDetails += hpHiddenField(Q+'wrong', x);
							}
							if (JCross[6]) {
								var x = HP[_clues][q] ? HP[_clues][q] : 0;
								qDetails += hpHiddenField(Q+'clues', x);
							}
							if (JCross[7]) {
								var x = (HP[_hints][AD] && HP[_hints][AD][q]) ? HP[_hints][AD][q] : 0;
								qDetails += hpHiddenField(Q+'hints', x);
							}
							if (JCross[8]) {
								var x = (HP[_checks][AD] && HP[_checks][AD][q]) ? HP[_checks][AD][q] : '';
								qDetails += hpHiddenField(Q+'checks', x);
							}
						} // end for i
					} // end if clue
				} // end if q
			} // end for col
		} // end for row
		if (JCross[2]) { // show number of letters
			qDetails = hpHiddenField('JCross_letters', letters) + qDetails;
		}
		if (JCross[1]) { // show penalties
			var x = (window.Penalties) ? Penalties : 0;
			qDetails = hpHiddenField('JCross_penalties', x) + qDetails;
		}
	}
	return qDetails;
}
function GetJCrossClue(id) {
	var obj = (document.getElementById) ? document.getElementById(id) : null;
	return (obj) ? GetTextFromNodeN(obj, 'Clue') : '';
}
function GetJCrossWord(a, r, c, goDown) {
	// a is a 2-dimensional array of letters, r is a row number, c is a column number
	var s = '';
	while (r<a.length && c<a[r].length && a[r][c]) {
		s += a[r][c];
		if (goDown) {
			r++;
		} else {
			c++;
		}
	}
	return s;
}
function GetJMatchText(q, className) {
	var obj = (document.getElementById) ? document.getElementById('Questions') : null;
	return (obj) ? GetTextFromNodeN(obj, className, q) : '';
}
function GetJMatchRHS(v, q, getCorrect) {
	var rhs = '';
	if (v==5.1 || v==6.1) { // Drag-and-drop
		var max_i = (window.F && window.D) ? D.length : 0;
		for (var i=0; i<max_i; i++) {
			if (F[q][1]==D[i][getCorrect ? 1 : 2]) break;
		}
		if (i<max_i) rhs = D[i][0];
	} else if (v==5 || v==6) { // drop-down list of options
		var obj=document.getElementById(Status[q][2]);
		if (obj) { // not correct yet
			if (getCorrect) {
				var k = GetKeyFromSelect(obj);
				var i_max = obj.options.length;
				for (var i=0; i<i_max; i++) {
					if (obj.options[i].value==k) break;
				}
				if (i>=i_max) i = 0; // shouldn't happen
			} else {
				// get current guess, if any
				var i = obj.selectedIndex;
			}
			if (i) rhs = obj.options[i].innerHTML;
		} else { // correct
			rhs = GetJMatchText(q, 'RightItem');
		}
	}
	return rhs;
}
function GetJMixQuestionDetails(hp, v) {
	qDetails = '';
	// check the quiz version
	if (hp==5 || hp==6) {
		var q = 0; // question number
		// format 'Q' (a padded, two-digit version of 'q')
		var Q = getQ('JMix', q);
		// add separator, if required
		if (JMix[0]) qDetails += makeSeparator(Q);
		// add 'score' for this question
		var score = HP[_correct]==null ? 0 : ((Segments.length-Penalties)/Segments.length);
		qDetails += hpHiddenField(Q+'score', Math.floor(score*100)+'%');
		if (JMix[1]) { // number of wrong guesses
			qDetails += hpHiddenField(Q+'wrongGuesses', Penalties);
		}
		if (JMix[2]) { // right answer
			var x = (HP[_correct][q]) ? HP[_correct][q] : '';
			qDetails += hpHiddenField(Q+'correct', x);
		}
		if (JMix[3]) { // wrong answer(s)
			var x = (HP[_wrong][q]) ? HP[_wrong][q] : '';
			qDetails += hpHiddenField(Q+'wrong', x);
		}
		if (JMix[5]) { // checks
			var x = (HP[_checks][q]) ? HP[_checks][q] : 0;
			qDetails += hpHiddenField(Q+'checks', x);
		}
		if (JMix[6]) { // hints
			var x = (HP[_hints][q]) ? HP[_hints][q] : 0;
			qDetails += hpHiddenField(Q+'hints', x);
		}
	}
	return qDetails;
}
function GetJMixSequence(indexes) {
	var s = new Array();
	for (var i=0; i<indexes.length; i++) {
		s[i] = JMix[4] ? indexes[i] : GetJMixSegmentText(indexes[i]);
	}
	return s;
}
function GetJMixSegmentText(index){
	var i_max = Segments.length;
	for (var i=0; i<i_max; i++) {
		if (Segments[i][1] == index) break;
	}
	return (i<i_max) ? Segments[i][0] : '';
}
function GetJQuizQuestionDetails(hp, v) {
	var qDetails = '';
	// HP5.5 uses "Status" for v5 and v6 JMatch quizzes (HP6 uses "State")
	// var hp =  (window.Status) ? 5 : (window.State) ? 6 : 0;
	// check the quiz version
	if (hp==5 || hp==6) {
		// get details for each question
		var max_q = (hp==5) ? Status.length : State.length;
		for (var q=0; q<max_q; q++) {
			// skip this question if it was not used (HP6 v6 only)
			if (hp==6 && !State[q]) continue;
			// format 'Q' (a padded, two-digit version of 'q')
			var Q = getQ('JQuiz', q);
			// add separator
			if (JQuiz[0]) qDetails += makeSeparator(Q);
			if (hp==6 && JQuiz[11]) { // question type
				var x = parseInt(I[q][2]);
				x = (x==0) ? 'multiple-choice' : (x==1) ? 'short-answer' : (x==2) ? 'hybrid' : (x==3) ? 'multi-select' : 'n/a';
				qDetails += hpHiddenField(Q+'type', x);
			}
			// score (as %)
			var x = (hp==5) ? Status[q][4]*10 : I[q][0]*State[q][0];
			if (x<0) x = 0;
			qDetails += hpHiddenField(Q+'score', Math.floor(x)+'%');
			if (hp==6 && JQuiz[10]) { // weighting
				qDetails += hpHiddenField(Q+'weighting', I[q][0]);
			}
			if (JQuiz[1]) { // question text
				var x = (hp==5) ? I[q][0] : (document.getElementById) ? GetTextFromNodeN(document.getElementById('Q_'+q), 'QuestionText') : '';
				qDetails += hpHiddenField(Q+'question', x);
			}
			if (JQuiz[2]) { // student's correct answers
				var x = (HP[_correct][q]) ? HP[_correct][q] : '';
				qDetails += hpHiddenField(Q+'correct', x);
			}
			if (JQuiz[3]) { // ignored and wrong answers
				var x = (hp==5) ? new Array() : GetJQuizAnswerDetails(q, 1);
				if (hp==5) {
					for (var i=0; i<I[q][1].length; i++) {
						var correct = HP[_correct][q] ? HP[_correct][q] : '';
						if (I[q][1][i][0] && I[q][1][i][0].toUpperCase()!=correct.toUpperCase()) {
							x[x.length] = I[q][1][i][0];
						}
					}
				}
				if (DB[0] || x) qDetails += hpHiddenField(Q+'other', x);
			}
			if (hp==6 && JQuiz[7]) { // all selected answers
				var x = GetJQuizAnswerDetails(q, 0);
				qDetails += hpHiddenField(Q+'selected', x);
			}
			if (JQuiz[8]) { // wrong answers
				var x = (HP[_wrong][q]) ? HP[_wrong][q] : '';
				qDetails += hpHiddenField(Q+'wrong', x);
			}
			if (hp==6 && JQuiz[9]) { // ignored answers
				var x = GetJQuizAnswerDetails(q, 4);
				qDetails += hpHiddenField(Q+'ignored', x);
			}
			if (JQuiz[4]) { // number of hints
				var x = (HP[_hints][q]) ? HP[_hints][q] : 0;
				qDetails += hpHiddenField(Q+'hints', x);
			}
			if (JQuiz[5] || JQuiz[12]) { // number of checks
				if (JQuiz[12]) { // strictly checks only
					var x = (HP[_checks][q]) ? HP[_checks][q] : 0;
				} else { // checks (+ hints in HP6)
					var x = (hp==5) ? Status[q][1] : (State[q][2]-1);
				}
				qDetails += hpHiddenField(Q+'checks', x);
			}
			if (JQuiz[13]) { // ShowAnswer button
				var x = (HP[_clues][q]) ? HP[_clues][q] : 0;
				qDetails += hpHiddenField(Q+'clues', x);
			}
		} // end for
	} // end if
	return qDetails;
}
function GetTextFromNodeN(obj, className, n) {
	// returns the text under the nth node of obj with the target class name
	var txt = '';
	if (obj && className) {
		if (typeof(n)=='undefined') {
			n = 0;
		}
		var nodes = GetNodesByClassName(obj, className);
		if (n<nodes.length) {
			txt += GetTextFromNode(nodes[n]);
		}
	}
	return txt;
}
function GetNodesByClassName(obj, className) {
	// returns an array of nodes with the target classname
	var nodes = new Array();
	if (obj) {
		if (className && obj.className==className) {
			nodes.push(obj);
		} else if (obj.childNodes) {
			for (var i=0; i<obj.childNodes.length; i++) {
				nodes = nodes.concat(GetNodesByClassName(obj.childNodes[i], className));
			}
		}
	}
	return nodes;
}
function GetTextFromNode(obj) {
	// return text in (and under) a single DOM node
	var txt = '';
	if (obj) {
		if (obj.nodeType==3) {
			txt = obj.nodeValue + ' ';
		}
		if (obj.childNodes) {
			for (var i=0; i<obj.childNodes.length; i++) {
				txt += GetTextFromNode(obj.childNodes[i]);
			}
		}
	}
	return txt;
}
function GetJQuizAnswerDetails(q, flag) {
	// flag : the type of information required about the student's answers
	//	0 : all student's answers
	//	1 : student's wrong and ignored answers
	//	2 : student's correct answers
	//	3 : student's wrong answers
	//	4 : ignored answers
	var x = State[q][5]; //Sequence of answers chosen by number
	if (I[q][2]=='3') { // multi-select
		if (flag==4) {
			var x = new Array();
		} else {
			// get required part of 'x' and convert to array
            if (x.charAt(0)=='|') {
                // HP 6.0 and 6.1 (always has leading bar)
    			var i = x.lastIndexOf('|');
    			var x = x.substring((flag==2 ? (i+1) : 1), ((flag==0 || flag==2) ? x.length : i)).split('|');
            } else {
                // HP 6.2 (no leading delimiter)
    			var i = x.lastIndexOf(' | ');
    			var x = x.substring((flag==2 ? (i+3) : 0), ((flag==0 || flag==2) ? x.length : i)).split(' | ');
            }
		}
		for (var i=0; i<x.length; i++) {
			var a = new Array();
			for (var ii=0; ii<x[i].length; ii++) {
				if (x[i].charAt(ii)=='Y') {
					var s = JQuiz[6] ? String.fromCharCode(97+ii) : I[q][3][ii][0];
					if (s && s.replace && window.RegExp) {
						s = s.replace(new RegExp('\\+', 'g'), '&#43;');
					}
					a.push(s);
				}
			}
			x[i] = a.join('+');
		}
	} else if (x) { // multiple-choice, short-answer and hybrid
        if (x.charAt(x.length-1)==',') {
            // HP 6.0 and 6.1 (always has trailing comma)
            x = x.substring(0, x.length-1).split(',');
        } else {
            // HP 6.2 (short answer also contains student entered text)
            x = x.split(' | ');
        }
		if (flag) {
			var a = new Array();
			if (flag==1 || flag==2 || flag==3) {
				for (var i=0; i<x.length; i++) {
                    var is_correct = false;
                    if (x[i].length==1) { // single letter
                        var ii = x[i].charCodeAt(0) - 65;
                        if (I[q][3] && I[q][3][ii] && I[q][3][ii][2]) {
                            var is_correct = true;
                        }
                    }
                    if (is_correct) {
                        if (flag==2) {
                            a.push(x[i]);
                        }
                    } else {
                        if (flag==1 || flag==3) {
                            a.push(x[i]);
                        }
                    }
				}
			}
			if (flag==1) {
				x = a;
				a = new Array();
			}
			if (flag==1 || flag==4) {
				for (var i=0; i<I[q][3].length; i++) {
					var s = String.fromCharCode(65+i);
					for (var ii=0; ii<x.length; ii++) {
						if (x[ii]==s) break;
					}
					if (ii==x.length) a.push(s);
				}
			}
			x = a;
		}
		// convert answer indexes to values, if required
		if (JQuiz[6]==false) {
			for (var i=0; i<x.length; i++) {
                if (x[i].length==1) { // single letter
                    var ii = x[i].charCodeAt(0) - 65;
                    if (I[q][3] && I[q][3][ii]) {
        				x[i] = I[q][3][ii][0];
                    }
                }
			}
		}
	} else {
		x = new Array();
	}
	return x;
}
function GetRhubarbDetails(v) {
	qDetails = '';
	if (v==6) {
		var q = 0; // always zero
		var Q = getQ('Rhubarb', q);
		if (document.title) { // use quiz title as question name
			qDetails += hpHiddenField(Q+'name', document.title);
		}
		if (Rhubarb[0]) { // correct words
			var x = (HP[_correct][q]) ? HP[_correct][q] : '';
			if (Rhubarb[1]) { // count of correct words
				for (var i=0,ii=0; i<x.length; i++) {
					if (x[i]) ii++;
				}
				x = ii;
			}
			qDetails += hpHiddenField(Q+'correct', x);
		}
		if (Rhubarb[2]) { // wrong words
			var x = (HP[_wrong][q]) ? HP[_wrong][q] : '';
			if (Rhubarb[3]) { // count of wrong words
				x = x.length;
			}
			qDetails += hpHiddenField(Q+'wrong', x);
		}
		if (Rhubarb[4]) { // ignored
			var x = '';
			qDetails += hpHiddenField(Q+'ignored', x);
		}
		if (Rhubarb[5]) { // hints
			var x = (HP[_hints][q]) ? HP[_hints][q] : '';
			qDetails += hpHiddenField(Q+'hints', x);
		}
	}
	return qDetails;
}
function GetSequiturDetails(v) {
	qDetails = '';
	if (v==6) {
		var q = 0; // always zero
		var Q = getQ('Sequitur', q);
		if (document.title) { // use quiz title as question name
			qDetails += hpHiddenField(Q+'name', document.title);
		}
		if (Sequitur[0]) { // number of correct buttons chosen
			var x = (HP[_correct][q]) ? HP[_correct][q] : '';
			qDetails += hpHiddenField(Q+'correct', x);
		}
		if (Sequitur[1]) { // number of wrong buttons chosen
			var x = (HP[_wrong][q]) ? HP[_wrong][q] : '';
			qDetails += hpHiddenField(Q+'wrong', x);
		}
	}
	return qDetails;
}
// *********************
//	click event handlers
// *********************
function hpClick(x, args) {
	// x is the button type
	// args is either empty, a single argument, or an array of arguments
	var btn = (x==1) ? 'Hint' : (x==2) ? 'Clue' : (x==3) ? 'Check' : (x==4)  ? 'Enter' : '';
	if (btn) {
		// convert args to array, if necessary
		var t = typeof(args);
		if (t=='object') {
			// do nothing (args is already an array)
		} else if (t=='undefined') {
			args = new Array();
		} else {
			args = new Array(''+args);
		}
		// call handler for this kind of button
		var x = eval('hpClick'+btn+'('+hpVersion()+','+hpQuizType()+','+hpQuizVersion()+',args);');
	}
}
function hpClickHint(hp, t, v, args) {
	if (t==2 || t==5 || t==6 || t==7) { // JCloze, JMix, JQuiz, Rhubarb
		var q = args[0]; // clue/question number
		if (!HP[_hints][q]) HP[_hints][q] = 0;
		HP[_hints][q]++;
	}
	if (t==3) { // JCross
		if (v==6 || v==5) {
			var q = args[0]; // clue/question number
			var AD = args[1]; // direction ('A' or 'D')
			if (!HP[_hints][AD]) HP[_hints][AD] = new Array();
			if (!HP[_hints][AD][q]) HP[_hints][AD][q] = 0;
			HP[_hints][AD][q]++;
		}
	}
	return true;
}
function hpClickClue(hp, t, v, args) {
	if (t==2 || t==3 || t==6) { // JCloze or JCross, or JQuiz (ShowAnswer button)
		var q = args[0]; // clue/question number
		if (!HP[_clues][q]) HP[_clues][q] = 0;
		HP[_clues][q]++;
	}
	return true;
}
function hpClickCheck(hp, t, v, args) {
	if (t==2) { // JCloze
    if (v==5 || v==6) {
			var r = hpRottmeier();
			var already_correct = 'true';
			if (r==0) {
				already_correct = (hp==5) ? 'State[i][4]==1' : 'State[i].AnsweredCorrectly==true';
			} else if (r==1) { // DropDown
				already_correct = 'GapList[i][1].GapLocked==true';
			} else if (r==2.1) { // Find-It 3a
				already_correct = 'GapList[i][1].ErrorFound==true';
			} else if (r==2.2) { // Find-It 3b
				already_correct = 'GapList[i][1].GapSolved==true';
			}
			var i_max = I.length;
			for (var i=0; i<i_max; i++) {
				if (eval(already_correct)) continue;
				var g = '';
				if (r==0 || r==2.2) {
					g = GetGapValue(i);
				} else if (r==1) { // DropDown
					if (hp==5) {
						g = eval('document.Cloze.Gap'+i+'.value');
					} else if (hp==6) {
						var ii = Get_SelectedDropValue(i);
                        if (isNaN(ii) || ii<0) { // 'null' || -1
                            g = ''; // no guess yet
                        } else {
                			if (window.MakeIndividualDropdowns) {
                                var is_wrong = (ii!=0);
                                g = I[i][1][ii][0];
                            } else {
                                var is_wrong = (ii!=i);
                                g = I[ii][1][0][0];
                            }
                        }
					}
				} else if (r==2.1 && i==args[0]) { // Find-It 3a
					g = I[i][1][0][0];
				}
				if (g) {
					if (!HP[_checks][i]) HP[_checks][i] = 0;
					HP[_checks][i]++;
					if (!HP[_guesses][i]) HP[_guesses][i] = new Array();
					var ii = HP[_guesses][i].length;
					// is this a new guess at this gap?
					if (ii==0 || g!=HP[_guesses][i][ii-1]) {
						HP[_guesses][i][ii] = g;
                        if (r==1) {
                            // Rottmeier DropDown 2.4
                            // do nothing
                        } else {
    						var G = g.toUpperCase();
    						var ii_max = I[i][1].length;
    						for (var ii=0; ii<ii_max; ii++) {
    							if (window.CaseSensitive) {
    								if (g==I[i][1][ii][0]) break;
    							} else {
    								if (G==I[i][1][ii][0].toUpperCase()) break;
    							}
    						}
                            var is_wrong = (ii==ii_max);
                        }
						if (is_wrong) { // guess is wrong
							if (!HP[_wrong][i]) HP[_wrong][i] = new Array();
							var ii_max = HP[_wrong][i].length;
							for (var ii=0; ii<ii_max; ii++) {
								if (HP[_wrong][i][ii]==g) break;
							}
							if (ii==ii_max) {
								HP[_wrong][i][ii] = g;
							}
						} else { // guess is correct
							HP[_correct][i] = g;
						}
					}
				}
			}
		}
	}
	if (t==3) { // JCross
		if (v==5 || v==6) {
			var q = args[0]; // clue/question number
			for (var row=0; row<L.length; row++) {
				for (var col=0; col<L[row].length; col++) {
					var q = (v==5) ? C[row][col] : CL[row][col];
					if (q) {
						hpClickCheckJCrossV5V6(hp, v, 'A', q, row, col);
						hpClickCheckJCrossV5V6(hp, v, 'D', q, row, col);
					}
				}
			}
		}
	}
	if (t==4) { // JMatch
		var a = new Array();
		var extra = ''; // extra js code to eval(uate)
		var guess = ''; // js code to eval(uate) guess
		var correct = ''; // js code to eval(uate) correct answer
		if (window.D && window.F) {
			// drag-and-drop, i.e. v5+ and v6+ (HP5 and HP6)
			a = F;
			guess = 'GetJMatchRHS(v,i)';
			correct = 'GetJMatchRHS(v,i,true)';
		} else  if (window.GetKeyFromSelect) {
			// HP6 v6
			a = Status;
			guess = 'GetJMatchRHS(v,i)';
			correct = 'GetJMatchRHS(v,i,true)';
		} else if (window.GetAnswer) {
			// HP5 v6,v5
			a = I;
			guess = "(I[i][2]==0||I[i][0]=='')?'':GetAnswer(i)";
			correct = 'I[i][3])';
		} else if (window.Draggables) {
			// HP5 v4
			a = Draggables;
			s = "Draggables[i].correct=='1'";
		} else if (window.CorrectAnswers) {
			// HP5 v3
			a = CorrectAnswers;
			guess = 'document.QuizForm.elements[i*2].selectedIndex';
			correct = 'CorrectAnswers[i]';
		}
		for (var i=0; i<a.length; i++) {
			// check this match has not already been finished
			if (!HP[_correct][i]) {
				// do extra setup, if necessary
				if (extra) eval(extra);
				// get the guess, if any
				var g = ''+eval(guess);
				if (g) {
					// is the guess correct?
					if (g==eval(correct)) {
						HP[_correct][i] = g;
					} else { // wrong answer
						// initialize wrong guess array if necessary
						if (!HP[_wrong][i]) HP[_wrong][i] = new Array();
						// check to see if the guess is already in the guess array
						var i_max = HP[_wrong][i].length;
						for (var ii=0; ii<i_max; ii++) {
							if (HP[_wrong][i][ii]==g) break;
						}
						// add the guess if it was not found
						if (ii==i_max) {
							HP[_wrong][i][ii]=g;
						} else {
							g = null; // this is not a new answer
						}
					}
					// increment checks for this question, if necessary
					if (g) {
						if (!HP[_checks][i]) HP[_checks][i] = 0;
						HP[_checks][i]++;
					}
				}
			}
		}
	} // end if JMatch
	if (t==5) { // JMix
		// get question number (always 0)
		var q = args[0];
		// check question has not already been answered correctly
		if (!HP[_correct][q]) {
			// match current guess against possible correct answers
			var a_max = Answers.length;
			for (var a=0; a<a_max; a++) {
				var i_max = Answers[a].length;
				for (var i=0; i<i_max; i++) {
					if (Answers[a][i] != GuessSequence[i]) break;
				}
				if (i==i_max) break; // correct answer was found
			}
			// at this point, (a==a_max) means guess is wrong
			// get array of segment texts in this g(uess)
			var g = GetJMixSequence(GuessSequence);
			// convert g(uess) array and to a s(tring)
			var s = '';
			var i_max = g.length;
			for (var i=0; i<i_max; i++) {
				g[i] = trim(g[i]);
				if (g[i]!='') {
					s += (s=='' ? '' : '+') +  g[i];
				}
			}
			if (s) {
				if (a==a_max) { // wrong
					if (!HP[_wrong][q]) HP[_wrong][q] = new Array();
					var i = HP[_wrong][q].length;
					HP[_wrong][q][i] = s;
				} else { // correct
					HP[_correct][q] = s;
				}
				// increment checks for this question
				if (!HP[_checks][q]) HP[_checks][q] = 0;
				HP[_checks][q]++;
			}
		}
	}
	if (t==6) { // JQuiz
		if (hp==5 || hp==6) {
			var q = args[0]; // clue/question number
			if (hp==5) {
				if (v==5) {
					var g = TrimString(eval('BottomFrame.document.QForm' + q + '.Guess').value);
				} else if (v==6) {
					var g = TrimString(eval('document.QForm.Guess').value);
				}
			} else  { // HP 6
				var g = args[1];
			}
			// increment check count
			if (!HP[_checks][q]) HP[_checks][q] = 0;
			HP[_checks][q]++;
			if (g) {
				var G = g.toUpperCase(); // used for shortanswer only
				var correct_answer = ''; // used for multiselect only
				// set index of answer array in I (the question array)
				var ans = (hp==5) ? 1 : 3;
				var i_max = I[q][ans].length;
				for (var i=0; i<i_max; i++) {
					// is this a (possible) correct answer?
					if (hp==5 || (hp==6 && I[q][ans][i][2])) {
						if (hp==6 && I[q][2]==3) { // multiselect
							correct_answer += (correct_answer  ? '&#43;' : '') + I[q][ans][i][0];
						} else { // multichoice, shortanswer
							if (window.CaseSensitive) {
								if (g==I[q][ans][i][0]) break;
							} else {
								if (G==I[q][ans][i][0].toUpperCase()) break;
							}
						}
					}
				}
				if (i==i_max && g!=correct_answer) { // wrong
					if (!HP[_wrong][q]) HP[_wrong][q] = new Array();
					var i_max = HP[_wrong][q].length;
					for (var i=0; i<i_max; i++) {
						if (HP[_wrong][q][i]==g) break;
					}
					if (i==i_max) HP[_wrong][q][i] = g;
				} else {
					HP[_correct][q] = g;
				}
			}
		}
	}
	if (t==7) { // Rhubarb
		if (hp==6) {
			var q = 0; // question number (always zero)
			var g = args[0]; // InputWord from CheckGuess()
			if (g) {
				var G = g.toUpperCase();
				var i_max = Words.length;
				for (var i=0; i<i_max; i++) {
					if (G==Words[i].toUpperCase()) break;
				}
				if (i<i_max) { // correct
					if (!HP[_correct][q]) HP[_correct][q] = new Array();
					HP[_correct][q][i] = g;
				} else { // wrong
					if (!HP[_wrong][q]) HP[_wrong][q] = new Array();
					var i_max = HP[_wrong][q].length;
					for (var i=0; i<i_max; i++) {
						if (G==HP[_wrong][q][i].toUpperCase()) break;
					}
					if (i==i_max) HP[_wrong][q][i] = g;
				}
			}
		}
	}
	if (t==8) { // Sequitur
		if (hp==6) {
			var q = 0; // question number (always zero)
			if (CurrentCorrect==args[0]) { // correct button chosen
				if (!HP[_correct][q]) HP[_correct][q] = 0;
				HP[_correct][q]++;
			} else {
				if (!HP[_wrong][q]) HP[_wrong][q] = 0;
				HP[_wrong][q]++;
			}
		}
	}
	//return true;
}
function hpClickCheckJCrossV5V6(hp, v, AD, q, row, col) {
	// v is the version of Hot Potatoes
	// AD is the direction ('A' or 'D')
	// make sure HP[_checks] and HP[_correct] are initialized
	if (!HP[_checks][AD]) HP[_checks][AD] = new Array();
	if (!HP[_correct][AD]) HP[_correct][AD] = new Array();
	// get clue, if any
	var clue = (hp==5) ? eval('window.'+AD) : GetJCrossClue('Clue_'+AD+'_' + q);
	// is this a question that has not been answered correctly yet?
	if (clue && !HP[_correct][AD][q]) {
		var check = false;
		var guess = GetJCrossWord(G, row, col, (AD=='D'));
		var correct = GetJCrossWord(L, row, col, (AD=='D'));
		if (guess==correct) {
			HP[_correct][AD][q] = correct;
			check = true;
		} else if (guess) {
			// make sure HP[_wrong] is initialized
			if (!HP[_wrong][AD]) HP[_wrong][AD] = new Array();
			if (!HP[_wrong][AD][q]) HP[_wrong][AD][q] = new Array();
			// check this guess has not been entered before
			var i_max = HP[_wrong][AD][q].length;
			for (var i=0; i<i_max; i++) {
				if (HP[_wrong][AD][q]==guess) break;
			}
			// add the guess if it has not been entered before
			if (i>=i_max) {
				HP[_wrong][AD][q][i] = guess;
				check = true;
			}
		}
		// update HP[_checks], if necessary
		if (check) {
			if (!HP[_checks][AD]) HP[_checks][AD] = new Array();
			if (!HP[_checks][AD][q]) HP[_checks][AD][q] = 0;
			HP[_checks][AD][q]++;
		}
	}
}
function hpClickEnter(hp, t, v, args) {
	if (t==3) { // JCross
		var q = args[0]; // clue/question number
		if (!HP[_enter][q]) HP[_enter][q] = 0;
		HP[_enter][q]++;
	}
	return true;
}
function GetJMatchQuestionDetails(hp, v) {
	var qDetails = '';
	// HP5.5 uses "I" for v5 and v6 JMatch quizzes
	// var hp5 = (window.I) ? true : false;
	// check the quiz version
	if (hp==5 || hp==6) {
		if (JMatch[1] && v==6.1) { // attempts
			qDetails += hpHiddenField('JMatch_attempts', Penalties+1);
		}
		// get number of questions
		var max_q = (hp==5 || v==6) ? Status.length : F.length;
		// get details for each question
		for (var q=0; q<max_q; q++) {
			// format 'Q' (a padded, two-digit version of 'q')
			var Q = getQ('JMatch', q);
			// add separator, if required
			if (JMatch[0] && (JMatch[1] || JMatch[2] || JMatch[3])) {
				qDetails += makeSeparator(Q);
			}
			if (JMatch[1] && (hp==5 || v==6)) { // attempts
				qDetails += hpHiddenField(Q+'attempts', Status[q][1]);
			}
			if (JMatch[2]) { // LHS text (the question)
				var x = (v==5) ? I[q][0] : (v==6) ? GetJMatchText(q, 'LeftItem') : F[q][0];
				qDetails += hpHiddenField(Q+'lhs', x);
			}
			if (JMatch[3]) { // correct answer (if any)
				var x = HP[_correct][q] ? HP[_correct][q] : '';
				qDetails += hpHiddenField(Q+'correct', x);
			}
			if (JMatch[4]) { // wrong answers (if any)
				var x = HP[_wrong][q] ? HP[_wrong][q] : '';
				qDetails += hpHiddenField(Q+'wrong', x);
			}
			if (JMatch[5]) { // checks
				var x = HP[_checks][q] ? HP[_checks][q] : 0;
				qDetails += hpHiddenField(Q+'checks', x);
			}
		} // end for
	}
	return qDetails;
}
// *********************
//   library functions
// *********************
function pad(i, l) {
	var s = (i+'');
	while (s.length<l) s = '0' + s;
	return s;
}
function getQ(section, q) {
	// Q is a padded, two-digit version of the question number, 'q', prefixed by 'section'
	return section + '_q' + (q<9 ? '0' : '') + (q+1) + '_';
}
function makeSeparator(Q) {
	return 	is_LMS() ? '' : hpHiddenField(Q.substring(0, Q.length-1), '---------------------------------');
}
function hpHiddenField(name, value, comma, forceHTML) {
	var field = '';
	var t = typeof(value);
	if (t=='string') {
		value = encode_entities(value);
	} else if (t=='object') { // array
		var values = value;
		var i_max = values.length;
		value = '';
		if (comma==null) comma = ',';
		for (var i=0; i<i_max; i++) {
			values[i] = trim(values[i]);
			if (values[i]!=null && values[i]!='') {
				value += (i==0 ? '' : comma) +  encode_entities(values[i]);
			}
		}
	}
	if (is_LMS() && !forceHTML) {
		if (value && value.indexOf && value.indexOf('<')>=0 && value.indexOf('>')>=0) {
			value = '<![CDATA[' + value + ']]>';
		}
		field = '<field><fieldname>' + name + '</fieldname><fielddata>' + value + '</fielddata></field>';
	} else {
		field = '<input type=hidden name="' + name + '" value="' + value + '">';
	}
	return field;
}
function trim(s) {
	if (s==null) s = '';
	var i = 0;
	var ii = s.length;
	while (i<ii && s.charAt(i)==' ') {
		i++;
	}
	while (ii>i && s.charAt(ii-1)==' ') {
		ii--;
	}
	return s.substring(i, ii);
}
function encode_entities(s_in) {
	var i_max = (s_in) ? s_in.length : 0;
	var s_out = '';
	for (var i=0; i<i_max; i++) {
		var c = s_in.charCodeAt(i);
		// 34 : double quote .......["] &amp;
		// 38 : single quote .......['] &apos;
		// 43 : plus sign ..........[+]
		// 44 : comma ..............[,]
		// 60 : left angle bracket .[<] &lt;
		// 62 : right angle bracket [>] &gt;
		// >=128 multibyte character
		s_out += (c==43 || c==44 || c>=128) ? ('&#x' + pad(c.toString(16), 4) + ';') : s_in.charAt(i);
	}
	return s_out;
}
// *********************
//	initialization
//	  functions
// *********************
function getTime(obj) {
	obj = obj ? obj : new Date();
	// get year, month and day
	//	for an LMS : yyyy-mm-dd
	//	for email  : DayName MonthName dd yyyy
	var s = is_LMS() ?
		obj.getFullYear() + '-' + pad(obj.getMonth()+1, 2) + '-' + pad(obj.getDate(), 2) :
		MSG[16][obj.getDay()] + ' ' + MSG[17][obj.getMonth()] + ' ' + pad(obj.getDate(), 2) + ' ' + obj.getFullYear()
	;
	// get hours, minutes and seconds (hh:mm:ss)
	s += ' ' + pad(obj.getHours(), 2) + ':' + pad(obj.getMinutes(), 2) + ':' + pad(obj.getSeconds(), 2);
	// get time difference
	//	for an LMS : +xxxx
	//	for email  : GMT+xxxx
	var x = obj.getTimezoneOffset(); // e.g. -540
	if (!isNaN(x)) {
		s += ' ' + (is_LMS() ? '' : 'GMT') + (x<0 ? '+' : '-');
		x = Math.abs(x);
		s += pad(parseInt(x/60), 2) + pad(x - (parseInt(x/60)*60), 2);
	}
	return s;
}
function getFunc(fn) {
	if (typeof(fn)=='string') {
		fn = eval('window.' + fn);
	}
	return (typeof(fn)=='function') ? fn : null;
}
function getFuncCode(fn, extraCode, anchorCode, beforeAnchor) {
	var s = '';
	var obj = getFunc(fn);
	if (obj) {
		s = obj.toString();
		var i1 = s.indexOf('{')+1;
		var i2 = s.lastIndexOf('}');
		if (i1>0 && i1<i2) {
			s = s.substring(i1, i2);
		}
	}
	if (extraCode) {
		if (anchorCode) {
			if (beforeAnchor) {
				s = replaceLast(anchorCode, extraCode + anchorCode, s);
			} else {
				s = replaceLast(anchorCode, anchorCode + extraCode, s);
			}
		} else {
			if (beforeAnchor) {
				s = extraCode + s;
			} else {
				s = s + extraCode;
			}
		}
	}
	return s;
}
function getArgsStr(args, addQuotes) {
	// make s(tring) version of function args array
	var s = '';
	var i_max = args.length;
	for (var i=0; i<i_max; i++) {
        if (addQuotes) {
            s += '"' + args[i] + '",';
        } else {
            if (s) {
                s += ',';
            }
            s += args[i];
        }
	}
	return s;
}
function getFuncArgs(fn, flag) {
	// flag==0 : return args as string
	// flag==1 ; return args as array
	var i = 0;
	var a = new Array();
	var obj = getFunc(fn);
	if (obj) {
		var s = obj.toString();
		var i1 = s.indexOf('(') + 1;
		var i2 = s.indexOf(')', i1);
		// add args to a(rray)
		while (i1>0 && i1<i2) {
			var i3 = s.indexOf(',', i1); // next comma
			if (i3<0 || i3>i2) i3 = i2;
			a[i++] = trim(s.substring(i1, i3));
			i1 = i3+1;
		}
	}
	return flag ? a : getArgsStr(a);
}
function getPrompt(fn) {
	// the LoginPrompt is the text string in the first prompt(...) statement
	//	v5 : in the StartUp function
	//	v6 : in the GetUserName function
	// Note: netscape uses double-quote as delimiter, others use single quote
	var s = getFuncCode(fn);
	var i1 = s.indexOf('prompt') + 8;
	var i2 = s.indexOf(s.charAt(i1-1), i1);
	var p = (i1>=8 && i2>i1) ? s.substring(i1, i2) : '';
	// make sure browser has decoded the unicode prompt properly
	// this check is mainly for ns4, but there may be others
	if (window.RegExp) {
		var r = new RegExp('u([0-9A-F]{4})');
		var m = r.exec(p);
		while (m) {
			p = p.replace(m[0], '&#' + parseInt(m[1], 16) + ';');
			m = r.exec(p);
		}
	}
	return p;
}
function getStartUpCode(fn) {
	// the main initialization code comes from the StartUp function
	//	v5 : the code before "UserName", if any,
	//	     and the code after the 2nd subsequent '}'
	//	v6 : the code before and after 'GetUserName();'
	//	     i.e. all the code except the call to 'GetUserName();'
	var s = getFuncCode(fn);
	var i1 = s.indexOf('GetUserName();');
	if (i1>=0) { // v6
		var i2 = i1 + 14;
	} else { // v5
		var i1 = s.indexOf('UserName');
		var i2 = s.indexOf('}', s.indexOf('}', i1+8)+1)+1;
	}
	return (0<i1 && i1<i2) ? s.substring(0, i1) + s.substring(i2) : '';
}
function is_LMS() {
	if (!window.hpCheckedForm) {
		window.hpCheckedForm = true;
		window.hpFoundForm = hpFindForm('store') ? true : false;
	}
	return hpFoundForm;
}
function hpFeedback() {
	if (FEEDBACK[0]) {
		var url = '';
		var html = '';
		if (FEEDBACK[1] && FEEDBACK[2]) { // formmail
			html += '<html><body>'
				+ '<form action="' + FEEDBACK[0] + '" method="POST">'
				+ '<table border="0">'
				+ '<tr><th valign="top" align="right">' + FEEDBACK[7] + ':</th><td>' + document.title + '</td></tr>'
				+ '<tr><th valign="top" align="right">' + FEEDBACK[8] + ': </th><td>'
			;
			if (typeof(FEEDBACK[1])=='string') {
				html += FEEDBACK[1] + hpHiddenField('recipient', FEEDBACK[1], ',', true);
			} else if (typeof(FEEDBACK[1])=='object') {
				var i_max = FEEDBACK[1].length;
				if (i_max==1) { // one teacher
					html += FEEDBACK[1][0][0] + hpHiddenField('recipient', FEEDBACK[1][0][0]+' &lt;'+FEEDBACK[1][0][1]+'&gt;', ',', true);
				} else if (i_max>1) { // several teachers
					html += '<select name="recipient">';
					for (var i=0; i<i_max; i++) {
						html += '<option value="'+FEEDBACK[1][i][1]+'">' + FEEDBACK[1][i][0] + '</option>';
					}
					html += '</select>';
				}
			}
			html += '</td></tr>'
				+	'<tr><th valign="top" align="right">' + FEEDBACK[9] + ':</th>'
				+	'<td><TEXTAREA name="message" rows="10" cols="40"></TEXTAREA></td></tr>'
				+	'<tr><td>&nbsp;</td><td><input type="submit" value="' + FEEDBACK[6] + '">'
				+ 	hpHiddenField('realname', FEEDBACK[2], ',', true)
				+ 	hpHiddenField('email', FEEDBACK[3], ',', true)
				+ 	hpHiddenField('subject', document.title, ',', true)
				+ 	hpHiddenField('title', document.title, ',', true)
				+ 	hpHiddenField('return_link_title', FEEDBACK[10], ',', true)
				+ 	hpHiddenField('return_link_url', 'javascript:self.close()', ',', true)
				+	'</td></tr></table></form></body></html>'
			;
		} else if (FEEDBACK[1]) { // url only
			if (typeof(FEEDBACK[1])=='object') {
				var i_max = FEEDBACK[1].length;
				if (i_max>1) { // several teachers
					html += '<html><body>'
						+ '<form action="' + FEEDBACK[0] + '" method="POST" onsubmit="this.action+=this.recipient.options[this.recipient.selectedIndex].value">'
						+ '<table border="0">'
						+ '<tr><th valign="top" align="right">' + FEEDBACK[7] + ':</th><td>' + document.title + '</td></tr>'
						+ '<tr><th valign="top" align="right">' + FEEDBACK[8] + ': </th><td>'
					;
					html += '<select name="recipient">';
					for (var i=0; i<i_max; i++) {
						html += '<option value="'+FEEDBACK[1][i][1]+'">' + FEEDBACK[1][i][0] + '</option>';
					}
					html += '</select>';
					html += '</td></tr>'
						+	'<tr><td>&nbsp;</td><td><input type="submit" value="' + FEEDBACK[6] + '">'
						+	'</td></tr></table></form></body></html>'
					;
				} else if (i_max==1) { // one teacher
					url = FEEDBACK[0] + FEEDBACK[1][0][1];
				}
			} else if (typeof(FEEDBACK[1])=='string') {
				url = FEEDBACK[0] + FEEDBACK[1];
			}
		} else {
			url = FEEDBACK[0];
		}
		if (url || html) {
			var w = openWindow(url, 'feedback', FEEDBACK[4], FEEDBACK[5], 'RESIZABLE,SCROLLBARS', html);
			if (!w) {
				 // unable to open popup window
				alert(MSG[18]);
			}
		}
	}
}
// ********************
//	intercept clicks
// ********************
function hpNewFunction(f, a, s) {
    if (window.C && C.safari) {
        if (f=='CheckAnswers') {
            if (s.indexOf('TotalChars-State[i].HintsAndChecks/')>=0) {
                // special fix for "CheckAnswers" in JCloze
                s = s.replace(/TotalChars-State\[i\]\.HintsAndChecks/g, '(TotalChars-State[i].HintsAndChecks)');
            }
            if (s.indexOf('TotalChars-GapList[x][1].HintsAndChecks/')>=0) {
                // special fix for "CheckAnswers" in JCloze (Find-It)
                s = s.replace(/TotalChars-GapList\[x\]\[1\]\.HintsAndChecks/g, '(TotalChars-GapList[x][1].HintsAndChecks)');
            }
            if (s.indexOf('CorrectLetters-Penalties/')>=0) {
                // special fix for "CheckAnswers" in JMatch
                s = s.replace(/CorrectLetters-Penalties/g, '(CorrectLetters-Penalties)');
            }
            if (s.indexOf('TotCorrectChoices-Penalties/')>=0) {
                // special fix for "CheckAnswers" in JMix (v6)
                s = s.replace(/TotCorrectChoices-Penalties/g, '(TotCorrectChoices-Penalties)');
            }
            if (s.indexOf('TotalCorrect-Penalties/')>=0) {
                // special fix for "CheckAnswers" in JMix (v6+) Drag-and_Drop
                s = s.replace(/TotalCorrect-Penalties/g, '(TotalCorrect-Penalties)');
            }
        }
        if (s.indexOf('replace(\\[')>=0) {
            s = s.replace(/\\\[/g, '/\\[');
            s = s.replace(/\\\]/g, '\\]/g');
        }
        if (s.indexOf('for (i')>=0 || s.indexOf('for (x')>=0) {
            s = s.replace(/for \(/g, 'for (var ');
        }
        eval('window.' + f + '=function(' + getArgsStr(a) + '){' + s + '}');
    } else {
        eval('window.' + f + '=new Function(' + getArgsStr(a, true) + 's);');
    }
}
function hpInterceptFeedback() {
	// modify the function which writes feedback
	// 	v6: ShowMessage(Feedback)
	//		but Rhubarb prints score in other functions, so use 'CheckFinished'
	//	v5: WriteFeedback(Feedback)
	//	v4: WriteFeedback(Stuff)
	//	v3: WriteFeedback(Feedback) [except JMatch]
	//	v3: CheckAnswer()           [JMatch only]
	var f = '';
	if (window.CheckWord) { // Rhubarb
		f = 'CheckFinished';
		window.FEEDBACK = null;
	} else if (window.ShowText) { // Sequitur
		f = 'CheckAnswer';
		window.FEEDBACK = null;
	} else { // JBC, JCloze, JCross, JMatch, JMix, JQuiz
		f = window.ShowMessage ? 'ShowMessage' : window.WriteFeedback ? 'WriteFeedback' : 'CheckAnswer';
	}
	if (f) {
		var s = getFuncCode(f) + 'Finish();';
		var a = getFuncArgs(f, true);
		if (a[0] && window.FEEDBACK && FEEDBACK[0]) {
			s = a[0] + "+='<br /><br />" + '<a href="javascript:hpFeedback();">' + FEEDBACK[6] + "</A>';" + s;
		}
        hpNewFunction(f, a, s);
	}
}
function hpInterceptHints() {
	// modify the function which shows hints
	//	JBC:    none
	//	JCloze  v3-v6: ShowHint()
	//	JCross  v3: Cheat(), v4: ShowHint(), v5-v6[HP5]: ShowHint(Across,x,y,BoxName), v6[HP6]: ShowHint(Across,ClueNum,x,y,BoxId)
	//	JMatch: none
	//	JMix    v5-v6: CheckAnswer(CheckType=1)
	//	JQuiz   v3: CheckAnswer(ShowHint=true, QNum), v4: CheckAnswer(ShowHint=true), v5-v6[HP5]: CheckAnswer(ShowHint=true,QNum), v6[HP6]: ShowHint(QNum)
	var x = ''; // extra code, if any
	if (window.Cheat) {
		// JCross v3 ?
	} else if (window.ShowHint) {
		var f = 'ShowHint';
		var a = getFuncArgs(f, true);
		if (a.length==0) {
			if (window.FindCurrent) {
				// JCloze v3-v6
				x = 'var q=window.Locked?-1:FindCurrent();if(q>=0&&GetHint(q))hpClick(1,q);';
			} else {
				// JCross v4
				// work out which box would have a hint added
				// work out which question that box is part of using GridMap and WinLetters
			}
		} else if (a[0]=='Across') {
			if (a[1]=='ClueNum') {
				// JCross v6 [HP6]
				x = "var args=new Array(ClueNum,Across?'A':'D');hpClick(1,args);";
			} else if (a[1]=='x' && a[2]=='y') {
				// JCross v5-v6 [HP5]
				x = "var args=new Array(C[x][y],Across?'A':'D');hpClick(1,args);";
			}
		} else if (a[0]=='QNum') {
			// JQuiz v6[HP6]
			x = 'hpClick(1,QNum);';
		}
	} else if (window.Hint) {
		// Rhubarb
		var f = 'Hint';
		var a = getFuncArgs(f, true);
		x = 'hpClick(1,0);'; // question number is always zero

	} else if (window.CheckAnswer) {
		var f = 'CheckAnswer';
		var a = getFuncArgs(f, true);
		if (a[0]=='ShowHint') {
			if (a[1]=='QNum') {
				// JQuiz v3, v5-v6[HP5]
				x = 'if(ShowHint)hpClick(1,QNum);';
			} else {
				// JQuiz v4
				x = 'if(ShowHint)hpClick(1,QNum-1);'; // QNum is a global variable
			}
		} else if (a[0]=='CheckType') {
			// JMix v5-v6
			x = 'if(CheckType==1)hpClick(1,0);'; // question number is always 0;
		}
	}
	// add the e(x)tra code, if any, to the start of the hint (f)unction
	if (x) {
		var s = getFuncCode(f, x, '', true);
        hpNewFunction(f, a, s);
	}
}
function hpInterceptClues() {
	// modify the function which shows clues (or ShowAnswers in JQuiz)
	//	JBC:    none
	//	JCloze  v3-v6: ShowClue(ItemNum)
	//	JCross  v3-v4: ShowClue(ClueNum), v5-v6: ShowClue(ClueNum,x,y)
	//	JMatch  none
	//	JMix    none
	//	JQuiz   ShowAnswers(QNum)
	var x = ''; // extra code, if any
	if (window.ShowClue) {
		var f = 'ShowClue';
		var a = getFuncArgs(f, true);
		if (a[0]=='ItemNum') {
			// JCloze (v3-v6)
			x = 'if(!window.Locked)hpClick(2,ItemNum);'; // v6 [HP6] uses window.Locked
		} else if (a[0]=='ClueNum') {
			if (a[1]=='x' && a[2]=='y') {
				if (window.A && window.D) {
					// JCross v5-v6 [HP5]
					x = 'if(A[ClueNum]||D[ClueNum])hpClick(2,ClueNum);';
				} else if (document.getElementById) {
					// JCross v6 [HP6]
					x = "if(document.getElementById('clue_' + ClueNum)||document.getElementById('Clue_D_' + ClueNum))hpClick(2,ClueNum);";
				}
			} else {
				if (window.AClues && window.DClues) {
					// JCross v3-v4
					x = 'if(AClues[ClueNum]||DClues[ClueNum])hpClick(2,ClueNum);';
				}
			}
		}
	}
	// JQuiz: there is no "ShowClue" function but there is a "ShowAnswer" function
	if (window.ShowAnswers) {
		var f = 'ShowAnswers';
		var a = getFuncArgs(f, true);
		if (window.State) {
			if (window.ShowMessage) {
				// JQuiz v6 [HP6]
				x = 'if(State[QNum][0]<1)hpClick(2,QNum);';
			} else if (window.WriteFeedback) {
				// JQuiz v3-v4
				x = 'if(State[QNum-1][0]<1)hpClick(2,QNum-1);';
			}
		} else if (window.Status) {
			// JQuiz v5-v6 [HP5]
			x = 'if(Status[QNum][0]<0)hpClick(5,QNum);';
		}
	}
	// add the e(x)tra code, if any, to the start of the clue (f)unction
	if (x) {
		var s = getFuncCode(f, x, '', true);
		var s = getFuncCode(f, '', '', true);
        hpNewFunction(f, a, s);
	}
}
function hpInterceptChecks() {
	// modify the function which handles checks
	//	JBC:    none
	//	JCloze  CheckAnswers()
	//		NB: Rottmeier Find-It 3a: CheckText(GapState,GapId)
	//	JCross  none
	//	JMatch  HP5 v3, v5, v6: CheckAnswer(), HP5 v4: CheckResults(), HP6: CheckAnswers()
	//	JMix    CheckAnswer(CheckType)
	//	JQuiz
	//		HP5: CheckAnswer(ShowHint, QNum)
	//		HP6: CheckMCAnswer, CheckMultiSelAnswer, CheckShortAnswer
	//	Rhubarb  CheckWord(InputWord)
	//	Sequitur CheckAnswer(Chosen, Btn)
	// HP6 JQuiz has three "Check Answer" functions
	var f = new Array('CheckMCAnswer', 'CheckMultiSelAnswer', 'CheckShortAnswer');
	for (var i=0; i<f.length; i++) {
		if (eval('window.' + f[i])) {
			var a = getFuncArgs(f[i], true);
			var x = "";
			if (f[i]=='CheckMCAnswer') {
				x += "var args=new Array(QNum,I[QNum][3][ANum][0]);";
			} else if (f[i]=='CheckShortAnswer') {
				x += ""
				+ "var obj=document.getElementById('Q_'+QNum+'_Guess');"
				+ "var args=new Array(QNum,obj.value);"
				;
			} else if (f[i]=='CheckMultiSelAnswer') {
				x += ""
				+ "var g='';"
				+ "for (var ANum=0; ANum<I[QNum][3].length; ANum++){"
				+ 	"var obj=document.getElementById('Q_'+QNum+'_'+ANum+'_Chk');"
				+ 	"if (obj.checked)g+=(g?'&#43;':'')+I[QNum][3][ANum][0];"
				+ "}"
				+ "var args=new Array(QNum,g);"
				;
			}
			if (x) {
				x = "if(!Finished&&State[QNum].length&&State[QNum][0]<0){" + x + "hpClick(3,args)}";
				var s = getFuncCode(f[i], x, '', true);
                hpNewFunction(f[i], a, s);
			}
		}
	}
	var f = ''; // function name
	var x = ''; // extra code, if any
	if (window.CheckAnswer) {
		f = 'CheckAnswer';
		var a = getFuncArgs(f, true);
		if (a[0]=='ShowHint') {
			if (a[1]=='QNum') {
				// JQuiz v3, v5-v6[HP5]
				x = 'if(!ShowHint&&Status[QNum][0]<1)hpClick(3,QNum);';
			} else {
				// JQuiz v4
				x = 'if(!ShowHint&&State[QNum-1][0]<1)hpClick(3,QNum-1);'; // QNum is a global variable
			}
		} else if (a[0]=='CheckType') {
			// JMix v5-v6
			x = 'if(CheckType==0)hpClick(3,0);'; // question number is always 0;
		} else if (a[0]=='Chosen') {
			// Sequitur
			x = 'if (!(CurrentNumber==TotalSegments||AllDone||Btn.innerHTML==IncorrectIndicator))hpClick(3,Chosen);';
		}
	} else if (window.CheckWord) {
		f = 'CheckWord';
		var a = getFuncArgs(f, true);
		if (a[0]=='InputWord') {
			// Rhubarb
			x = 'if(!window.AllDone)hpClick(3,InputWord);';
		}
	} else if (window.CheckText && !window.CheckAnswers) { // Rottmeier Find-It (3a)
		f = 'CheckText';
		var a = getFuncArgs(f, true);
		if ((a[0]=='bool' && a[1]=='item') || (a[0]=='GapState' && a[1]=='GapId')) {
			x = 'if(!window.Finished&&'+a[0]+')hpClick(3,'+a[1]+');';
		}
	}
	if (f) {
		var s = getFuncCode(f, x, '', true);
        hpNewFunction(f, a, s);
	}
	// JMatch has three possible check functions, depending on the version
	// (NB: other quiz types also have these functions)
	var f = new Array('CheckAnswers', 'CheckAnswer', 'CheckResults');
	for (var i=0; i<f.length; i++) {
		if (eval('window.' + f[i])) {
			var a = getFuncArgs(f[i], true);
			if (a.length==0) {
				var s = getFuncCode(f[i], "hpClick(3);", '', true);
                hpNewFunction(f[i], a, s);
				break; // out of the loop
			}
		}
	}
}
// ***************
//  fix IE5 and NS6
// ***************
// add Array.push if required (allows v6 quizzes to run on ie5win)
if (Array.prototype && Array.prototype.push==null) {
	Array.prototype.push = new Function("x", "this[this.length]=x");
}
// add attachEvent function, if required (allows HP5 v6 quizzes to run on ie5mac)
// 	NOTE: to allow v6 quizzes on ie5mac, the following code
// 	needs to be inserted BEFORE the Hot Potatoes javascript
if (window.attachEvent==null) {
	window.attachEvent = new Function('evt', 'fn', 'eval("window."+evt+"="+fn)');
}
if (document.attachEvent==null) {
	document.attachEvent = new Function('evt', 'fn', 'eval("document."+evt+"="+fn)');
}
// fix the ShowMessage function for NS6
// by removing calls to a button's "focus()" method
if (navigator.userAgent.indexOf("Netscape6")>=0 && window.ShowMessage) {
	var s = ShowMessage.toString();
	var r = new RegExp('document\\.getElementById\\((\'|")FeedbackOKButton(\'|")\\)\\.focus\\(\\);', 'gi');
	s = s.substring(s.indexOf('{')+1, s.lastIndexOf('}')).replace(r, '');
	window.ShowMessage = new Function('Feedback', s);
}
// ns6.0 (in JMix at least) has an error in the FocusAButton function too
// this could be fixed as follows ...
//if (window.FocusAButton) {
//	window.FocusAButton = new Function('return true');
//}
// however, ns6.0 then crashes completely when the mouse moves over a link, so don't bother
// Hot Potatoes quiz sniffing
// === v3 ===
// JBC uses "QuizForm", which contains elements called "Q*_**" (* and ** start at 1)
// JCloze uses "Cloze" form
// JCross uses "Crossword" form
// JMatch uses "QuizForm", which contains elements called "1,2,3..x" and "x+1,x+2...",
// 	and "CheckForm" form, which contains an element called "ScoreBox"
// 	it is also the only HP quiz type to use an array called "CorrectAnswers"
// JQuiz uses "QForm*" forms (* starts at 1), which each contain an element called "Guess"
// === v4 ===
// JBC uses "QForm" form in "QuestionDiv", which contains elements called "FB* (* starts at 0)"
// JCloze uses "Cloze" form in "QuestionDiv"
// JCross uses "Crossword" form in "CWDiv"
// JMatch uses "ExCheck" form in "TitleDiv"
// (no JMix in hp4)
// JQuiz uses "QForm" form in "QuestionDiv", which contains an element called "Answer"
// === v5 ===
// JBC uses "QForm" form, which contains elements called "FB_*_**" (* and ** start at 0)
// JCloze uses "Cloze" form
// JCross writes out "AnswerForm" from a variable called "GetAnswerOpener"
//	HP5.3: uses "AnswerForm" in "BottomFrame"
//	HP5.5: uses "AnswerForm" in "TopFrame", but it is only there when an answer is being input
// JMatch uses "QForm" form, which contains elements called "sel*" (which disappear by the time the quiz is finished)
// JMix uses "ButtonForm"
// JQuiz uses "QForm*" and "Buttons*" (one for each question)
// === v6 ===
// JBC uses "QForm" form (elements have no name or id)
// JCloze uses "Cloze" form (elements have no name or id)
// JCross does not use any forms,
//	HP5: has "GridDiv" in "MainDiv"
//	HP6: has "Clues" table in "MainDiv"
// JMatch has "MatchDiv" in "MainDiv"
//	HP5: uses "QForm" form, which contains elements called "sel*"
// 	HP6: uses "QForm" form, which contains elements called "s*_**"
// JMix does not use any forms, but has "SegmentDiv" in "MainDiv"
// JQuiz
//	HP5: uses "QForm" form, which contains an element called "Guess"
//	HP6: has "Questions" ordered list in "MainDiv"
// === v6+ ===
// JMatch has DIVs called "D*" and  "F*" (* starts at 0)
// JMix has DIVs called "D*" and  "Drop*" (* starts at 0)
// useful sniffing tools (Cut and Paste to browser address box)
//javascript:var s="";var x=new quiz_obj();for(X in x)s+=","+X+"="+x[X];alert(s.substring(1));
//javascript:var s="";var x=document.layers;for(var i=0;i<x.length;i++)s+=","+x[i].name;alert(s.substring(1))
//javascript:var s="";var x=document.forms;for(var i=0;i<x.length;i++)s+=","+x[i].id;alert(s.substring(1))
//javascript:var s="";var x=document.forms;for(var i=0;i<x.length;i++)s+=","+x[i].name;alert(s.substring(1))
//javascript:var s="";var x=document.forms.QForm.elements;for(var i=0;i<x.length;i++)s+=","+x[i].id;alert(s.substring(1))
//javascript:var s="";var x=document.forms.QForm.elements;for(var i=0;i<x.length;i++)s+=","+x[i].name;alert(s.substring(1))
function hpDetectQuiz() {
	// "sniff" (=detect) the quiz's type and intended browser version
	// and cache the values in a global variable called "quiz"
	// Hot Potatoes version
	//	5 : HP5.3 (mac) or HP5.5 (win)
	//	6 : HP6.0 (mac) or HP6.0 (win)
	// intended browser version
	//	3   : ns3, ie3 (frames)
	//	4   : ns4, ie4 (cross browser dhtml)
	//	5   : ie5 (frames, send results via CGI)
	//	6   : ie6, op7, gecko (w3 standards)
	//	6.1 : "drag and drop" versions of JMatch and JMix v6
	// quiz type
	// 	0 : unknown
	//	1 : jbc
	//	2 : jcloze
	//	3 : jcross
	//	4 : jmatch
	//	5 : jmix
	//	6 : jquiz
	//	7 : rhubarb (TexToys)
	//	8 : Sequitur (TexToys)
	// rottmeier quiz type
	//	1 : drop-down (JCloze)
	//	2 : find-it (JCloze)
	// shortcut to window object
	var w = window;
	// create the global "quiz" object, if necessary
	if (!w.quiz) w.quiz = new Object();
	// Hot Potatoes version
	// 	HP6 v6:    Client()
	// 	HP5 v4-v5: BrowserCheck()
	//	    v3:    WinStringToMac() [JCloze, JCross, JQuiz]
	//	    v3:    winrightchar     [JBC, JMatch]
	//	    v3:    DownTime()       [JBC, JCloze, JQuiz]
	if (!quiz.hp) {
		quiz.hp = (w.Client) ? 6 : (w.BrowserCheck) ? 5 : (w.WinStringToMac || w.winrightchar) ? 5 : -1;
	}
	// check the version and type are not already set
	if (!quiz.v || !quiz.t) {
		// initialize version and type
		var v = 0;
		var t = 0;
		// set shortcuts to DOM objects
		var d = document;
		var f = d.forms;
		if (f.QuizForm && f.CheckForm && w.CorrectAnswers) {
			v = 3;
			t = 4; // jmatch
		} else if (w.FeedbackFrame && w.CodeFrame) {
 			v = 3;
			f = CodeFrame.document.forms;
			t = (f.QuizForm) ? 1 : (f.Cloze) ? 2 : (f.Crossword) ? 3 : (f.QForm1) ? 6 : 0;
		} else if (w.DynLayer) {
			v = 4;
			if (d.layers) {
				// for NS4, adjust "f" to point to a forms object in a layer
				var lyr = d.QuestionDiv || d.CWDiv || d.TitleDiv || null;
				if (lyr) f = lyr.document.forms;
			}
			t = (f.QForm && f.QForm.FB0) ? 1 : (f.Cloze) ? 2 : (f.Crossword) ? 3 : (f.ExCheck) ? 4 : (f.QForm && f.QForm.Answer) ? 6 : 0;
		} else if (w.TopFrame && w.BottomFrame) {
			v = 5;
			f = BottomFrame.document.forms;
			t = (f.QForm && f.QForm.elements[0].name.substring(0,3)=='FB_') ? 1 : (f.Cloze) ? 2 : (w.GetAnswerOpener && GetAnswerOpener.indexOf('AnswerForm')>=0) ? 3 : (f.QForm && w.RItems) ? 4 : (f.ButtonForm) ? 5 : (f.QForm0 && f.Buttons0) ? 6 : 0;
		} else if (hpObj(d, 'MainDiv')) {
			v = 6;
			var obj = (f.QForm) ? f.QForm.elements : null;
			t = (obj && obj.length>0 && obj[0].id=='') ? 1 : (f.Cloze) ? 2 : (hpObj(d, 'GridDiv') || hpObj(d, 'Clues')) ? 3 : hpObj(d, 'MatchDiv') ? 4 : hpObj(d, 'SegmentDiv') ? 5 : ((f.QForm && f.QForm.Guess) || hpObj(d, 'Questions')) ? 6 : 0;
		} else if (hpObj(d, 'D0')) {
			v = 6.1; // drag and drop (HP5 and HP6)
			t = (hpObj(d, 'F0')) ? 4 : (hpObj(d, 'Drop0')) ? 5 : 0;
		} else if (w.Words && f.Rhubarb) {
			v = 6;
			t = 7; // rhubarb (TexToys)
		} else if (w.Segments && hpObj(d, 'Story')) {
			v = 6;
			t = 8; // sequitur (TexToys)
		}
		quiz.v = v; // intended browser version
		quiz.t = t; // quiz type
	}
}
function hpRottmeier() {
	hpDetectQuiz();
	if (typeof(quiz.r)=='undefined') { // first-time only
		quiz.r = 0;
		if (quiz.t==2) { // JCloze
			if (quiz.hp==5) { // HP5
				// ??
			} else if (quiz.hp==6) { // HP6
				if (window.Create_StateArray) { // Rottmeier
					var obj = new Create_StateArray();
					if (typeof(obj.GapLocked)=='boolean') {
						quiz.r = 1; // drop-down (v2.4)
					} else if (typeof(obj.ErrorFound)=='boolean') {
						if (typeof(obj.GapSolved)!='boolean') {
							quiz.r = 2.1; // find-it (v3.1a)
						} else {
							quiz.r = 2.2; // find-it (v3.1b)
						}
					}
					obj = null; // prevents memory leakage on some versions of IE
				}
			}
		}
	}
	return quiz.r;
}
function hpVersion() {
	hpDetectQuiz();
	return quiz.hp;
}
function hpQuizType() {
	hpDetectQuiz();
	return quiz.t;
}
function hpQuizVersion() {
	hpDetectQuiz();
	return quiz.v;
}
function hpScoreEngine(score_i, a, s, aa, ss, count_c, count_i) {
	// calculate the score for the quiz so far
	// score_i : amount by which to increment "score"
	// a  : outer array
	// s  : condition, if any, on outer array (=a)
	//      if true, the score will be incremented by "score_i"
	// aa : inner array, if any
	// ss : condition, if any, on inner array (=aa)
	// count_c : condition, if any, on which "count" is to be incremented
	// count_i : amount by which to increment "count"
	// "a" and "aa" may be passed as arrays or strings containing the name of an array
	// "s" and "ss" are strings containing an expression to be eval(uated)
	// "score_i", "count_i" and "count_c" strings containing an expression to be eval(uated)
	var score = 0;
	var count = 0;
	// set default condition to increment "count", and amount by which to increment the count
	if (count_c==null) count_c = "true";
	if (count_i==null) count_i = "1";
	// set length of outer array. if any
	var l = (typeof(a)=="string") ? eval(a + ".length") : a ? a.length : 0;
	// loop through outer array
	for (var i=0; i<l; i++) {
		if (s==null && ss==null) {
			score += eval(score_i);
			if (eval(count_c)) count += eval(count_i);
		} else if (s) {
			score += eval(s) ? eval(score_i) : 0;
			if (eval(count_c)) count += eval(count_i);
		} else if (ss) {
			// set length of inner array, if any
			var ll = (typeof(aa)=="string") ? eval(aa + ".length") : aa ? aa.length : 0;
			// loop through inner array. checking inner condition
			for (var ii=0; ii<ll; ii++) {
				score += eval(ss) ? eval(score_i) : 0;
				if (eval(count_c)) count += eval(count_i);
			}
		}
	}
	if (count) {
		// get p(enalties) for JCross and JMatch (and JMix ?)
		if (window.Penalties) {
			score -= (Penalties - (hpFinished() ? 0 : 1));
		}
		// adjust count for Find-It 3a and 3b
		if (window.TotWrongChoices) {
			if (window.CheckText && !window.CheckAnswers) { // Find-It 3a
				// this seems a little odd, but will replicate behavior of CalculateScore()
				count = score + TotWrongChoices;
			} else {
				count += TotWrongChoices;
			}
		}
		score = Math.floor(100*score/count);
		if (score<0) { // just in case
			score = 0;
		}
	}
	return score;
}
function hpScore() {
	var x = ''; // score
	var hp = hpVersion();
	var t = hpQuizType();
	var v = hpQuizVersion();
	if (t==1) { // jbc
		if (v==3) x = hpScoreEngine(1, DoneStatus, "i>0 && a[i]=='0'"); // doesn't work
		else if (v==4) x = hpScoreEngine(1, DoneStatus, "a[i]==0");    // doesn't work
		else if (v==5 || v==6) x = hpScoreEngine("a[i][3]", Status, "a[i][3]");
	} else if (t==2) { // jcloze
		if (v==3 || v==4) x = hpScoreEngine("a[i]", Scores);
		else if (hp==5) x = hpScoreEngine("a[i][3]", State); // v==5 && v==6
		else if (hp==6) {
			var r = hpRottmeier();
			if (r==0) x = x = hpScoreEngine("a[i].ItemScore", State);
			else if (r==1) x = hpScoreEngine("a[i][1].Score", GapList, "a[i][1].GapLocked"); // dropdown
			else if (r==2.1) x = hpScoreEngine(1, GapList, "a[i][1].ErrorFound"); // Find-It 3a
			else if (r==2.2) x = hpScoreEngine("a[i][1].Score", GapList, "a[i][1].GapSolved"); // Find-It 3b
		}
	} else if (t==3) { // jcross
		if (v==3) x = hpScoreEngine(1, CorrectAnswers, "document.QuizForm.elements[i*2].selectedIndex==a[i]");
		else if (v==4) x = hpScoreEngine(1, WinLetters, "ConvertCase(GetBoxValue(i),1).charAt(0)==a[i].charAt(0)");
		else if (v==5 || v==6) {
            if (window.CaseSensitive) { // HP 6.2
                x = hpScoreEngine(1, L, "", "L[i]", "L[i][ii] && L[i][ii]==G[i][ii]", "L[i][ii]");
            } else {
                x = hpScoreEngine(1, L, "", "L[i]", "L[i][ii] && L[i][ii].toUpperCase()==G[i][ii].toUpperCase()", "L[i][ii]");
            }
        }
	} else if (t==4) { // jmatch
		if (v==3) x = hpScoreEngine(1, CorrectAnswers, "document.QuizForm.elements[i*2].selectedIndex==a[i]");
		else if (v==4) x = hpScoreEngine(1, Draggables, "a[i].correct=='1'");
		else if (v==5) x = hpScoreEngine(1, I, "I[i][2]<1 && I[i][0].length>0 && Status[i][0]==1");
		else if (v==6) x = hpScoreEngine(1, Status, "Status[i][0]==1");
		else if (v==5.1 || v==6.1) x = hpScoreEngine(1, D, "D[i][2]==D[i][1] && D[i][2]>0", "", "", "i<F.length");
	} else if (t==5) { // jmix
		// there was no v3 or v4 of JMix
		if (v==5 || v==6 || v==6.1) x = Math.floor(100*(Segments.length-Penalties)/Segments.length);
	} else if (t==6) { // jquiz
		if (hp==5) {
			if (v==3 || v==4) x = hpScoreEngine("a[i][4]/10", State, "a[i][0]==1");
			else if (v==5 || v==6) x = hpScoreEngine("a[i][4]/10", Status, "a[i][0]==1", "", "", "true", "1");
		} else if (hp==6) {
			if (v==6) x = hpScoreEngine("I[i][0]*a[i][0]", State, "a[i]&&a[i][0]>=0", "", "", "a[i]", "I[i][0]");
		}
	} else if (t==7) { // rhubarb
		if (v==6) {
			x = Math.floor(100*Correct/TotalWords);
		}
	} else if (t==8) { // sequitur
		if (v==6) {
			var myTotalPoints = TotalPoints - (hpFinished() ? 0 : (OptionsThisQ-1));
			x = Math.floor(100*ScoredPoints/myTotalPoints);
		}
	}
	return x; // result
}
function hpFinishedEngine(a, s, aa, ss) {
	// determine whether or not all quistions in a quiz are finished
	// a  : outer array
	// s  : condition, if any, on outer array
	//      if true for any element in "a", the quiz is NOT finished
	// aa : inner array, if any
	// ss : condition, if any, on inner array
	//      if true for any element in "aa", the quiz is NOT finished
	// the arrays "a" and "aa" may be passed as arrays or strings to be eval(uated)
	// the conditions "s" and "ss" are specified as strings to be eval(uated)
	// assume a positive result
	var x = true;
	// set length of outer array. if any
	var l = (typeof(a)=="string") ? eval(a + ".length") : a ? a.length : 0;
	// loop through outer array
	for (var i=0; i<l; i++) {
		// do outer condition, if any
		if (s && eval(s)) x = false;
		// set length of inner array, if any
		var ll = (typeof(aa)=="string") ? eval(aa + ".length") : aa ? aa.length : 0;
		// loop through inner array. checking inner condition
		for (var ii=0; ii<ll; ii++) {
			if (ss && eval(ss)) x = false;
		}
	}
	return x;
}
function hpTimedOut() {
	// v5 uses "min" and "sec"
	// v6 uses Seconds
	return (typeof(self.Seconds)=='number' && Seconds==0) || (typeof(self.min)=='number' && min==0 && typeof(self.sec)=='number' && sec==0);
}
function hpFinished() {
	// assume false result
	var x = false;
	var hp = hpVersion();
	var t = hpQuizType();
	var v = hpQuizVersion();
	if (t==1) { // jbc
		if (v==3) x = hpFinishedEngine(DoneStatus, "i>0 && a[i]=='0'");
		else if (v==4) x = hpFinishedEngine(DoneStatus, "a[i]==0");
		else if (v==5 || v==6) x = hpFinishedEngine(Status, "a[i][0]==0");
	} else if (t==2) { // jcloze
		var r = hpRottmeier();
		if (r==1) x = hpFinishedEngine(GapList, "a[i][1].GapLocked==false"); // drop-down
		else if (r==2.1) x = hpFinishedEngine(GapList, "a[i][1].ErrorFound==false"); // find-it 3a
		else if (r==2.2) x = hpFinishedEngine(GapList, "a[i][1].GapSolved==false"); // find-it 3b
		else if (v==3 || v==4 || v==5 || v==6) x = hpFinishedEngine(I, "CheckAnswer(i)==-1");
		// also:   else if (v==5 || v==6) x = hpFinishedEngine(State, "a[i][4]!=1")
	} else if (t==3) { // jcross
		if (v==3) x = hpFinishedEngine(document.Crossword.elements, "ConvertCase(is.mac?unescape(MacStringToWin(a[i].value)):a[i].value,1)!=Letters[i]");
		else if (v==4) x = hpFinishedEngine(WinLetters, "ConvertCase(GetBoxValue(i),1).charAt(0) != a[i].charAt(0)");
		else if (v==5 || v==6) {
            if (window.CaseSensitive) { // 6.2
                x = hpFinishedEngine(L, "", "L[i]", "L[i][ii] && L[i][ii]!=G[i][ii]");
            } else {
                x = hpFinishedEngine(L, "", "L[i]", "L[i][ii] && L[i][ii].toUpperCase()!=G[i][ii].toUpperCase()");
            }
		}
	} else if (t==4) { // jmatch
		if (v==3) x = hpFinishedEngine(CorrectAnswers, "document.QuizForm.elements[i*2].selectedIndex != a[i]");
		else if (v==4) x = hpFinishedEngine(Draggables, "a[i].correct!='1'");
		else if (v==5) x = hpFinishedEngine(I, "I[i][2]<1 && I[i][0].length>0 && Status[i][0]<1 && GetAnswer(i)!=I[i][3]");
		else if (v==6) x = hpFinishedEngine(Status, "Status[i][0]<1");
		else if (v==5.1 || v==6.1) x = hpFinishedEngine(D, "", F, "F[ii][1]==D[i][1]&&D[i][1]!=D[i][2]");
	} else if (t==5) { // jmix
		// there was no v3 or v4 of JMix
		if (v==5 || v==6 || v==6.1) x = !hpFinishedEngine(Answers, "a[i].join(',')=='" + GuessSequence.join(',') + "'");
	} else if (t==6) { // jquiz
		if (v==3 || v==4) x = hpFinishedEngine(State, "a[i][0]==0");
		else if (v==5 || v==6) {
			if (hp==5) x = hpFinishedEngine(Status, "a[i][0]<1");
			else if (hp==6) x = hpFinishedEngine(State, "a[i] && a[i][0]<0");
		}
	} else if (t==7) { // rhubarb
		if (v==6) x = hpFinishedEngine(DoneList, "a[i]==1");
	} else if (t==8) { // sequitur
		if (v==6) x = (CurrentNumber==TotalSegments || AllDone);
	}
	return x;
}
function hpObj(d, id) {
	return d.getElementById ? d.getElementById(id) : d.all ? d.all[id] : d[id];
}
function GetViewportHeight() {
	if (window.innerHeight) {
		return innerHeight;
	} else {
		if (hpIsStrict()) {
			return document.documentElement.clientHeight;
		} else {
			return document.body.clientHeight;
		}
	}
}
function hpIsStrict() {
	if (!window.hpStrictIsSet) {
		window.hpStrictIsSet = true;
		window.hpStrict = false;
		var s = document.compatMode;
		if (s && s=="CSS1Compat") { // ie6
			window.hpStrict = true;
		} else {
			var obj = document.doctype;
			if (obj) {
				var s = obj.systemId || obj.name; // n6 || ie5mac
				if (s && s.indexOf("strict.dtd") >= 0) {
					window.hpStrict = true;
				}
			}
		}
	}
	return window.hpStrict;
}
// **************
//  initialization
// **************
hpInterceptFeedback();
hpInterceptHints();
hpInterceptClues();
hpInterceptChecks();
function hpFindForm(formname, w) {
	if (w==null) w = self;
	var f = w.document.forms[formname];
	if (f==null && w.frames) {
		for (var i=0; i<w.frames.length; i++) {
				f = hpFindForm(formname, w.frames[i]);
				if (f) break;
		}
	}
	return f;
}
function Finish(quizstatus) {
	var mark = hpScore();
	window.hpForm = hpFindForm('store');
	if (hpForm) { // LMS
		hpForm.starttime.value = getTime(Start_Time);
		hpForm.endtime.value = getTime();
		hpForm.mark.value = mark;
		hpForm.detail.value = '<?xml version="1.0"?><hpjsresult><fields>'+GetQuestionDetails()+'</fields></hpjsresult>';
		if (hpForm.status) {
			if (!quizstatus) {
				// 4=completed, 3=abandoned, 2=timed-out or 1=in-progress
				quizstatus = hpFinished() ? 4 : hpTimedOut() ? 2 : 1;
			}
			hpForm.status.value = quizstatus;
		}
		if (!window.hpQuizResultsSent) {
			if (hpForm.status && quizstatus==4) {
				window.hpQuizResultsSent = true;
			}
			if (quizstatus==4) { // completed
				// wait 2 seconds for student to see feedback
				setTimeout("hpForm.submit();", 2000);
			} else {
				hpForm.submit();
			}
		}
	} else if (hpFinished()) {
		SendAllResults(mark);
	}
}
// create form to send results
if (DB[7] && DB[8] && !is_LMS()) {
	ResultForm = ''
		+ '<html><body>'
		+ '<form name="Results" action="" method="post" enctype="x-www-form-encoded">'
		+ 	hpHiddenField('recipient', '')
		+ 	hpHiddenField('subject', '')
		+ 	hpHiddenField('Exercise', '')
		+ 	hpHiddenField('realname', '')
		+ 	hpHiddenField('Score', '')
		+ 	hpHiddenField('Start_Time', '')
		+ 	hpHiddenField('End_Time', '')
		+ 	hpHiddenField('title', 'Thanks!')
		+ '</form>'
		+ '</body></html>'
	;
}
// reassign the StartUp function
var p = getPrompt(window.GetUserName || window.StartUp);
var c = getStartUpCode(window.StartUp);
if (p && c) {
    if (window.C && C.safari) {
        eval('window.StartUp=function(){QuizLogin("' + p + '")}');
        eval('window.StartQuiz=function(){' + c + '}');
    } else {
    	window.StartUp = new Function('QuizLogin("' + p + '")');
    	window.StartQuiz = new Function(c);
    }
	// "QuizLogin" finshes by calling "StartQuiz"
}
// reassign the SendResults function
window.SendResults = SendAllResults;
// set start time
var Start_Time = new Date();
//-->
