/*  lab.js:  e.g. for a mathematical "lab" assignment; requires curve.js, jQueryUI, extra.css,
	and lab.css; may use ids 'lab-model', 'lab-text', and 'lab-qa-form'; may call modelA() if
	defined; checks urlParams_.dev.

	Copyright 2011, Mathscribe, Inc.  */


"use strict";


if (document.nodeType != 9 /* Document */)
	alert("You need a newer web browser for this page to work correctly.");

var urlParams_ = function() {
	var res = {}, s = window.location.search;
	if (s)
		F.iter(function(pv) {
				var j = pv.indexOf('=');
				if (j == -1)	res[decodeURIComponent(pv)] = true;
				else
					res[decodeURIComponent(pv.substring(0, j))] =
						decodeURIComponent(pv.substring(j+1));
			}, s.substring(1).split(/[&;]/));
	return res;
}();

function focus_select(e) /* IE/Win may require a delay first if switching windows */ {
	e.focus();
	if (e.type == 'text' || e.tagName == 'TEXTAREA')	e.select();
}
function delay_focus_select(e) { setTimeout(F(focus_select, e), 10); }


function avoidInf(g) { return typeof g == 'number' && ! isFinite(g) ? NaN : g; }

function squeezeVal(e) { return e.value.replace(/\s/g, ''); }


function getModelEP(cMM, modelE) {
	if (! modelE)	modelE = $('#lab-model')[0];
	
	return F(modelE).cMM && F(modelE).cMM() == cMM ? modelE : null;
}
function dataElts(cMM, es, combineData, modelE) /* DOM elts that provide data to plot */ {
	if (! modelE)	modelE = $('#lab-model')[0];
	
	function eltData(e) {
		if (e.tagName == 'TBODY')	return M.rowsVs(e);
		if (e.tagName == 'INPUT') {
			if (typeof F(e).ok == 'number')	// note caller must init F(e).ok early for modelA()
				return parseNumber(squeezeVal(e));
			return matchPoint(squeezeVal(e)) || [NaN, NaN];
		}
		F.err(err_eltData_);
	}
	function dataF() { return F.applyF(combineData, F.map(eltData, es)); }
	function changeF() {
		var modelEP = getModelEP(cMM, modelE);
		if (modelEP)	F(modelEP).data(dataF());
	}
	$(F.slice(es)).unbind('keyup').unbind('change').bind('keyup change', changeF);
	return dataF();
}
function model(cMM /* "current mathematical model" */, indeps, deps, params, data, hideFQs,
		graphsOptions, resE)
		/* uses/modifies indeps[j].name/bounds/[step/nDecs/initVal/unitS],
		deps[i].name/bounds/f0/draw0/[formulaF/formulaClass/color/nDigits/xss/fuzzEps/unitS],
		params[k].name/bounds/userChangeF/[step/nDecs/initVal/unitS],
		graphsOptions.[gridWidth/gridHeight/userChangeF];
		calls f0(xs, i), draw0(xs, j, i, M.dt2_), [formulaF(i)],
		params[k].userChangeF(val, k, event, elt),
		[graphsOptions.userChangeF(x, j, event, elt)];
		assumes resE is a block-level element & in the document tree;
		defines F(resE).cMM/data/hideFQs functions */ {
	if (! resE) {
		if (cMM && cMM.nodeType) {
			resE = cMM;
			cMM = null;
		} else	resE = $('#lab-model')[0];
	}
	if (! params)	params = [];
	if (! data)	data = [];
	if (! hideFQs)	hideFQs = [];
	if (! graphsOptions)	graphsOptions = {};
	
	var graphsE = $('<div/>')[0], formulasE = $('<div/>').addClass('ma-formulas')[0],
		paramsE = $('<div/>').addClass('ma-params')[0];
	$(resE).empty().addClass('ma-model').append(graphsE, formulasE, paramsE);
	var resOps = F(resE), nIndeps = indeps.length, nDeps = deps.length;
	resOps.cMM = F.constant(cMM);
	
	if (data.tagName == 'TBODY')	data = dataElts(cMM, [data], F.id, resE);
	
	function f1(f0, xs, i) { return hideFQs[i] ? null : avoidInf(f0(xs, i)); }
	function draw1(draw0, xs, j, i, dt2) {
		var crc2d = dt2.crc2d;
		
		if (! hideFQs[i]) {
			draw0(xs, j, i, dt2);
			
			crc2d.globalAlpha = 0.75;
			F.iter(function(xs) { M.plot2([xs[j], deps[i].f0(xs, i)]); }, deps[i].xss || []);
		}
		
		crc2d.globalAlpha = 0.75;	//+ use xs[k] with k != j
		F.iter(function(v) {
				if (v.length + i >= nIndeps + nDeps)
					M.plot2([v[j], v[v.length - nDeps + i]]);
			}, data);
		crc2d.globalAlpha = 1;
	}
	F.iter(function(dep) {
			dep.f = F(f1, dep.f0);
			dep.draw = F(draw1, dep.draw0);
		}, deps);
	M.graphs(graphsE, indeps, deps, graphsOptions);
	
	function setFuzz() {
		F.iter(function(dep, i) {
				dep.fuzz = null;
				if (hideFQs[i] || dep.fuzzEps == Infinity)	return;
				var z = 0, n = (dep.xss || []).length;
				for (var d = 0; d < data.length; d++) {
					var v = data[d];
					if (v.length + i < nIndeps + nDeps)	continue;
					var y0 = v[v.length - nDeps + i];
					if (isNaN(y0))	continue;
					var y = dep.f0(v.slice(0, nIndeps), i);
					if (typeof y != 'number')	return;
					var dy = y - y0;
					z += dy*dy;
					n++;
				}
				if (z)	dep.fuzz = Math.sqrt(z / n);
			}, deps);
	}
	function changedFs() {
		setFuzz();
		F(graphsE).indepVals([]);
		
		$(formulasE).empty();
		F.iter(function(dep, i) {
				if (hideFQs[i])	return;
				
				var g = dep.formulaF;	/* null/undefined, string, DOM block-level element,
					array, or function */
				if (typeof g == 'function')	g = g(i);
				F.iter(function(h) {
						if (! h)	return;
						if (typeof h == 'string')	h = M(h, true);
						var h$ = $('<div/>').append(h);
						if (dep.formulaClass)	h$.addClass(dep.formulaClass);
						else	h$.css('color', dep.color);
						$(formulasE).append(h$);
					}, Array.isArray(g) ? g : [g]);
			}, deps);
	}
	changedFs();
	
	F.iter(function(param, k) {
			M.sliderParam($('<div/>').appendTo(paramsE)[0], param.name, param.bounds,
				function(val, event, elt) {
					param.userChangeF(val, k, event, elt);
					changedFs();
				}, param);
		}, params);
	
	resOps.data = function(dataP) {
		if (dataP) {
			data = dataP;
			setFuzz();
			F(graphsE).indepVals([]);
		}
		return data;
	}
	resOps.hideFQs = function(hideFQsP) {
		if (hideFQsP != null) {
			hideFQs = hideFQsP;
			changedFs();
		}
		return hideFQs;
	}
	
	return resE;
}

function showFs(cMM) {
	var modelEP = getModelEP(cMM);
	if (modelEP)	F(modelEP).hideFQs([]);
	else	alert("You're working on a different example.");
}

function checkElt(e) /* uses F(e).ok/squeezeQ */ {
	var val = e.value, ok = F(e).ok;
	if (F(e).squeezeQ || typeof ok == 'number')	val = val.replace(/\s/g, '');
	
	var okQ;
	if (ok == null)	okQ = /\S/.test(val);
	else if (typeof ok == 'number')
		okQ = Math.abs(parseNumber(val) - ok) <= 1e-10 * Math.abs(ok);
	else if (ok.constructor == RegExp /* assumes same frame */)	okQ = ok.test(val);
	else if (typeof ok == 'function')	okQ = ok(val);
	else if (typeof ok == 'boolean' && typeof e.checked == 'boolean')
		okQ = e.checked == ok;
	else	F.err(err_checkElt_);
	
	var wrongQ = $(e).hasClass('wrong');
	if (okQ && wrongQ)	$(e).removeClass('wrong');
	else if (! okQ && ! wrongQ)	$(e).addClass('wrong');
	
	return okQ;
}
function initAnswerElt(e, ok, squeezeQ) {
	F(e).ok = ok;
	if (squeezeQ)	F(e).squeezeQ = true;
}
function checkBefore(eltP, okModelFP) {
	var form = $('#lab-qa-form')[0], badEP = null;
	for (var i = 0; i < form.elements.length; i++) {
		var e = form.elements[i];
		if (e == eltP)	break;
		var okQ = checkElt(e);
		if (! okQ && ! badEP)	badEP = e;
	}
	if (badEP && ! urlParams_.dev) {
		alert('You must ' + (/\S/.test(badEP.value) ? 'correctly ' : '') +
			'complete each example before continuing.');
		delay_focus_select(badEP);
	} else if (okModelFP)	okModelFP();
	return badEP;
}

$(function() {
	if ($('#lab-model').length && self.modelA)	modelA();
	$('#lab-qa-form').
		keypress(function(event) { $(event.target).removeClass('wrong'); }).
		change(function(event) { checkElt(event.target); });
});

var x5_ = { name: 'x', bounds: [-5, 5] }, x10_ = { name: 'x', bounds: [-10, 10] };
var ineqOpSA2_ = [ '<', '\u2264' /* ≤ */, '=', '\u2265' /* ≥ */, '>' ];
function ineqOpS(nP) { return ineqOpSA2_[2 + (nP || 0)]; }
function ineqDepF(dep, y, relNP) {
	if (arguments.length < 3)	relNP = dep.ineqOpN;
	
	if (relNP == null)	return y;	// e.g. in case dep.fuzz, or sysDep() with (! stringsQ)
	return dep.name + ineqOpS(relNP) + M.numToS(y, dep.nDigits) +
		(dep.unitS ? '{'+dep.unitS+'}' : '');
}
function cubicDep(dep, coefs, xS, dotXsP, degsIncQ) /* sum(coefs[i] * (x-x0)^i), degree 3 or
		less, checks dep.[x0/ineqOpN]; coefs and dep.x0/ineqOpN may vary over time */ {
	if (xS == null)	xS = 'x';
	if (dep.name == null)	dep.name = 'y';
	function x0() { return dep.x0 != null ? dep.x0 : 0; }
	
	dep.f0 = function(xs) { return ineqDepF(dep, M.evalCoefsPoly(coefs, xs[0] - x0())); };
	dep.draw0 = function() {
		var relN = dep.ineqOpN || 0;
		M.graph2cubic(coefs, dep.x0);
		if (Math.abs(relN) <= 1)	M.stroke(relN);
		if (relN) {
			var dt2 = M.dt2_, crc2d = dt2.crc2d;
			crc2d.globalAlpha = 0.3;
			var y = dt2.ys[relN > 0 ? 1 : 0];
			M.lineTo2([dt2.xs[1], y]);
			M.lineTo2([dt2.xs[0], y]);
			M.fill();
			crc2d.globalAlpha = 1;
		}
	};
	dep.formulaF = function() {
		var x0S = M.numToS(x0()), argS = x0S == '0' ? xS : '(' + M.minusS(xS, x0S) + ')';
		return dep.name + ineqOpS(dep.ineqOpN) + M.coefsPolyS(coefs, argS, degsIncQ);
	};
	if (dotXsP)	dep.xss = F.map1(F.array, dotXsP);
	return dep;
}
function ptSlopeDep(dep, y0_m, xS, dotXsP) /* checks dep.x0, allows infinite m; y0_m and dep.x0
		may vary over time */ {
	if (xS == null)	xS = 'x';
	if (dep.name == null)	dep.name = 'y';
	function x0() { return dep.x0 != null ? dep.x0 : 0; }
	
	dep = cubicDep(dep, y0_m, xS, dotXsP);
	var draw0 = dep.draw0;
	dep.draw0 = function() {
		if (Math.abs(y0_m[1]) == Infinity) {
			var x = x0(), dt2 = M.dt2_;
			M.moveTo2([x, dt2.ys[0]]);
			M.lineTo2([x, dt2.ys[1]]);
			M.stroke();
		} else	draw0();
	};
	dep.formulaF = function() {
		var x0S = M.numToS(x0());
		if (Math.abs(y0_m[1]) == Infinity)	return xS + '=' + x0S;
		var mS = M.numToS(y0_m[1]), xmS = M.minusS(xS, x0S);
		return M.minusS(dep.name, M.numToS(y0_m[0])) + '=' +
			M.timesS(mS, x0S == '0' || mS == '1' ? xmS : '(' + xmS + ')');
	};
	return dep;
}
function stdLineDep(dep, ABC, xSP, dotXsP) /* Ax+By <=> C, checks dep.[ineqOpN]; ABC and
		dep.ineqOpN may vary over time */ {
	if (dep.name == null)	dep.name = 'y';
	
	dep.f0 = function(xs) {
		var B = ABC[1], relNP = dep.ineqOpN;
		if (! B)	return NaN;	// B is 0 or NaN
		return ineqDepF(dep, (ABC[2] - ABC[0]*xs[0]) / B, B < 0 && relNP ? - relNP : relNP);
	};
	dep.draw0 = function() {
		if (F.any(isNaN, ABC))	return;
		var A = ABC[0], B = ABC[1], C = ABC[2], relN = dep.ineqOpN || 0, dt2 = M.dt2_,
			crc2d = dt2.crc2d;
		if (A == 0 && B == 0) {
			if (C == 0 ? Math.abs(relN) <= 1 : 0 < C ? relN < 0 : relN > 0) {
				crc2d.globalAlpha = 0.3;
				var hv0 = M.xyToHv([dt2.xs[0], dt2.ys[1]], dt2),
					hv1 = M.xyToHv([dt2.xs[1], dt2.ys[0]], dt2);
				crc2d.fillRect(hv0[0], hv0[1], hv1[0] - hv0[0], hv1[1] - hv0[1]);
				crc2d.globalAlpha = 1;
			}
		} else if (Math.abs(A) < Math.abs(B)) {
			var x0 = dt2.xs[0], x1 = dt2.xs[1];
			M.moveTo2([x0, (C - A*x0) / B]);
			M.lineTo2([x1, (C - A*x1) / B]);
			if (Math.abs(relN) <= 1)	M.stroke(relN);
			if (relN) {
				crc2d.globalAlpha = 0.3;
				var y = dt2.ys[(relN > 0) == (B > 0) ? 1 : 0];
				M.lineTo2([x1, y]);
				M.lineTo2([x0, y]);
				M.fill();
				crc2d.globalAlpha = 1;
			}
		} else {
			var y0 = dt2.ys[0], y1 = dt2.ys[1];
			M.moveTo2([(C - B*y0) / A, y0]);
			M.lineTo2([(C - B*y1) / A, y1]);
			if (Math.abs(relN) <= 1)	M.stroke(relN);
			if (relN) {
				crc2d.globalAlpha = 0.3;
				var x = dt2.xs[(relN > 0) == (A > 0) ? 1 : 0];
				M.lineTo2([x, y1]);
				M.lineTo2([x, y0]);
				M.fill();
				crc2d.globalAlpha = 1;
			}
		}
	};
	dep.formulaF = function() {
		var ss = F.map1(M.numToS, ABC);
		return M.plusS(M.timesS(ss[0], xSP || 'x'), M.timesS(ss[1], dep.name)) +
			ineqOpS(dep.ineqOpN) + ss[2];
	};
	if (dotXsP)	dep.xss = F.map1(F.array, dotXsP);
	return dep;
}
function absDep(dep, coefs, xS, dotXsP, degsIncQ) /* sum(coefs[i] * abs(x-x0)^i),
		coefs.length == 2, checks dep.[x0]; coefs and dep.x0 may vary over time */ {
	if (xS == null)	xS = 'x';
	if (dep.name == null)	dep.name = 'y';
	function x0() { return dep.x0 != null ? dep.x0 : 0; }
	
	function f(x) { return M.evalCoefsPoly(coefs, Math.abs(x - x0())); }
	dep.f0 = function(xs) { return ineqDepF(dep, f(xs[0])); };
	dep.draw0 = function() {
		var dt2 = M.dt2_, h = x0(), b = dt2.xs[0], c = dt2.xs[1];
		M.moveTo2([b, f(b)]);
		if (b < h)	M.lineTo2([h, f(h)]);
		if (h < c)	M.lineTo2([c, f(c)]);
		M.stroke();
	};
	dep.formulaF = function() {
		var x0S = M.numToS(x0()), argS = '`,{|' + M.minusS(xS, x0S) + '|}';
		return dep.name + ineqOpS(dep.ineqOpN) + M.coefsPolyS(coefs, argS, degsIncQ);
	};
	if (dotXsP)	dep.xss = F.map1(F.array, dotXsP);
	return dep;
}
function circleDep(dep, r_center, xS) /* r_center may vary over time */ {
	if (xS == null)	xS = 'x';
	if (dep.name == null)	dep.name = 'y';
	
	dep.f0 = function(xs) {
		var r = r_center[0], C = r_center[1], h = xs[0] - C[0], v = Math.sqrt(r*r - h*h);
		return v ? [C[1] + v, C[1] - v] : isNaN(v) ? null : C[1];
	};
	dep.draw0 = function() {
		M.circle(r_center[0], r_center[1]);
		M.stroke();
	};
	dep.formulaF = function() {
		var rS = M.numToS(r_center[0]), CSS = F.map1(M.numToS, r_center[1]), yS = dep.name,
			hS = CSS[0] == '0' ? xS : '('+M.minusS(xS, CSS[0])+')',
			vS = CSS[1] == '0' ? yS : '('+M.minusS(yS, CSS[1])+')';
		return hS+'^2+'+vS+'^2='+
			(/^[01]$/.test(rS) ? rS : (/[^\d.]/.test(rS) ? '('+rS+')' : rS)+'^2');
	};
	return dep;
}
function sysDep(deps, stringsQ) /* uses deps[0].name/bounds/[color/nDigits/unitS/xss];
		assumes typeof deps[k].f0(xs, i) == (stringsQ ? 'string' : 'number'),
		and deps[i].formulaF a function that returns a string */ {
	var dep0 = deps[0], res = { name: dep0.name, bounds: dep0.bounds };
	F.iter(function(p) { if (dep0[p] != null) res[p] = dep0[p]; },
		['xss', 'color', 'nDigits', 'unitS']);
	res.f0 = function(xs, i) {
		var ys = F.map(function(dep) {
				var y = dep.f0(xs, i);
				if (! stringsQ)	return y;
				var h$ = $('<div/>').append(M(y, false));
				if (dep.formulaClass)	h$.addClass(dep.formulaClass);
				return h$[0];
			}, deps);
		return stringsQ ? $('<div/>').append($(ys))[0] : ys;
	};
	res.draw0 = function(xs, j, i, dt2) {
		var crc2d = dt2.crc2d;
		F.iter(function(dep) {
				var colorQ = dep.color != null && dep.color != res.color;
				if (colorQ)	crc2d.strokeStyle = crc2d.fillStyle = dep.color;
				dep.draw0(xs, j, i, dt2);
				if (colorQ)	crc2d.strokeStyle = crc2d.fillStyle = res.color;
			}, deps);
	};
	res.formulaF = function(i) {
		return F.map(function(dep) {
				var h$ = $('<div/>').append(M(dep.formulaF(i), true));
				if (dep.formulaClass)	h$.addClass(dep.formulaClass);
				else if (dep.color != null && dep.color != res.color)
					h$.css('color', dep.color);
				return h$[0];
			}, deps);
	};
	return res;
}

function parseNumber(s /* no white space */) /* returns NaN on failure */ {
	s = String(s);
	if (s.charAt(0) == '+')	s = s.substring(1);
	if (s.charAt(0) == '-')	return - parseNumber(s.substring(1));
	if (s == '')	return NaN;
	var x = Number(s);
	if (! isNaN(x))	return x;
	if (s.charAt(0) == '(' && s.charAt(s.length-1) == ')')	s = s.substring(1, s.length - 1);
	var ss = /^(-?\d+)\/(\d+)$/.exec(s);
	return ss ? ss[1] / ss[2] : NaN;
}
function parseCoef(s /* no white space */) /* returns NaN on failure */ {
	return s == '' || s == '+' ? 1 : s == '-' ? -1 : parseNumber(s);
}

function matchPoint(s /* no white space */) /* returns [x y] or null; note it's often good for
		the caller to also plot the result, e.g. via dataElts() */ {
	var ss = /^\((-?\d*[.\/]?\d*),(-?\d*[.\/]?\d*)\)$/.exec(s);
	if (! ss)	return null;
	var x = parseNumber(ss[1]), y = parseNumber(ss[2]);
	if (isNaN(x) || isNaN(y))	return null;
	return [x, y];
}

function matchSlopeInterceptEq(s /* no white space */) /* returns [m b] or null */ {
	var ss = /^y=(-?\d*[.\/]?\d*|-?\(-?\d+\/\d+\))x([-+]\d*\.?\d*)?$/i.exec(s), m, b;
	if (! ss) {
		ss = /^y=(-?\d*\.?\d*)$/i.exec(s);
		if (! ss)	return null;
		m = 0;
		b = parseNumber(ss[1]);
	} else {
		m = parseCoef(ss[1]);
		b = parseNumber(ss[2] || '0');
	}
	return isNaN(m) || isNaN(b) ? null : [m, b];
}
function matchPtSlopeEq(s /* no white space */) /* returns [y0 m x0] or null */ {
	var ss1 = /^y(|[-+]\d*\.?\d*)=(.+)$/i.exec(s);
	if (! ss1)	return null;
	var y0 = - parseNumber(ss1[1] || '0'),
		ss2 = /^(-?\d*[.\/]?\d*|-?\(-?\d+\/\d+\))(\(?x.*)?$/.exec(ss1[2]);
	if (! ss2)	return null;
	var m = parseCoef(ss2[1]), x0;
	if (! ss2[2]) {
		if (m)	return null;
		x0 = 0;
	} else {
		var ss3 = /^\(x([-+]\d*\.?\d*)\)$/i.exec(ss2[2]);
		if (! ss3) {
			ss3 = /^x([-+]\d*\.?\d*)?$/i.exec(ss2[2]);
			if (! ss3 || ss2[1] && ss3[1])	return null;
		}
		var x0 = - parseNumber(ss3[1] || '0');
	}
	return isNaN(y0) || isNaN(m) || isNaN(x0) ? null : [y0, m, x0];
}
function matchStdLineEq(s /* no white space */) /* returns [A B C] or null,
		A and B not both 0 */ {
	var ss1 = /^(.*)=(.*)$/.exec(s);
	if (! ss1)	return null;
	var ss2 = /^(.*)x(.*)$/i.exec(ss1[1]), A = 0, B = 0, C = parseNumber(ss1[2]);
	if (! ss2) {
		ss2 = /^(.*)y$/i.exec(ss1[1]);
		if (! ss2)	return null;
		B = parseCoef(ss2[1]);
	} else {
		A = parseCoef(ss2[1]);
		if (ss2[2]) {
			var ss3 = /^([-+].*)y$/i.exec(ss2[2]);
			if (! ss3)	return null;
			B = parseCoef(ss3[1]);
		}
	}
	return isNaN(A) || isNaN(B) || isNaN(C) || A == 0 && B == 0 ? null : [A, B, C];
}

function checkRegExp2EltSqueezeF(r1, r2, e) {
	return function(s /* no white space */) {
		var t = squeezeVal(e);
		return r1.test(s) ? ! r1.test(t) : r2.test(s) && ! r2.test(t);
	}
}


var msie_q_ = $.browser.msie;	//@@ elim.
//@@:
function onsubmit1_(form, labQAForm, form1 /* [, <elt names>] */) {
	if (checkBefore(null, null))	return false;
	
	form1.email.value = squeezeVal(form1.email);
	form1.e_tgt.value = squeezeVal(form1.e_tgt);
	var missingE = null;
	if (! /\S/.test(form1.realname.value))	missingE = form1.realname;
	else if (! form1.email.value)	missingE = form1.email;
	else if (! form1.e_tgt.value)	missingE = form1.e_tgt;
	if (missingE) {
		alert('Error:  Missing required input');
		delay_focus_select(missingE);
		return false;
	}
	
	var s = location.pathname + ' - ' + form1.realname.value.replace(/[^\w ',.-]/g, '_'),
		server_q = /\.k12\.[a-z]{2}\.us$/.test(form1.e_tgt.value), t = '', sep = '\n\n';
	for (var i = 0; i < labQAForm.elements.length; i++) {
		var e = labQAForm.elements[i], j;
		if (! (e.type == 'textarea' && e.ok == null)) {
			for (j = 3; j < arguments.length; j++)	if (e.name == arguments[j])	break;
			if (j == arguments.length)	continue;
		}
		t += (t ? sep : '') + e.name + ':  ' + e.value;
	}
	if (server_q) {
		form1.subject.value = s;
		form1.answers.value = t;
		form1.action = 'http://www.mathscribe.com/cgi-bin/LabSend.pl';
		form1.submit();
	} else {
		t += (t ? sep : '') + 'EMail:  ' + form1.email.value;
		var to = form1.e_tgt.value;
		form.action = 'mailto:' + encodeURIComponent(to) + '?subject=' + encodeURIComponent(s);
		if (msie_q_)	form.lab.value = t;	// needed for IE5/Mac and IE4/PC at least
		else	form.action += '&body=' + encodeURIComponent(t);
	}
	
	if (form.elements[form.elements.length - 1].value == 'Next Lab')
		form.elements[form.elements.length - 1].disabled = false;
	
	return ! server_q;
}

$(function() {
	/*@@ > rel_1:
	var form = forms[0], n = 0, s = '';
	for (var i = 0; i < form.elements.length; i++) {
		var e = form.elements[i];
		if (e.type == 'textarea') {
			n++;
			if (e.ok != null)	s += ' -' + e.name;
		} else if ((e.type == 'text' || e.type == 'radio' || e.type == 'checkbox')
		&& e.ok == null)
			s += ' +' + e.name;
	}
	alert(n + s);*/
});

