var fdjt_revision='1.5-123-ga03adcd';
var fdjt_buildhost='sbooks.net';
var fdjt_buildtime='Wed Feb 22 12:43:06 UTC 2012';
/* -*- Mode: Javascript; -*- */

/* Copyright (C) 2009-2012 beingmeta, inc.
   This file was created from several component files and is
   part of the FDJT web toolkit (www.fdjt.org)

   This program comes with absolutely NO WARRANTY, including implied
   warranties of merchantability or fitness for any particular
   purpose.

   The copyright notice of the individual files are all prefixed by
   a copyright notice of the form "Copyright (C) ...".

   Use, modification, and redistribution of this program is permitted
   under either the GNU General Public License (GPL) Version 2 (or
   any later version) or under the GNU Lesser General Public License
   (version 3 or later).

   These licenses may be found at www.gnu.org, particularly:
   http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
   http://www.gnu.org/licenses/lgpl-3.0-standalone.html

*/

var fdjt_versions=((fdjt_versions)||(new Array()));
fdjt_versions.decl=function(name,num){
    if ((!(fdjt_versions[name]))||(fdjt_versions[name]<num)) fdjt_versions[name]=num;};

// Some augmentations
if (!(Array.prototype.indexOf))
    Array.prototype.indexOf=function(elt,i){
	if (!(i)) i=0; var len=this.length;
	while (i<len) if (this[i]===elt) return i; else i++;
	return -1;};
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys
if(!Object.keys) {
    Object.keys = function(o){
	if (o !== Object(o))
            throw new TypeError('Object.keys called on non-object');
	var ret=[],p;
	for(p in o) if(Object.prototype.hasOwnProperty.call(o,p)) ret.push(p);
	return ret;}};

if (!String.prototype.trim) {
    String.prototype.trim = (function () {
	var trimLeft  = /^\s+/,
        trimRight = /\s+$/

	return function () {
	    return this.replace(trimLeft, "").replace(trimRight, "")
	}
    })()};

/* -*- Mode: Javascript; -*- */

/* Copyright (C) 2009-2012 beingmeta, inc.
   This file is a part of the FDJT web toolkit (www.fdjt.org)
   This file provides extended Javascript utility functions
   of various kinds.

   This program comes with absolutely NO WARRANTY, including implied
   warranties of merchantability or fitness for any particular
   purpose.

   Use, modification, and redistribution of this program is permitted
   under either the GNU General Public License (GPL) Version 2 (or
   any later version) or under the GNU Lesser General Public License
   (version 3 or later).

   These licenses may be found at www.gnu.org, particularly:
   http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
   http://www.gnu.org/licenses/lgpl-3.0-standalone.html

*/

var fdjtString=
    (function(){
	function fdjtString(string){
	    if ((typeof string !== 'string')&&
		(!(string instanceof String)))
		return stringify(string);
	    var output="";
	    var cmd=string.indexOf('%'); var i=1;
	    while (cmd>=0) {
		if (cmd>0) output=output+string.slice(0,cmd);
		if (string[cmd+1]==='%') output=output+'%';
		else if (string[cmd+1]==='o') {
		    var arg=arguments[i++];
		    if (typeof arg === 'string')
			output=output+"'"+arg+"'";
		    else if (typeof arg === 'number')
			output=output+arg;
		    else output=output+stringify(arg);}
		else if (string[cmd+1]==='j') {
		    var arg=arguments[i++];
		    output=output+JSON.stringify(arg);}
		else if ((string[cmd+1]==='x')&&
			 (typeof arguments[i] === 'number')&&
			 (arguments[i]>=0)&&
			 ((arguments[i]%1)>=0)) {
		    var arg=arguments[i++];
		    output=output+arg.toString(16);}
		else if (arguments[i])
		    output=output+arguments[i++];
		else if (typeof arguments[i] === 'undefined') {
		    output=output+'?undef?'; i++;}
		else output=output+arguments[i++];
		string=string.slice(cmd+2);
		cmd=string.indexOf('%');}
	    output=output+string;
	    return output;}

	fdjtString.revid="$Id$";
	fdjtString.version=parseInt("$Revision$".slice(10,-1));

	fdjtString.nbsp="\u00A0";
	fdjtString.middot="\u00B7";
	fdjtString.emdash="\u2013";
	fdjtString.endash="\u2014";
	fdjtString.lsq="\u2018";
	fdjtString.rsq="\u2019";
	fdjtString.ldq="\u201C";
	fdjtString.rdq="\u201D";

	function stringify(arg){
	    if (typeof arg === 'undefined') return '?undef?';
	    else if (!(arg)) return arg;
	    else if (arg.tagName) {
		var output="["+arg.tagName;
		if (arg.className)
		    output=output+"."+arg.className.replace(/\s+/g,'.');
		if (arg.id) output=output+"#"+arg.id;
		return output+"]";}
	    else if (arg.nodeType) {
		if (arg.nodeType===3)
		    return '["'+arg.nodeValue+'"]';
		else return '<'+arg.nodeType+'>';}
	    else if (arg.oid) return arg.oid;
	    else if (arg._fdjtid) return '#@'+arg._fdjtid;
	    else if ((arg.type)&&((arg.target)||arg.srcElement)) {
		var target=arg.target||arg.srcElement;
		var ox=arg.offsetX, oy=arg.offsetY;
		var result="["+arg.type+"@"+stringify(target)+"(m="+
		    (((arg.shiftKey===true)?"s":"")+
		     ((arg.ctrlKey===true)?"c":"")+
		     ((arg.altKey===true)?"a":"")+
		     (arg.button||0));
		if (ox) result=result+",x="+ox+",y="+oy;
		else if (arg.touches) {
		    var i=0; var n=arg.touches.length;
		    result=result+",touches="+n;}
		else if ((arg.keyCode)||(arg.charCode))
		    result=result+",kc="+arg.keyCode+",cc="+arg.charCode;
		return result+")]";}
	    else return arg;}

	var spacechars=" \n\r\t\f\x0b\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u202f\u205f\u3000\uf3ff";

	fdjtString.truncate=function(string,lim){
	    if (!(lim)) lim=42;
	    if (string.length<lim) return string;
	    else return string.slice(0,lim);}

	fdjtString.isEmpty=function(string){
	    if (typeof string === "string")  {
		var i=0; var lim=string.length;
		if (lim===0) return true;
		while (i<lim) {
		    if (spacechars.indexOf(string[i])>=0) i++;
		    else return false;}
		return true;}
	    else return false;}

	fdjtString.findSplit=function(string,split,escape){
	    var start=0;
	    var next;
	    while ((next=string.indexOf(split,start))>=0) 
		if ((escape) && (next>0) && (string[next-1]===escape))
		    start=next+1;
	    else return next;
	    return -1;};

	fdjtString.split=function(string,split,escape,mapfn){
	    if ((mapfn) || (escape)) {
		var results=[];
		var start=0; var next;
		while ((next=string.indexOf(split,start))>=0) 
		    if ((escape) && (next>0) && (string[next-1]===escape))
			start=next+1;
		else if ((mapfn) && (next>start)) {
		    results.push(mapfn(string.slice(start,next))); start=next+1;}
		else if (next>start) {
		    results.push(string.slice(start,next)); start=next+1;}
		else start=next+1;
		if (string.length>start)
		    if (mapfn) results.push(mapfn(string.slice(start)));
		else results.push(string.slice(start));
		return results;}
	    else return string.split(split);};

	fdjtString.semiSplit=function(string,escape,mapfn){
	    if ((mapfn) || (escape)) {
		var results=[];
		var start=0; var next;
		while ((next=string.indexOf(';',start))>=0) 
		    if ((escape) && (next>0) && (string[next-1]===escape))
			start=next+1;
		else if ((mapfn) && (next>start)) {
		    results.push(mapfn(string.slice(start,next))); start=next+1;}
		else if (next>start) {
		    results.push(string.slice(start,next)); start=next+1;}
		else start=next+1;
		if (string.length>start)
		    if (mapfn) results.push(mapfn(string.slice(start)));
		else results.push(string.slice(start));
		return results;}
	    else return string.split(';');};

	fdjtString.lineSplit=function(string,escapes,mapfn){
	    if ((mapfn) || (escape)) {
		var results=[];
		var start=0; var next;
		while ((next=string.indexOf('\n',start))>=0) 
		    if ((escape) && (next>0) && (string[next-1]===escape))
			start=next+1;
		else if ((mapfn) && (next>start)) {
		    results.push(mapfn(string.slice(start,next))); start=next+1;}
		else if (next>start) {
		    results.push(string.slice(start,next)); start=next+1;}
		else start=next+1;
		if (string.length>start)
		    if (mapfn) results.push(mapfn(string.slice(start)));
		else results.push(string.slice(start));
		return results;}
	    else return string.split('\n');};

	var spacechars=" \n\r\t\f\x0b\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u202f\u205f\u3000\uf3ff";
	
	function trim(string){
	    var start=0; var len=string.length; 
	    if (len<=0) return string;
	    while ((start<len)&&
		   (spacechars.indexOf(string.charAt(start))>-1))
		start++;
	    if (start===len) return "";
	    var end=len-1;
	    while ((end>start)&&(spacechars.indexOf(string.charAt(end))>-1))
		end--;
	    if ((start>0)||(end<len)) return string.slice(start,end+1);
	    else return string;}
	fdjtString.trim=trim;

	function stdspace(string){
	    var spacechars=" \n\r\t\f\x0b\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u202f\u205f\u3000\uf3ff";
	    string=string.replace(/\s+/g," ");
	    var start=0; var len=string.length; 
	    if (len<=0) return string;
	    while ((start<len)&&
		   (spacechars.indexOf(string.charAt(start))>-1))
		start++;
	    if (start===len) return "";
	    var end=len-1;
	    while ((end>start)&&(spacechars.indexOf(string.charAt(end))>-1))
		end--;
	    if ((start>0)||(end<len)) return string.slice(start,end+1);
	    else return string;}
	fdjtString.stdspace=stdspace;

	function flatten(string){
	    return string.replace(/\s+/g," ");}
	fdjtString.flatten=flatten;

	function oneline(string){
	    string=trim(string);
	    var flat=string.replace(/\s*[\f\n\r]+\s+/gm," //\u00B7 ").
		replace(/\s*[\f\n\r]+\s*/gm," // ");
	    var tight=flat.replace(/\s\s+/g,"");
	    return tight;}
	fdjtString.oneline=oneline;

	function stripMarkup(string){
	    return string.replace(/<[^>]*>/g,"");}
	fdjtString.stripMarkup=stripMarkup;

	function unEscape(string){
	    if (string.indexOf('\\')>=0)
		return string.replace(/\\(.)/g,"$1");
	    else return string;}
	fdjtString.unEscape=unEscape;

	function normstring(string){
	    return string.replace(/\W*\s\W*/g," ").toLowerCase();}
	fdjtString.normString=normstring;

	function unEntify(string) {
	    return string.replace(/&#(\d+);/g,
				  function(whole,paren) {
				      return String.fromCharCode(+paren);});}
	fdjtString.unEntify=unEntify;

	function padNum(num,digits){
	    var ndigits=
		((num<10)?(1):(num<100)?(2):(num<1000)?(3):(num<10000)?(4):
		 (num<100000)?(5):(num<1000000)?(6):(num<1000000)?(7):
		 (num<100000000)?(8):(num<1000000000)?(9):(num<10000000000)?(10):(11));
	    var nzeroes=digits-ndigits;
	    switch (nzeroes) {
	    case 0: return ""+num;
	    case 1: return "0"+num;
	    case 2: return "00"+num;
	    case 3: return "000"+num;
	    case 4: return "0000"+num;
	    case 5: return "00000"+num;
	    case 6: return "000000"+num;
	    case 7: return "0000000"+num;
	    case 8: return "00000000"+num;
	    case 9: return "000000000"+num;
	    case 10: return "0000000000"+num;
	    default: return ""+num;}}
	fdjtString.padNum=padNum;

	/* Getting initials */

	function getInitials(string){
	    var words=string.split(/\W/); var initials="";
	    var i=0; var lim=words.length;
	    while (i<lim) {
		var word=words[i++];
		if (word.length)
		    initials=initials+word.slice(0,1);}
	    return initials;}
	fdjtString.getInitials=getInitials;

	/* More string functions */

	function hasPrefix(string,prefix){
	    return ((string.indexOf(prefix))===0);}
	fdjtString.hasPrefix=hasPrefix;

	function hasSuffix(string,suffix){
	    return ((string.lastIndexOf(suffix))===(string.length-suffix.length));}
	fdjtString.hasSuffix=hasSuffix;

	function commonPrefix(string1,string2,brk,foldcase){
	    var i=0; var last=0;
	    while ((i<string1.length) && (i<string2.length))
		if ((string1[i]===string2[i])||
		    ((foldcase)&&(string1[i].toLowerCase()===string2[i].toLowerCase())))
		    if (brk)
			if (brk===string1[i]) {last=i-1; i++;}
	    else i++;
	    else last=i++;
	    else break;
	    if (last>0) return string1.slice(0,last+1);
	    else return false;}
	fdjtString.commonPrefix=commonPrefix;

	function commonSuffix(string1,string2,brk,foldcase){
	    var i=string1.length, j=string2.length; var last=0;
	    while ((i>=0) && (j>=0))
		if ((string1[i]===string2[j])||
		    ((foldcase)&&(string1[i].toLowerCase()===string2[i].toLowerCase())))
		    if (brk)
			if (brk===string1[i]) {last=i+1; i--; j--;}
	    else {i--; j--;}
	    else {last=i; i--; j--;}
	    else break;
	    if (last>0) return string1.slice(last);
	    else return false;}
	fdjtString.commonSuffix=commonSuffix;

	function stripSuffix(string){
	    var start=string.search(/\.\w+$/);
	    if (start>0) return string.slice(0,start);
	    else return string;}
	fdjtString.stripSuffix=stripSuffix;

	function arrayContains(array,element){
	    if (array.indexOf)
		return (array.indexOf(element)>=0);
	    else {
		var i=0; var len=array.length;
		while (i<len)
		    if (array[i]===element) return true;
		else i++;
		return false;}}

	function prefixAdd(ptree,string,i) {
	    var strings=ptree.strings;
	    if (i===string.length) 
		if ((strings.indexOf) ?
		    (strings.indexOf(string)>=0) :
		    (arrayContains(strings,string)))
		    return false;
	    else {
		strings.push(string);
		return true;}
	    else if (ptree.splits) {
		var splitchar=string[i];
		var split=ptree[splitchar];
		if (!(split)) {
		    // Create a new split
		    split={};
		    split.strings=[];
		    // We don't really use this, but it might be handy for debugging
		    split.splitchar=splitchar;
		    ptree[splitchar]=split;
		    ptree.splits.push(split);}
		if (prefixAdd(split,string,i+1)) {
		    strings.push(string);
		    return true;}
		else return false;}
	    else if (ptree.strings.length<5)
		if ((strings.indexOf) ?
		    (strings.indexOf(string)>=0) :
		    (arrayContains(strings,string)))
		    return false;
	    else {
		strings.push(string);
		return true;}
	    else {
		// Subdivide
		ptree.splits=[];
		var strings=ptree.strings;
		var j=0; while (j<strings.length) prefixAdd(ptree,strings[j++],i);
		return prefixAdd(ptree,string,i);}}
	fdjtString.prefixAdd=prefixAdd;

	function prefixFind(ptree,prefix,i,plen){
	    if (!(plen)) plen=prefix.length;
	    if (i===plen)
		return ptree.strings;
	    else if (ptree.strings.length<=5) {
		var strings=ptree.strings;
		var results=[];
		var j=0; while (j<strings.length) {
		    var string=strings[j++];
		    if (hasPrefix(string,prefix)) results.push(string);}
		if (results.length) return results;
		else return false;}
	    else {
		var split=ptree[prefix[i]];
		if (split) return prefixFind(split,prefix,i+1,plen);
		else return false;}}
	fdjtString.prefixFind=prefixFind;

	function paraHash(node){
	    var text=node.innerText;
	    var words=text.split(/\W*\S+\W*/g);
	    var len=words.length;
	    return "_H"+
		((len>0)?(words[0][0]):".")+
		((len>1)?(words[1][0]):".")+
		((len>2)?(words[2][0]):".")+
		((len>3)?(words[3][0]):".")+
		((len>0)?(words[len-1][0]):".")+
		((len>1)?(words[len-2][0]):".")+
		((len>2)?(words[len-3][0]):".")+
		((len>3)?(words[len-4][0]):".");}
	fdjtString.paraHash=paraHash;

	return fdjtString;})();

/* Emacs local variables
   ;;;  Local variables: ***
   ;;;  compile-command: "make; if test -f ../makefile; then cd ..; make; fi" ***
   ;;;  End: ***
*/
/* -*- Mode: Javascript; -*- */

/* Copyright (C) 2009-2012 beingmeta, inc.
   This file is a part of the FDJT web toolkit (www.fdjt.org)
   This file provides extended Javascript utility functions
    of various kinds.

   This program comes with absolutely NO WARRANTY, including implied
   warranties of merchantability or fitness for any particular
   purpose.

    Use, modification, and redistribution of this program is permitted
    under either the GNU General Public License (GPL) Version 2 (or
    any later version) or under the GNU Lesser General Public License
    (version 3 or later).

    These licenses may be found at www.gnu.org, particularly:
      http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
      http://www.gnu.org/licenses/lgpl-3.0-standalone.html

*/

/* Time functions */

var fdjtTime=
    (function (){
	function fdjtTime() {
	    return (new Date()).getTime();}
	fdjtTime.revid="$Id$";
	fdjtTime.version=parseInt("$Revision$".slice(10,-1));

	var loaded=fdjtTime.loaded=(new Date()).getTime();
	fdjtTime.tick=function(){
	    return Math.floor((new Date()).getTime()/1000);};

	fdjtTime.dateString=function(tstamp){
	    if (typeof tstamp === 'number') {
		if (tstamp<131592918600)
		    tstamp=new Date(tstamp*1000);
		else tstamp=new Date(tstamp);}
	    return tstamp.toDateString();};
	fdjtTime.timeString=function(tstamp){
	    if (typeof tstamp === 'number') {
		if (tstamp<131592918600)
		    tstamp=new Date(tstamp*1000);
		else tstamp=new Date(tstamp);}
	    return tstamp.toString();};

	function shortString(tstamp){
	    var now=new Date();
	    if (typeof tstamp === 'number') {
		if (tstamp<131592918600)
		    tstamp=new Date(tstamp*1000);
		else tstamp=new Date(tstamp);}
	    var diff=(now.getTime()-tstamp.getTime())/1000;
	    if (diff>(12*3600))
		return tstamp.toDateString();
	    else {
		var hours=tstamp.getHours();
		var minutes=tstamp.getMinutes();
		return tstamp.toDateString()+" ("+
		    ((hours<10)?"0":"")+hours+":"+
		    ((minutes===0)?"00":(((minutes<10)?"0":"")+minutes));}}
	fdjtTime.shortString=shortString;
	fdjtTime.tick2shortstring=function(tick){
	    return shortString(new Date(tick*1000));};

	fdjtTime.tick2string=function(tick){
	    return (new Date(tick*1000)).toString();};
	fdjtTime.tick2date=function(tick){
	    return (new Date(tick*1000)).toDateString();};
	fdjtTime.tick2locale=function(tick){
	    return (new Date(tick*1000)).toLocaleString();};
	fdjtTime.tick2time=function(tick){
	    return (new Date(tick*1000)).toTimeString();};

	fdjtTime.secs2string=function(interval){
	    if (interval===1)
		return _("%1 second",interval);
	    else if (interval<10)
		return _("%1 seconds",interval);
	    else if (interval<60)
		return _("~%1 seconds",Math.round(interval/60));
	    else if (interval<120) {
		var minutes=Math.floor(interval/60);
		var seconds=Math.round(interval-(minutes*60));
		if (seconds===1)
		    return _("one minute, one second");
		else return _("one minute, %1 seconds",seconds);}
	    else if (interval<3600) {
		var minutes=Math.floor(interval/60);
		return _("~%1 minutes",minutes);}
	    else if (interval<(2*3600)) {
		var hours=Math.floor(interval/3600);
		var minutes=Math.round((interval-(hours*3600))/60);
		if (minutes===1)
		    return _("one hour and one minutes");
		else return _("one hour, %1 minutes",minutes);}
	    else if (interval<(24*3600)) {
		var hours=Math.floor(interval/3600);
		return _("~%1 hours",hours);}
	    else if (interval<(2*24*3600)) {
		var hours=Math.floor((interval-24*3600)/3600);
		if (hours===1)
		    return _("one day and one hour");
		else return _("one day, %1 hours",hours);}
	    else if (interval<(7*24*3600)) {
		var days=Math.floor(interval/(24*3600));
		return _("%1 days",days);}
	    else if (interval<(14*24*3600)) {
		var days=Math.floor((interval-(7*24*3600))/(24*3600));
		if (days===1)
		    return "one week and one day";
		else return _("one week and %1 days",days);}
	    else {
		var weeks=Math.floor(interval/(7*24*3600));
		var days=Math.round((interval-(days*7*24*3600))/(7*24*3600));
		return _("%1 weeks, %2 days",weeks,days);}};

	fdjtTime.secs2short=function(interval){
	    // This is designed for short intervals
	    if (interval<0.001)
		return Math.round(interval*1000000)+"us";
	    else if (interval<0.1)
		return Math.round(interval*1000)+"ms";
	    else if (interval<120)
		return (Math.round(interval*100)/100)+"s";
	    else {
		var min=Math.round(interval/60);
		var secs=Math.round(interval-min*6000)/100;
		return min+"m"+secs+"s";}};

	fdjtTime.runTimes=function(pname,start){
	    var point=start; var report="";
	    var i=2; while (i<arguments.length) {
		var phase=arguments[i++]; var time=arguments[i++];
		report=report+"; "+phase+": "+
		    ((time.getTime()-point.getTime())/1000)+"s";
		point=time;}
	    return pname+" "+((point.getTime()-start.getTime())/1000)+"s"+report;};

	fdjtTime.diffTime=function(time1,time2){
	    if (!(time2)) time2=new Date();
	    var diff=time1.getTime()-time2.getTime();
	    if (diff>0) return diff/1000; else return -(diff/1000);
	};

	fdjtTime.ET=function(arg){
	    if (!(arg)) arg=new Date();
	    return (arg.getTime()-loaded)/1000;};

	function timeslice(fcns,slice,space,done){
	    if (typeof slice !== 'number') slice=100;
	    if (typeof space !== 'number') space=100;
	    var i=0; var lim=fcns.length;
	    var slicefn=function(){
		var timelim=fdjtTime()+slice;
		var nextspace=false;
		while (i<lim) {
		    var fcn=fcns[i++];
		    if (!(fcn)) continue;
		    else if (typeof fcn === 'number') {
			nextspace=fcn; break;}
		    else fcn();
		    if (fdjtTime()>timelim) break;}
		if ((i<lim)&&((!(done))||(!(done()))))
		    setTimeout(slicefn,nextspace||space);};
	    return slicefn();}
	fdjtTime.timeslice=timeslice;

	function slowmap(fn,vec,watch,done,slice,space){
	    var i=0; var lim=vec.length; var chunks=0;
	    var used=0; var zerostart=fdjtTime();
	    if (!(slice)) slice=100;
	    if (!(space)) space=slice;
	    var stepfn=function(){
		var started=fdjtTime(); var now=started;
		var stopat=started+slice;
		if (watch) watch(((i==0)?'start':'resume'),i,lim,chunks,used,zerostart);
		while ((i<lim)&&((now=fdjtTime())<stopat)) {
		    var elt=vec[i];
		    if (watch) watch('element',i,lim,elt,used,now-zerostart);
		    fn(elt);
		    if (watch)
			watch('after',i,lim,elt,used+(fdjtTime()-started),
			      zerostart,fdjtTime()-now);
		    i++;}
		chunks=chunks+1;
		if (i<lim) {
		    used=used+(now-started);
		    if (watch) watch('suspend',i,lim,chunks,used,zerostart);
		    setTimeout(stepfn,space);}
		else {
		    now=fdjtTime(); used=used+(now-started);
		    if (done) {
			if (watch) watch('finishing',i,lim,chunks,used,zerostart);
			done();}
		    var donetime=((done)&&(fdjtTime()-now));
		    now=fdjtTime(); used=used+(now-started);
		    if (watch) watch('done',i,lim,chunks,used,zerostart,donetime);}};
	    setTimeout(stepfn,space);}
	fdjtTime.slowmap=slowmap;

	return fdjtTime;})();

var fdjtET=fdjtTime.ET;

/* Emacs local variables
;;;  Local variables: ***
;;;  compile-command: "make; if test -f ../makefile; then cd ..; make; fi" ***
;;;  End: ***
*/
/* -*- Mode: Javascript; -*- */

/* Copyright (C) 2009-2012 beingmeta, inc.
   This file is a part of the FDJT web toolkit (www.fdjt.org)
   This file provides extended Javascript utility functions
   of various kinds.

   This program comes with absolutely NO WARRANTY, including implied
   warranties of merchantability or fitness for any particular
   purpose.

   Use, modification, and redistribution of this program is permitted
   under either the GNU General Public License (GPL) Version 2 (or
   any later version) or under the GNU Lesser General Public License
   (version 3 or later).

   These licenses may be found at www.gnu.org, particularly:
   http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
   http://www.gnu.org/licenses/lgpl-3.0-standalone.html

*/

var fdjt$=false;

var fdjtDOM=
    (function(){
	var usenative=true;

	function fdjtDOM(spec){
	    var node;
	    if (spec.nodeType) node=spec;
	    else if (typeof spec==='string')
		if (spec[0]==='<') {
		    var container=document.createElement("DIV");
		    container.innerHTML=spec;
		    var children=container.childNodes;
		    var i=0; var len=children.length;
		    while (i<len)
			if (children[i].nodeType===1) return children[i];
		    else i++;
		    return false;}
	    else {
		var elts=spec.match(css_selector_regex);
		var classname=false;
		node=document.createElement(elts[0]);
		var i=1; var len=elts.length;
		while (i<len) {
		    var sel=elts[i++];
		    if (sel[0]==='#') node.id=sel.slice(1);
		    else if (sel[0]==='.')
			if (classname) classname=classname+" "+sel.slice(1);
		    else classname=sel.slice(1);
		    else if (sel[0]==='[') {
			var eqpos=sel.indexOf('=');
			if (eqpos<0) {
			    node.setAttribute(
				sel.slice(1,sel.length-1),
				sel.slice(1,sel.length-1));}
			else {
			    node.setAttribute(
				sel.slice(1,eqpos),
				sel.slice(eqpos+1,sel.length-1));}}
		    else {}}
		if (classname) node.className=classname;}
	    else {
		node=document.createElement(spec.tagName||"span");
		for (attrib in spec) {
		    if (attrib==="tagName") continue;
		    else node.setAttribute(attrib,spec[attrib]);}}
	    domappend(node,arguments,1);
	    return node;}

	fdjtDOM.revid="$Id$";
	fdjtDOM.version=parseInt("$Revision$".slice(10,-1));
	fdjtDOM.useNative=function(flag) {
	    if (typeof flag === 'undefined') return usenative;
	    else usenative=flag;};
	
	fdjtDOM.clone=function(node){
	    return node.cloneNode(true);}

	function domappend(node,content,i) {
	    if (content.nodeType)
		node.appendChild(content);
	    else if (typeof content === 'string')
		node.appendChild(document.createTextNode(content));
	    else if (content.toDOM)
		domappend(node,content.toDOM());
	    else if (content.toHTML)
		domappend(node,content.toHTML());
	    else if (content.length) {
		if (typeof i === 'undefined') i=0;
		if ((NodeList)&&(content instanceof NodeList)) content=TOA(content);
		var len=content.length;
		while (i<len) {
		    var elt=content[i++];
		    if (!(elt)) {}
		    else if (typeof elt === 'string')
			node.appendChild(document.createTextNode(elt));
		    else if (elt.nodeType)
			node.appendChild(elt);
		    else if (elt.length)
			domappend(node,elt,0);
		    else if (elt.toDOM)
			domappend(node,elt.toDOM());
		    else if (elt.toHTML)
			domappend(node,elt.toHTML());
		    else if (elt.toString)
			node.appendChild(document.createTextNode(elt.toString()));
		    else node.appendChild(document.createTextNode(""+elt));}}
	    else node.appendChild(document.createTextNode(""+content));}
	function dominsert(before,content,i) {
	    var node=before.parentNode;
	    if (content.nodeType)
		node.insertBefore(content,before);
	    else if (typeof content === 'string')
		node.insertBefore(content,before);
	    else if (content.toDOM)
		dominsert(before,content.toDOM());
	    else if (content.toHTML)
		dominsert(before,content.toHTML());
	    else if (content.length) {
		if (typeof i === 'undefined') i=0;
		if ((NodeList)&&(content instanceof NodeList)) content=TOA(content);
		var j=content.length-1;
		while (j>=i) {
		    var elt=content[j--];
		    if (!(elt)) {}
		    else if (typeof elt === 'string')
			node.insertBefore(document.createTextNode(elt),before);
		    else if (elt.nodeType)
			node.insertBefore(elt,before);
		    else if (elt.length)
			dominsert(before,elt,0);
		    else if (elt.toDOM)
			dominsert(before,elt.toDOM());
		    else if (elt.toHTML)
			dominsert(before,elt.toHTML());
		    else if (elt.toString)
			node.insertBefore(document.createTextNode(elt.toString()),before);
		    else node.insertBefore(document.createTextNode(""+elt),before);}}
	    else node.insertBefore(document.createTextNode(""+elt),before);}

	fdjtDOM.appendArray=domappend;
	
	function toArray(arg) {
	    var result=new Array(arg.length);
	    var i=0; var lim=arg.length;
	    while (i<lim) {result[i]=arg[i]; i++;}
	    return result;}
	fdjtDOM.toArray=toArray;
	function extendArray(result,arg) {
	    var i=0; var lim=arg.length;
	    while (i<lim) {result.push(arg[i]); i++;}
	    return result;}
	function TOA(arg,start) {
	    if (arg instanceof Array) {
		if (start) return arg.slice(start);
		else return arg;}
	    start=start||0;
	    var i=0; var lim=arg.length-start;
	    var result=new Array(lim);
	    while (i<lim) {result[i]=arg[i+start]; i++;}
	    return result;}
	fdjtDOM.Array=TOA;

	/* Utility patterns and functions */

	function parsePX(arg,dflt){
	    if (typeof dflt === 'undefined') dflt=0;
	    if (arg===0) return 0;
	    else if (!(arg)) return dflt;
	    else if (arg==="none") return dflt;
	    else if (arg==="auto") return dflt;
	    else if (typeof arg === 'number') return arg;
	    else if (typeof arg === 'string') {
		var len=arg.length; var num=false;
		if ((len>2)&&(arg[len-1]==='x')&&(arg[len-2]==='p'))
		    num=parseInt(arg.slice(0,-2));
		else num=parseInt(arg);
		if (num===0) return 0;
		else if (isNaN(num)) return dflt;
		else if (typeof num === 'number') return num;
		else return dflt;}
	    else return false;}
	fdjtDOM.parsePX=parsePX;

	var css_selector_regex=/((^|[.#])\w+)|(\[\w+=\w+\])/g;

	var whitespace_pat=/(\s)+/;
	var trimspace_pat=/^(\s)+|(\s)+$/;
	var classpats={};
	function classPat(name){
	    var rx=new RegExp("\\b"+name+"\\b","g");
	    classpats[name]=rx;
	    return rx;};

	function string_trim(string){
	    var start=string.search(/\S/); var end=string.search(/\s+$/g);
	    if ((start===0) && (end<0)) return string;
	    else return string.slice(start,end);}

	function nodeString(node){
	    if (node.nodeType===3) 
		return "<'"+node.value+"'>";
	    else if (node.nodeType===1) {
		var output="<"+node.tagName;
		if (node.id) output=output+"#"+node.id;
		if (node.tagName==='input') {
		    output+"[type="+node.type+"]";
		    output+"[name="+node.name+"]";}
		else if (node.tagName==='textarea')
		    output+"[name="+node.name+"]";
		else if (node.tagName==='img') {
		    if (node.alt) output=output+"[alt="+node.alt+"]";
		    else if (node.src) output=output+"[src="+node.src+"]";}
		else {}
		if (node.className)
		    output=output+"."+node.className.replace(/\s+/g,'.');
		return output+">";}
	    else return node.toString();}
	fdjtDOM.nodeString=nodeString;
	
	/* Simple class/attrib manipulation functions */

	function hasClass(elt,classname,attrib){
	    var classinfo=((attrib) ? (elt.getAttribute(attrib)||"") :
			   (elt.className));
	    if (!(classinfo)) return false;
	    else if (classname===true) return true;
	    else if (classinfo===classname) return true;
	    else if (typeof classname === 'string')
		if (classinfo.indexOf(' ')<0) return false;
	    else classname=classpats[classname]||classPat(classname);
	    else {}
	    if (classinfo.search(classname)>=0) return true;
	    else return false;}
	fdjtDOM.hasClass=hasClass;

	function addClass(elt,classname,attrib){
	    if (!(elt)) return;
	    else if (typeof elt === 'string') {
		if (!(elt=document.getElementById(elt)))
		    return;}
	    else if ((NodeList)&&(elt instanceof NodeList))
		return addClass(TOA(elt),classname,attrib);
	    else if ((elt.length)&&(!(elt.nodeType))) { // (assume array)
		var elts=TOA(elt);
		var i=0; var lim=elts.length;
		while (i<lim) addClass(elts[i++],classname,attrib||false);
		return;}
	    var classinfo=
		(((attrib) ? (elt.getAttribute(attrib)||"") :(elt.className))||null);
	    if (!(classinfo)) {
		elt.className=classname; return true;}
	    var class_regex=classpats[classname]||classPat(classname);
	    var newinfo=classinfo;
	    if (classinfo===classname) return false;
	    else if (classinfo.search(class_regex)>=0) return false;
	    else newinfo=classname+" "+classinfo;
	    if (attrib) {
		elt.setAttribute(attrib,newinfo);
		// This sometimes trigger a CSS update that doesn't happen otherwise
		elt.className=elt.className;}
	    else elt.className=newinfo;
	    return true;}
	fdjtDOM.addClass=addClass;
	fdjtDOM.aC=addClass;

	fdjtDOM.classAdder=function(elt,classname){
	    return function() {
		if (elt) addClass(elt,classname);}};

	function dropClass(elt,classname,attrib){
	    if (!(elt)) return;
	    else if (typeof elt === 'string') {
		if (!(elt=document.getElementById(elt)))
		    return;}
	    else if ((NodeList)&&(elt instanceof NodeList))
		return dropClass(TOA(elt),classname,attrib);
	    else if ((elt.length)&&(!(elt.nodeType))) {
		var elts=TOA(elt);
		var i=0; var lim=elts.length;
		while (i<lim) dropClass(elts[i++],classname,attrib||false);
		return;}
	    var classinfo=
		(((attrib) ? (elt.getAttribute(attrib)||"") :(elt.className))||null);
	    if (!(classinfo)) return false;
	    var class_regex=
		((typeof classname === 'string')?
		 (classpats[classname]||classPat(classname)):
		 classname);
	    var newinfo=classinfo;
	    if (classinfo===classname) 
		newinfo=null;
	    else if (classinfo.search(class_regex)>=0) 
		newinfo=classinfo.replace(class_regex,"");
	    else return false;
	    if (newinfo)
		newinfo=newinfo.
		replace(whitespace_pat," ").
		replace(trimspace_pat,"");
	    if (attrib)
		if (newinfo) {
		    elt.setAttribute(attrib,newinfo);
		    elt.className=elt.className;}
	    else if (!(keep)) {
		elt.removeAttribute(attrib);
		elt.className=elt.className;}
	    else {}
	    else elt.className=newinfo;
	    return true;}
	fdjtDOM.dropClass=dropClass;
	fdjtDOM.dC=dropClass;

	fdjtDOM.classDropper=function(elt,classname){
	    return function() {
		if (elt) dropClass(elt,classname);}};

	function swapClass(elt,drop,add,attrib) {
	    dropClass(elt,drop,attrib); addClass(elt,add,attrib);}
	fdjtDOM.swapClass=swapClass;

	function toggleClass(elt,classname,attrib){
	    if (typeof elt === 'string') elt=document.getElementById(elt);
	    else if ((NodeList)&&(elt instanceof NodeList))
		return toggleClass(TOA(elt),classname,attrib);
	    else if ((elt.length)&&(!(elt.nodeType))) {
		var elts=TOA(elt);
		var i=0; var lim=elts.length;
		while (i<lim) toggleClass(elts[i++],classname,attrib||false);
		return;}
	    var classinfo=
		(((attrib) ? (elt.getAttribute(attrib)||"") :
		  (elt.className))||null);
	    if (!(classinfo)) {
		if (attrib) elt.setAttribute(attrib,classname);
		else elt.className=classname;
		return true;}
	    var class_regex=
		((typeof classname === 'string')?
		 (classpats[classname]||classPat(classname)):
		 classname);
	    var newinfo=classinfo;
	    if (classinfo===classname) 
		newinfo=null;
	    else if (classinfo.search(class_regex)>=0) 
		newinfo=classinfo.replace(class_regex,"");
	    else {
		if (attrib)
		    elt.setAttribute(attrib,classinfo+' '+classname);
		else elt.className=classinfo+' '+classname;
		return true;}
	    if (newinfo)
		newinfo=newinfo.
		replace(whitespace_pat," ").
		replace(trimspace_pat,"");
	    if (attrib)
		if (newinfo) {
		    elt.setAttribute(attrib,newinfo);
		    elt.className=elt.className;}
	    else if (!(keep)) {
		elt.removeAttribute(attrib);
		elt.className=elt.className;}
	    else {}
	    else elt.className=newinfo;
	    return false;}
	fdjtDOM.toggleClass=toggleClass;
	fdjtDOM.tC=toggleClass;
	
	fdjtDOM.isTextInput=function(target){
	    return ((target.tagName==='INPUT')||(target.tagName==='TEXTAREA'));};

	/* Simple CSS selectors */

	var selectors={};

	function Selector(spec,tagcs) {
	    if (!(spec)) return this; // just cons with type
	    else if (selectors[spec]) return selectors[spec]; // check cache
	    else if (!(this instanceof Selector))
		// handle case of the forgotten 'new'
		return Selector.call(new Selector(),spec);
	    if (spec.indexOf(',')>0) { // compound selectors
		var specs=spec.split(','); var compound=[];
		var i=0; var lim=specs.length;
		while (i<lim) {
		    var sub=string_trim(specs[i++]);
		    compound.push(new Selector(sub));}
		this.compound=compound;
		selectors[spec]=this;
		return this;}
	    // Otherwise, parse and set up this
	    var elts=spec.match(css_selector_regex);
	    var i=0; var lim=elts.length;
	    var classes=[]; var classnames=[]; var attribs=false;
	    if (!((elts[0][0]==='.')||(elts[0][0]==='#')||(elts[0][0]==='['))) {
		this.tag=((tagcs)?(elts[0]):(elts[0].toUpperCase()));
		i=1;}
	    while (i<lim)
		if (elts[i][0]==='#') this.id=elts[i++].slice(1);
	    else if (elts[i][0]==='.') {
		classnames.push(elts[i].slice(1));
		classes.push(classPat(elts[i++].slice(1)));}
	    else if (elts[i][0]==='[') {
		var aelts=elts[i++]; var eltsend=aelts.length-1;
		if (!(attribs)) attribs={};
		var eqpos=aelts.indexOf('=');
		if (eqpos<0)
		    attribs[aelts.slice(1,eltsend)]=true;
		else if (aelts[eqpos+1]==='~') 
		    attribs[aelts.slice(1,eqpos)]=
		    classPat(aelts.slice(eqpos+2,eltsend));
		else attribs[aelts.slice(1,eqpos)]=aelts.slice(eqpos+1,eltsend);}
	    else fdjtLog.uhoh("weird elts %o",elts[i++]);
	    if (classes.length) {
		this.classes=classes; this.classnames=classnames;}
	    if (attribs) this.attribs=attribs;
	    selectors[spec]=this;
	    return this;}
	Selector.prototype.match=function(elt){
	    if (this.compound) {
		var compound=this.compound; var i=0; var lim=compound.length;
		while (i<lim) if (compound[i++].match(elt)) return true;
		return false;} 
	    if ((this.tag)&&(this.tag!==elt.tagName)) return false;
	    else if ((this.id)&&(this.id!==elt.id)) return false;
	    if (this.classes)
		if (elt.className) {
		    var classname=elt.className; var classes=this.classes;
		    var i=0; var lim=classes.length;
		    while (i<lim) if (classname.search(classes[i++])<0) return false;}
	    else return false;
	    if (this.attribs) {
		var attribs=this.attribs;
		for (var name in attribs) {
		    var val=elt.getAttribute(name);
		    if (!(val)) return false;
		    var need=this[name];
		    if (need===true) {}
		    else if (typeof need === 'string') {
			if (need!==val) return false;}
		    else if (val.search(need)<0) return false;}}
	    return true;};
	Selector.prototype.find=function(elt,results){
	    var pickfirst=false;
	    if (!(results)) results=[];
	    if (this.compound) {
		var compound=this.compound; var i=0; var lim=compound.length;
		while (i<lim) compound[i++].find(elt,results);
		return results;}
	    if (this.id) {
		var elt=document.getElementById(this.id);
		if (!(elt)) return results;
		else if (this.match(elt)) {
		    results.push(elt); return results;}
		else return results;}
	    var candidates=[];
	    var classnames=this.classnames; var attribs=this.attribs;
	    if (this.classes) 
		if (elt.getElementsByClassName)
		    candidates=elt.getElementsByClassName(classnames[0]);
	    else gatherByClass(elt,this.classes[0],candidates);
	    else if ((this.tag)&&(elt.getElementsByTagName))
		candidates=elt.getElementsByTagName(this.tag);
	    else if (this.attribs) {
		var attribs=this.attribs;
		for (var name in attribs) {
		    gatherByAttrib(elt,name,attribs[name],candidates);
		    break;}}
	    else if (this.tag) {
		gatherByTag(elt,this.tag,candidates);}
	    else {}
	    if (candidates.length===0) return candidates;
	    if (((this.tag)&&(!(this.classes))&&(!(this.attribs)))||
		((!(this.tag))&&(this.classes)&&(this.classes.length===1)&&
		 (!(this.attribs))))
		// When there's only one test, don't bother filtering
		if (results.length) return extendArray(results,candidates);
	    else if (candidates instanceof Array)
		return candidates;
	    else return toArray(candidates);
	    var i=0; var lim=candidates.length;
	    while (i<lim) {
		var candidate=candidates[i++];
		if (this.match(candidate)) results.push(candidate);}
	    return results;};
	fdjtDOM.Selector=Selector;
	fdjtDOM.sel=function(spec){
	    if (!(spec)) return false;
	    else if (spec instanceof Selector) return spec;
	    else if (spec instanceof Array) {
		if (spec.length)
		    return new Selector(spec.join(","));
		else return false;}
	    else if (typeof spec === 'string')
		return new Selector(spec);
	    else {
		fdjtLog.warn("Non selector spec: %o",spec);
		return false;}};

	function gatherByClass(node,pat,results){
	    if (node.nodeType===1) {
		if ((node.className)&&(node.className.search(pat)>=0))
		    results.push(node);
		var children=node.childNodes;
		if (children) {
		    var i=0; var lim=children.length; var result;
		    while (i<lim) gatherByClass(children[i++],pat,results);}}}
	function gatherByTag(node,tag,results){
	    if (node.nodeType===1) {
		if (node.tagName===tag) results.push(node);
		var children=node.childNodes;
		if (children) {
		    var i=0; var lim=children.length; var result;
		    while (i<lim) gatherByTag(children[i++],tag,results);}}}
	function gatherByAttrib(node,attrib,val,results){
	    if (node.nodeType===1) {
		if ((node.getAttribute(attrib))&&
		    ((typeof val === 'string')?
		     (node.getAttribute(attrib)===val):
		     (node.getAttribute(attrib).search(val)>=0)))
		    results.push(node);
		var children=node.childNodes;
		if (children) {
		    var i=0; var lim=children.length; var result;
		    while (i<lim) gatherByTag(children[i++],tag,results);}}}
	
	function gather_children(node,pat,attrib,results){
	    if (!(attrib)) gatherByClass(node,pat,results);
	    else if (attrib==='class') gatherByClass(node,pat,results);
	    else if (attrib==='tagName') gatherByTag(node,pat,results);
	    else gatherByAttrib(node,attrib,pat,results);}

	/* Real simple DOM search */

	function getParent(elt,parent,attrib){
	    if (!(parent)) return false;
	    else if (parent.nodeType) {
		while (elt) {
		    if (elt===parent) return parent;
		    else elt=elt.parentNode;}
		return false;}
	    else if (typeof parent === 'function') {
		while (elt) {
		    if (parent(elt)) return elt;
		    else elt=elt.parentNode;}
		return false;}
	    else if (parent instanceof Selector) {
		while (elt) {
		    if (parent.match(elt)) return elt;
		    else elt=elt.parentNode;}
		return false;}
	    else if (typeof parent === 'string')
		return getParent(elt,new Selector(parent));
	    else throw { error: 'invalid parent spec'};}
	fdjtDOM.getParent=getParent;
	fdjtDOM.hasParent=getParent;
	fdjtDOM.$P=getParent;
	fdjtDOM.inherits=function(node,spec) {
	    var sel=new Selector(spec);
	    return ((sel.match(node))?(node):(getParent(node,sel)));};

	function getChildren(node,classname,attrib,results){
	    if (typeof node === "string") node=fdjtID(node);
	    if (!(node)) return [];
	    if (!(results)) results=[]; 
	    if (!(attrib)) {
		if (typeof classname === 'function')
		    filter_children(node,classname,results);
		else if (classname instanceof Selector)
		    return classname.find(node,results);
		else if (typeof classname === 'string') {
		    if ((usenative) && (node.querySelectorAll))
			return node.querySelectorAll(classname);
		    else return getChildren(
			node,new Selector(classname),false,results);}}
	    else if (!(typeof attrib === 'string'))
		throw { error: 'bad selector arg', selector: classname};
	    else {
		var pat=(classpats[classname]||classPat(classname));
		gather_children(node,classname,attrib||false,results);}
	    return results;}
	fdjtDOM.getChildren=getChildren;
	fdjt$=fdjtDOM.$=function(spec,root){
	    return toArray(getChildren(root||document,spec));};
	fdjt$1=fdjtDOM.getFirstChild=function(elt,spec){
	    var children=getChildren(elt,spec);
	    if (children.length) return children[0]; else return false;};
	fdjtDOM.getChild=fdjtDOM.getFirstChild;

	function filter_children(node,filter,results){
	    if (node.nodeType===1) {
		if (filter(node)) results.push(node);
		var children=node.childNodes;
		if (children) {
		    var i=0; var lim=children.length; var result;
		    while (i<lim) filter_children(children[i++],filter,results);}}}

	fdjtDOM.getAttrib=function(elt,attrib,ns){
	    var probe;
	    if ((ns)&&(elt.getAttributeByNS))
		probe=elt.getAttributeNS(attrib,ns);
	    if (probe) return probe;
	    else return elt.getAttribute(attrib)||
		elt.getAttribute("data-"+attrib);};

	fdjtDOM.findAttrib=function(scan,attrib,ns){
	    var dattrib="data-"+attrib;
	    while (scan) {
		if ((ns)&&(scan.getAttributeNS)&&
		    (scan.getAttributeNS(attrib,ns)))
		    return scan.getAttributeNS(attrib,ns);
		else if (scan.getAttribute) {
		    if (scan.getAttribute(attrib))
			return scan.getAttribute(attrib);
		    else if (scan.getAttribute(dattrib))
			return scan.getAttribute(dattrib);
		    else scan=scan.parentNode;}
		else scan=scan.parentNode;}
	    return false;};
	
	/* Manipulating the DOM */

	fdjtDOM.replace=function(existing,replacement){
	    var cur=existing;
	    if (typeof existing === 'string')
		if (existing[0]==='#')
		    cur=document.getElementById(existing.slice(1));
	    else cur=document.getElementById(existing);
	    if (cur) {
		cur.parentNode.replaceChild(replacement,cur);
		if ((cur.id)&&(!(replacement.id))) replacement.id=cur.id;}
	    else fdjtLog.uhoh("Can't find %o to replace it with %o",
			      existing,replacement);};
	function remove_node(node){
	    if (node instanceof Array) {
		var i=0; var lim=node.length;
		while (i<lim) remove_node(node[i++]);
		return;}
	    var cur=node;
	    if (typeof node === 'string')
		if (node[0]==='#') cur=document.getElementById(node.slice(1));
	    else cur=document.getElementById(node);
	    if (cur) cur.parentNode.removeChild(cur);
	    else fdjtLog.uhoh("Can't find %o to remove it",node);}
	fdjtDOM.remove=remove_node;
	
	fdjtDOM.append=function (node) {
	    if (typeof node === 'string') node=document.getElementById(node);
	    domappend(node,arguments,1);};
	fdjtDOM.prepend=function (node) {
	    if (typeof node === 'string') node=document.getElementById(node);
	    if (node.firstChild)
		dominsert(node.firstChild,arguments,1);
	    else domappend(node,arguments,1);};

	fdjtDOM.insertBefore=function (before) {
	    if (typeof before === 'string')
		before=document.getElementById(before);
	    dominsert(before,arguments,1);};
	fdjtDOM.insertAfter=function (after) {
	    if (typeof after === 'string')
		after=document.getElementById(after);
	    if (after.nextSibling)
		dominsert(after.nextSibling,arguments,1);
	    else domappend(after.parentNode,arguments,1);};
	
	/* DOM construction shortcuts */

	function tag_spec(spec,tag){
	    if (!(spec)) return tag;
	    else if (typeof spec === 'string') {
		var wordstart=spec.search(/\w/g);
		var puncstart=spec.search(/\W/g);
		if (puncstart<0) return tag+"."+spec;
		else if (wordstart!==0) return tag+spec;
		return spec;}
	    else if (spec.tagName) return spec;
	    else {
		spec.tagName=tag;
		return spec;}}

	fdjtDOM.Input=function(spec,name,value,title){
	    if (spec.search(/\w/)!==0) spec='INPUT'+spec;
	    var node=fdjtDOM(spec);
	    node.name=name;
	    if (value) node.value=value;
	    if (title) node.title=title;
	    return node;};
	fdjtDOM.Checkbox=function(name,value,checked){
	    var node=fdjtDOM("INPUT");
	    node.type="checkbox"
	    node.name=name;
	    if (value) node.value=value;
	    if (checked) node.checked=true;
	    else node.checked=false;
	    return node;};
	fdjtDOM.Anchor=function(href,spec){
	    spec=tag_spec(spec,"A");
	    var node=fdjtDOM(spec); node.href=href;
	    domappend(node,arguments,2);
	    return node;};
	fdjtDOM.Image=function(src,spec,alt,title){
	    spec=tag_spec(spec,"IMG");
	    var node=fdjtDOM(spec); node.src=src;
	    if (alt) node.alt=alt;
	    if (title) node.title=title;
	    domappend(node,arguments,4);
	    return node;};

	function getInputs(root,name,type){
	    var results=[];
	    var inputs=root.getElementsByTagName('input');
	    var i=0; var lim=inputs.length;
	    while (i<lim) {
		if (((!(name))||(inputs[i].name===name))&&
		    ((!(type))||(inputs[i].type===type)))
		    results.push(inputs[i++]); 
		else i++;}
	    if ((!type)||(type==='textarea')||(type==='text')) {
		var inputs=root.getElementsByTagName('textarea');
		var i=0; var lim=inputs.length;
		while (i<lim) {
		    if (((!(name))||(inputs[i].name===name))&&
			((!(type))||(inputs[i].type===type)))
			results.push(inputs[i++]); 
		    else i++;}}
	    if ((!type)||(type==='button')||(type==='submit')) {
		var inputs=root.getElementsByTagName('button');
		var i=0; var lim=inputs.length;
		while (i<lim) {
		    if (((!(name))||(inputs[i].name===name))&&
			((!(type))||(inputs[i].type===type)))
			results.push(inputs[i++]); 
		    else i++;}}
	    if ((!type)||(type==='select')) {
		var inputs=root.getElementsByTagName('select');
		var i=0; var lim=inputs.length;
		while (i<lim) {
		    if ((!(name))||(inputs[i].name===name))
			results.push(inputs[i++]); 
		    else i++;}}
	    return results;}

	fdjtDOM.getInputs=getInputs;
	fdjtDOM.getInput=function(root,name,type){
	    var results=getInputs(root,name||false,type||false);
	    if ((results)&&(results.length===1))
		return results[0];
	    else if ((results)&&(results.length)) {
		fdjtLog.warn(
		    "Ambiguous input reference name=%o type=%o under %o",
		    name,type,root);
		return results[0];}
	    else return false;};
	
	function getInputValues(root,name){
	    var results=[];
	    var inputs=root.getElementsByTagName('input');
	    var i=0; var lim=inputs.length;
	    while (i<lim) {
		var input=inputs[i++];
		if (input.name!==name) continue;
		if ((input.type==='checkbox')||(input.type==='radio')) {
		    if (!(input.checked)) continue;}
		results.push(input.value);}
	    return results;}
	fdjtDOM.getInputValues=getInputValues;

	/* Getting style information generally */

	function getStyle(elt,prop){
	    if (typeof elt === 'string') elt=document.getElementById(elt);
	    if (!(elt)) return elt;
	    if (elt.nodeType!==1) throw "Not an element";
	    try {
		var style=
		    ((window.getComputedStyle)&&
		     (window.getComputedStyle(elt,null)))||
		    (elt.currentStyle);
		if (!(style)) return false;
		else if (prop) return style[prop];
		else return style;}
	    catch (ex) {
		fdjtLog("Unexpected style error %o",ex);
		return false;}}
	fdjtDOM.getStyle=getStyle;

	function styleString(elt){
	    var style=elt.style; var result;
	    if (!(style)) return false;
	    var i=0; var lim=style.length;
	    if (lim===0) return false;
	    while (i<lim) {
		var p=style[i];
		var v=style[p];
		if (i===0) result=p+": "+v;
		else result=result+"; "+p+": "+v;
		i++;}
	    return result;}
	fdjtDOM.styleString=styleString;

	/* Getting display style */

	var display_styles={
	    "DIV": "block","P": "block","BLOCKQUOTE":"block",
	    "H1": "block","H2": "block","H3": "block","H4": "block",
	    "H5": "block","H6": "block","H7": "block","H8": "block",
	    "UL": "block","LI": "list-item",
	    "DL": "block","DT": "list-item","DD": "list-item",
	    "SPAN": "inline","EM": "inline","STRONG": "inline",
	    "TT": "inline","DEFN": "inline","A": "inline",
	    "TD": "table-cell","TR": "table-row",
	    "TABLE": "table", "PRE": "preformatted"};

	function getDisplayStyle(elt){
	    if ((!(elt))||(!(elt.nodeType))||(elt.nodeType!==1))
		return false;
	    return (((window.getComputedStyle)&&
		     (window.getComputedStyle(elt,null))&&
		     (window.getComputedStyle(elt,null).display))||
		    (display_styles[elt.tagName])||
		    "inline");}
	fdjtDOM.getDisplay=getDisplayStyle;

	/* Generating text from the DOM */

	function flatten(string){return string.replace(/\s+/," ");};

	function textify(arg,flat,inside){
	    if (arg.text) return flatten(arg.text);
	    else if (arg.nodeType)
		if (arg.nodeType===3) return arg.nodeValue;
	    else if (arg.nodeType===1) {
		var children=arg.childNodes;
		var display_type=getDisplayStyle(arg);
		var string=""; var suffix="";
		// Figure out what suffix and prefix to use for this element
		// If inside is false, don't use anything.
		if (!(inside)) {}
		else if (!(display_type)) {}
		else if (display_type==="inline") {}
		else if (flat) suffix=" ";
		else if ((display_type==="block") ||
			 (display_type==="table") ||
			 (display_type==="preformatted")) {
		    string="\n"; suffix="\n";}
		else if (display_type==="table-row") suffix="\n";
		else if (display_type==="table-cell") string="\t";
		else {}
		var i=0; while (i<children.length) {
		    var child=children[i++];
		    if (!(child.nodeType)) continue;
		    if (child.nodeType===3)
			if (flat)
			    string=string+flatten(child.nodeValue);
		    else string=string+child.nodeValue;
		    else if (child.nodeType===1) {
			var stringval=textify(child,flat,true);
			if (stringval) string=string+stringval;}
		    else continue;}
		return string+suffix;}
	    else {}
	    else if (arg.toString)
		return arg.toString();
	    else return arg.toString();}
	fdjtDOM.textify=textify;

	/* Geometry functions */

	function getGeometry(elt,root,outer,withstack){
	    if (!(withstack)) withstack=false;
	    if (typeof elt === 'string')
		elt=document.getElementById(elt);
	    var top = elt.offsetTop;
	    var left = elt.offsetLeft;
	    var stack = ((withstack) ? (new Array(elt)) : false);
	    var width=elt.offsetWidth;
	    var height=elt.offsetHeight;
	    var rootp=((root)&&(root.offsetParent));

	    if (elt===root) 
		return {left: 0,top: 0,width:width,height: height};
	    elt=elt.offsetParent;
	    while (elt) {
		if ((root)&&((elt===root)||(elt===rootp))) break;
		if (withstack) withstack.push(elt);
		top += elt.offsetTop;
		left += elt.offsetLeft;
		elt=elt.offsetParent;}
	    
	    if (outer) {
		var outer_width, outer_height;
		var style=getStyle(elt);
		var t_margin=parsePX(style.marginTop);
		var r_margin=parsePX(style.marginRight);
		var b_margin=parsePX(style.marginBottom);
		var l_margin=parsePX(style.marginLeft);
		outer_width=width+l_margin+r_margin;
		outer_height=height+t_margin+b_margin;
		return {left: left, top: top, width: width,height: height,
			right:left+width,bottom:top+height,
			top_margin: t_margin, bottom_margin: b_margin,
			left_margin: l_margin, right_margin: r_margin,
			outer_height: outer_height,outer_width: outer_width,
			stack:withstack};}
	    else return {left: left, top: top, width: width,height: height,
			 right:left+width,bottom:top+height,
			 stack:withstack};}
	fdjtDOM.getGeometry=getGeometry;

	function geomString(geom){
	    return +((typeof geom.width == 'number')?(geom.width):"?")+
		"x"+((typeof geom.height == 'number')?(geom.height):"?")+
		"@l:"+((typeof geom.left == 'number')?(geom.left):"?")+
		",t:"+((typeof geom.top == 'number')?(geom.top):"?")+
		"/r:"+((typeof geom.right == 'number')?(geom.right):"?")+
		",b:"+((typeof geom.bottom == 'number')?(geom.bottom):"?");}
	fdjtDOM.geomString=geomString;

	function isVisible(elt,partial){
	    var start=elt;
	    if (!(partial)) partial=false;
	    var top = elt.offsetTop;
	    var left = elt.offsetLeft;
	    var width = elt.offsetWidth;
	    var height = elt.offsetHeight;
	    var winx=(window.pageXOffset||document.documentElement.scrollLeft||0);
	    var winy=(window.pageYOffset||document.documentElement.scrollTop||0);
	    var winxedge=winx+(document.documentElement.clientWidth);
	    var winyedge=winy+(document.documentElement.clientHeight);
	    
	    while(elt.offsetParent) {
		if (elt===window) break;
		elt = elt.offsetParent;
		top += elt.offsetTop;
		left += elt.offsetLeft;}

	    if ((elt)&&(!((elt===window)||(elt===document.body)))) {
		// fdjtLog("%o l=%o t=%o",elt,elt.scrollLeft,elt.scrollTop);
		if ((elt.scrollTop)||(elt.scrollLeft)) {
		    fdjtLog("Adjusting for inner DIV");
		    winx=elt.scrollLeft; winy=elt.scrollTop;
		    winxedge=winx+elt.scrollWidth;
		    winyedge=winy+elt.scrollHeight;}}

	    /*
	      fdjtLog("fdjtIsVisible%s %o top=%o left=%o height=%o width=%o",
	      ((partial)?("(partial)"):""),start,
	      top,left,height,width);
	      fdjtLog("fdjtIsVisible %o winx=%o winy=%o winxedge=%o winyedge=%o",
	      elt,winx,winy,winxedge,winyedge);
	    */
	    
	    if (partial)
		// There are three cases we check for:
		return (
		    // top of element in window
		    ((top > winy) && (top < winyedge) &&
		     (left > winx) && (left < winxedge)) ||
			// bottom of element in window
			((top+height > winy) && (top+height < winyedge) &&
			 (left+width > winx) && (left+width < winxedge)) ||
			// top above/left of window, bottom below/right of window
			(((top < winy) || (left < winx)) &&
			 ((top+height > winyedge) && (left+width > winxedge))));
	    else return ((top > winy) && (left > winx) &&
			 (top + height) <= (winyedge) &&
			 (left + width) <= (winxedge));}
	fdjtDOM.isVisible=isVisible;

	function isAtTop(elt,delta){
	    if (!(delta)) delta=50;
	    var top = elt.offsetTop;
	    var left = elt.offsetLeft;
	    var width = elt.offsetWidth;
	    var height = elt.offsetHeight;
	    var winx=(window.pageXOffset||document.documentElement.scrollLeft||0);
	    var winy=(window.pageYOffset||document.documentElement.scrollTop||0);
	    var winxedge=winx+(document.documentElement.clientWidth);
	    var winyedge=winy+(document.documentElement.clientHeight);
	    
	    while(elt.offsetParent) {
		elt = elt.offsetParent;
		top += elt.offsetTop;
		left += elt.offsetLeft;}

	    return ((top>winx) && (top<winyedge) && (top<winx+delta));}
	fdjtDOM.isAtTop=isAtTop;

	function textwidth(node){
	    if (node.nodeType===3) return node.nodeValue.length;
	    else if ((node.nodeType===1)&&(node.childNodes)) {
		var children=node.childNodes;
		var i=0; var lim=children.length; var width=0;
		while (i<lim) {
		    var child=children[i++];
		    if (child.nodeType===3) width=width+child.nodeValue.length;
		    else if (child.nodeType===1)
			width=width+textwidth(child);
		    else {}}
		return width;}
	    else return 0;}
	fdjtDOM.textWidth=textwidth;

	function countBreaks(arg){
	    if (typeof arg === 'string') {
		return arg.match(/\W*\s+\W*/g).length;}
	    else if (!(arg.nodeType)) return 0;
	    else if (arg.nodeType===1) {}
	    else if (arg.nodeType===3)
		return arg.nodeValue.match(/\W*\s+\W*/g).length;
	    else return 0;}
	fdjtDOM.countBreaks=countBreaks;

	function wordOffset(arg){
	    var scan=arg; var count=0;
	    while (scan=(scan.previousSibling||scan.parentNode)) {
		if (scan.nodeType===3)
		    count=count+(scan.nodeValue.match(/\W*\s+\W*/g).length);
		else if (scan.nodeType===1)
		    count=count+countBreaks(scan);
		else {}}
	    return count;}

	function hasContent(node,recur,test){
	    if (node===recur) return false;
	    else if (node.nodeType===3)
		return (child.nodeValue.search(/\w/g)>=0);
	    else if (node.nodeType!==1) return false;
	    else if ((test)&&(test.match)&&(test.match(node)))
		return true;
	    else if ((test===true)&&
		     ((node.tagName==='IMG')||
		      (node.tagName==='OBJECT')))
		return true;
	    else if (node.childNodes) {
		var children=node.childNodes;
		var i=0; while (i<children.length) {
		    var child=children[i++];
		    if (child===recur) return false;
		    else if (child.nodeType===3) {
			if (child.nodeValue.search(/\w/g)>=0) return true;
			else continue;}
		    else if (child.nodeType!==1) continue;
		    else if (recur) {
			if (hasContent(child,recur,test)) return true;
			else continue;}
		    else continue;}
		return false;}
	    else return false;}
	fdjtDOM.hasContent=hasContent;

	function hasText(node){
	    if (node.childNodes) {
		var children=node.childNodes;
		var i=0; while (i<children.length) {
		    var child=children[i++];
		    if (child.nodeType===3)
			if (child.nodeValue.search(/\w/g)>=0) return true;
		    else {}}
		return false;}
	    else return false;}
	fdjtDOM.hasText=hasText;

	/* A 'refresh method' does a className eigenop to force IE redisplay */

	fdjtDOM.refresh=function(elt){
	    elt.className=elt.className;};
	fdjtDOM.setAttrib=function(elt,attrib,val){
	    if ((typeof elt === 'string')&&(fdjtID(elt)))
		elt=fdjtID(elt);
	    elt.setAttribute(attrib,val);
	    elt.className=elt.className;};
	fdjtDOM.dropAttrib=function(elt,attrib){
	    if ((typeof elt === 'string')&&(fdjtID(elt)))
		elt=fdjtID(elt);
	    elt.removeAttribute(attrib);
	    elt.className=elt.className;};

	/* Determining if something has overflowed */
	fdjtDOM.overflowing=function(node){
	    return (node.scrollHeight>node.clientHeight);}
	fdjtDOM.voverflow=function(node){
	    return (node.scrollHeight/node.clientHeight);}
	fdjtDOM.hoverflow=function(node){
	    return (node.scrollWidth/node.clientWidth);}

	/* Sizing to fit */

	var default_trace_adjust=false;

	function getInsideBounds(container){
	    var left=false; var top=false;
	    var right=false; var bottom=false;
	    var children=container.childNodes;
	    var i=0; var lim=children.length;
	    while (i<lim) {
		var child=children[i++];
		if (typeof child.offsetLeft !== 'number') continue;
		var style=getStyle(child);
		if (style.position!=='static') continue;
		var child_left=child.offsetLeft-parsePX(style.marginLeft);
		var child_top=child.offsetTop-parsePX(style.marginTop);
		var child_right=child.offsetLeft+child.offsetWidth+parsePX(style.marginRight);
		var child_bottom=child.offsetTop+child.offsetHeight+parsePX(style.marginBottom);
		if (left===false) {
		    left=child_left; right=child_right;
		    top=child_top; bottom=child_bottom;}
		else {
		    if (child_left<left) left=child_left;
		    if (child_top<top) top=child_top;
		    if (child_right>right) right=child_right;
		    if (child_bottom>bottom) bottom=child_bottom;}}
	    return {left: left,right: right,top: top, bottom: bottom,
		    width: right-left,height:bottom-top};}
	fdjtDOM.getInsideBounds=getInsideBounds;
	function applyScale(container,scale,traced){
	    var images=fdjtDOM.getChildren(container,"IMG");
	    var ilim=images.length;
	    var oldscale=container.scale||100;
	    if (scale) {
		container.scale=scale;
		container.style.fontSize=scale+'%';
		var rounded=10*Math.round(scale/10);
		fdjtDOM.addClass(container,"fdjtscaled");
		fdjtDOM.swapClass(
		    container,/\bfdjtscale\d+\b/,"fdjtscale"+rounded);}
	    else if (!(container.scale)) return;
	    else {
		delete container.scale;
		container.style.fontSize="";
		fdjtDOM.dropClass(container,"fdjtscaled");
		fdjtDOM.dropClass(container,/\bfdjtscale\d+\b/);}
	    var iscan=0; while (iscan<ilim) {
		var image=images[iscan++];
		if ((fdjtDOM.hasClass(image,"nofdjtscale"))||
		    (fdjtDOM.hasClass(image,"noautoscale")))
		    continue;
		// Reset dimensions to get real info
		image.style.maxWidth=image.style.width=
		    image.style.maxHeight=image.style.height='';
		if (scale) {
		    var width=image.offsetWidth;
		    var height=image.offsetHeight;
		    image.style.maxWidth=image.style.width=
			Math.round(width*(scale/100))+'px';
		    image.style.maxHeight=image.style.height=
			Math.round(height*(scale/100))+'px';}}}
	
	function adjustInside(elt,container,step,min,pad){
	    var trace_adjust=(elt.traceadjust)||
		(container.traceadjust)||fdjtDOM.trace_adjust||
		((elt.className)&&(elt.className.search(/\btraceadjust\b/)>=0))||
		((container.className)&&
		 (container.className.search(/\btraceadjust\b/)>=0))||
		default_trace_adjust;
	    if (!(step)) step=5;
	    if (!(min)) min=50;
	    if (!(pad)) pad=1;
	    var scale=100;
	    function adjust(){
		var outside=getGeometry(container);
		var inside=getGeometry(elt,container);
		var style=getStyle(container);
		var maxwidth=
		    outside.width-
		    (parsePX(style.paddingLeft,0)+
		     parsePX(style.borderLeft,0)+
		     parsePX(style.paddingRight,0)+
		     parsePX(style.borderRight,0));
		var maxheight=
		    outside.height-
		    (parsePX(style.paddingTop,0)+
		     parsePX(style.borderTop,0)+
		     parsePX(style.paddingBottom,0)+
		     parsePX(style.borderBottom,0));
		if (trace_adjust)
		    fdjtLog("adjustInside scale=%o step=%o min=%o pad=%o [l%o,t%o,r%o,b%o] << %ox%o < %ox%o",
			    scale,step,min,pad,
			    inside.left,inside.top,inside.right,inside.bottom,
			    maxwidth*pad,maxheight*pad,
			    maxwidth,maxheight);
		if ((inside.top>=0)&&(inside.bottom<=(pad*maxheight))&&
		    (inside.left>=0)&&(inside.right<=(pad*maxwidth)))
		    return;
		else if (scale<=min) return;
		else {
		    scale=scale-step;
		    applyScale(elt,scale,trace_adjust);
		    setTimeout(adjust,10);}}
	    setTimeout(adjust,10);}
	function adjustToFit(container,threshold,padding){
	    var trace_adjust=(container.traceadjust)||
		fdjtDOM.trace_adjust||
		((container.className)&&(container.className.search(/\btraceadjust\b/)>=0))||
		default_trace_adjust;
	    var style=getStyle(container);
	    var geom=getGeometry(container);
	    var maxheight=((style.maxHeight)&&(parsePX(style.maxHeight)))||
		(geom.height);
	    var maxwidth=((style.maxWidth)&&(parsePX(style.maxWidth)))||
		(geom.width);
	    var goodenough=threshold||0.1;
	    var scale=(container.scale)||100.0;
	    var bounds=getInsideBounds(container);
	    var hpadding=
		(fdjtDOM.parsePX(style.paddingLeft)||0)+
		(fdjtDOM.parsePX(style.paddingRight)||0)+
		(fdjtDOM.parsePX(style.borderLeftWidth)||0)+
		(fdjtDOM.parsePX(style.borderRightWidth)||0)+
		padding;
	    var vpadding=
		(fdjtDOM.parsePX(style.paddingTop)||0)+
		(fdjtDOM.parsePX(style.paddingBottom)||0)+
		(fdjtDOM.parsePX(style.borderTopWidth)||0)+
		(fdjtDOM.parsePX(style.borderBottomWidth)||0)+
		padding;
	    maxwidth=maxwidth-hpadding; maxheight=maxheight-vpadding; 
	    var itfits=((bounds.height/maxheight)<=1)&&((bounds.width/maxwidth)<=1);
	    if (trace_adjust) 
		fdjtLog("Adjust (%o) %s cur=%o%s, best=%o~%o, limit=%ox%o=%o, box=%ox%o=%o, style=%s",
			goodenough,fdjtDOM.nodeString(container),
			scale,((itfits)?" (fits)":""),
			container.bestscale||-1,container.bestfit||-1,
			maxwidth,maxheight,maxwidth*maxheight,
			bounds.width,bounds.height,bounds.width*bounds.height,
			styleString(container));
	    if (itfits) {
		/* Figure out how well it fits */
		var fit=Math.max((1-(bounds.width/maxwidth)),
				 (1-(bounds.height/maxheight)));
		var bestfit=container.bestfit||1.5;
		if (!(trace_adjust)) {}
		else if (container.bestscale) 
		    fdjtLog("%s %o~%o vs. %o~%o",
			    ((fit<goodenough)?"Good enough!":
			     ((fit<bestfit)?"Better!":"Worse!")),
			    scale,fit,container.bestscale,container.bestfit);
		else fdjtLog("First fit %o~%o",scale,fit);
		if (fit<bestfit) {
		    container.bestscale=scale; container.bestfit=fit;}
		// If it's good enough, just return
		if (fit<goodenough) {
		    container.goodscale=scale; return;}}
	    // Figure out the next scale factor to try
	    var dh=bounds.height-maxheight; var dw=bounds.width-maxwidth;
	    var rh=maxheight/bounds.height; var rw=maxwidth/bounds.width;
	    var newscale=
		((itfits)?
		 (scale*Math.sqrt
		  ((maxwidth*maxheight)/(bounds.width*bounds.height))):
		 (rh<rw)?(scale*rh):(scale*rw));
	    if (trace_adjust)
		fdjtLog("[%fs] Trying newscale=%o, rw=%o rh=%o",
			fdjtET(),newscale,rw,rh);
	    applyScale(container,newscale,trace_adjust);}
	fdjtDOM.applyScale=applyScale;
	fdjtDOM.adjustToFit=adjustToFit;
	fdjtDOM.adjustInside=adjustInside;
	fdjtDOM.insideBounds=getInsideBounds;
	fdjtDOM.finishScale=function(container){
	    var traced=(container.traceadjust)||
		fdjtDOM.trace_adjust||default_trace_adjust;
	    if (!(container.bestscale)) {
		applyScale(container,false,traced);
		fdjtLog("No good scaling for %o style=%s",
			fdjtDOM.nodeString(container),
			fdjtDOM.styleString(container));
		return;}
	    else if (container.scale===container.bestscale) {}
	    else applyScale(container,container.bestscale,traced);
	    if (traced)
		fdjtLog("Final scale %o~%o for %o style=%s",
			container.bestscale,container.bestfit,
			fdjtDOM.nodeString(container),
			fdjtDOM.styleString(container));
	    delete container.bestscale;
	    delete container.bestfit;
	    delete container.goodscale;};
	
	/* Getting various kinds of metadata */

	function getHTML(){
	    var children=document.childNodes;
	    var i=0; var lim=children.length;
	    while (i<lim)
		if (children[i].tagName==='HTML') return children[i];
	    else i++;
	    return false;}
	fdjtDOM.getHTML=getHTML;

	function getHEAD(){
	    var children=document.childNodes;
	    var i=0; var lim=children.length;
	    while (i<lim)
		if (children[i].tagName==='HTML') {
		    var grandchildren=children[i].childNodes;
		    i=0; lim=grandchildren.length;
		    while (i<lim)
			if (grandchildren[i].tagName==='HEAD')
			    return grandchildren[i];
		    else i++;
		    return false;}
	    else i++;
	    return false;}
	fdjtDOM.getHEAD=getHEAD;

	function getMeta(name,multiple,matchcase,dom){
	    var results=[];
	    var matchname=((!(matchcase))&&(name.toUpperCase()));
	    var elts=((document.getElementsByTagName)?
		      (document.getElementsByTagName("META")):
		      (getChildren(document,"META")));
	    var i=0; while (i<elts.length) {
		if (elts[i])
		    if ((elts[i].name===name)||
			((matchname)&&(elts[i].name)&&
			 (elts[i].name.toUpperCase()===matchname))) {
			if (multiple) {
			    if (dom) results.push(elts[i++]);
			    else results.push(elts[i++].content);}
			else if (dom) return elts[i];
			else return elts[i].content;}
		else i++;}
	    if (multiple) return results;
	    else return false;}
	fdjtDOM.getMeta=getMeta;

	// This gets a LINK href field
	function getLink(name,multiple,matchcase,dom){
	    var results=[];
	    var matchname=((!((matchcase)))&&(name.toUpperCase()));
	    var elts=((document.getElementsByTagName)?
		      (document.getElementsByTagName("LINK")):
		      (getChildren(document,"LINK")));
	    var i=0; while (i<elts.length) {
		if (elts[i])
		    if ((elts[i].rel===name)||
			((matchname)&&(elts[i].rel)&&
			 (elts[i].rel.toUpperCase()===matchname))) {
			if (multiple) {
			    if (dom) results.push(elts[i++]);
			    else results.push(elts[i++].href);}
			else if (dom) return elts[i];
			else return elts[i].href;}
		else i++;}
	    if (multiple) return results;
	    else return false;}
	fdjtDOM.getLink=getLink;

	/* Going forward */

	var havechildren=((document)&&
			  (document.body)&&
			  (document.body.childNodes)&&
			  (document.body.children));

	// NEXT goes to the next sibling or the parent's next sibling
	function next_node(node){
	    while (node) {
		if (node.nextSibling)
		    return node.nextSibling;
		else node=node.parentNode;}
	    return false;}
	function next_element(node){
	    if (node.nextElementSibling)
		return node.nextElementSibling;
	    else {
		var scan=node;
		while (scan=scan.nextSibling) {
		    if (!(scan)) return null;
		    else if (scan.nodeType===1) break;
		    else {}}
		return scan;}}
	function scan_next(node,test,justelts){
	    if (!(test))
		if (justelts) {
		    if (havechildren) return node.nextElementSibling;
		    else return next_element(node);}
	    else return next_node(node);
	    var scan=((justelts)?
		      ((havechildren)?
		       (node.nextElementSibling):(next_element(node))):
		      ((node.nextSibling)||(next_node(node))));
	    while (scan)
		if (test(scan)) return scan;
	    else if (justelts)
		scan=((scan.nextElementSibling)||(next_element(scan)));
	    else scan=((scan.nextSibling)||(next_node(scan)));
	    return false;}

	// FORWARD goes to the first deepest child
	function forward_node(node){
	    if ((node.childNodes)&&((node.childNodes.length)>0))
		return node.childNodes[0];
	    else while (node) {
		if (node.nextSibling)
		    return node.nextSibling;
		else node=node.parentNode;}
	    return false;}
	function forward_element(node,n){
	    var scan;
	    if (n) {
		var i=0; scan=node;
		while (i<n) {scan=forward_element(scan); i++;}
		return scan;}
	    if (havechildren) {
		if ((node.children)&&(node.children.length>0)) {
		    return node.children[0];}
		if (scan=node.nextElementSibling) return scan;
		while (node=node.parentNode)
		    if (scan=node.nextElementSibling) return scan;
		return false;}
	    else {
		if (node.childNodes) {
		    var children=node.childNodes; var i=0; var lim=children.length;
		    while (i<lim)
			if ((scan=children[i++])&&(scan.nodeType===1)) return scan;}
		while (scan=node.nextSibling) if (scan.nodeType===1) return scan;
		while (node=node.parentNode)
		    if (scan=next_element(node)) return scan;
		return false;}}
	function scan_forward(node,test,justelts){
	    if (!(test)) {
		if (justelts) return forward_element(node);
		else return forward_node(node);}
	    var scan=((justelts)?(forward_element(node)):(forward_node(node)));
	    while (scan) {
		if (test(scan)) return scan;
		else if (justelts) scan=next_element(scan);
		else scan=next_node(scan);}
	    return false;}

	fdjtDOM.nextElt=next_element;
	fdjtDOM.forwardElt=forward_element;
	fdjtDOM.forward=scan_forward;
	fdjtDOM.next=scan_next;

	/* Scanning backwards */

	// PREV goes the parent if there's no previous sibling
	function prev_node(node){
	    while (node) {
		if (node.previousSibling)
		    return node.previousSibling;
		else node=node.parentNode;}
	    return false;}
	function previous_element(node){
	    if (havechildren)
		return node.previousElementSibling;
	    else {
		var scan=node;
		while (scan=scan.previousSibling) 
		    if (!(scan)) return null;
		else if (scan.nodeType===1) break;
		else {}
		if (scan) return scan;
		else return scan.parentNode;}}
	function scan_previous(node,test,justelts){
	    if (!(test))
		if (justelts) {
		    if (havechildren) return node.previousElementSibling;
		    else return previous_element(node);}
	    else return previous_node(node);
	    var scan=((justelts)?
		      ((havechildren)?(node.previousElementSibling):
		       (previous_element(node))):
		      (previous_node(node)));
	    while (scan)
		if (test(scan)) return scan;
	    else if (justelts)
		scan=((havechildren)?(scan.previousElementSibling):(previous_element(scan)));
	    else scan=prev_node(scan);
	    return false;}

	// BACKWARD goes to the final (deepest last) child
	//  of the previous sibling
	function backward_node(node){
	    if (node.previousSibling) {
		var scan=node.previousSibling;
		// If it's not an element, just return it
		if (scan.nodeType!==1) return scan;
		// Otherwise, return the last and deepest child
		while (scan) {
		    var children=scan.childNodes;
		    if (!(children)) return scan;
		    else if (children.length===0) return scan;
		    else scan=children[children.length-1];}
		return scan;}
	    else return node.parentNode;}

	function backward_element(node){
	    if (havechildren)
		return ((node.previousElementSibling)?
			(get_final_child((node.previousElementSibling))):
			(node.parentNode));
	    else if ((node.previousElementSibling)||(node.previousSibling)) {
		var start=(node.previousElementSibling)||(node.previousSibling);
		if (start.nodeType===1) 
		    return get_final_child(start);
		else return start;}
	    else return node.parentNode;}
	// We use a helper function because 
	function get_final_child(node){
	    if (node.nodeType===1) {
		if (node.childNodes) {
		    var children=node.childNodes;
		    if (!(children.length)) return node;
		    var scan=children.length-1;
		    while (scan>=0) {
			var child=get_final_child(children[scan--]);
			if (child) return child;}
		    return node;}
		else return node;}
	    else return false;}
	
	function scan_backward(node,test,justelts){
	    if (!(test)) {
		if (justelts) return backward_element(node);
		else return backward_node(node);}
	    var scan=((justelts)?
		      (backward_element(node)):
		      (backward_node(node)));
	    while (scan) {
		if (test(scan)) return scan;
		else if (justelts) scan=next_element(scan);
		else scan=next_node(scan);}
	    return false;}
	
	fdjtDOM.prevElt=previous_element;
	fdjtDOM.backwardElt=backward_element;
	fdjtDOM.backward=scan_backward;
	fdjtDOM.prev=scan_previous;

	/* Viewport/window functions */

	fdjtDOM.viewTop=function(win){
	    win=win||window;
	    return (win.pageYOffset||win.scrollY||
		    win.document.documentElement.scrollTop||0);};
	fdjtDOM.viewLeft=function(win){
	    win=win||window;
	    return (win.pageXOffset||win.scrollX||
		    win.document.documentElement.scrollLeft||0);};
	fdjtDOM.viewHeight=function(win){
	    win=win||window;
	    var docelt=((win.document)&&(win.document.documentElement));
	    return (win.innerHeight)||((docelt)&&(docelt.clientHeight));};
	fdjtDOM.viewWidth=function(win){
	    win=win||window;
	    var docelt=((win.document)&&(win.document.documentElement));
	    return ((win.innerWidth)||((docelt)&&(docelt.clientWidth)));};

	/* Stylesheet manipulation */

	// Adapted from 
	// http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript

        // Return requested style object
	function getCSSRule(ruleName, deleteFlag) {
	    ruleName=ruleName.toLowerCase();
            // If browser can play with stylesheets
	    if (document.styleSheets) {
		// For each stylesheet
		for (var i=0; i<document.styleSheets.length; i++) {
		    var styleSheet=document.styleSheets[i];
		    var cssRule=false;
		    var cssRules=styleSheet.cssRules||styleSheet.rules;
		    var n_rules=((cssRules)&&(cssRules.length));
		    var ii=0; while (ii<n_rules) {
			if (cssRules[ii])  {
			    var cssRule=cssRules[ii];
			    if (cssRule.selectorText.toLowerCase()==ruleName) {
				if (deleteFlag=='delete') {
				    if (styleSheet.cssRules) {
					styleSheet.deleteRule(ii);}
				    // Delete rule IE style.
				    return true;}
				// found and not deleting.
				else {return cssRule;}
                                // end found cssRule
			    }}   
			ii++;}
		    /* end for stylesheets */ }
		return false;}
	    return false;}
	fdjtDOM.getCSSRule=getCSSRule;

	function dropCSSRule(ruleName) {// Delete a CSS rule   
	    return getCSSRule(ruleName,'delete');}
	fdjtDOM.dropCSSRule=dropCSSRule;

	function addCSSRule(selector,text,sheet) {// Create a new css rule
	    if (!(sheet)) {
		var styles=fdjtID("FDJTSTYLES");
		if (!(styles)) {
		    var head=document.getElementsByTagName("HEAD");
		    if (head.length===0) return; else head=head[0];
		    styles=fdjtDOM("style#FDJTSTYLES");
		    head.appendChild(styles);}
		sheet=styles.sheet;}
	    if (sheet.insertRule) {
		var rules=sheet.cssRules||sheet.rules;
		var i=0; var lim=rules.length;
		while (i<lim) {
		    var rule=rules[i];
		    if (rule.selectorText===selector) break;
		    else i++;}
		if (i<lim) {
		    if (sheet.deleteRule) sheet.deleteRule(i);
		    else if (sheet.removeRule) sheet.removeRule(i);
		    else {}}
		var rules=sheet.cssRules||sheet.rules;
		var ruletext=selector+' {'+text+'}';
		if (sheet.insertRule)
		    sheet.insertRule(ruletext, rules.length);
		else if (sheet.addRule)
		    sheet.addRule(selector,text);
		else return false;
		return ruletext;}
	    else return false;}
	fdjtDOM.addCSSRule=addCSSRule;

	/* Listeners (should be in UI?) */

	function addListener(node,evtype,handler){
	    if (!(node)) node=document;
	    if (typeof node === 'string') node=fdjtID(node);
	    else if (node instanceof Array) {
		var i=0; var lim=node.length;
		while (i<lim) addListener(node[i++],evtype,handler);
		return;}
	    else if (node.length)
		return addListener(TOA(node),evtype,handler);
	    // OK, actually do it
	    if (evtype==='title') { 
		// Not really a listener, but helpful
		if (typeof handler === 'string') 
		    if (node.title)
			node.title='('+handler+') '+node.title;
		else node.title=handler;}
	    else if (evtype[0]==='=')
		node[evtype.slice(1)]=handler;
	    else if (node.addEventListener)  {
		// fdjtLog("Adding listener %o for %o to %o",handler,evtype,node);
		return node.addEventListener(evtype,handler,false);}
	    else if (node.attachEvent)
		return node.attachEvent('on'+evtype,handler);
	    else fdjtLog.warn('This node never listens: %o',node);}
	fdjtDOM.addListener=addListener;

	function addListeners(node,handlers){
	    if (handlers) 
		for (var evtype in handlers) {
		    if (handlers[evtype])
			addListener(node,evtype,handlers[evtype]);}}
	fdjtDOM.addListeners=addListeners;

	fdjtDOM.T=function(evt) {
	    evt=evt||event; return (evt.target)||(evt.srcElement);};

	fdjtDOM.cancel=function(evt){
	    evt=evt||event;
	    if (evt.preventDefault) evt.preventDefault();
	    else evt.returnValue=false;
	    evt.cancelBubble=true;};

	fdjtDOM.init=function(){
	    havechildren=((document)&&
			  (document.body)&&
			  (document.body.childNodes)&&
			  (document.body.children));};

	if (navigator.userAgent.search("WebKit")>=0) {
	    fdjtDOM.transition='-webkit-transition';
	    fdjtDOM.transitionProperty='-webkit-transition-property';
	    fdjtDOM.transform='-webkit-transform';
	    fdjtDOM.columnWidth='-webkit-column-width';
	    fdjtDOM.columnGap='-webkit-column-gap';}
	else if (navigator.userAgent.search("Mozilla")>=0) {
	    fdjtDOM.transitionProperty='-moz-transition-property';
	    fdjtDOM.transition='-moz-transition';
	    fdjtDOM.transform='-moz-transform';
	    fdjtDOM.columnWidth='MozColumnWidth';
	    fdjtDOM.columnGap='MozColumnGap';}
	else {
	    fdjtDOM.transitionProperty='transition-property';
	    fdjtDOM.transition='transition';
	    fdjtDOM.transform='transform';}
	
	/* Selection-y functions */

	fdjtDOM.getSelectedRange=function(){
	    var sel;
	    if (window.getSelection)
		sel=window.getSelection();
	    else if (document.selection)
		sel=document.selection.createRange();
	    else return false;
	    if (!(sel)) return false;
	    if (sel.getRangeAt)
		return sel.getRangeAt(0);
	    else if (document.createRange) {
		var range=document.createRange();
		range.setStart(
		    selectionObject.anchorNode,selectionObject.anchorOffset);
		range.setEnd(
		    selectionObject.focusNode,selectionObject.focusOffset);
		return range;}
	    else return false;}

	function node2text(node,accum){
	    if (!(accum)) accum="";
	    if (node.nodeType===3) {
		var stringval=node.nodeValue;
		if (stringval) accum=accum+stringval;
		return accum;}
	    else if (node.nodeType===1) {
		var children=node.childNodes;
		var i=0, lim=children.length;
		while (i<lim) {
		    accum=node2text(children[i++],accum);}
		return accum;}
	    else return accum;}
	fdjtDOM.node2text=node2text;
	
	function get_text_pos(node,pos,cur){
	    if (cur>pos) return false;
	    else if (node.nodeType===3) {
		var stringval=node.nodeValue;
		if (pos<(cur+stringval.length))
		    return { node: node, off: pos-cur};
		else return cur+stringval.length;}
	    else if (node.nodeType===1) {
		var children=node.childNodes;
		var i=0, lim=children.length;
		while (i<lim) {
		    cur=get_text_pos(children[i++],pos,cur);
		    if (!(typeof cur === 'number')) return cur;}
		return cur;}
	    else return cur;}

	function textPos(node,pos,sofar){
	    var result=get_text_pos(node,pos,sofar||0);
	    if (typeof result !== 'number') return result;
	    else return {node: node,off: pos};}
	fdjtDOM.textPos=textPos;

	fdjtDOM.refineRange=function(range){
	    if ((range.startContainer.nodeType===3)&&
		(range.endContainer.nodeType===3))
		return range;
	    var start_info=textPos(range.startContainer,range.startOffset);
	    var end_info=textPos(range.endContainer,range.endOffset);
	    var newrange=document.createRange();
	    newrange.setStart(start_info.node,start_info.off);
	    newrange.setEnd(end_info.node,end_info.off);
	    return newrange;}
	
	function get_text_off(scan,upto,sofar){
	    if (!(sofar)) sofar=0;
	    if (scan===upto) return [sofar];
	    else if (scan.nodeType===3)
		return sofar+scan.nodeValue.length;
	    else if (scan.nodeType===1) {
		var children=scan.childNodes;
		var i=0, lim=children.length;
		while (i<lim) {
		    var child=children[i++];
		    sofar=get_text_off(child,upto,sofar);
		    if (typeof sofar !== 'number') return sofar;}
		return sofar;}
	    else return sofar;}
	function textOff(node,pos){
	    var off=get_text_off(node,pos,0);
	    if (off) return off[0]; else return false;}
	fdjtDOM.textOff=textOff;
	
	function getIDParent(scan) {
	    while (scan) {
		if (scan.id) break;
		else scan=scan.parentNode;}
	    return scan;}

	fdjtDOM.getRangeInfo=function(range,within){
	    var start=range.startContainer;
	    if (!(within)) within=getIDParent(start);
	    var start_edge=textOff(within,start,0);
	    var end=range.endContainer;
	    var ends_in=((start===end)?(within):
			 (getParent(end,within))?(within):
			 (getIDParent(end)));
	    var end_edge=((start===end)?(start_edge):
			  textOff(ends_in,end,0));
	    return {start: start_edge+range.startOffset,
		    starts_in: within.id,ends_in: ends_in.id,
		    end: end_edge+range.endOffset};}

	function findString(node,string,count){
	    if (!(count)) count=1;
	    var fulltext=node2text(node); var loc=-1, cur=0;
	    while ((loc=fulltext.indexOf(string,cur))>=0) {
		if (count===1) {
		    var start=get_text_pos(node,loc,0);
		    var end=get_text_pos(node,loc+string.length,0);
		    if ((!start)||(!end)) return false;
		    var range=document.createRange();
		    range.setStart(start.node,start.off);
		    range.setEnd(end.node,end.off);
		    return range;}
		else {count--; cur=loc+string.length;}}
	    return false;}
	fdjtDOM.findString=findString;

	var docuri=false; var docbase=false;
	function init_docuri(){
	    if (docuri) return;
	    docuri=getLink("refuri")||getLink("canonical")||
		document.location.href;
	    docbase=getMeta("baseid");}
	

	function getAssignedIDs(node){
	    var refuris=[];
	    var baseids=[];
	    var scan=node;
	    var refuri=false;
	    while (scan) {
		if ((scan)&&(scan.getAttribute)&&
		    ((refuri=scan.getAttribute("data-refuri"))||
		     (refuri=scan.getAttribute("refuri")))) {
		    var baseid=scan.getAttribute("data-baseid")||
			scan.getAttribute("baseid")||
			false;
		    refuris.push(refuri); baseids.push(baseid);
		    scan=scan.parentNode;}
		else scan=scan.parentNode;}
	    var link=getLink("refuri")||getLink("canonical");
	    var base=getLink("baseid");
	    if (docuri) init_docuri();
	    refuris.push(docuri);
	    baseids.push(docbase);
	    var ids=[];
	    if (node.id) ids.push(node.id);
	    if (node.childNodes) {
		var children=node.childNodes, child;
		var i=0; var lim=children.length;
		while (i<lim) {
		    if (((child=children[i++]).nodeType===1)&&
			(child.tagName==='A')) {
			if (child.name) ids.push(child.name);
			else if (child.id) ids.push(child.id);}}}
	    var refs=[];
	    var i=0; var lim=ids.length;
	    while (i<lim) {
		var id=ids[i++];
		var j=0; var jlim=refuris.length;
		while (j<jlim) {
		    if ((!(baseids[j]))||
			(id.search(baseids[j])===0))
			refs.push(refuris[j]+"#"+id);
		    j++;}}
	    return refs;}

	fdjtDOM.getParaHash=function(node){
	    return paraHash(textify(node));}

	return fdjtDOM;
    })();

function fdjtID(id) {
    return ((id)&&
	    ((document.getElementById(id))||
	     ((id[0]==='#')&&
	      (document.getElementById(id.slice(1))))));}
function _(string) { return string;}

/* Emacs local variables
   ;;;  Local variables: ***
   ;;;  compile-command: "make; if test -f ../makefile; then cd ..; make; fi" ***
   ;;;  End: ***
*/
/* -*- Mode: Javascript; -*- */

/* Copyright (C) 2009-2012 beingmeta, inc.
   This file is a part of the FDJT web toolkit (www.fdjt.org)
   This file provides extended Javascript utility functions
   of various kinds.

   This program comes with absolutely NO WARRANTY, including implied
   warranties of merchantability or fitness for any particular
   purpose.

   Use, modification, and redistribution of this program is permitted
   under either the GNU General Public License (GPL) Version 2 (or
   any later version) or under the GNU Lesser General Public License
   (version 3 or later).

   These licenses may be found at www.gnu.org, particularly:
   http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
   http://www.gnu.org/licenses/lgpl-3.0-standalone.html

*/

/*
  _fdjtid: unique integer assigned to objects
  fdjtKB.register (assigns unique ID)
  fdjtKB.Pool (creates a pool of named objects)
  fdjtKB.Set (creates a sorted array for set operations)
  fdjtKB.Ref (objects created within a pool)
*/

var fdjtKB=
    (function(){
	// This is the top level object/module 
	fdjtKB={};
	fdjtKB.revid="$Id$";
	fdjtKB.version=parseInt("$Revision$".slice(10,-1));
	fdjtKB.persist=((window.localStorage)?(true):(false));

	// This turns on debugging, which is further controlled
	//  by properties on pools
	var debug=false;
	fdjtKB.setDebug=function(flag){debug=flag;};

	// This checks if a reference is a 'real object'
	// I.E., something which shouldn't be used as a key
	//  or fast set member and not an array either
	var arrayobjs=(typeof new Array(1,2,3) === 'object');
	var stringobjs=(typeof new String() === 'object');
	function isobject(x){
	    return ((typeof x === 'object')&&
		    (!((arrayobjs)&&(x instanceof Array))));}
	function objectkey(x){
	    if (typeof x !== 'object') return x;
	    else if (x instanceof String) return x.toString();
	    else return x._id||x._fdjtid||register(x);}
	fdjtKB.objectkey=objectkey;
	fdjtKB.isobject=isobject;
	

	// We allocate 16 million IDs for miscellaneous objects
	//  and use counter to track them.
	var counter=0;
	function register(x){
	    return (x._id)||(x._fdjtid)||(x._fdjtid=(++counter));}
	fdjtKB.register=register;
	
	// This lets us figure out what inits were run in this session.
	var init_start=fdjtTime();
	
	// Pools are uniquely named id->object mappings
	// This table maps those unique names to the objects themselves
	// Pools can have aliases, so the name->pool mapping is many to one
	var pools={};
	
	function Pool(name) {
	    if (!(name)) return this;
	    if (pools[name]) return pools[name];
	    pools[name]=this; this.name=name; this.map={};
	    this.index=false; this.storage=false;
	    this.inits=false; this.effects=false; this.xforms={};
	    // Whether _id fields in this pool are 'absolute' (globally unique)
	    this.absref=false; 
	    return this;}
	fdjtKB.Pool=Pool;
	fdjtKB.PoolRef=function(name,create){
	    if (!(name)) return this;
	    if (pools[name]) return pools[name];
	    else if (!(create)) return false;
	    else return new Pool(name);};
	
	Pool.prototype.toJSON=function(){return "@@"+this.name;};
	
	// Check if a named pool exists
	Pool.probe=function(id) {return pools[id]||false;};

	Pool.prototype.addAlias=function(name) {
	    if (pools[name])
		if (pools[name]===this) return this;
	    else throw {error: "pool alias conflict"};
	    else pools[name]=this;};

	Pool.prototype.addEffect=function(prop,handler) {
	    if (!(this.effects)) this.effects={};
	    this.effects[prop]=handler;};
	Pool.prototype.addInit=function(handler) {
	    if (!(this.inits)) this.inits=[];
	    this.inits.push(handler);};

	Pool.prototype.probe=function(id) {
	    if (this.map[id]) return (this.map[id]);
	    else return false;};

	Pool.prototype.load=function(ref) {
	    if (typeof ref==='string')
		return this.ref(ref).load();
	    else return ref.load();};

	Pool.prototype.ref=function(qid,cons) {
	    if (qid instanceof Ref) return qid;
	    if (this.map[qid]) return this.map[qid];
	    if (!(cons)) cons=this.cons(qid);
	    else if (cons instanceof Ref) {}
	    else cons=this.cons(qid);
	    if (!(cons._id)) cons._id=qid;
	    this.map[qid]=cons; cons.pool=this;
	    return cons;};
	Pool.prototype.drop=function(qid) {
	    var val=this.map[qid];
	    if ((val)&&(val.ondrop)) val.ondrop();
	    if (this.storage) this.storage.drop(val);
	    if (!(val)) return;
	    delete this.map[qid];
	    if (val.uuid) delete this.map[val.uuid];
	    if (val.oid) delete this.map[val.oid];}
	
	Pool.prototype.Import=function(data) {
	    if (data instanceof Array) {
		var i=0; var lim=data.length;
		while (i<lim) this.Import(data[i++]);
		return;}
	    else {
		var qid=data._id||data.oid||data.uuid;
		if ((debug)&&(this.traceimport))
		    fdjtLog("[%fs] Import to %s %o <== %o",
			    fdjtET(),this.name,obj,data);
		if (this.storage) this.storage.Import(data);
		if (qid) {
		    var obj=(this.map[qid]);
		    if (obj) obj.update(data);
		    else {
			obj=this.ref(qid);
			obj.init(data);}
		    return obj;}
		else return data;}};
	
	Pool.prototype.find=function(prop,val){
	    if (!(this.index)) return [];
	    return this.index(false,prop,val);};

	var uuid_pattern=
	    /[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}/;
	var refmaps=[];
	fdjtKB.addRefMap=function(map){
	    var i=0; var lim=refmaps.length;
	    while (i<lim) if (refmaps[i++]===map) return false;
	    refmaps.push(map);
	    return refmaps.length;};

	function getPool(arg){
	    var atpos; 
	    if (arg instanceof Ref) return arg.pool;
	    else if (typeof arg === 'number') return false;
	    else if (typeof arg === 'string') {
		var ref=parseRef(arg);
		if (ref) return ref.pool;
		else return false;}
	    else return false;}
	fdjtKB.getPool=getPool;

	function parseRef(arg,pool){
	    if (((arg[0]===':')&&(arg[1]==='@'))&&
		(((slash=arg.indexOf('/',2))>=0)))  {
		var pool=fdjtKB.PoolRef(arg.slice(1,slash+1));
		return pool.ref(arg);}
	    else if (((arg[0]==='@'))&&
		     (((slash=arg.indexOf('/',2))>=0)))  {
		var pool=fdjtKB.PoolRef(arg.slice(0,slash+1));
		return pool.ref(arg);}
	    else if ((atpos=arg.indexOf('@'))>1)  {
		var pool=fdjtKB.PoolRef(arg.slice(atpos+1));
		return pool.ref(arg.slice(0,atpos));}
	    else if (pool)
		return pool.ref(arg);
	    else if (arg.search(uuid_pattern)===0) {
		var uuid_type=arg.slice(34);
		var pool=fdjtKB.PoolRef("-UUIDTYPE="+uuid_type);
		if (pool) return pool.ref(arg);
		else return false;}
	    else if ((arg[0]===':')&&(arg[1]==='#')&&(arg[2]==='U')&&
		     (arg.search(uuid_pattern)===3)) {
		var uuid_type=arg.slice(37); var uuid=arg.slice(3);
		if ((pool)&&(pool.ref(uuid))) return pool.ref(uuid);
		var pool=fdjtKB.PoolRef("-UUIDTYPE="+uuid_type);
		if (pool) return pool.ref(uuid);
		return false;}
	    else if (refmaps.length) {
		var i=0; var lim=refmaps.length;
		while (i<lim) {
		    var refmap=refmaps[i++];
		    var ref=((typeof refmap === 'function')?
			     (refmap(arg)):(refmap[arg]));
		    if (ref) return ref;}
		return false;}
	    else if (pool) return pool.ref(arg);
	    else return false;}
	
	function getRef(arg,pool){
	    if (!(arg)) return false;
	    else if (arg instanceof Ref) return arg;
	    else if (typeof arg === 'number') return false;
	    else if (typeof arg === 'string')
		return parseRef(arg,pool);
	    else return false;}
	fdjtKB.ref=fdjtKB.getRef=getRef;
	function loadRef(arg){
	    var obj=getRef(arg);
	    if (obj) return obj.load();
	    else return undefined;}
	fdjtKB.load=fdjtKB.loadRef=loadRef;
	
	function doimport(data){
	    if (data instanceof Array) {
		var i=0; var lim=data.length; var results=[];
		while (i<lim) results.push(doimport(data[i++]));
		return results;}
	    else {
		var qid=data._id||data.uuid||data.oid;
		if (qid) {
		    var pool=getPool(qid);
		    if (pool) return pool.Import(data);
		    else return data;}
		else return data;}}
	fdjtKB.Import=doimport;

	// Array utility functions
	function arr_contains(arr,val,start){
	    return (arr.indexOf(val,start||0)>=0);}
	function arr_position(arr,val,start){
	    return arr.indexOf(val,start||0);}

	/* Fast sets */
	function set_sortfn(a,b) {
	    if (a===b) return 0;
	    else if (typeof a === typeof b) {
		if (typeof a === "number")
		    return a-b;
		else if (typeof a === "string")
		    if (a<b) return -1;
		else return 1;
		else if (a._id)
		    if (b._id)
			if (a._id<b._id) return -1;
		else if (a._id===b._id) return 0;
		else return 1;
		else return 1;
		else if (b._id) return -1;
		else if (a._fdjtid)
		    if (b._fdjtid) return a._fdjtid-b._fdjtid;
		else {
		    b._fdjtid=++counter;
		    return -1;}
		else if (b._fdjtid) {
		    a._fdjtid=++counter;
		    return 1;}
		else {
		    a._fdjtid=++counter;
		    b._fdjtid=++counter;
		    return -1;}}
	    else if (typeof a < typeof b) return -1;
	    else return 1;
	}

	function length_sortfn(a,b) {
	    if (a.length===b.length) return 0;
	    else if (a.length<b.length) return -1;
	    else return 1;}

	function intersection(set1,set2){
	    if (typeof set1 === 'string') set1=[set1];
	    if (typeof set2 === 'string') set2=[set2];
	    if ((!(set1))||(set1.length===0)) return [];
	    if ((!(set2))||(set2.length===0)) return [];
	    if (set1._sortlen!==set1.length) set1=Set(set1);
	    if (set2._sortlen!==set2.length) set2=Set(set2);
	    var results=new Array();
	    var i=0; var j=0; var len1=set1.length; var len2=set2.length;
	    var allstrings=set1._allstrings&&set2._allstrings;
	    var new_allstrings=true;
	    while ((i<len1) && (j<len2))
		if (set1[i]===set2[j]) {
		    if ((new_allstrings)&&(typeof set1[i] !== 'string'))
			new_allstrings=false;
		    results.push(set1[i]);
		    i++; j++;}
	    else if ((allstrings)?
		     (set1[i]<set2[j]):
		     (set_sortfn(set1[i],set2[j])<0)) i++;
	    else j++;
	    results._allstrings=new_allstrings;
	    results._sortlen=results.length;
	    return results;}
	fdjtKB.intersection=intersection;

	function difference(set1,set2){
	    if (typeof set1 === 'string') set1=[set1];
	    if (typeof set2 === 'string') set2=[set2];
	    if ((!(set1))||(set1.length===0)) return [];
	    if ((!(set2))||(set2.length===0)) return set1;
	    if (set1._sortlen!==set1.length) set1=Set(set1);
	    if (set2._sortlen!==set2.length) set2=Set(set2);
	    var results=new Array();
	    var i=0; var j=0; var len1=set1.length; var len2=set2.length;
	    var allstrings=set1._allstrings&&set2._allstrings;
	    var new_allstrings=true;
	    while ((i<len1) && (j<len2)) {
		if (set1[i]===set2[j]) {
		    i++; j++;}
		else if ((allstrings)?
			 (set1[i]<set2[j]):
			 (set_sortfn(set1[i],set2[j])<0)) {
		    if ((new_allstrings)&&(typeof set1[i] !== 'string'))
			new_allstrings=false;
		    results.push(set1[i]);
		    i++;}
		else j++;}
	    results._allstrings=new_allstrings;
	    results._sortlen=results.length;
	    return results;}
	fdjtKB.difference=difference;
	
	function union(set1,set2){
	    if (typeof set1 === 'string') set1=[set1];
	    if (typeof set2 === 'string') set2=[set2];
	    if ((!(set1))||(set1.length===0)) return set2;
	    if ((!(set2))||(set2.length===0)) return set1;
	    if (set1._sortlen!==set1.length) set1=Set(set1);
	    if (set2._sortlen!==set2.length) set2=Set(set2);
	    var results=new Array();
	    var i=0; var j=0; var len1=set1.length; var len2=set2.length;
	    var allstrings=set1._allstrings&&set2._allstrings;
	    while ((i<len1) && (j<len2))
		if (set1[i]===set2[j]) {
		    results.push(set1[i]); i++; j++;}
	    else if ((allstrings)?
		     (set1[i]<set2[j]):
		     (set_sortfn(set1[i],set2[j])<0))
		results.push(set1[i++]);
	    else results.push(set2[j++]);
	    while (i<len1) results.push(set1[i++]);
	    while (j<len2) results.push(set2[j++]);
	    results._allstrings=allstrings;
	    results._sortlen=results.length;
	    return results;}
	fdjtKB.union=union;

	function merge(set1,set2){
	    if (typeof set1 === 'string') set1=[set1];
	    if (typeof set2 === 'string') set2=[set2];
	    if ((!(set1))||(set1.length===0)) {
		set1.concat(set2);
		set1._sortlen=set2._sortlen;
		set1._allstrings=set2._allstrings;
		return set1;}
	    if ((!(set2))||(set2.length===0)) return set1;
	    var results=set1;
	    set1=[].concat(results);
	    var i=0; var j=0; var len1=set1.length; var len2=set2.length;
	    var allstrings=set1._allstrings&&set2._allstrings;
	    while ((i<len1) && (j<len2))
		if (set1[i]===set2[j]) {
		    results.push(set1[i]); i++; j++;}
	    else if ((allstrings)?
		     (set1[i]<set2[j]):
		     (set_sortfn(set1[i],set2[j])<0))
		results.push(set1[i++]);
	    else results.push(set2[j++]);
	    while (i<len1) results.push(set1[i++]);
	    while (j<len2) results.push(set2[j++]);
	    results._allstrings=allstrings;
	    results._sortlen=results.length;
	    return results;}
	fdjtKB.merge=merge;

	function overlaps(set1,set2){
	    if (typeof set1 === 'string') set1=[set1];
	    if (typeof set2 === 'string') set2=[set2];
	    if ((!(set1))||(set1.length===0)) return false;
	    if ((!(set2))||(set2.length===0)) return false;
	    if (set1._sortlen!==set1.length) set1=Set(set1);
	    if (set2._sortlen!==set2.length) set2=Set(set2);
	    var i=0; var j=0; var len1=set1.length; var len2=set2.length;
	    var allstrings=set1._allstrings&&set2._allstrings;
	    var new_allstrings=true;
	    while ((i<len1) && (j<len2))
		if (set1[i]===set2[j]) return true;
	    else if ((allstrings)?
		     (set1[i]<set2[j]):
		     (set_sortfn(set1[i],set2[j])<0)) i++;
	    else j++;
	    return false;}
	fdjtKB.overlaps=overlaps;

	/* Sets */
	/* sets are really arrays that are sorted to simplify set operations.
	   the ._sortlen property tells how much of the array is sorted */
	function Set(arg){
	    if (arguments.length===0) return [];
	    else if (arguments.length===1) {
		if (!(arg)) return [];
		else if (arg instanceof Array) {
		    if ((!(arg.length))||(arg._sortlen===arg.length))
			return arg;
		    else if (arg._sortlen) return setify(arg);
		    else return setify([].concat(arg));}
		else {
		    var result=[arg]; 
		    if (typeof arg === 'string') result._allstrings=true;
		    result._sortlen=1;
		    return result;}}
	    else {
		var result=[];
		for (arg in arguments)
		    if (!(arg)) {}
		else if (arg instanceof Array) result.concat(arg);
		else result.push(arg);
		return setify(result);}}
	fdjtKB.Set=Set;

	function setify(array) {
	    if (array._sortlen===array.length) return array;
	    // else if ((array._sortlen)&&(array._sortlen>1))
	    else if (array.length===0) return array;
	    else {
		var allstrings=true;
		for (elt in array)
		    if (typeof elt !== 'string') {allstrings=false; break;}
		array._allstrings=allstrings;
		if (allstrings) array.sort();
		else array.sort(set_sortfn);
		var read=1; var write=1; var lim=array.length;
		var cur=array[0];
		while (read<lim)
		    if (array[read]!==cur) {
			cur=array[read++]; write++;}
		else read++;
		array._sortlen=array.length=write;
		return array;}}
	
	function set_add(set,val) {
	    if (val instanceof Array) {
		var changed=false;
		for (elt in val) 
		    if (set_add(set,elt)) changed=true;
		return changed;}
	    else if (set.indexOf) {
		var pos=set.indexOf(val);
		if (pos>=0) return false;
		else set.push(val);
		return true;}
	    else {
		var i=0; var lim=set.length;
		while (i<lim)
		    if (set[i]===val) return false; else i++;
		if (typeof val !== 'string') set._allstrings=false;
		set.push(val);
		return true;}}
	
	function set_drop(set,val) {
	    if (val instanceof Array) {
		var changed=false;
		for (elt in val)
		    if (set_drop(set,elt)) changed=true;
		return changed;}
	    else if (set.indexOf) {
		var pos=set.indexOf(val);
		if (pos<0) return false;
		else set.splice(pos,1);
		return true;}
	    else {
		var i=0; var lim=set.length;
		while (i<lim)
		    if (set[i]===val) {
			array.splice(i,1);
			return true;}
		else i++;
		return false;}}
	
	/* Maps */
	function Map() {
	    this.scalar_map={}; this.object_map={};
	    return this;}
	Map.prototype.get=function(key) {
	    if (isobject(key))
		return this.object_map
	    [key._id||key.oid||key.uuid||key._fdjtid||register(key)];
	    else return this.scalar_map[key];};
	Map.prototype.set=function(key,val) {
	    if (isobject(key))
		this.object_map
	    [key._id||key.oid||key.uuid||key._fdjtid||register(key)]=val;
	    else this.scalar_map[key]=val;};
	Map.prototype.add=function(key,val) {
	    if (isobject(key)) {
		var objkey=key._id||key.oid||key.uuid||key._fdjtid||
		    register(key);
		var cur=this.object_map[objkey];
		if (!(cur)) {
		    this.object_map[objkey]=[val];
		    return true;}
		else if (!(cur instanceof Array)) {
		    if (cur===val) return false;
		    else {
			this.object_map[objkey]=[cur,val];
			return true;}}
		else if (arr_contains(cur,val)) return false;
		else {
		    cur.push(val); return true;}}
	    else  {
		var cur=this.scalar_map[key];
		if (!(cur)) {
		    this.scalar_map[key]=[val];
		    return true;}
		else if (!(cur instanceof Array)) {
		    if (cur===val) return false;
		    else {
			this.scalar_map[key]=[cur,val];
			return true;}}
		else if (arr_contains(cur,val)) return false;
		else {
		    cur.push(val); return true;}}};
	Map.prototype.drop=function(key,val) {
	    if (!(val)) {
		if (isobject(key))
		    delete this.object_map
		[key._id||key.oid||key.uuid||key._fdjtid||register(key)];
		else delete this.scalar_map[key];}
	    else if (isobject(key)) {
		var objkey=key._id||key.oid||key.uuid||key._fdjtid||
		    register(key);
		var cur=this.object_map[key];
		if (!(cur)) return false;
		else if (!(cur instanceof Array)) {
		    if (cur===val) {
			delete this.object_map[objkey];
			return true;}
		    else return false;}
		else if ((pos=arr_position(val,cur))>=0) {
		    if (cur.length===1) delete this.object_map[objkey];
		    else cur.splice(pos);
		    return true;}
		else return false;}
	    else {
		var cur=this.scalar_map[key]; var pos=-1;
		if (!(cur)) return false;
		else if (!(cur instanceof Array)) {
		    if (cur===val) {
			delete this.scalar_map[key];
			return true;}
		    else return false;}
		else if ((pos=arr_position(val,cur))>=0) {
		    if (cur.length===1)
			delete this.scalar_map[key];
		    else cur.splice(pos);
		    return true;}
		else return false;}};
	fdjtKB.Map=Map;

	/* Indices */

	function Index() {
	    var scalar_indices={};
	    var object_indices={};
	    var dontindex=false;
	    var index=function(item,prop,val,add){
		var valkey; var indices=scalar_indices;
		if (!(prop))
		    return {
			scalars: scalar_indices,
			objects: object_indices};
		else if ((dontindex)?(dontindex[prop]):(prop[0]==='_'))
		    return false;
		else if (!(val))
		    return {
			scalars: scalar_indices[prop],
			objects: object_indices[prop]};
		else if (isobject(val)) {
		    valkey=val._id||val.uuid||val.oid||val._fdjtid||
			register(val);
		    indices=object_indices;}
		else valkey=val;
		var index=indices[prop];
		if (!(item))
		    if (!(index)) return [];
		else return Set(index[valkey]);
 		var itemkey=
		    ((isobject(item))?
		     (item._id||item.uuid||item.oid||
		      item._fdjtid||register(item)):
		     (item));
		if (!(index))
		    if (add) {
			indices[prop]=index={};
			index[valkey]=[itemkey];
			return true;}
		else return false;
		var curvals=index[valkey];
		if (curvals) {
		    var pos=arr_position(curvals,itemkey);
		    if (pos<0) {
			if (add) {
			    curvals.push(itemkey);
			    return true;}
			else return false;}
		    else if (add) return false;
		    else {
			var sortlen=curvals._sortlen;
			curvals.splice(pos,1);
			if (pos<sortlen) curvals._sortlen--;
			return true;}}
		else if (add) {
		    index[valkey]=Set(itemkey);
		    return true;}
		else return false;};
	    return index;}
	fdjtKB.Index=Index;

	/* Refs */

	function Ref(pool,qid) {
	    if (pool) this.pool=pool;
	    if (qid) this._id=qid;
	    return this;}
	fdjtKB.Ref=Ref;
	Pool.prototype.cons=function(qid){return new Ref(this,qid);};

	Ref.prototype.load=function(){
	    if (this._init) return this;
	    else if (this.pool.storage) 
		return this.pool.storage.load(this);
	    else return undefined;};
	Ref.prototype.get=function(prop){
	    if (this.hasOwnProperty(prop)) return this[prop];
	    else if (this.pool.storage) {
		var fetched=this.pool.storage.get(this,prop);
		if (typeof fetched !== 'undefined')
		    this[prop]=fetched;
		else if (this.hasOwnProperty(prop))
		    return this[prop];
		else return fetched;}
	    else return undefined;};
	Ref.prototype.getSet=function(prop){
	    if (this.hasOwnProperty(prop)) {
		var val=this[prop];
		if (val instanceof Array)
		    if (val._sortlen===val.length) return val;
		else return setify(val);
		else return [val];}
	    else if (this.pool.storage) {
		var fetched=this.pool.storage.get(this,prop);
		if (typeof fetched !== 'undefined')
		    this[prop]=fetched;
		return setify(fetched);}
	    else return [];};
	Ref.prototype.getArray=function(prop){
	    if (this.hasOwnProperty(prop)) {
		var val=this[prop];
		if (val instanceof Array) return val;
		else return [val];}
	    else if (this.pool.storage) {
		var fetched=this.pool.storage.get(this,prop);
		if (typeof fetched !== 'undefined')
		    this[prop]=fetched;
		return [fetched];}
	    else return [];};
	Ref.prototype.add=function(prop,val){
	    if (this.pool.xforms[prop])
		val=this.pool.xforms[prop](val)||val;
	    if (this.hasOwnProperty(prop)) {
		var cur=this[prop];
		if (cur===val) return false;
		else if (cur instanceof Array)
		    if (!(set_add(cur,val))) return false;
		else {}
		else this[prop]=Set([cur,val]);}
	    else this[prop]=val;
	    if (this.pool.storage)
		this.pool.storage.add(this,prop,val);
	    if ((this.pool.effects)&&(this.pool.effects[prop]))
		this.pool.effects[prop](this,prop,val);
	    if (this.pool.index)
		this.pool.index(this,prop,val,true);};
	Ref.prototype.drop=function(prop,val){
	    if (typeof val === 'undefined') val=this[prop];
	    if (this.pool.xforms[prop])
		val=this.pool.xforms[prop](val)||val;
	    var vals=false;
	    if (this.hasOwnProperty(prop)) {
		var cur=this[prop];
		if (cur===val) delete this[prop];
		else if (cur instanceof Array) {
		    if (!(set_drop(cur,val))) return false;
		    if (cur.length===0) delete this[prop];}
		else return false;
		if (this.pool.storage)
		    this.pool.storage.drop(this,prop,val);
		if (this.pool.index)
		    this.pool.index(this,prop,val,false);
		return true;}
	    else return false;};
	Ref.prototype.test=function(prop,val){
	    if (this.pool.xforms[prop])
		val=this.pool.xforms[prop](val)||val;
	    if (this.hasOwnProperty(prop)) {
		if (typeof val === 'undefined') return true;
		var cur=this[prop];
		if (cur===val) return true;
		else if (cur instanceof Array)
		    if (arr_contains(cur,val)) return true;
		else return false;
		else return false;}
	    else if (this.pool.storage) {
		var fetched=this.pool.storage.get(this,prop);
		if (typeof fetched !== 'undefined')
		    this[prop]=fetched;
		else return false;
		if (typeof val === 'undefined') return true;
		else return this.test(prop,val);}
	    else return false;};
	Ref.prototype.ondrop=function(){
	    for (var prop in this)
		if ((prop!=='pool')&&(prop!=='qid'))
		    this.drop(prop,this[prop]);};
	function init_ref(data){
	    var pool=this.pool; var map=pool.map;
	    if ((this._init)&&(this._init>init_start)) {
		this.update_ref(data);
		return;}
	    if ((debug)&&(pool.traceref))
		fdjtLog("Initial reference to %o <== %o",this,data);
	    for (key in data)
		if (!((key==='qid')||(key==='pool'))) {
		    var value=data[key];
		    // Add ref aliases when unique
		    if ((key==='uuid')||(key==='oid')) {
			if (!(map[value])) map[value]=this;
			else if (map[value]!==this)
			    fdjtLog.warn("identifier conflict %o=%o for %o and %o",
					 key,value,map[value],this);
			else {}}
		    if (value instanceof Array) {
			var i=0; var len=value.length;
			while (i<len) this.add(key,value[i++]);}
		    else this.add(key,value);}
	    var inits=pool.inits;
	    if ((inits)&&(debug)&&(pool.traceinit))
		fdjtLog("Running pool inits for %o: %o",this,inits);
	    var i=0; var lim;
	    this._init=fdjtTime();
	    if (inits) {
		var lim=inits.length;
		while (i<lim) inits[i++](this);}
	    var inits=this._inits; delete this._inits;
	    if ((inits)&&(debug)&&(pool.traceinit))
		fdjtLog("Running delayed inits for %o: %o",this,inits);
	    if (inits) {
		delete this._inits;
		i=0; lim=inits.length;
		while (i<lim) inits[i++](this);}
	    return this;}
	Ref.prototype.init=init_ref;
	// This isn't right
	function update_ref(data){
	    var pool=this.pool; var map=pool.map;
	    for (var key in data) {
		if (key==="pool") continue;
		var val=data[key], cur=this[key];
		if (val===cur) continue;
		else if (!(cur)) {
		    if (val instanceof Array) {
			var i=0, lim=val.length;
			while (i<lim) this.add(key,val[i++]);}
		    else this.add(key,val);}
		else if ((val instanceof Array)||
			 (cur instanceof Array)) {
		    var toadd=difference(val,cur);
		    var todrop=difference(cur,val);
		    var i=0; var lim=todrop.length;
		    while (i<lim) this.drop(key,todrop[i++]);
		    var i=0; var lim=toadd.length;
		    while (i<lim) this.add(key,toadd[i++]);}
		else {
		    this.drop(key,cur);
		    this.add(key,val);}}
	    return this;}
	Ref.prototype.update=update_ref;
	Ref.prototype.oninit=function(fcn,name){
	    var debugging=((debug)&&(this.pool.traceinit));
	    if (this._init) {
		if (debugging) {
		    if (name)
			fdjtLog("Init (%s) %o on pre-existing %o",name,fcn,this);
		    else fdjtLog("Init %o on pre-existing %o",fcn,this);}
		fcn(this);
		return true;}
	    else if (this._inits) {
		// Save up the init functions
		if (!(name)) {
		    if (this._inits.indexOf(fcn)<0) {
			if (debugging) fdjtLog("Delaying init on %o: %o",this,fcn);
			this._inits.push(fcn);}}
		// Don't do anything if the named init has already been added
		else if (this._inits[name]) {
		    /* Note that name can't be anything that an array object
		       might inherit (like 'length'). */ }
		else {
		    if (debugging)
			fdjtLog("Delaying init %s on %o: %o",name,this,fcn);
		    this._inits[name]=fcn;
		    this._inits.push(fcn);}}
	    else if (name) {
		if (debugging)
		    fdjtLog("Delaying init %s on %o: %o",name,this,fcn);
		this._inits=[fcn];
		this._inits[name]=fcn;}
	    else {
		fdjtLog("Delaying init on %o: %o",this,fcn);
		this._inits=[fcn];}
	    return false;};

	/* Using offline storage to back up pools
	   In the simplest model, the QID is just used as a key
	   in local storage to store a JSON version of the object. */

	function OfflineKB(pool){
	    this.pool=pool;
	    return this;}
	function offline_get(obj,prop){
	    var qid=obj._id||obj.uuid||obj.oid;
	    var data=fdjtState.getLocal(qid);
	    if (data) obj.init(data);
	    return obj[prop];}
	OfflineKB.prototype.load=function(obj){
	    var qid=obj._id||obj.uuid||obj.oid;
	    var data=fdjtState.getLocal(qid,true);
	    if (data) return obj.init(data);
	    else return undefined;};
	OfflineKB.prototype.get=offline_get;
	OfflineKB.prototype.add=function(obj,slotid,val){
	    var qid=obj._id||obj.uuid||obj.oid;
	    if ((slotid)&&(val))
		fdjtState.setLocal(qid,JSON.stringify(obj));};
	OfflineKB.prototype.drop=function(obj,slotid,val){
	    var qid=obj._id||obj.uuid||obj.oid;
	    if (!(slotid)) fdjtState.dropLocal(qid);
	    else fdjtState.setLocal(qid,JSON.stringify(obj));};
	OfflineKB.prototype.Import=function(obj){
	    var qid=obj._id||obj.uuid||obj.oid;
	    fdjtState.setLocal(qid,obj,true);};
	fdjtKB.OfflineKB=OfflineKB;
	
	/* Miscellaneous array and table functions */

	fdjtKB.add=function(obj,field,val,nodup){
	    if (arguments.length===2)
		return set_add(obj,field);
	    else if (obj instanceof Ref)
		return obj.add.apply(obj,arguments);
	    else if (nodup) 
		if (obj.hasOwnProperty(field)) {
		    var vals=obj[field];
		    if (!(arr_contains(vals,val))) obj[field].push(val);
		    else {}}
	    else obj[field]=new Array(val);
	    else if (obj.hasOwnProperty(field))
		obj[field].push(val);
	    else obj[field]=new Array(val);
	    if ((obj._all) && (!(arr_contains(obj._all,field))))
		obj._all.push(field);};

	fdjtKB.drop=function(obj,field,val){
	    if (arguments.length===2)
		return set_drop(obj,field);
	    else if (obj instanceof Ref)
		return obj.drop.apply(obj,arguments);
	    else if (!(val))
		/* Drop all vals */
		obj[field]=new Array();
	    else if (obj.hasOwnProperty(field)) {
		var vals=obj[field];
		var pos=arr_position(vals,val);
		if (pos<0) return;
		else vals.splice(pos,1);}
	    else {}};

	fdjtKB.test=function(obj,field,val){
	    if (arguments.length===2)
		return set_contains(obj,field);
	    else if (obj instanceof Ref)
		return obj.test.apply(obj,arguments);
	    else if (typeof val === "undefined")
		return (((obj.hasOwnProperty) ?
			 (obj.hasOwnProperty(field)) : (obj[field])) &&
			((obj[field].length)>0));
	    else if (obj.hasOwnProperty(field)) { 
		if (arr_position(obj[field],val)<0)
		    return false;
		else return true;}
	    else return false;};

	fdjtKB.insert=function(array,value){
	    if (arr_position(array,value)<0) array.push(value);};

	fdjtKB.remove=function(array,value,count){
	    var pos=arr_position(array,value);
	    if (pos<0) return array;
	    array.splice(pos,1);
	    if (count) {
		count--;
		while ((count>0) &&
		       ((pos=arr_position(array,value,pos))>=0)) {
		    array.splice(pos,1); count--;}}
	    return array;};

	fdjtKB.indexOf=function(array,elt,pos){
	    if (pos) return array.indexOf(elt,pos);
	    else return array.indexOf(elt);};

	fdjtKB.contains=arr_contains;
	fdjtKB.position=arr_position;
	
	return fdjtKB;})();

/* Emacs local variables
   ;;;  Local variables: ***
   ;;;  compile-command: "make; if test -f ../makefile; then cd ..; make; fi" ***
   ;;;  End: ***
*/
/* -*- Mode: Javascript; -*- */

/* Copyright (C) 2009-2012 beingmeta, inc.
   This file is a part of the FDJT web toolkit (www.fdjt.org)
   This file provides extended Javascript utility functions
   of various kinds.

   This program comes with absolutely NO WARRANTY, including implied
   warranties of merchantability or fitness for any particular
   purpose.

   Use, modification, and redistribution of this program is permitted
   under either the GNU General Public License (GPL) Version 2 (or
   any later version) or under the GNU Lesser General Public License
   (version 3 or later).

   These licenses may be found at www.gnu.org, particularly:
   http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
   http://www.gnu.org/licenses/lgpl-3.0-standalone.html

*/

var fdjtState=
    (function(){

	function fdjtState(name,val,persist){
	    if (arguments.length===1)
		return ((window.sessionStorage)&&(getSession(name)))||
		((window.sessionStorage)&&(getLocal(name)))||
		getCookie(name);
	    else if (persist)
		if (window.localStorage)
		    if (val) setLocal(name,val);
	    else dropLocal(name);
	    else {
		var domain=fdjtState.domain||location.hostname;
		var path=fdjtState.path||"/";
		var duration=fdjtState.duration||(3600*24*365*7);
		if (val) setCookie(name,val,duration,path,domain);
		else clearCookie(name,path,domain);}
	    else if (val)
		if (window.sessionStorage) setSession(name,val);
	    else setCookie(name,val);
	    else if (window.sessionStorage) dropSession(name);
	    else clearCookie(name);};
	fdjtState.domain=false;
	fdjtState.path=false;
	fdjtState.duration=false;

	/* Old-school cookies */

	function getCookie(name,parse){
	    try {
		var cookies=document.cookie;
		var namepat=new RegExp("(^|(; ))"+name+"=");
		var pos=cookies.search(namepat);
		var valuestring;
		if (pos>=0) {
		    var start=cookies.indexOf('=',pos)+1;
		    var end=cookies.indexOf(';',start);
		    if (end>0) valuestring=cookies.slice(start,end);
		    else valuestring=cookies.slice(start);}
		else return false;
		if (parse)
		    return JSON.parse(decodeURIComponent(valuestring));
		else return decodeURIComponent(valuestring);}
	    catch (ex) {
		return false;}}
	fdjtState.getCookie=getCookie;

	function setCookie(name,value,expires,path,domain){
	    try {
		if (value) {
		    var valuestring=
			((typeof value === 'string') ? (value) :
			 (value.toJSON) ? (value.toJSON()) :
			 (value.toString) ? (value.toString()) : (value));
		    var cookietext=name+"="+encodeURIComponent(valuestring);
		    if (expires)
			if (typeof(expires)==='string')
			    cookietext=cookietext+'; '+expires;
		    else if (expires.toGMTString)
			cookietext=cookietext+"; expires="+expires.toGMTString();
		    else if (typeof(expires)==='number')
			if (expires>0) {
			    var now=new Date();
			    now.setTime(now.getTime()+expires);
			    cookietext=cookietext+"; expires="+now.toGMTString;}
		    else cookietext=cookietext+"; expires=Sun 1 Jan 2000 00:00:00 UTC";
		    else {}
		    if (path) cookietext=cookietext+"; path="+path;
		    // This certainly doesn't work generally and might not work ever
		    if (domain) cookietext=cookietext+"; domain="+domain;
		    // fdjtTrace("Setting cookie %o cookietext=%o",name,cookietext);
		    document.cookie=cookietext;}
		else clearCookie(name,path,domain);}
	    catch (ex) {
		fdjtLog.warn("Error setting cookie %s",name);}}
	fdjtState.setCookie=setCookie;
	
	function clearCookie(name,path,domain){
	    try {
		var valuestring="ignoreme";
		var cookietext=name+"="+encodeURIComponent(valuestring)+
		    "; expires=Sun 1 Jan 2000 00:00:00 UTC";
		if (path) cookietext=cookietext+"; path="+path;
		// This certainly doesn't work generally and might not work ever
		if (domain) cookietext=cookietext+"; domain="+domain;
		// fdjtTrace("Clearing cookie %o: text=%o",name,cookietext);
		document.cookie=cookietext;}
	    catch (ex) {
		fdjtLog.warn("Error clearing cookie %s",name);}}
	fdjtState.clearCookie=clearCookie;

	/* Session storage */

	function setSession(name,val,unparse){
	    if (unparse) val=JSON.stringify(val);
	    if (window.sessionStorage)
		window.sessionStorage[name]=val;
	    else setCookie(name,val);}
	fdjtState.setSession=setSession;

	function getSession(name,parse){
	    var val=((window.sessionStorage)?
		     (window.sessionStorage[name]):
		     (fdjtGetCookie(name)));
	    if (val)
		if (parse) return JSON.parse(val); else return val;
	    else return false;}
	fdjtState.getSession=getSession;

	function dropSession(name){
	    if (window.sessionStorage)
		return window.sessionStorage.removeItem(name);
	    else clearCookie(name);}
	fdjtState.dropSession=dropSession;

	/* Local storage (persists between sessions) */

	function setLocal(name,val,unparse){
	    if (!(name)) throw { error: "bad name",name: name};
	    if (unparse) val=JSON.stringify(val);
	    if (window.localStorage)
		window.localStorage[name]=val;}
	fdjtState.setLocal=setLocal;

	function getLocal(name,parse){
	    if (window.localStorage) {
		var val=window.localStorage[name];
		if (val)
		    if (parse) return JSON.parse(val); else return val;
		else return false;}
	    else return false;}
	fdjtState.getLocal=getLocal;

	function dropLocal(name){
	    if (window.localStorage)
		return window.localStorage.removeItem(name);
	    else return false;}
	fdjtState.dropLocal=dropLocal;
	
	function clearLocal(){
	    if (window.localStorage) {
		var storage=window.localStorage;
		var i=0; var lim=storage.length;
		var keys=[];
		while (i<lim) keys.push(storage.key(i++));
		i=0; while (i<lim) storage.removeItem(keys[i++]);}}
	fdjtState.clearLocal=clearLocal;

	/* Gets arguments from the query string */
	function getQuery(name,multiple,matchcase,verbatim){
	    if (!(location.search))
		if (multiple) return [];
	    else return false;
	    var results=[];
	    var ename=encodeURIComponent(name);
	    var namepat=new RegExp
	    ("(&|^|\\?)"+ename+"(=|&|$)",((matchcase)?"g":"gi"));
	    var query=location.search;
	    var start=query.search(namepat);
	    while (start>=0) {
		// Skip over separator if non-initial
		if ((query[start]==='?')||(query[start]==='&')) start++;
		// Skip over the name
		var valstart=start+ename.length; var end=false;
		if (query[valstart]==="=") {
		    var valstring=query.slice(valstart+1);
		    end=valstring.search(/(&|$)/g);
		    if (end<=0) {
			results.push("");
			if (!(multiple)) break;}
		    else {
			results.push(valstring.slice(0,end));
			if (!(multiple)) break;}}
		else if (multiple)
		    results.push(query.slice(start,end));
		else if (verbatim)
		    return query.slice(start,end);
		else return querydecode(query.slice(start,end));
		if (end>0) {
		    query=query.slice(end);
		    start=query.search(namepat);}}
	    if (!(verbatim)) {
		var i=0; var lim=results.length;
		while (i<lim) {results[i]=querydecode(results[i]); i++;}}
	    if (multiple) return results;
	    else if (results.length)
		return results[0];
	    else return false;}
	fdjtState.getQuery=getQuery;
	
	function querydecode(string){
	    if (decodeURIComponent)
		return decodeURIComponent(string);
	    else return 
	    string.replace
	    (/%3A/gi,":").replace
	    (/%2F/gi,"/").replace
	    (/%3F/gi,"?").replace
	    (/%3D/gi,"=").replace
	    (/%20/gi," ").replace
	    (/%40/gi,"@").replace
	    (/%23/gi,"#");}

	function test_opt(pos,neg){
	    var pospat=((pos)&&(new RegExp("\\b"+pos+"\\b")));
	    var negpat=((neg)&&negative_opt_pat(neg));
	    var i=2; while (i<arguments.length) {
		var arg=arguments[i++];
		if (!(arg)) continue;
		else if (typeof arg === 'string')
		    if ((pospat)&&(arg.search(pospat)>=0)) return true;
		else if ((negpat)&&(arg.search(negpat)>=0)) return false;
		else continue;
		else if (arg.length) {
		    var j=0; var len=arg.length;
		    while (j<len)
			if ((pos)&&(arg[j]===pos)) return true;
		    else if ((neg)&&(arg[j]===neg)) return false;
		    else j++;
		    return false;}
		else continue;}
	    return false;}
	fdjtState.testOption=test_opt;

	function negative_opt_pat(neg){
	    if (!(neg)) return neg;
	    else if (typeof neg === 'string')
		return (new RegExp("\\b"+neg+"\\b","gi"));
	    else if (neg.length) {
		var rule="\\b(";
		var i=0; while (i<neg.length) {
		    var name=neg[i];
		    if (i>0) rule=rule+"|";
		    rule=rule+"("+name+")";
		    i++;}
		rule=rule+")\\b";
		return new RegExp(rule,"gi");}
	    else return false;}

	fdjtState.argVec=function(argobj,start){
	    var i=start||0;
	    var result=new Array(argobj.length-i);
	    while (i<argobj.length) {
		result[i-start]=argobj[i]; i++;}
	    return result;};

	var zeros="000000000000000000000000000000000000000000000000000000000000000";
	function zeropad(string,len){
	    if (string.length===len) return string;
	    else if (string.length>len) return string.slice(0,len);
	    else return zeros.slice(0,len-string.length)+string;}
	
	// This is a random nodeid used to generate UUIDs
	//  We use it because we can't access the MAC address
	var nodeid=
	    zeropad(((Math.floor(Math.random()*65536)).toString(16)+
		     (Math.floor(Math.random()*65536)).toString(16)+
		     (Math.floor(Math.random()*65536)).toString(16)+
		     (Math.floor(Math.random()*65536)|0x01)).toString(16),
		    12);
	
	var default_version=17; 
	var clockid=Math.floor(Math.random()*16384); var msid=1;
	var last_time=new Date().getTime();
	
	fdjtState.getNodeID=function(){return nodeid;};
	fdjtState.setNodeID=function(arg){
	    if (typeof arg==='number')
		nodeid=zeropad(arg.toString(16),12);
	    else if (typeof arg === 'string')
		if (arg.search(/[^0123456789abcdefABCDEF]/)<0)
		    nodeid=zeropad(arg,12);
	    else throw {error: 'invalid node id',value: arg};
	    else throw {error: 'invalid node id',value: arg};};

	function getUUID(node){
	    var now=new Date().getTime();
	    if (now<last_time) {now=now*10000; clockid++;}
	    else if (now===last_time)	now=now*10000+(msid++);
	    else {now=now*10000; msid=1;}
	    now=now+122192928000000000;
	    if (!(node)) node=nodeid;
	    var timestamp=now.toString(16); var tlen=timestamp.length;
	    if (tlen<15) timestamp=zeros.slice(0,15-tlen)+timestamp;
	    return timestamp.slice(7)+"-"+timestamp.slice(3,7)+
		"-1"+timestamp.slice(0,3)+
		"-"+(32768+(clockid%16384)).toString(16)+
		"-"+((node)?
		     ((typeof node === 'number')?
		      (zeropad(node.toString(16),12)):
		      (zeropad(node,12))):
		     (nodeid));}
	fdjtState.getUUID=getUUID;
	
	// Getting version information
	function versionInfo(){
	    var s=navigator.userAgent; var result={};
	    var start;
	    while ((start=s.search(/\w+\/\d/g))>=0) {
		var slash=s.indexOf('/',start);
		var afterslash=s.slice(slash+1);
		var num_end=afterslash.search(/\W/);
		var numstring=afterslash.slice(0,num_end);
		try {
		    result[s.slice(start,slash)]=parseInt(numstring);}
		catch (ex) {
		    result[s.slice(start,slash)]=numstring;}
		s=afterslash.slice(num_end);}
	    if (result['Chrome']) result.browser='Chrome';
	    else if (result['Opera']) result.browser='Opera';
	    else if (result['Safari']) result.browser='Safari';
	    else if ((result['Safari'])&&(result['Mobile']))
		result.browser='MobileSafari';
	    else if (result['Firefox']) result.browser='Firefox';
	    else if ((result['Explorer'])||(result['IE'])||
		     (result['InternetExplorer'])||(result['MSIE']))
		result.browser='IE';
	    else if (result['Mozilla']) result.browser='Mozilla';
	    else result.browser='Browser';
	    result.platform=navigator.platform||"Turing";
	    return result;}
	fdjtState.versionInfo=versionInfo;

	return fdjtState;})();

/* Emacs local variables
   ;;;  Local variables: ***
   ;;;  compile-command: "make; if test -f ../makefile; then cd ..; make; fi" ***
   ;;;  End: ***
*/
/* -*- Mode: Javascript; -*- */

/* Copyright (C) 2009-2012 beingmeta, inc.
   This file is a part of the FDJT web toolkit (www.fdjt.org)
   This file provides extended Javascript utility functions
   of various kinds.

   This program comes with absolutely NO WARRANTY, including implied
   warranties of merchantability or fitness for any particular
   purpose.

   Use, modification, and redistribution of this program is permitted
   under either the GNU General Public License (GPL) Version 2 (or
   any later version) or under the GNU Lesser General Public License
   (version 3 or later).

   These licenses may be found at www.gnu.org, particularly:
   http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
   http://www.gnu.org/licenses/lgpl-3.0-standalone.html

*/

var fdjtLog=(function(){
    var backlog=[];

    function fdjtLog(string){
	var output=false;
	if (((fdjtLog.doformat)||(string.search("%j")))&&
	    (typeof fdjtString !== 'undefined'))
	    output=fdjtString.apply(null,arguments);
	if (fdjtLog.console_fn) {
	    if (output) fdjtLog.console_fn.call(fdjtLog.console,output);
	    else fdjtLog.console_fn.apply(fdjtLog.console,arguments);}
	if (fdjtLog.console) {
	    var domconsole=fdjtLog.console;
	    var timespan=fdjtDOM("span.time",fdjtET());
	    var entry=fdjtDOM("div.fdjtlog");
	    if (output) entry.innerHTML=output;
	    else entry.innerHTML=fdjtString.apply(null,arguments);
	    fdjtDOM.prepend(entry,timespan);
	    if (typeof domconsole === 'string') {
		var found=document.getElementById(domconsole);
		if (found) {
		    domconsole=fdjtLog.console=found;
		    var i=0; var lim=backlog.length;
		    while (i<lim) fdjtDOM(domconsole,backlog[i++]);
		    backlog=[];}
		else domconsole=false;}
	    else if (!(domconsole.nodeType)) domconsole=false;
	    if (domconsole)
		fdjtDOM.append(domconsole,entry);
	    else backlog.push(entry);}
	if ((fdjtLog.useconsole)||
	    ((!(fdjtLog.console))&&(!(fdjtLog.console_fn))))
	    if ((window.console) && (window.console.log) &&
		(window.console.count)) {
		if (output)
		    window.console.log.call(
			window.console,"["+fdjtET()+"s] "+output);
		else {
		    var newargs=new Array(arguments.length+1);
		    newargs[0]="[%fs] "+string;
		    newargs[1]=fdjtET();
		    var i=1; var lim=arguments.length;
		    while (i<lim) {newargs[i+1]=arguments[i]; i++;}
		    window.console.log.apply(window.console,newargs);}}}
    fdjtLog.console=null;
    fdjtLog.id="$Id$";
    fdjtLog.version=parseInt("$Revision$".slice(10,-1));

    fdjtLog.warn=function(string){
	if ((!(fdjtLog.console_fn))&&
	    (!(window.console)&&(window.console.log)&&(window.console.log.count))) {
	    var output=fdjtString.apply(null,arguments);
	    alert(output);}
	else fdjtLog.apply(null,arguments);};

    fdjtLog.uhoh=function(string){
	if (fdjtLog.debugging) fdjtLog.warn.call(this,arguments);}

    fdjtLog.bkpt=function(string){
	var output=false;
	if ((fdjtLog.doformat)&&(typeof fdjtString !== 'undefined'))
	    output=fdjtString.apply(null,arguments);
	if (fdjtLog.console_fn)
	    if (output) fdjtLog.console_fn(fdjtLog.console,output);
	else fdjtLog.console_fn.apply(fdjtLog.console,arguments);
	else if ((window.console) && (window.console.log) &&
		 (window.console.count))
	    if (output)
		window.console.log.call(window.console,output);
	else window.console.log.apply(window.console,arguments);
    };

    fdjtLog.useconsole=true;

    return fdjtLog;})();

// This is for temporary trace statements; we use a different name
//  so that they're easy to find.
var fdjtTrace=fdjtLog;

/**
 * HumaneJS
 * Humanized Messages for Notifications
 * @author Marc Harter (@wavded)
 * @contributers
 *   Alexander (@bga_)
 *   Jose (@joseanpg)
 * @example
 *  humane('hello world');
 */
;(function(win,doc){
    var eventOn, eventOff;
    if (win.addEventListener) {
	eventOn = function(obj,type,fn){obj.addEventListener(type,fn,false)};
	eventOff = function(obj,type,fn){obj.removeEventListener(type,fn,false)};
    } else {
	eventOn = function(obj,type,fn){obj.attachEvent('on'+type,fn)};
	eventOff = function(obj,type,fn){obj.detachEvent('on'+type,fn)};
    }

    var eventing = false;
    var animationInProgress = false;
    var humaneEl = null;
    // Table mapping msg node IDs into the nodes themselves
    var msgnodes={};
    var timeout = null;
    // ua sniff for filter support
    var useFilter = /msie [678]/i.test(navigator.userAgent);
    var isSetup = false;
    var queue = [];

    eventOn(win,'load',function(){
        var transitionSupported = (function(style){
            var prefixes = ['MozT','WebkitT','OT','msT','KhtmlT','t'];
            for(var i = 0, prefix; prefix = prefixes[i]; i++){
                if(prefix+'ransition' in style) return true;
            }
            return false;
        }(doc.body.style));

        if(!transitionSupported) animate = jsAnimateOpacity; // override animate
        setup();
        run();
    });

    function setup() {
	var probe=doc.getElementById('HUMANE');
	if (probe) humaneEl=probe;
	else {
            humaneEl = doc.createElement('div');
            humaneEl.id = 'HUMANE';
            humaneEl.className = 'humane';
            doc.body.appendChild(humaneEl);}
        if(useFilter) humaneEl.filters.item('DXImageTransform.Microsoft.Alpha').Opacity = 0; // reset value so hover states work
        isSetup = true;
    }

    function remove() {
        eventOff(doc.body,'mousemove',remove);
        eventOff(doc.body,'click',remove);
        eventOff(doc.body,'keypress',remove);
        eventOff(doc.body,'touchstart',remove);
        eventing = false;
        if(animationInProgress) animate(0);
    }

    function run() {
        if(animationInProgress && !fdjtLog.notify.forceNew) return;
        if(!queue.length){
            remove();
            return;
        }

        animationInProgress = true;

        if(timeout){
            clearTimeout(timeout);
            timeout = null;
        }

        timeout = setTimeout(function(){
	    // allow notification to stay alive for timeout
            if(!eventing){
                eventOn(doc.body,'mousemove',remove);
                eventOn(doc.body,'click',remove);
                eventOn(doc.body,'keypress',remove);
                eventOn(doc.body,'touchstart',remove);
                eventing = true;
                if(!fdjtLog.notify.waitForMove) remove();
            }
        }, fdjtLog.notify.timeout);

	var msg=queue.shift();
	if (msg.nodeType) {
	    humaneEl.innerHTML = "";
	    humaneEl.appendChild(msg);}
	else if (typeof msg !== 'string')
	    throw new Exception("Bad arg to Humane");
	else if ((msg.length>1)&&(msg[0]==='#')) {
	    var nodeid=msg.slice(1);
	    node=msgnodes[nodeid];
	    if ((!(node))&&(node=document.getElementById(nodeid)))
		msgnodes[nodeid]=node;
	    if (node) {
		humaneEl.innerHTML = "";
		humaneEl.appendChild(node);}
	    else humaneEl.innerHTML = msg;}
	else humaneEl.innerHTML = msg;
        animate(1);
    }

    function animate(level){
        if(level === 1){
            humaneEl.className = "humane humane-show";
        } else {
            humaneEl.className = "humane";
            end();
        }
    }

    function end(){
        animationInProgress = false;
        setTimeout(run,500);
    }

    // if CSS Transitions not supported, fallback to JS Animation
    var setOpacity = (function(){
        if(useFilter){
            return function(opacity){
                humaneEl.filters.item('DXImageTransform.Microsoft.Alpha').Opacity = opacity*100;
            }
        } else {
            return function(opacity){
                humaneEl.style.opacity = String(opacity);
            }
        }
    }());
    function jsAnimateOpacity(level,callback){
        var interval;
        var opacity;

        if (level === 1) {
            opacity = 0;
            if(fdjtLog.notify.forceNew){
                opacity = useFilter ? humaneEl.filters.item('DXImageTransform.Microsoft.Alpha').Opacity/100|0 : humaneEl.style.opacity|0;
            }
            humaneEl.style.visibility = "visible";
            interval = setInterval(function(){
                if(opacity < 1) {
                    opacity +=0.1;
                    if (opacity>1) opacity = 1;
                    setOpacity(opacity);
                }
                else {
                    clearInterval(interval);
                }
            }, 500 / 20);
        } else {
            opacity = 1;
            interval = setInterval(function(){
                if(opacity > 0) {
                    opacity -=0.1;
                    if (opacity<0) opacity = 0;
                    setOpacity(opacity);
                }
                else {
                    clearInterval(interval);
                    humaneEl.style.visibility = "hidden";
                    end();
                }
            }, 500 / 20);
        }
    }

    function notify(message){
	fdjtLog.apply(null,arguments);
	if (arguments.length>1)
	    message=fdjtString.apply(null,arguments);
        queue.push(message);
        if(isSetup) run();}

    function msg(message){
	if (!(message)) {
            if(!eventing){
                eventOn(doc.body,'mousemove',remove);
                eventOn(doc.body,'click',remove);
                eventOn(doc.body,'keypress',remove);
                eventOn(doc.body,'touchstart',remove);
                eventing = true;}
	    animationInProgress=true;
	    animate(1);
	    return;}
	if (arguments.length>1)
	    message=fdjtString.apply(null,arguments);
        queue.push(message);
        if(isSetup) run();}

    fdjtLog.notify = notify;
    fdjtLog.notify.timeout = 2000;
    fdjtLog.notify.waitForMove = true;
    fdjtLog.notify.forceNew = false;

    fdjtLog.Humane=msg;
    fdjtLog.HumaneHide=remove;

}(window,document));


var fdjtNotify=fdjtLog.notify;

/* Emacs local variables
   ;;;  Local variables: ***
   ;;;  compile-command: "make; if test -f ../makefile; then cd ..; make; fi" ***
   ;;;  End: ***
*/
/*
    http://www.JSON.org/json2.js
    2009-06-29

    Public Domain.

    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

    See http://www.JSON.org/js.html

    This file creates a global JSON object containing two methods: stringify
    and parse.

        JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.

            replacer    an optional parameter that determines how object
                        values are stringified for objects. It can be a
                        function or an array of strings.

            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or '&nbsp;'),
                        it contains the characters used to indent at each level.

            This method produces a JSON text from a JavaScript value.

            When an object value is found, if the object contains a toJSON
            method, its toJSON method will be called and the result will be
            stringified. A toJSON method does not serialize: it returns the
            value represented by the name/value pair that should be serialized,
            or undefined if nothing should be serialized. The toJSON method
            will be passed the key associated with the value, and this will be
            bound to the object holding the key.

            For example, this would serialize Dates as ISO strings.

                Date.prototype.toJSON = function (key) {
                    function f(n) {
                        // Format integers to have at least two digits.
                        return n < 10 ? '0' + n : n;
                    }

                    return this.getUTCFullYear()   + '-' +
                         f(this.getUTCMonth() + 1) + '-' +
                         f(this.getUTCDate())      + 'T' +
                         f(this.getUTCHours())     + ':' +
                         f(this.getUTCMinutes())   + ':' +
                         f(this.getUTCSeconds())   + 'Z';
                };

            You can provide an optional replacer method. It will be passed the
            key and value of each member, with this bound to the containing
            object. The value that is returned from your method will be
            serialized. If your method returns undefined, then the member will
            be excluded from the serialization.

            If the replacer parameter is an array of strings, then it will be
            used to select the members to be serialized. It filters the results
            such that only members with keys listed in the replacer array are
            stringified.

            Values that do not have JSON representations, such as undefined or
            functions, will not be serialized. Such values in objects will be
            dropped; in arrays they will be replaced with null. You can use
            a replacer function to replace those with JSON values.
            JSON.stringify(undefined) returns undefined.

            The optional space parameter produces a stringification of the
            value that is filled with line breaks and indentation to make it
            easier to read.

            If the space parameter is a non-empty string, then that string will
            be used for indentation. If the space parameter is a number, then
            the indentation will be that many spaces.

            Example:

            text = JSON.stringify(['e', {pluribus: 'unum'}]);
            // text is '["e",{"pluribus":"unum"}]'


            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

            text = JSON.stringify([new Date()], function (key, value) {
                return this[key] instanceof Date ?
                    'Date(' + this[key] + ')' : value;
            });
            // text is '["Date(---current time---)"]'


        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.

            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.

            Example:

            // Parse the text. Values that look like ISO date strings will
            // be converted to Date objects.

            myData = JSON.parse(text, function (key, value) {
                var a;
                if (typeof value === 'string') {
                    a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
                    if (a) {
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                            +a[5], +a[6]));
                    }
                }
                return value;
            });

            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
                var d;
                if (typeof value === 'string' &&
                        value.slice(0, 5) === 'Date(' &&
                        value.slice(-1) === ')') {
                    d = new Date(value.slice(5, -1));
                    if (d) {
                        return d;
                    }
                }
                return value;
            });


    This is a reference implementation. You are free to copy, modify, or
    redistribute.

    This code should be minified before deployment.
    See http://javascript.crockford.com/jsmin.html

    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    NOT CONTROL.
*/

/*jslint evil: true */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
    lastIndex, length, parse, prototype, push, replace, slice, stringify,
    test, toJSON, toString, valueOf
*/

// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.

var JSON = JSON || {};

(function () {

    function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }

    if (typeof Date.prototype.toJSON !== 'function') {

        Date.prototype.toJSON = function (key) {

            return isFinite(this.valueOf()) ?
                   this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z' : null;
        };

        String.prototype.toJSON =
        Number.prototype.toJSON =
        Boolean.prototype.toJSON = function (key) {
            return this.valueOf();
        };
    }

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        rep;


    function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

        escapable.lastIndex = 0;
        return escapable.test(string) ?
            '"' + string.replace(escapable, function (a) {
                var c = meta[a];
                return typeof c === 'string' ? c :
                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            }) + '"' :
            '"' + string + '"';
    }


    function str(key, holder) {

// Produce a string from holder[key].

        var i,          // The loop counter.
            k,          // The member key.
            v,          // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

        if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

// What happens next depends on the value's type.

        switch (typeof value) {
        case 'string':
            return quote(value);

        case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

            return isFinite(value) ? String(value) : 'null';

        case 'boolean':
        case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

            return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

        case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

            if (!value) {
                return 'null';
            }

// Make an array to hold the partial results of stringifying this object value.

            gap += indent;
            partial = [];

// Is the value an array?

            if (Object.prototype.toString.apply(value) === '[object Array]') {

// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                v = partial.length === 0 ? '[]' :
                    gap ? '[\n' + gap +
                            partial.join(',\n' + gap) + '\n' +
                                mind + ']' :
                          '[' + partial.join(',') + ']';
                gap = mind;
                return v;
            }

// If the replacer is an array, use it to select the members to be stringified.

            if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    k = rep[i];
                    if (typeof k === 'string') {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            } else {

// Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

            v = partial.length === 0 ? '{}' :
                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                        mind + '}' : '{' + partial.join(',') + '}';
            gap = mind;
            return v;
        }
    }

// If the JSON object does not yet have a stringify method, give it one.

    if (typeof JSON.stringify !== 'function') {
        JSON.stringify = function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

            var i;
            gap = '';
            indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

// If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === 'string') {
                indent = space;
            }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== 'function' &&
                    (typeof replacer !== 'object' ||
                     typeof replacer.length !== 'number')) {
                throw new Error('JSON.stringify');
            }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

            return str('', {'': value});
        };
    }


// If the JSON object does not yet have a parse method, give it one.

    if (typeof JSON.parse !== 'function') {
        JSON.parse = function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

            var j;

            function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

            cx.lastIndex = 0;
            if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' +
                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                });
            }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

            if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                return typeof reviver === 'function' ?
                    walk({'': j}, '') : j;
            }

// If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError('JSON.parse');
        };
    }
}());
/* -*- Mode: Javascript; -*- */

/* Copyright (C) 2007-2012 beingmeta, inc.
   This file is a part of the FDJT web toolkit (www.fdjt.org)
   This file provides an abstraction layer for Ajax calls

   This program comes with absolutely NO WARRANTY, including implied
   warranties of merchantability or fitness for any particular
   purpose.

    Use, modification, and redistribution of this program is permitted
    under either the GNU General Public License (GPL) Version 2 (or
    any later version) or under the GNU Lesser General Public License
    (version 3 or later).

    These licenses may be found at www.gnu.org, particularly:
      http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
      http://www.gnu.org/licenses/lgpl-3.0-standalone.html
*/

var fdjtAjax=
    (function(){
	
	function compose_uri(base_uri,args){
	    var uri=base_uri; var need_amp=false;
	    if (base_uri[-1]==='&') need_amp=false;
	    else if (base_uri.indexOf('?')>=0) need_amp=true;
	    else uri=base_uri+"?";
	    var i=0; while (i<args.length) {
		if (!(args[i])) {i=i+2; continue;}
		uri=uri+((need_amp) ? ("&") : (""))+args[i]+"="+args[i+1];
		need_amp=true;
		i=i+2;}
	    return uri;}

	var trace_ajax=false;
	
	function fdjtAjax(callback,base_uri,args){
	    var req=new XMLHttpRequest();
	    var uri=compose_uri(base_uri,args);
	    req.open("GET",uri,true);
	    req.withCredentials=true;
	    req.onreadystatechange=function () {
		if ((req.readyState == 4) && (req.status == 200)) {
		    callback(req);}};
	    req.send(null);
	    return req;}
	fdjtAjax.revid="$Id$";
	fdjtAjax.version=parseInt("$Revision$".slice(10,-1));

	fdjtAjax.textCall=function(callback,base_uri){
	    return fdjtAjax(function(req) {
		callback(req.responseText);},
			    base_uri,fdjtDOM.Array(arguments,2));};

	fdjtAjax.jsonCall=function(callback,base_uri){
	    return fdjtAjax(function(req) {
		callback(JSON.parse(req.responseText));},
			    base_uri,fdjtDOM.Array(arguments,2));};

	fdjtAjax.xmlCall=function(callback,base_uri){
	    return fdjtAjax(function(req) {
		callback(req.responseXML);},
			    base_uri,fdjtDOM.Array(arguments,2));};

	fdjtAjax.jsonpCall=function(uri,id,cleanup){
	    if ((id)&&($ID(id))) return false;
	    var script_elt=fdjtNewElement("SCRIPT");
	    if (id) script_elt.id=id;
	    if (cleanup) script_elt.oncleanup=cleanup;
	    script_elt.language='javascript';
	    script_elt.src=uri;
	    document.body.appendChild(script_elt);};

	fdjtAjax.jsonpFinish=function(id){
	    var script_elt=$ID(id);
	    if (!(script_elt)) return;
	    if (script_elt.oncleanup) script_elt.oncleanup();
	    fdjtDOM.remove(script_elt);};

	function add_query_param(parameters,name,value){
	    return ((parameters)?(parameters+"&"):(""))+
		name+"="+encodeURIComponent(value);}

	function formParams(form) {
	    fdjtUI.AutoPrompt.cleanup(form);
	    var parameters=false;
	    var inputs=fdjtDOM.getChildren(form,"INPUT");
	    var i=0; while (i<inputs.length) {
		var input=inputs[i++];
		if ((!(input.disabled))&&
		    (((/(radio)|(checkbox)/i).exec(input.type))?
		     (input.checked):(true)))
		    parameters=add_query_param(
			parameters,input.name,input.value);}
	    var textareas=fdjtDOM.getChildren(form,"TEXTAREA");
	    i=0; while (i<textareas.length) {
		var textarea=textareas[i++];
		if (!(textarea.disabled)) {
		    parameters=add_query_param(
			parameters,textarea.name,textarea.value);}}
	    var selectboxes=fdjtDOM.getChildren(form,"SELECT");
	    i=0; while (i<selectboxes.length) {
		var selectbox=selectboxes[i++]; var name=selectbox.name;
		var options=fdjtDOM.getChildren(selectbox,"OPTION");
		var j=0; while (j<options.length) {
		    var option=options[j++];
		    if (option.selected)
			parameters=add_query_param(
			    parameters,name,option.value);}}
	    return parameters;}
	fdjtAjax.formParams=formParams;

	function add_field(result,name,value,multifields,downcase) {
	    if (downcase) name=name.toLowerCase();
	    if ((multifields)&&(fdjtKB.contains(multifields,name))) {
		if (result[name]) result[name].push(value);
		else result[name]=[value];}
	    else {
		var cur=result[name];
		if (!cur) result[name]=value;
		else if (cur instanceof Array) cur.push(value);
		else result[name]=[cur,value];}}

	function formJSON(form,multifields,downcase) {
	    fdjtUI.AutoPrompt.cleanup(form);
	    var result={};
	    var inputs=fdjtDOM.getChildren(form,"INPUT");
	    var i=0; while (i<inputs.length) {
		var input=inputs[i++];
		if ((!(input.disabled)) &&
		    (((input.type==="radio") || (input.type==="checkbox")) ?
		     (input.checked) : (true)))
		    add_field(result,input.name,input.value,multifields,downcase||false);}
	    var textareas=fdjtDOM.getChildren(form,"TEXTAREA");
	    i=0; while (i<textareas.length) {
		var textarea=textareas[i++];
		if (!(textarea.disabled)) 
		    add_field(result,textarea.name,textarea.value,multifields,downcase||false);}
	    var selectboxes=fdjtDOM.getChildren(form,"SELECT");
	    i=0; while (i<selectboxes.length) {
		var selectbox=selectboxes[i++]; var name=selectbox.name;
		var options=fdjtDOM.getChildren(selectbox,"OPTION");
		var j=0; while (j<options.length) {
		    var option=options[j++];
		    if (option.selected) add_field(result,name,option.value,multifields,downcase||false);}}
	    return result;}
	fdjtAjax.formJSON=formJSON;

	function ajaxSubmit(form,callback,cbctype){
	    var ajax_uri=form.getAttribute("ajaxaction")||form.action;
	    if (!(ajax_uri)) return false;
	    // Whether to do AJAX synchronously or not.
	    var syncp=form.getAttribute("synchronous");
	    if (!(callback)) {
		if (form.oncallback) callback=form.oncallback;
		else if (form.getAttribute("ONCALLBACK")) {
		    callback=new Function
		    ("req","form",input_elt.getAttribute("ONCALLBACK"));
		    form.oncallback=callback;}}
	    if (trace_ajax)
		fdjtLog("Direct %s AJAX submit to %s for %o with callback %o",
			((syncp)?("synchronous"):("asynchronous")),
			ajax_uri,form,callback);
	    // Firefox doesn't run the callback on synchronous calls
	    var success=false; var callback_run=false;
	    var req=new XMLHttpRequest();
	    var params=formParams(form);
	    fdjtDOM.addClass(form,"submitting");
	    if (form.method==="GET")
		req.open('GET',ajax_uri+"?"+params,(!(syncp)));
	    else if (form.method==="PUT")
		req.open('PUT',ajax_uri,(!(syncp)));
	    else req.open('POST',ajax_uri,(!(syncp)));
	    if (cbctype) req.setRequestHeader("Accept",cbctype);
	    req.withCredentials=true;
	    req.onreadystatechange=function () {
		if (trace_ajax)
		    fdjtLog("Got callback (%d,%d) %o for %o, callback=%o",
			    req.readyState,req.status,req,ajax_uri,callback);
		if ((req.readyState === 4) && (req.status>=200) && (req.status<300)) {
		    if ((callback)&&(trace_ajax))
			fdjtLog("Got callback (%d,%d) %o for %o, calling %o",
				req.readyState,req.status,req,ajax_uri,callback);
		    fdjtDOM.dropClass(form,"submitting");
		    success=true; 
		    if (callback) callback(req,form);
	      	    callback_run=true;}
		else {
		    if (trace_ajax)
			fdjtLog("Got callback (%d,%d) %o for %o, not calling %o",
				req.readyState,req.status,req,ajax_uri,callback);
		    callback_run=false;}};
	    try {
		if (form.method==="GET") req.send();
		else {
		    req.setRequestHeader(
			"Content-type", "application/x-www-form-urlencoded");
		    req.send(params);}
		success=true;}
	    catch (ex) {}
	    if ((syncp) && (!(callback_run))) {
		if (trace_ajax)
		    fdjtLog("Running callback (rs=%d,status=%d) %o for %o, calling %o",
			    req.readyState,req.status,req,ajax_uri,callback);
		if ((req.readyState === 4) && (req.status>=200) && (req.status<300)) {
		    fdjtDOM.dropClass(form,"submitting");
		    success=true;
		    if (callback) callback(req,form);}};
	    return success;}
	fdjtAjax.formSubmit=ajaxSubmit;

	function jsonpSubmit(form){
	    var jsonp_uri=form.getAttribute("jsonpuri");
	    if (!(jsonp_uri)) return false;
	    var success=false;
	    var jsonid=((form.id)?("JSONP"+form.id):("FORMJSONP"));
	    var params=formParams(form);
	    fdjtDOM.addClass(form,"submitting");
	    try {
		jsonpCall(jsonp_uri+"?"+params,jsonid,
			  function(){fdjtDropClass(form,"submitting")});}
	    catch (ex) {
		jsonpFinish(jsonid);
		fdjtLog.warn("Attempted JSONP call signalled %o",ex);
		return false;}
	    return true;}

	function form_submit(evt,callback){
	    evt=evt||event||null;
	    var form=fdjtUI.T(evt);
	    fdjtUI.AutoPrompt.cleanup(form);
	    if (fdjtDOM.hasClass(form,"submitting")) {
		fdjtDOM.dropClass(form,"submitting");
		return;}
	    // if (form.fdjtlaunchfailed) return;
	    form.fdjtsubmit=true;
	    fdjtDOM.addClass(form,"submitting");
	    if (ajaxSubmit(form,callback)) {
		// fdjtLog("Ajax commit worked");
		fdjtUI.cancel(evt);
		return false;}
	    else if (jsonpSubmit(form)) {
		// fdjtLog("Json commit worked");
		fdjtUI.cancel(evt);
		return false;}
	    else return;}

	function copy_args(args,i){
	    var lim=args.length; if (!(i)) i=0;
	    var copy=new Array(lim-i);
	    while (i<lim) {copy[i]=args[i]; i++;}
	    return copy;}

	/* Synchronous calls */
	function sync_get(callback,base_uri,args){
	    var req=new XMLHttpRequest();
	    var uri=compose_uri(base_uri,args);
	    req.open("GET",uri,false);
	    req.send(null);
	    if (callback) return callback(req);
	    else return req;}
	fdjtAjax.get=function(base_uri){
	    return sync_get(false,base_uri,copy_args(arguments,1));};
	fdjtAjax.getText=function(base_uri) {
	    return sync_get(function (req) { return req.responseText; },
			    base_uri,copy_args(arguments,1));};
	fdjtAjax.getJSON=function(base_uri) {
	    return sync_get(function (req) { return JSON.parse(req.responseText); },
			    base_uri,fdjtDOM.Array(arguments,1));};
	fdjtAjax.getXML=function(base_uri) {
	    return fdjtAjaxGet(function (req) { return JSON.parse(req.responseXML); },
			       base_uri,fdjtDOM.Array(arguments,1));};

	fdjtAjax.onsubmit=form_submit;

	return fdjtAjax;})();

/* Emacs local variables
;;;  Local variables: ***
;;;  compile-command: "make; if test -f ../makefile; then cd ..; make; fi" ***
;;;  End: ***
*/
/* -*- Mode: Javascript; Character-encoding: utf-8; -*- */

/* Copyright (C) 2009-2012 beingmeta, inc.
   This file is a part of the FDJT web toolkit (www.fdjt.org)
   It implements a method for breaking narrative HTML content
   across multiple pages, attempting to honor page break constraints,
   etc.

   Check out the 'mini manual' at the bottom of the file or read the
   code itself.

   This program comes with absolutely NO WARRANTY, including implied
   warranties of merchantability or fitness for any particular
   purpose.

   Use, modification, and redistribution of this program is permitted
   under either the GNU General Public License (GPL) Version 2 (or any
   later version) or under the GNU Lesser General Public License
   (version 3 or later).

   These licenses may be found at www.gnu.org, particularly:
   http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
   http://www.gnu.org/licenses/lgpl-3.0-standalone.html

   Use and redistribution (especially embedding in other CC licensed
   content) is also permitted under the terms of the Creative Commons
   "Attribution-NonCommercial" license:

   http://creativecommons.org/licenses/by-nc/3.0/ 

   Other uses may be allowed based on prior agreement with
   beingmeta, inc.  Inquiries can be addressed to:

   licensing@beingmeta.com

   Enjoy!

*/

/*
 * A JavaScript implementation of various hashing algorithms, merged
 *   into a single object.
 * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 */

if (typeof fdjtHash === 'undefined')
    var fdjtHash=(function(){
	/*
	 * Configurable variables. You may need to tweak these to be compatible with
	 * the server-side, but the defaults work in most cases.
	 */
	var hexcase = 0;   /* hex output format. 0 - lowercase; 1 - uppercase        */
	var b64pad  = "";  /* base-64 pad character. "=" for strict RFC compliance   */
	var enc=false;

	function gethexcase(){ return hexcase;}
	function sethexcase(v){ hexcase=v;}
	function getpadchar(){ return b64pad;}
	function setpadchar(v){ b64pad=v;}

	function getenc(){ return ;}
	function setenc(v) {
	    if (typeof v === 'string')
		enc=fdjtHash[v]||false;
	    else enc=v;}

	/*
	 * Convert a raw string to a hex string
	 */
	function rstr2hex(input)
	{
	    try { hexcase } catch(e) { hexcase=0; }
	    var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
	    var output = "";
	    var x;
	    for(var i = 0; i < input.length; i++)
	    {
		x = input.charCodeAt(i);
		output += hex_tab.charAt((x >>> 4) & 0x0F)
		    +  hex_tab.charAt( x        & 0x0F);
	    }
	    return output;
	}

	/*
	 * Convert a raw string to a base-64 string
	 */
	function rstr2b64(input)
	{
	    try { b64pad } catch(e) { b64pad=''; }
	    var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	    var output = "";
	    var len = input.length;
	    for(var i = 0; i < len; i += 3)
	    {
		var triplet = (input.charCodeAt(i) << 16)
		    | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
		    | (i + 2 < len ? input.charCodeAt(i+2)      : 0);
		for(var j = 0; j < 4; j++)
		{
		    if(i * 8 + j * 6 > input.length * 8) output += b64pad;
		    else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
		}
	    }
	    return output;
	}

	/*
	 * Convert a raw string to an arbitrary string encoding
	 */
	function rstr2any(input, encoding)
	{
	    if (!(encoding)) {
		if (enc) return enc(input);
		else return rstr2hex(input);}
	    var divisor = encoding.length;
	    var i, j, q, x, quotient;

	    /* Convert to an array of 16-bit big-endian values, forming the dividend */
	    var dividend = Array(Math.ceil(input.length / 2));
	    for(i = 0; i < dividend.length; i++)
	    {
		dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
	    }

	    /*
	     * Repeatedly perform a long division. The binary array forms the dividend,
	     * the length of the encoding is the divisor. Once computed, the quotient
	     * forms the dividend for the next step. All remainders are stored for later
	     * use.
	     */
	    var full_length = Math.ceil(input.length * 8 /
					(Math.log(encoding.length) / Math.log(2)));
	    var remainders = Array(full_length);
	    for(j = 0; j < full_length; j++)
	    {
		quotient = Array();
		x = 0;
		for(i = 0; i < dividend.length; i++)
		{
		    x = (x << 16) + dividend[i];
		    q = Math.floor(x / divisor);
		    x -= q * divisor;
		    if(quotient.length > 0 || q > 0)
			quotient[quotient.length] = q;
		}
		remainders[j] = x;
		dividend = quotient;
	    }

	    /* Convert the remainders to the output string */
	    var output = "";
	    for(i = remainders.length - 1; i >= 0; i--)
		output += encoding.charAt(remainders[i]);

	    return output;
	}

	/*
	 * Encode a string as utf-8.
	 * For efficiency, this assumes the input is valid utf-16.
	 */
	function str2rstr_utf8(input)
	{
	    var output = "";
	    var i = -1;
	    var x, y;

	    while(++i < input.length)
	    {
		/* Decode utf-16 surrogate pairs */
		x = input.charCodeAt(i);
		y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
		if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
		{
		    x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
		    i++;
		}

		/* Encode output as utf-8 */
		if(x <= 0x7F)
		    output += String.fromCharCode(x);
		else if(x <= 0x7FF)
		    output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
						  0x80 | ( x         & 0x3F));
		else if(x <= 0xFFFF)
		    output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
						  0x80 | ((x >>> 6 ) & 0x3F),
						  0x80 | ( x         & 0x3F));
		else if(x <= 0x1FFFFF)
		    output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
						  0x80 | ((x >>> 12) & 0x3F),
						  0x80 | ((x >>> 6 ) & 0x3F),
						  0x80 | ( x         & 0x3F));
	    }
	    return output;
	}

	/*
	 * Encode a string as utf-16
	 */
	function str2rstr_utf16le(input)
	{
	    var output = "";
	    for(var i = 0; i < input.length; i++)
		output += String.fromCharCode( input.charCodeAt(i)        & 0xFF,
					       (input.charCodeAt(i) >>> 8) & 0xFF);
	    return output;
	}

	function str2rstr_utf16be(input)
	{
	    var output = "";
	    for(var i = 0; i < input.length; i++)
		output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
					      input.charCodeAt(i)        & 0xFF);
	    return output;
	}

	/*
	 * Convert a raw string to an array of little-endian words
	 * Characters >255 have their high-byte silently ignored.
	 */
	function rstr2binl(input)
	{
	    var output = Array(input.length >> 2);
	    for(var i = 0; i < output.length; i++)
		output[i] = 0;
	    for(var i = 0; i < input.length * 8; i += 8)
		output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
	    return output;
	}

	/*
	 * Convert an array of little-endian words to a string
	 */
	function binl2rstr(input)
	{
	    var output = "";
	    for(var i = 0; i < input.length * 32; i += 8)
		output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF);
	    return output;
	}

	/*
	 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
	 * to work around bugs in some JS interpreters.
	 */
	function safe_add(x, y)
	{
	    var lsw = (x & 0xFFFF) + (y & 0xFFFF);
	    var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
	    return (msw << 16) | (lsw & 0xFFFF);
	}

	/*
	 * Bitwise rotate a 32-bit number to the left.
	 */
	function bit_rol(num, cnt)
	{
	    return (num << cnt) | (num >>> (32 - cnt));
	}

	/*
	 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
	 * Digest Algorithm, as defined in RFC 1321.
	 * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
	 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
	 * Distributed under the BSD License
	 * See http://pajhome.org.uk/crypt/md5 for more info.
	 */

	/*
	 * These are the functions you'll usually want to call
	 * They take string arguments and return either hex or base-64 encoded strings
	 */
	function hex_md5(s)    { return rstr2hex(rstr_md5(str2rstr_utf8(s))); }
	function b64_md5(s)    { return rstr2b64(rstr_md5(str2rstr_utf8(s))); }
	function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); }
	function hex_hmac_md5(k, d)
	{ return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
	function b64_hmac_md5(k, d)
	{ return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
	function any_hmac_md5(k, d, e)
	{ return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); }

	/*
	 * Perform a simple self-test to see if the VM is working
	 */
	function md5_vm_test()
	{
	    return hex_md5("abc").toLowerCase() == "900150983cd24fb0d6963f7d28e17f72";
	}

	/*
	 * Calculate the MD5 of a raw string
	 */
	function rstr_md5(s)
	{
	    return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
	}

	/*
	 * Calculate the HMAC-MD5, of a key and some data (raw strings)
	 */
	function rstr_hmac_md5(key, data)
	{
	    var bkey = rstr2binl(key);
	    if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);

	    var ipad = Array(16), opad = Array(16);
	    for(var i = 0; i < 16; i++)
	    {
		ipad[i] = bkey[i] ^ 0x36363636;
		opad[i] = bkey[i] ^ 0x5C5C5C5C;
	    }

	    var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
	    return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
	}

	/* Calculate the MD5 of an array of little-endian words, and a bit length.
	 */
	function binl_md5(x, len)
	{
	    /* append padding */
	    x[len >> 5] |= 0x80 << ((len) % 32);
	    x[(((len + 64) >>> 9) << 4) + 14] = len;

	    var a =  1732584193;
	    var b = -271733879;
	    var c = -1732584194;
	    var d =  271733878;

	    for(var i = 0; i < x.length; i += 16)
	    {
		var olda = a;
		var oldb = b;
		var oldc = c;
		var oldd = d;

		a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
		d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
		c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
		b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
		a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
		d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
		c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
		b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
		a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
		d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
		c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
		b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
		a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
		d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
		c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
		b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);

		a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
		d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
		c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
		b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
		a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
		d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
		c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
		b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
		a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
		d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
		c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
		b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
		a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
		d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
		c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
		b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);

		a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
		d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
		c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
		b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
		a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
		d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
		c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
		b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
		a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
		d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
		c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
		b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
		a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
		d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
		c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
		b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);

		a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
		d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
		c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
		b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
		a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
		d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
		c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
		b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
		a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
		d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
		c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
		b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
		a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
		d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
		c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
		b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);

		a = safe_add(a, olda);
		b = safe_add(b, oldb);
		c = safe_add(c, oldc);
		d = safe_add(d, oldd);
	    }
	    return Array(a, b, c, d);
	}

	/*
	 * These functions implement the four basic operations the algorithm uses.
	 */
	function md5_cmn(q, a, b, x, s, t)
	{
	    return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
	}
	function md5_ff(a, b, c, d, x, s, t)
	{
	    return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
	}
	function md5_gg(a, b, c, d, x, s, t)
	{
	    return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
	}
	function md5_hh(a, b, c, d, x, s, t)
	{
	    return md5_cmn(b ^ c ^ d, a, b, x, s, t);
	}
	function md5_ii(a, b, c, d, x, s, t)
	{
	    return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
	}

	/*
	 * A JavaScript implementation of the RIPEMD-160 Algorithm
	 * Version 2.2 Copyright Jeremy Lin, Paul Johnston 2000 - 2009.
	 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
	 * Distributed under the BSD License
	 * See http://pajhome.org.uk/crypt/md5 for details.
	 * Also http://www.ocf.berkeley.edu/~jjlin/jsotp/
	 */

	/*
	 * These are the functions you'll usually want to call
	 * They take string arguments and return either hex or base-64 encoded strings
	 */
	function hex_rmd160(s)    { return rstr2hex(rstr_rmd160(str2rstr_utf8(s))); }
	function b64_rmd160(s)    { return rstr2b64(rstr_rmd160(str2rstr_utf8(s))); }
	function any_rmd160(s, e) { return rstr2any(rstr_rmd160(str2rstr_utf8(s)), e); }
	function hex_hmac_rmd160(k, d)
	{ return rstr2hex(rstr_hmac_rmd160(str2rstr_utf8(k), str2rstr_utf8(d))); }
	function b64_hmac_rmd160(k, d)
	{ return rstr2b64(rstr_hmac_rmd160(str2rstr_utf8(k), str2rstr_utf8(d))); }
	function any_hmac_rmd160(k, d, e)
	{ return rstr2any(rstr_hmac_rmd160(str2rstr_utf8(k), str2rstr_utf8(d)), e); }

	/*
	 * Perform a simple self-test to see if the VM is working
	 */
	function rmd160_vm_test()
	{
	    return hex_rmd160("abc").toLowerCase() == "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc";
	}

	/*
	 * Calculate the rmd160 of a raw string
	 */
	function rstr_rmd160(s)
	{
	    return binl2rstr(binl_rmd160(rstr2binl(s), s.length * 8));
	}

	/*
	 * Calculate the HMAC-rmd160 of a key and some data (raw strings)
	 */
	function rstr_hmac_rmd160(key, data)
	{
	    var bkey = rstr2binl(key);
	    if(bkey.length > 16) bkey = binl_rmd160(bkey, key.length * 8);

	    var ipad = Array(16), opad = Array(16);
	    for(var i = 0; i < 16; i++)
	    {
		ipad[i] = bkey[i] ^ 0x36363636;
		opad[i] = bkey[i] ^ 0x5C5C5C5C;
	    }

	    var hash = binl_rmd160(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
	    return binl2rstr(binl_rmd160(opad.concat(hash), 512 + 160));
	}


	/*
	 * Calculate the RIPE-MD160 of an array of little-endian words, and a bit length.
	 */
	function binl_rmd160(x, len)
	{
	    /* append padding */
	    x[len >> 5] |= 0x80 << (len % 32);
	    x[(((len + 64) >>> 9) << 4) + 14] = len;

	    var h0 = 0x67452301;
	    var h1 = 0xefcdab89;
	    var h2 = 0x98badcfe;
	    var h3 = 0x10325476;
	    var h4 = 0xc3d2e1f0;

	    for (var i = 0; i < x.length; i += 16) {
		var T;
		var A1 = h0, B1 = h1, C1 = h2, D1 = h3, E1 = h4;
		var A2 = h0, B2 = h1, C2 = h2, D2 = h3, E2 = h4;
		for (var j = 0; j <= 79; ++j) {
		    T = safe_add(A1, rmd160_f(j, B1, C1, D1));
		    T = safe_add(T, x[i + rmd160_r1[j]]);
		    T = safe_add(T, rmd160_K1(j));
		    T = safe_add(bit_rol(T, rmd160_s1[j]), E1);
		    A1 = E1; E1 = D1; D1 = bit_rol(C1, 10); C1 = B1; B1 = T;
		    T = safe_add(A2, rmd160_f(79-j, B2, C2, D2));
		    T = safe_add(T, x[i + rmd160_r2[j]]);
		    T = safe_add(T, rmd160_K2(j));
		    T = safe_add(bit_rol(T, rmd160_s2[j]), E2);
		    A2 = E2; E2 = D2; D2 = bit_rol(C2, 10); C2 = B2; B2 = T;
		}
		T = safe_add(h1, safe_add(C1, D2));
		h1 = safe_add(h2, safe_add(D1, E2));
		h2 = safe_add(h3, safe_add(E1, A2));
		h3 = safe_add(h4, safe_add(A1, B2));
		h4 = safe_add(h0, safe_add(B1, C2));
		h0 = T;
	    }
	    return [h0, h1, h2, h3, h4];
	}

	function rmd160_f(j, x, y, z)
	{
	    return ( 0 <= j && j <= 15) ? (x ^ y ^ z) :
		(16 <= j && j <= 31) ? (x & y) | (~x & z) :
		(32 <= j && j <= 47) ? (x | ~y) ^ z :
		(48 <= j && j <= 63) ? (x & z) | (y & ~z) :
		(64 <= j && j <= 79) ? x ^ (y | ~z) :
		"rmd160_f: j out of range";
	}
	function rmd160_K1(j)
	{
	    return ( 0 <= j && j <= 15) ? 0x00000000 :
		(16 <= j && j <= 31) ? 0x5a827999 :
		(32 <= j && j <= 47) ? 0x6ed9eba1 :
		(48 <= j && j <= 63) ? 0x8f1bbcdc :
		(64 <= j && j <= 79) ? 0xa953fd4e :
		"rmd160_K1: j out of range";
	}
	function rmd160_K2(j)
	{
	    return ( 0 <= j && j <= 15) ? 0x50a28be6 :
		(16 <= j && j <= 31) ? 0x5c4dd124 :
		(32 <= j && j <= 47) ? 0x6d703ef3 :
		(48 <= j && j <= 63) ? 0x7a6d76e9 :
		(64 <= j && j <= 79) ? 0x00000000 :
		"rmd160_K2: j out of range";
	}
	var rmd160_r1 = [
	    0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
	    7,  4, 13,  1, 10,  6, 15,  3, 12,  0,  9,  5,  2, 14, 11,  8,
	    3, 10, 14,  4,  9, 15,  8,  1,  2,  7,  0,  6, 13, 11,  5, 12,
	    1,  9, 11, 10,  0,  8, 12,  4, 13,  3,  7, 15, 14,  5,  6,  2,
	    4,  0,  5,  9,  7, 12,  2, 10, 14,  1,  3,  8, 11,  6, 15, 13
	];
	var rmd160_r2 = [
	    5, 14,  7,  0,  9,  2, 11,  4, 13,  6, 15,  8,  1, 10,  3, 12,
	    6, 11,  3,  7,  0, 13,  5, 10, 14, 15,  8, 12,  4,  9,  1,  2,
	    15,  5,  1,  3,  7, 14,  6,  9, 11,  8, 12,  2, 10,  0,  4, 13,
	    8,  6,  4,  1,  3, 11, 15,  0,  5, 12,  2, 13,  9,  7, 10, 14,
	    12, 15, 10,  4,  1,  5,  8,  7,  6,  2, 13, 14,  0,  3,  9, 11
	];
	var rmd160_s1 = [
	    11, 14, 15, 12,  5,  8,  7,  9, 11, 13, 14, 15,  6,  7,  9,  8,
	    7,  6,  8, 13, 11,  9,  7, 15,  7, 12, 15,  9, 11,  7, 13, 12,
	    11, 13,  6,  7, 14,  9, 13, 15, 14,  8, 13,  6,  5, 12,  7,  5,
	    11, 12, 14, 15, 14, 15,  9,  8,  9, 14,  5,  6,  8,  6,  5, 12,
	    9, 15,  5, 11,  6,  8, 13, 12,  5, 12, 13, 14, 11,  8,  5,  6
	];
	var rmd160_s2 = [
	    8,  9,  9, 11, 13, 15, 15,  5,  7,  7,  8, 11, 14, 14, 12,  6,
	    9, 13, 15,  7, 12,  8,  9, 11,  7,  7, 12,  7,  6, 15, 13, 11,
	    9,  7, 15, 11,  8,  6,  6, 14, 12, 13,  5, 14, 13, 13,  7,  5,
	    15,  5,  8, 11, 14, 14,  6, 14,  6,  9, 12,  9, 12,  5, 15,  8,
	    8,  5, 12,  9, 12,  5, 14,  6,  8, 13,  6,  5, 15, 13, 11, 11
	];

	/*
	 * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
	 * in FIPS 180-1
	 * Version 2.2 Copyright Paul Johnston 2000 - 2009.
	 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
	 * Distributed under the BSD License
	 * See http://pajhome.org.uk/crypt/md5 for details.
	 */

	/*
	 * These are the functions you'll usually want to call
	 * They take string arguments and return either hex or base-64 encoded strings
	 */
	function hex_sha1(s)    { return rstr2hex(rstr_sha1(str2rstr_utf8(s))); }
	function b64_sha1(s)    { return rstr2b64(rstr_sha1(str2rstr_utf8(s))); }
	function any_sha1(s, e) { return rstr2any(rstr_sha1(str2rstr_utf8(s)), e); }
	function hex_hmac_sha1(k, d)
	{ return rstr2hex(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); }
	function b64_hmac_sha1(k, d)
	{ return rstr2b64(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); }
	function any_hmac_sha1(k, d, e)
	{ return rstr2any(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d)), e); }

	/*
	 * Perform a simple self-test to see if the VM is working
	 */
	function sha1_vm_test()
	{
	    return hex_sha1("abc").toLowerCase() == "a9993e364706816aba3e25717850c26c9cd0d89d";
	}

	/*
	 * Calculate the SHA1 of a raw string
	 */
	function rstr_sha1(s)
	{
	    return binb2rstr(binb_sha1(rstr2binb(s), s.length * 8));
	}

	/*
	 * Calculate the HMAC-SHA1 of a key and some data (raw strings)
	 */
	function rstr_hmac_sha1(key, data)
	{
	    var bkey = rstr2binb(key);
	    if(bkey.length > 16) bkey = binb_sha1(bkey, key.length * 8);

	    var ipad = Array(16), opad = Array(16);
	    for(var i = 0; i < 16; i++)
	    {
		ipad[i] = bkey[i] ^ 0x36363636;
		opad[i] = bkey[i] ^ 0x5C5C5C5C;
	    }

	    var hash = binb_sha1(ipad.concat(rstr2binb(data)), 512 + data.length * 8);
	    return binb2rstr(binb_sha1(opad.concat(hash), 512 + 160));
	}

	/*
	 * Calculate the SHA-1 of an array of big-endian words, and a bit length
	 */
	function binb_sha1(x, len)
	{
	    /* append padding */
	    x[len >> 5] |= 0x80 << (24 - len % 32);
	    x[((len + 64 >> 9) << 4) + 15] = len;

	    var w = Array(80);
	    var a =  1732584193;
	    var b = -271733879;
	    var c = -1732584194;
	    var d =  271733878;
	    var e = -1009589776;

	    for(var i = 0; i < x.length; i += 16)
	    {
		var olda = a;
		var oldb = b;
		var oldc = c;
		var oldd = d;
		var olde = e;

		for(var j = 0; j < 80; j++)
		{
		    if(j < 16) w[j] = x[i + j];
		    else w[j] = bit_rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
		    var t = safe_add(safe_add(bit_rol(a, 5), sha1_ft(j, b, c, d)),
				     safe_add(safe_add(e, w[j]), sha1_kt(j)));
		    e = d;
		    d = c;
		    c = bit_rol(b, 30);
		    b = a;
		    a = t;
		}

		a = safe_add(a, olda);
		b = safe_add(b, oldb);
		c = safe_add(c, oldc);
		d = safe_add(d, oldd);
		e = safe_add(e, olde);
	    }
	    return Array(a, b, c, d, e);

	}

	/*
	 * Perform the appropriate triplet combination function for the current
	 * iteration
	 */
	function sha1_ft(t, b, c, d)
	{
	    if(t < 20) return (b & c) | ((~b) & d);
	    if(t < 40) return b ^ c ^ d;
	    if(t < 60) return (b & c) | (b & d) | (c & d);
	    return b ^ c ^ d;
	}

	/*
	 * Determine the appropriate additive constant for the current iteration
	 */
	function sha1_kt(t)
	{
	    return (t < 20) ?  1518500249 : (t < 40) ?  1859775393 :
		(t < 60) ? -1894007588 : -899497514;
	}

	/*
	 * A JavaScript implementation of the Secure Hash Algorithm, SHA-256, as defined
	 * in FIPS 180-2
	 * Version 2.2 Copyright Angel Marin, Paul Johnston 2000 - 2009.
	 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
	 * Distributed under the BSD License
	 * See http://pajhome.org.uk/crypt/md5 for details.
	 * Also http://anmar.eu.org/projects/jssha2/
	 */

	/*
	 * These are the functions you'll usually want to call
	 * They take string arguments and return either hex or base-64 encoded strings
	 */
	function hex_sha256(s)    { return rstr2hex(rstr_sha256(str2rstr_utf8(s))); }
	function b64_sha256(s)    { return rstr2b64(rstr_sha256(str2rstr_utf8(s))); }
	function any_sha256(s, e) { return rstr2any(rstr_sha256(str2rstr_utf8(s)), e); }
	function hex_hmac_sha256(k, d)
	{ return rstr2hex(rstr_hmac_sha256(str2rstr_utf8(k), str2rstr_utf8(d))); }
	function b64_hmac_sha256(k, d)
	{ return rstr2b64(rstr_hmac_sha256(str2rstr_utf8(k), str2rstr_utf8(d))); }
	function any_hmac_sha256(k, d, e)
	{ return rstr2any(rstr_hmac_sha256(str2rstr_utf8(k), str2rstr_utf8(d)), e); }

	/*
	 * Perform a simple self-test to see if the VM is working
	 */
	function sha256_vm_test()
	{
	    return hex_sha256("abc").toLowerCase() ==
		"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad";
	}

	/*
	 * Calculate the sha256 of a raw string
	 */
	function rstr_sha256(s)
	{
	    return binb2rstr(binb_sha256(rstr2binb(s), s.length * 8));
	}

	/*
	 * Calculate the HMAC-sha256 of a key and some data (raw strings)
	 */
	function rstr_hmac_sha256(key, data)
	{
	    var bkey = rstr2binb(key);
	    if(bkey.length > 16) bkey = binb_sha256(bkey, key.length * 8);

	    var ipad = Array(16), opad = Array(16);
	    for(var i = 0; i < 16; i++)
	    {
		ipad[i] = bkey[i] ^ 0x36363636;
		opad[i] = bkey[i] ^ 0x5C5C5C5C;
	    }

	    var hash = binb_sha256(ipad.concat(rstr2binb(data)), 512 + data.length * 8);
	    return binb2rstr(binb_sha256(opad.concat(hash), 512 + 256));
	}

	/*
	 * Main sha256 function, with its support functions
	 */
	function sha256_S (X, n) {return ( X >>> n ) | (X << (32 - n));}
	function sha256_R (X, n) {return ( X >>> n );}
	function sha256_Ch(x, y, z) {return ((x & y) ^ ((~x) & z));}
	function sha256_Maj(x, y, z) {return ((x & y) ^ (x & z) ^ (y & z));}
	function sha256_Sigma0256(x) {return (sha256_S(x, 2) ^ sha256_S(x, 13) ^ sha256_S(x, 22));}
	function sha256_Sigma1256(x) {return (sha256_S(x, 6) ^ sha256_S(x, 11) ^ sha256_S(x, 25));}
	function sha256_Gamma0256(x) {return (sha256_S(x, 7) ^ sha256_S(x, 18) ^ sha256_R(x, 3));}
	function sha256_Gamma1256(x) {return (sha256_S(x, 17) ^ sha256_S(x, 19) ^ sha256_R(x, 10));}
	function sha256_Sigma0512(x) {return (sha256_S(x, 28) ^ sha256_S(x, 34) ^ sha256_S(x, 39));}
	function sha256_Sigma1512(x) {return (sha256_S(x, 14) ^ sha256_S(x, 18) ^ sha256_S(x, 41));}
	function sha256_Gamma0512(x) {return (sha256_S(x, 1)  ^ sha256_S(x, 8) ^ sha256_R(x, 7));}
	function sha256_Gamma1512(x) {return (sha256_S(x, 19) ^ sha256_S(x, 61) ^ sha256_R(x, 6));}

	var sha256_K = new Array
	(
	    1116352408, 1899447441, -1245643825, -373957723, 961987163, 1508970993,
		-1841331548, -1424204075, -670586216, 310598401, 607225278, 1426881987,
	    1925078388, -2132889090, -1680079193, -1046744716, -459576895, -272742522,
	    264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986,
		-1740746414, -1473132947, -1341970488, -1084653625, -958395405, -710438585,
	    113926993, 338241895, 666307205, 773529912, 1294757372, 1396182291,
	    1695183700, 1986661051, -2117940946, -1838011259, -1564481375, -1474664885,
		-1035236496, -949202525, -778901479, -694614492, -200395387, 275423344,
	    430227734, 506948616, 659060556, 883997877, 958139571, 1322822218,
	    1537002063, 1747873779, 1955562222, 2024104815, -2067236844, -1933114872,
		-1866530822, -1538233109, -1090935817, -965641998
	);

	function binb_sha256(m, l)
	{
	    var HASH = new Array(1779033703, -1150833019, 1013904242, -1521486534,
				 1359893119, -1694144372, 528734635, 1541459225);
	    var W = new Array(64);
	    var a, b, c, d, e, f, g, h;
	    var i, j, T1, T2;

	    /* append padding */
	    m[l >> 5] |= 0x80 << (24 - l % 32);
	    m[((l + 64 >> 9) << 4) + 15] = l;

	    for(i = 0; i < m.length; i += 16)
	    {
		a = HASH[0];
		b = HASH[1];
		c = HASH[2];
		d = HASH[3];
		e = HASH[4];
		f = HASH[5];
		g = HASH[6];
		h = HASH[7];

		for(j = 0; j < 64; j++)
		{
		    if (j < 16) W[j] = m[j + i];
		    else W[j] = safe_add(safe_add(safe_add(sha256_Gamma1256(W[j - 2]), W[j - 7]),
						  sha256_Gamma0256(W[j - 15])), W[j - 16]);

		    T1 = safe_add(safe_add(safe_add(safe_add(h, sha256_Sigma1256(e)), sha256_Ch(e, f, g)),
                                           sha256_K[j]), W[j]);
		    T2 = safe_add(sha256_Sigma0256(a), sha256_Maj(a, b, c));
		    h = g;
		    g = f;
		    f = e;
		    e = safe_add(d, T1);
		    d = c;
		    c = b;
		    b = a;
		    a = safe_add(T1, T2);
		}

		HASH[0] = safe_add(a, HASH[0]);
		HASH[1] = safe_add(b, HASH[1]);
		HASH[2] = safe_add(c, HASH[2]);
		HASH[3] = safe_add(d, HASH[3]);
		HASH[4] = safe_add(e, HASH[4]);
		HASH[5] = safe_add(f, HASH[5]);
		HASH[6] = safe_add(g, HASH[6]);
		HASH[7] = safe_add(h, HASH[7]);
	    }
	    return HASH;
	}

	/*
	 * A JavaScript implementation of the Secure Hash Algorithm, SHA-512, as defined
	 * in FIPS 180-2
	 * Version 2.2 Copyright Anonymous Contributor, Paul Johnston 2000 - 2009.
	 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
	 * Distributed under the BSD License
	 * See http://pajhome.org.uk/crypt/md5 for details.
	 */

	/*
	 * These are the functions you'll usually want to call
	 * They take string arguments and return either hex or base-64 encoded strings
	 */
	function hex_sha512(s)    { return rstr2hex(rstr_sha512(str2rstr_utf8(s))); }
	function b64_sha512(s)    { return rstr2b64(rstr_sha512(str2rstr_utf8(s))); }
	function any_sha512(s, e) { return rstr2any(rstr_sha512(str2rstr_utf8(s)), e);}
	function hex_hmac_sha512(k, d)
	{ return rstr2hex(rstr_hmac_sha512(str2rstr_utf8(k), str2rstr_utf8(d))); }
	function b64_hmac_sha512(k, d)
	{ return rstr2b64(rstr_hmac_sha512(str2rstr_utf8(k), str2rstr_utf8(d))); }
	function any_hmac_sha512(k, d, e)
	{ return rstr2any(rstr_hmac_sha512(str2rstr_utf8(k), str2rstr_utf8(d)), e);}

	/*
	 * Perform a simple self-test to see if the VM is working
	 */
	function sha512_vm_test()
	{
	    return hex_sha512("abc").toLowerCase() ==
		"ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a" +
		"2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f";
	}

	/*
	 * Calculate the SHA-512 of a raw string
	 */
	function rstr_sha512(s)
	{
	    return binb2rstr(binb_sha512(rstr2binb(s), s.length * 8));
	}

	/*
	 * Calculate the HMAC-SHA-512 of a key and some data (raw strings)
	 */
	function rstr_hmac_sha512(key, data)
	{
	    var bkey = rstr2binb(key);
	    if(bkey.length > 32) bkey = binb_sha512(bkey, key.length * 8);

	    var ipad = Array(32), opad = Array(32);
	    for(var i = 0; i < 32; i++)
	    {
		ipad[i] = bkey[i] ^ 0x36363636;
		opad[i] = bkey[i] ^ 0x5C5C5C5C;
	    }

	    var hash = binb_sha512(ipad.concat(rstr2binb(data)), 1024 + data.length * 8);
	    return binb2rstr(binb_sha512(opad.concat(hash), 1024 + 512));
	}

	/*
	 * Calculate the SHA-512 of an array of big-endian dwords, and a bit length
	 */
	var sha512_k;
	function binb_sha512(x, len)
	{
	    if(sha512_k == undefined)
	    {
		//SHA512 constants
		sha512_k = new Array(
		    new int64(0x428a2f98, -685199838), new int64(0x71374491, 0x23ef65cd),
		    new int64(-1245643825, -330482897), new int64(-373957723, -2121671748),
		    new int64(0x3956c25b, -213338824), new int64(0x59f111f1, -1241133031),
		    new int64(-1841331548, -1357295717), new int64(-1424204075, -630357736),
		    new int64(-670586216, -1560083902), new int64(0x12835b01, 0x45706fbe),
		    new int64(0x243185be, 0x4ee4b28c), new int64(0x550c7dc3, -704662302),
		    new int64(0x72be5d74, -226784913), new int64(-2132889090, 0x3b1696b1),
		    new int64(-1680079193, 0x25c71235), new int64(-1046744716, -815192428),
		    new int64(-459576895, -1628353838), new int64(-272742522, 0x384f25e3),
		    new int64(0xfc19dc6, -1953704523), new int64(0x240ca1cc, 0x77ac9c65),
		    new int64(0x2de92c6f, 0x592b0275), new int64(0x4a7484aa, 0x6ea6e483),
		    new int64(0x5cb0a9dc, -1119749164), new int64(0x76f988da, -2096016459),
		    new int64(-1740746414, -295247957), new int64(-1473132947, 0x2db43210),
		    new int64(-1341970488, -1728372417), new int64(-1084653625, -1091629340),
		    new int64(-958395405, 0x3da88fc2), new int64(-710438585, -1828018395),
		    new int64(0x6ca6351, -536640913), new int64(0x14292967, 0xa0e6e70),
		    new int64(0x27b70a85, 0x46d22ffc), new int64(0x2e1b2138, 0x5c26c926),
		    new int64(0x4d2c6dfc, 0x5ac42aed), new int64(0x53380d13, -1651133473),
		    new int64(0x650a7354, -1951439906), new int64(0x766a0abb, 0x3c77b2a8),
		    new int64(-2117940946, 0x47edaee6), new int64(-1838011259, 0x1482353b),
		    new int64(-1564481375, 0x4cf10364), new int64(-1474664885, -1136513023),
		    new int64(-1035236496, -789014639), new int64(-949202525, 0x654be30),
		    new int64(-778901479, -688958952), new int64(-694614492, 0x5565a910),
		    new int64(-200395387, 0x5771202a), new int64(0x106aa070, 0x32bbd1b8),
		    new int64(0x19a4c116, -1194143544), new int64(0x1e376c08, 0x5141ab53),
		    new int64(0x2748774c, -544281703), new int64(0x34b0bcb5, -509917016),
		    new int64(0x391c0cb3, -976659869), new int64(0x4ed8aa4a, -482243893),
		    new int64(0x5b9cca4f, 0x7763e373), new int64(0x682e6ff3, -692930397),
		    new int64(0x748f82ee, 0x5defb2fc), new int64(0x78a5636f, 0x43172f60),
		    new int64(-2067236844, -1578062990), new int64(-1933114872, 0x1a6439ec),
		    new int64(-1866530822, 0x23631e28), new int64(-1538233109, -561857047),
		    new int64(-1090935817, -1295615723), new int64(-965641998, -479046869),
		    new int64(-903397682, -366583396), new int64(-779700025, 0x21c0c207),
		    new int64(-354779690, -840897762), new int64(-176337025, -294727304),
		    new int64(0x6f067aa, 0x72176fba), new int64(0xa637dc5, -1563912026),
		    new int64(0x113f9804, -1090974290), new int64(0x1b710b35, 0x131c471b),
		    new int64(0x28db77f5, 0x23047d84), new int64(0x32caab7b, 0x40c72493),
		    new int64(0x3c9ebe0a, 0x15c9bebc), new int64(0x431d67c4, -1676669620),
		    new int64(0x4cc5d4be, -885112138), new int64(0x597f299c, -60457430),
		    new int64(0x5fcb6fab, 0x3ad6faec), new int64(0x6c44198c, 0x4a475817));
	    }

	    //Initial hash values
	    var H = new Array(
		new int64(0x6a09e667, -205731576),
		new int64(-1150833019, -2067093701),
		new int64(0x3c6ef372, -23791573),
		new int64(-1521486534, 0x5f1d36f1),
		new int64(0x510e527f, -1377402159),
		new int64(-1694144372, 0x2b3e6c1f),
		new int64(0x1f83d9ab, -79577749),
		new int64(0x5be0cd19, 0x137e2179));

	    var T1 = new int64(0, 0),
	    T2 = new int64(0, 0),
	    a = new int64(0,0),
	    b = new int64(0,0),
	    c = new int64(0,0),
	    d = new int64(0,0),
	    e = new int64(0,0),
	    f = new int64(0,0),
	    g = new int64(0,0),
	    h = new int64(0,0),
	    //Temporary variables not specified by the document
	    s0 = new int64(0, 0),
	    s1 = new int64(0, 0),
	    Ch = new int64(0, 0),
	    Maj = new int64(0, 0),
	    r1 = new int64(0, 0),
	    r2 = new int64(0, 0),
	    r3 = new int64(0, 0);
	    var j, i;
	    var W = new Array(80);
	    for(i=0; i<80; i++)
		W[i] = new int64(0, 0);

	    // append padding to the source string. The format is described in the FIPS.
	    x[len >> 5] |= 0x80 << (24 - (len & 0x1f));
	    x[((len + 128 >> 10)<< 5) + 31] = len;

	    for(i = 0; i<x.length; i+=32) //32 dwords is the block size
	    {
		int64copy(a, H[0]);
		int64copy(b, H[1]);
		int64copy(c, H[2]);
		int64copy(d, H[3]);
		int64copy(e, H[4]);
		int64copy(f, H[5]);
		int64copy(g, H[6]);
		int64copy(h, H[7]);

		for(j=0; j<16; j++)
		{
		    W[j].h = x[i + 2*j];
		    W[j].l = x[i + 2*j + 1];
		}

		for(j=16; j<80; j++)
		{
		    //sigma1
		    int64rrot(r1, W[j-2], 19);
		    int64revrrot(r2, W[j-2], 29);
		    int64shr(r3, W[j-2], 6);
		    s1.l = r1.l ^ r2.l ^ r3.l;
		    s1.h = r1.h ^ r2.h ^ r3.h;
		    //sigma0
		    int64rrot(r1, W[j-15], 1);
		    int64rrot(r2, W[j-15], 8);
		    int64shr(r3, W[j-15], 7);
		    s0.l = r1.l ^ r2.l ^ r3.l;
		    s0.h = r1.h ^ r2.h ^ r3.h;

		    int64add4(W[j], s1, W[j-7], s0, W[j-16]);
		}

		for(j = 0; j < 80; j++)
		{
		    //Ch
		    Ch.l = (e.l & f.l) ^ (~e.l & g.l);
		    Ch.h = (e.h & f.h) ^ (~e.h & g.h);

		    //Sigma1
		    int64rrot(r1, e, 14);
		    int64rrot(r2, e, 18);
		    int64revrrot(r3, e, 9);
		    s1.l = r1.l ^ r2.l ^ r3.l;
		    s1.h = r1.h ^ r2.h ^ r3.h;

		    //Sigma0
		    int64rrot(r1, a, 28);
		    int64revrrot(r2, a, 2);
		    int64revrrot(r3, a, 7);
		    s0.l = r1.l ^ r2.l ^ r3.l;
		    s0.h = r1.h ^ r2.h ^ r3.h;

		    //Maj
		    Maj.l = (a.l & b.l) ^ (a.l & c.l) ^ (b.l & c.l);
		    Maj.h = (a.h & b.h) ^ (a.h & c.h) ^ (b.h & c.h);

		    int64add5(T1, h, s1, Ch, sha512_k[j], W[j]);
		    int64add(T2, s0, Maj);

		    int64copy(h, g);
		    int64copy(g, f);
		    int64copy(f, e);
		    int64add(e, d, T1);
		    int64copy(d, c);
		    int64copy(c, b);
		    int64copy(b, a);
		    int64add(a, T1, T2);
		}
		int64add(H[0], H[0], a);
		int64add(H[1], H[1], b);
		int64add(H[2], H[2], c);
		int64add(H[3], H[3], d);
		int64add(H[4], H[4], e);
		int64add(H[5], H[5], f);
		int64add(H[6], H[6], g);
		int64add(H[7], H[7], h);
	    }

	    //represent the hash as an array of 32-bit dwords
	    var hash = new Array(16);
	    for(i=0; i<8; i++)
	    {
		hash[2*i] = H[i].h;
		hash[2*i + 1] = H[i].l;
	    }
	    return hash;
	}

	//A constructor for 64-bit numbers
	function int64(h, l)
	{
	    this.h = h;
	    this.l = l;
	    //this.toString = int64toString;
	}

	//Copies src into dst, assuming both are 64-bit numbers
	function int64copy(dst, src)
	{
	    dst.h = src.h;
	    dst.l = src.l;
	}

	//Right-rotates a 64-bit number by shift
	//Won't handle cases of shift>=32
	//The function revrrot() is for that
	function int64rrot(dst, x, shift)
	{
	    dst.l = (x.l >>> shift) | (x.h << (32-shift));
	    dst.h = (x.h >>> shift) | (x.l << (32-shift));
	}

	//Reverses the dwords of the source and then rotates right by shift.
	//This is equivalent to rotation by 32+shift
	function int64revrrot(dst, x, shift)
	{
	    dst.l = (x.h >>> shift) | (x.l << (32-shift));
	    dst.h = (x.l >>> shift) | (x.h << (32-shift));
	}

	//Bitwise-shifts right a 64-bit number by shift
	//Won't handle shift>=32, but it's never needed in SHA512
	function int64shr(dst, x, shift)
	{
	    dst.l = (x.l >>> shift) | (x.h << (32-shift));
	    dst.h = (x.h >>> shift);
	}

	//Adds two 64-bit numbers
	//Like the original implementation, does not rely on 32-bit operations
	function int64add(dst, x, y)
	{
	    var w0 = (x.l & 0xffff) + (y.l & 0xffff);
	    var w1 = (x.l >>> 16) + (y.l >>> 16) + (w0 >>> 16);
	    var w2 = (x.h & 0xffff) + (y.h & 0xffff) + (w1 >>> 16);
	    var w3 = (x.h >>> 16) + (y.h >>> 16) + (w2 >>> 16);
	    dst.l = (w0 & 0xffff) | (w1 << 16);
	    dst.h = (w2 & 0xffff) | (w3 << 16);
	}

	//Same, except with 4 addends. Works faster than adding them one by one.
	function int64add4(dst, a, b, c, d)
	{
	    var w0 = (a.l & 0xffff) + (b.l & 0xffff) + (c.l & 0xffff) + (d.l & 0xffff);
	    var w1 = (a.l >>> 16) + (b.l >>> 16) + (c.l >>> 16) + (d.l >>> 16) + (w0 >>> 16);
	    var w2 = (a.h & 0xffff) + (b.h & 0xffff) + (c.h & 0xffff) + (d.h & 0xffff) + (w1 >>> 16);
	    var w3 = (a.h >>> 16) + (b.h >>> 16) + (c.h >>> 16) + (d.h >>> 16) + (w2 >>> 16);
	    dst.l = (w0 & 0xffff) | (w1 << 16);
	    dst.h = (w2 & 0xffff) | (w3 << 16);
	}

	//Same, except with 5 addends
	function int64add5(dst, a, b, c, d, e)
	{
	    var w0 = (a.l & 0xffff) + (b.l & 0xffff) + (c.l & 0xffff) + (d.l & 0xffff) + (e.l & 0xffff);
	    var w1 = (a.l >>> 16) + (b.l >>> 16) + (c.l >>> 16) + (d.l >>> 16) + (e.l >>> 16) + (w0 >>> 16);
	    var w2 = (a.h & 0xffff) + (b.h & 0xffff) + (c.h & 0xffff) + (d.h & 0xffff) + (e.h & 0xffff) + (w1 >>> 16);
	    var w3 = (a.h >>> 16) + (b.h >>> 16) + (c.h >>> 16) + (d.h >>> 16) + (e.h >>> 16) + (w2 >>> 16);
	    dst.l = (w0 & 0xffff) | (w1 << 16);
	    dst.h = (w2 & 0xffff) | (w3 << 16);
	}

	return {
	    "_get_hexcase_": gethexcase, "_set_hexcase_": sethexcase,
	    "_get_padchar_": getpadchar, "_set_padchar_": setpadchar,
	    "_get_outenc_": getenc, "_set_outenc_": setenc,
	    "hex": rstr2hex, b64: "rst2b64", base64: "rst2b64",
	    "array": rstr2binl, "binl": rstr2binl,
	    hex_md5: hex_md5,
	    b64_md5: b64_md5,
	    md5: any_md5,
	    hex_hmac_md5: hex_hmac_md5,
	    b64_hmac_md5: b64_hmac_md5,
	    hmac_md5: any_hmac_md5,
	    hex_rmd160: hex_rmd160,
	    b64_rmd160: b64_rmd160,
	    rmd160: any_rmd160,
	    hex_hmac_rmd160: hex_hmac_rmd160,
	    b64_hmac_rmd160: b64_hmac_rmd160,
	    hmac_rmd160: any_hmac_rmd160,
	    hex_sha1: hex_sha1,
	    b64_sha1: b64_sha1,
	    sha1: any_sha1,
	    hex_hmac_sha1: hex_hmac_sha1,
	    b64_hmac_sha1: b64_hmac_sha1,
	    hmac_sha1: any_hmac_sha1,
	    hex_sha256: hex_sha256,
	    b64_sha256: b64_sha256,
	    sha256: any_sha256,
	    hex_hmac_sha256: hex_hmac_sha256,
	    b64_hmac_sha256: b64_hmac_sha256,
	    hmac_sha256: any_hmac_sha256,
	    hex_sha512: hex_sha512,
	    b64_sha512: b64_sha512,
	    sha512: any_sha512,
	    hex_hmac_sha512: hex_hmac_sha512,
	    b64_hmac_sha512: b64_hmac_sha512,
	    hmac_sha512: any_hmac_sha512};

    })();
/* -*- Mode: Javascript; Character-encoding: utf-8; -*- */

/* Copyright (C) 2011 beingmeta, inc.
   This file is a part of the FDJT web toolkit (www.fdjt.org)
   It implements a method for breaking narrative HTML content
   across multiple pages, attempting to honor page break constraints,
   etc.

   Check out the 'mini manual' at the bottom of the file or read the
   code itself.

   This program comes with absolutely NO WARRANTY, including implied
   warranties of merchantability or fitness for any particular
   purpose.

   Use, modification, and redistribution of this program is permitted
   under either the GNU General Public License (GPL) Version 2 (or any
   later version) or under the GNU Lesser General Public License
   (version 3 or later).

   These licenses may be found at www.gnu.org, particularly:
   http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
   http://www.gnu.org/licenses/lgpl-3.0-standalone.html

   Use and redistribution (especially embedding in other CC licensed
   content) is also permitted under the terms of the Creative Commons
   "Attribution-NonCommercial" license:

   http://creativecommons.org/licenses/by-nc/3.0/ 

   Other uses may be allowed based on prior agreement with
   beingmeta, inc.  Inquiries can be addressed to:

   licensing@beingmeta.com

   Enjoy!

*/

var WSN=(function(){

    var unicode_regex=/(\p{Mark})/g;
    
    function WSN(arg,sortfn,wordfn,keepdup){
	if (!(arg)) {
	    // Assume we're being used as a constructor.
	    if (sortfn) this.sortfn=sortfn;
	    if (wordfn) this.wordfn=wordfn;
	    if (keepdup) this.keepdup=keepdup;
	    return this;}
	if (typeof sortfn === 'undefined') sortfn=WSN.sortfn||false;
	if (typeof wordfn === 'undefined') wordfn=WSN.wordfn||false;
	if (typeof keepdup === 'undefined') keepdup=WSN.keepdup||false;
	if (typeof arg === 'string') {
	    var norm=
		((unicode_regex)?
		 (arg.toLowerCase().replace(unicode_regex,"")):
		 (arg.toLowerCase()));
	    if (norm.search(/\S/)>0)
		norm=norm.slice(norm.search(/\S/));
	    var words=norm.split(/\W*\s+\W*/g);
	    var nwords=words.length;
	    if (nwords===0) return "";
	    else words[0]=words[0].replace(/^\W+/,"");
	    if (nwords>1)
		words[nwords-1]=words[nwords-1].replace(/\W+$/,"");
	    if (wordfn) {
		if (typeof wordfn === 'number') {
		    var nwords=[];
		    var i=0; var lim=words.length;
		    while (i<lim) {
			var word=words[i++];
			if (word.length>wordfn) nwords.push(word);}
		    if (nwords.length) words=nwords;}
		else if (wordfn.call) {
		    var nwords=[];
		    var i=0; var lim=words.length;
		    while (i<lim) {
			var nword=wordfn(words[i++]);
			if (nword) nwords.push(nword);
			i++;}
		    if (nwords.length) words=nwords;}
		else  {
		    var nwords=[];
		    var i=0; var lim=words.length;
		    while (i<lim) {
			var word=words[i++];
			var nword=wordfn[word];
			if (nword==="") {}
			else if ((!(nword))||(typeof nword !== 'string'))
			    nwords.push(word);
			else nwords.push(nword);}
		    if (nwords.length) words=nwords;}}
	    var sorter=sortfn;
	    // By default, use lensort
	    // But if you're passed nativesort, just
	    //  pass false to sort()
	    if (sortfn===true) sorter=lensort;
	    else if (sortfn===nativesort) sorter=false;
	    else {}
	    if ((sortfn)&&(keepdup))
		return words.sort(sorter).join(" ");
	    else if (sortfn)
		return dedupfn(words.sort(sorter)).join(" ");
	    else return words.join(" ");}
	else if (!(arg.nodeType))
	    throw new Exception("bad arg to WSN");
	else if (arg.nodeType===3)
	    return WSN(arg.nodeValue);
	else if (arg.nodeType===1)
	    return WSN(textify(arg));
	else throw new Exception("bad arg to WSN");}
    
    function dedupfn(arr){
	var i=0; var lim=arr.length; var last=false;
	if (lim<2) return arr;
	else while (i<lim) {
	    if ((last)&&(arr[i]===last)) return dodedup(arr);
	    else last=arr[i++];}
	return arr;}
    function dodedup(arr){
	var last=arr[0]; var result=[last];
	var i=1; var lim=arr.length;
	while (i<lim) 
	    if (arr[i]===last) i++;
	    else result.push(last=arr[i++]);
	return result;}
    
    function lensort(x,y){
	var xl=x.length, yl=y.length;
	if (xl===yl) {
	    if (x>y) return -1;
	    else if (x<y) return 1;
	    else return 0;}
	else if (xl>yl) return -1;
	else return 1;}
    WSN.lensort=lensort;
    function nativesort(x,y){
	if (x>y) return -1;
	else if (x<y) return 1;
	else return 0;}
    WSN.nativesort=nativesort;

    function textify(arg,text){
	if (!(arg.nodeType)) return text||"";
	else if (arg.nodeType===3)
	    if (text) return text+arg.nodeValue; else return arg.nodeValue;
	else if (arg.nodeType===1) {
	    var children=arg.childNodes;
	    var style=((window.getComputedStyle)?
		       (window.getComputedStyle(arg)):
		       {position: 'static',display: 'block'});
	    if (style.position!=='static') return text||"";
	    if (style.display!=='inline')
		text="\n"+(text||"");
	    else if (!(text)) text="";
	    var i=0; var lim=children.length;
	    while (i<lim) {
		var child=children[i++];
		if (child.nodeType===3) text=text+child.nodeValue;
		else if (child.nodeType===1) text=textify(child,text);
		else {}}
	    return text;}
	else if (text) return text;
	else return "";}
    WSN.prototype.textify=WSN.textify=textify;

    function fuddle(arg,sortfn){return WSN(arg,sortfn||lensort);}
    WSN.fuddle=fuddle;

    function md5ID(arg){
	var wsn=WSN.apply(null,arguments);
	if (WSN.md5) return WSN.md5(wsn);
	else if ((fdjtHash)&&(fdjtHash.hex_md5))
	    return fdjtHash.hex_md5(wsn);
	else throw new Exception("No MD5 implementation");}
    WSN.md5ID=md5ID;
    
    function sha1ID(arg){
	var wsn=WSN.apply(null,arguments);
	if (WSN.sha1) return WSN.md5(wsn);
	else if ((fdjtHash)&&(fdjtHash.hex_sha1))
	    return fdjtHash.hex_sha1(wsn);
	else throw new Exception("No MD5 implementation");}
    WSN.sha1ID=sha1ID;

    function Hash(arg,hashfn,sortfn,wordfn,keepdups){
	if (typeof hashfn === 'undefined') hashfn=WSN.hashfn||false;
	if (typeof sortfn === 'undefined') sortfn=WSN.sortfn||false;
	if (typeof wordfn === 'undefined') wordfn=WSN.wordfn||false;
	if (typeof keepdup === 'undefined') keepdup=WSN.keepdup||false;
	var wsn=WSN(arg,sortfn,wordfn,keepdups);
	return ((hashfn)?(hashfn(wsn)):(wsn));}
    WSN.Hash=Hash;
    WSN.prototype.Hash=function(arg){
	return Hash(arg,this.hashfn||WSN.hashfn||false,
		    this.sortfn||WSN.sortfn||false,
		    this.wordfn||WSN.wordfn||false,
		    this.keepdup||WSN.keepdup||false);}

    function Map(nodes,hashfn,sortfn,wordfn,keepdups){
	if (typeof hashfn === 'undefined') hashfn=WSN.hashfn||false;
	if (typeof sortfn === 'undefined') sortfn=WSN.sortfn||false;
	if (typeof wordfn === 'undefined') wordfn=WSN.wordfn||false;
	if (typeof keepdup === 'undefined') keepdup=WSN.keepdup||false;
	var map={};
	var i=0; var lim=nodes.length;
	while (i<lim) {
	    var node=nodes[i++];
	    var wsn=WSN(node,sortfn,wordfn,keepdups);
	    var id=((hashfn)?(hashfn(wsn)):(wsn));
	    map[id]=node;}
	return map;}
    WSN.Map=Map;
    WSN.prototype.Map=function(arg){
	return Map(arg,this.hashfn||WSN.hashfn||false,
		   this.sortfn||WSN.sortfn||false,
		   this.wordfn||WSN.wordfn||false,
		   this.keepdup||WSN.keepdup||false);}
    
    function MapMD5(nodes,sortfn,wordfn,keepdups){
	var hashfn=WSN.md5||((fdjtHash)&&(fdjtHash.hex_md5));
	return Map(nodes,hashfn,sortfn,wordfn,keepdups);}
    function MapSHA1(nodes,sortfn,wordfn,keepdups){
	var hashfn=WSN.sha1||((fdjtHash)&&(fdjtHash.hex_sha1));
	return Map(nodes,hashfn,sortfn,wordfn,keepdups);}

    WSN.md5=((fdjtHash)&&(fdjtHash.hex_md5));
    WSN.sha1=((fdjtHash)&&(fdjtHash.hex_sha1));

    try {
	if (("A\u0300".search(unicode_regex))<0)
	    unicode_regex=false;}
    catch (ex) {
	unicode_regexes=false;}
    
    return WSN;})();

/* -*- Mode: Javascript; -*- */

/* Copyright (C) 2009-2012 beingmeta, inc.
   This file is a part of the FDJT web toolkit (www.fdjt.org)
   This file provides extended Javascript utility functions
   of various kinds.

   This program comes with absolutely NO WARRANTY, including implied
   warranties of merchantability or fitness for any particular
   purpose.

   Use, modification, and redistribution of this program is permitted
   under either the GNU General Public License (GPL) Version 2 (or
   any later version) or under the GNU Lesser General Public License
   (version 3 or later).

   These licenses may be found at www.gnu.org, particularly:
   http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
   http://www.gnu.org/licenses/lgpl-3.0-standalone.html

*/

var fdjtUI=((typeof fdjtUI === 'undefined')?{}:(fdjtUI));
fdjtUI.CoHi=(fdjtUI.CoHi)||{classname: "cohi"};
fdjtUI.AutoPrompt=(fdjtUI.AutoPrompt)||{};
fdjtUI.InputHelp=(fdjtUI.InputHelp)||{};
fdjtUI.Expansion=(fdjtUI.Expansion)||{};
fdjtUI.Collapsible=(fdjtUI.Collapsible)||{};
fdjtUI.Tabs=(fdjtUI.Tabs)||{};
fdjtUI.MultiText=(fdjtUI.MultiText)||{};

/* Co-highlighting */
/* When the mouse moves over a named element, the 'cohi' class is added to
   all elements with the same name. */
(function(){
    var highlights={};
    function highlight(namearg,classname_arg){
	var classname=((classname_arg) || (fdjtUI.CoHi.classname));
	var newname=(namearg.name)||(namearg);
	var cur=highlights[classname];
	if (cur===newname) return;
	if (cur) {
	    var drop=document.getElementsByName(cur);
	    var i=0, n=drop.length;
	    while (i<n) fdjtDOM.dropClass(drop[i++],classname);}
	highlights[classname]=newname||false;
	if (newname) {
	    var elts=document.getElementsByName(newname);
	    var n=elts.length, i=0;
	    while (i<n) fdjtDOM.addClass(elts[i++],classname);}}
    
    fdjtUI.CoHi.onmouseover=function(evt,classname_arg){
	var target=fdjtDOM.T(evt);
	while (target)
	    if ((target.tagName==='INPUT') || (target.tagName==='TEXTAREA') ||
		((target.tagName==='A') && (target.href)))
		return;
	else if (target.name) break;  
	else target=target.parentNode;
	if (!(target)) return;
	highlight(target.name,classname_arg);};
    fdjtUI.CoHi.onmouseout=function(evt,classname_arg){
	var target=fdjtDOM.T(evt);
	highlight(false,((classname_arg) || (fdjtUI.CoHi.classname)));};
})();

/* Text highlighting */
fdjtUI.Highlight=(function(){
    var highlight_class="fdjthighlight";
    var hasClass=fdjtDOM.hasClass;
    var hasParent=fdjtDOM.getParent;

    function textnode(s){
	return document.createTextNode(s);}

    function clear_highlights(node,hclass){
	var h=fdjtDOM.getChildren(
	    node||document.body,"."+(hclass||highlight_class));
	h=fdjtDOM.toArray(h);
	var i=0 , lim=h.length;
	while (i<lim) {
	    var hnode=h[i++];
	    if (hnode.firstChild)
		fdjtDOM.replace(hnode,hnode.firstChild);}}
    function highlight_node(node,hclass){
	if (!(hclass)) hclass=highlight_class;
	if (hasClass(node,hclass)) return node;
	var hispan=fdjtDOM("span."+hclass);
	fdjtDOM.replace(node,hispan);
	hispan.appendChild(node);}
    function highlight_text(text,hclass){
	return fdjtDOM("span."+(hclass||highlight_class),
		       text);}
    function highlight_node_range(node,start,end,hclass){
	var stringval=node.nodeValue;
	var parent=node.parentNode;
	if ((end===false)||(typeof end === 'undefined'))
	    end=stringval.length;
	if (start===end) return;
	var beginning=((start>0)&&(textnode(stringval.slice(0,start))));
	var middle=highlight_text(stringval.slice(start,end));
	var ending=((end<stringval.length)&&
		    (textnode(stringval.slice(end))));
	if ((beginning)&&(ending)) {
	    parent.replaceChild(ending,node);
	    parent.insertBefore(middle,ending);
	    parent.insertBefore(beginning,middle);}
	else if (beginning) {
	    parent.replaceChild(middle,node);
	    parent.insertBefore(beginning,middle);}
	else if (ending) {
	    parent.replaceChild(ending,node);
	    parent.insertBefore(middle,ending);}
	else parent.replaceChild(middle,node);}
    function highlight_range(range,hclass){
	range=fdjtDOM.refineRange(range);
	var starts_in=range.startContainer;
	var ends_in=range.endContainer;
	if (starts_in===ends_in)
	    return highlight_node_range(
		starts_in,range.startOffset,range.endOffset,
		hclass);
	else {
	    var scan=starts_in;
	    while ((scan)&&(!(scan.nextSibling)))
		scan=scan.parentNode;
	    scan=scan.nextSibling;
	    while (scan) {
		if (scan===ends_in) break;
		else if (hasParent(ends_in,scan))
		    scan=scan.firstChild;
		else {
		    highlight_node(scan);
		    while ((scan)&&(!(scan.nextSibling)))
			scan=scan.parentNode;
		    scan=scan.nextSibling;}}
	    // Do the ends
	    highlight_node_range(
		starts_in,range.startOffset,false,hclass);
	    highlight_node_range(ends_in,0,range.endOffset,hclass);}}

    highlight_range.clear=clear_highlights;
    highlight_range.highlight=highlight_range;
    return highlight_range;})();

/* CheckSpans:
   Text regions which include a checkbox where clicking toggles the checkbox. */
(function(){
    var hasClass=fdjtDOM.hasClass;
    var addClass=fdjtDOM.addClass;
    var dropClass=fdjtDOM.dropClass;
    var toggleClass=fdjtDOM.toggleClass;
    var getParent=fdjtDOM.getParent;
    var getChildren=fdjtDOM.getChildren;
    var getChild=fdjtDOM.getChild;

    function CheckSpan(spec,varname,val,checked){
	var input=fdjtDOM.Input('input[type=checkbox]',varname,val);
	var span=fdjtDOM(spec||"span.checkspan",input);
	if (checked) {
	    input.checked=true;
	    fdjtDOM.addClass(span,"ischecked");}
	else input.checked=false;
	if (arguments.length>4)
	    fdjtDOM.appendArray(span,arguments,4);
	return span;}
    fdjtUI.CheckSpan=CheckSpan;

    function checkable(elt){
	return (elt.nodeType===1)&&
	    (elt.tagName==='INPUT')&&
	    ((elt.type=='checkbox')||(elt.type=='radio'));}

    function checkspan_set(target,checked) {
	if (typeof target === 'string') target=fdjtID(target);
	else if (target.length) {
	    var i=0, lim=target.length;
	    while (i<lim) checkspan_set(target[i++],checked);
	    return;}
	if ((!(target))||(!(target.nodeType))) return;
	var checkspan=((hasClass(target,"checkspan"))?(target):
		       (getParent(target,".checkspan")));
	var input=getParent(target,"input");
	if (!(checkspan)) return false;
	var inputs=(getChildren(checkspan,checkable));
	if (inputs.length===0) return false;
	if (typeof checked === 'undefined') {
	    if (input) checked=input.checked;
	    else checked=(!((inputs[0]).checked));}
	if (input) input.checked=checked;
	if (checked) addClass(checkspan,"ischecked");
	else dropClass(checkspan,"ischecked");
	var i=0; var lim=inputs.length;
	while (i<lim) {
	    var cb=inputs[i++];
	    if (cb===input) continue;
	    if ((cb.checked)&&(!(checked))) cb.checked=false;
	    else if ((!(cb.checked))&&(checked)) cb.checked=true;
	    else continue;
	    var evt=document.createEvent("HTMLEvents");
	    evt.initEvent("change",false,true);
	    cb.dispatchEvent(evt);}
	return true;}
    fdjtUI.CheckSpan.set=checkspan_set;

    function checkspan_onclick(evt) {
	evt=evt||event;
	var target=evt.target||evt.srcTarget;
	if (getParent(target,"input"))
	    setTimeout(function(){checkspan_set(target);},100);
	else {
	    checkspan_set(target);
	    fdjtUI.cancel(evt);}
	return false;}
    fdjtUI.CheckSpan.onclick=checkspan_onclick;    
    })();

(function(){

    var hasClass=fdjtDOM.hasClass;
    var addClass=fdjtDOM.addClass;
    var dropClass=fdjtDOM.dropClass;

    function show_help_onfocus(evt){
	var target=fdjtDOM.T(evt);
	while (target)
	    if ((target.nodeType==1) &&
		((target.tagName === 'INPUT') ||
		 (target.tagName === 'TEXTAREA')) &&
		(target.getAttribute('helptext'))) {
		var helptext=fdjtID(target.getAttribute('helptext'));
		if (helptext) fdjtDOM.addClass(helptext,"showhelp");
		return;}
	else target=target.parentNode;}
    function autoprompt_onfocus(evt){
	evt=evt||event||null;
	var elt=fdjtDOM.T(evt);
	if ((elt) && (hasClass(elt,'isempty'))) {
	    elt.value=''; dropClass(elt,'isempty');}
	show_help_onfocus(evt);}

    function hide_help_onblur(evt){
	var target=fdjtDOM.T(evt);
	while (target)
	    if ((target.nodeType==1) &&
		((target.tagName === 'INPUT') || (target.tagName === 'TEXTAREA')) &&
		(target.getAttribute('HELPTEXT'))) {
		var helptext=fdjtID(target.getAttribute('HELPTEXT'));
		if (helptext) dropClass(helptext,"showhelp");
		return;}
	else target=target.parentNode;}
    function autoprompt_onblur(evt){
	var elt=fdjtDOM.T(evt);
	if (elt.value==='') {
	    addClass(elt,'isempty');
	    var prompt=(elt.prompt)||(elt.getAttribute('prompt'))||(elt.title);
	    if (prompt) elt.value=prompt;}
	else dropClass(elt,'isempty');
	hide_help_onblur(evt);}
    
    // Removes autoprompt text from empty fields
    function autoprompt_cleanup(form) {
	var elements=fdjtDOM.getChildren(form,".isempty");
	if (elements) {
	    var i=0; var lim=elements.length;
	    while (i<elements.length) elements[i++].value="";}}
    function autoprompt_onsubmit(evt) {
	var form=fdjtDOM.T(evt);
	autoprompt_cleanup(form);}

    var isEmpty=fdjtString.isEmpty;
    // Adds autoprompt handlers to autoprompt classes
    function autoprompt_setup(arg,nohandlers) {
	var forms=
	    ((arg.tagName==="FORM")?[arg]:
	     (fdjtDOM.getChildren(arg||document.body,"FORM")));
	var i=0; var lim=forms.length;
	while (i<lim) {
	    var form=forms[i++];
	    var inputs=fdjtDOM.getChildren
	    (form,"INPUT.autoprompt,TEXTAREA.autoprompt");
	    if (inputs.length) {
		var j=0; var jlim=inputs.length;
		while (j<jlim) {
		    var input=inputs[j++];
		    input.blur();
		    if (isEmpty(input.value)) {
			addClass(input,"isempty");
			var prompt=(input.prompt)||
			    (input.getAttribute('prompt'))||(input.title);
			if (prompt) input.value=prompt;}
		    if (!(nohandlers)) {
			fdjtDOM.addListener(input,"focus",autoprompt_onfocus);
			fdjtDOM.addListener(input,"blur",autoprompt_onblur);}}
		if (!(nohandlers))
		    fdjtDOM.addListener(form,"submit",autoprompt_onsubmit);}}}
    
    fdjtUI.AutoPrompt.setup=autoprompt_setup;
    fdjtUI.AutoPrompt.onfocus=autoprompt_onfocus;
    fdjtUI.AutoPrompt.onblur=autoprompt_onblur;
    fdjtUI.AutoPrompt.onsubmit=autoprompt_onsubmit;
    fdjtUI.AutoPrompt.cleanup=autoprompt_cleanup;
    fdjtUI.InputHelp.onfocus=show_help_onfocus;
    fdjtUI.InputHelp.onblur=hide_help_onblur;})();


(function(){
  function multitext_keypress(evt,sepch){
	evt=(evt)||(event);
	var ch=evt.charCode;
	var target=fdjtUI.T(evt);
	if (typeof sepch === 'string') sepch=sepch.charCodeAt(0);
	if ((ch!==13)&&(sepch)&&(sepch!=ch)) return;
	fdjtUI.cancel(evt);
	var checkbox=
	    fdjtDOM.Input("[type=checkbox]",target.name,target.value);
	var div=fdjtDOM("div.checkspan",checkbox,target.value);
	checkbox.checked=true;
	fdjtDOM(target.parentNode,div);
	target.value='';}
    fdjtUI.MultiText.keypress=multitext_keypress;})();

/* Tabs */

(function(){
    var hasClass=fdjtDOM.hasClass;
    var addClass=fdjtDOM.addClass;
    var dropClass=fdjtDOM.dropClass;
    
    function tab_onclick(evt,shownclass){
	var elt=fdjtUI.T(evt);
	if (!(shownclass)) {
	    shownclass=
		fdjtDOM.findAttrib(elt,"shownclass","http://fdjt.org/")||
		"fdjtshown";}
	if (elt) {
	    var content_id=false;
	    while (elt.parentNode) {
		if (content_id=fdjtDOM.getAttrib(elt,"contentid")) break;
		else elt=elt.parentNode;}
	    if (!(content_id)) return;
	    var content=document.getElementById(content_id);
	    var parent=fdjtDOM.getParent(elt,".tabs")||elt.parentNode;
	    var sibs=fdjtDOM.getChildren(parent,".tab")||parent.childNodes;
	    if (content===null) {
		fdjtLog("No content for "+content_id);
		return;}
	    var i=0; while (i<sibs.length) {
		var node=sibs[i++]; var cid;
		if ((node.nodeType===1) &&
		    (cid=fdjtDOM.getAttrib(node,"contentid"))) {
		    if (!(cid)) continue;
		    var cdoc=document.getElementById(cid);
		    if (node===elt) {}
		    else if (hasClass(node,shownclass)) {
			dropClass(node,shownclass);
			if (cdoc) dropClass(cdoc,shownclass);}}}
	    if (hasClass(elt,shownclass)) {
		dropClass(elt,shownclass);
		dropClass(content,shownclass);}
	    else {
		addClass(elt,shownclass);
		addClass(content,shownclass);}
	    var tabstate=fdjtDOM.findAttrib(elt,'tabstate');
	    if (!(tabstate)) {}
	    else if (tabstate==='#') {
		var scrollstate={};
		fdjtUI.scrollSave(scrollstate);
		document.location.hash=tabstate+content_id;
		fdjtUI.scrollRestore(scrollstate);}
	    else fdjtState.setCookie(tabstate,content_id);
	    // This lets forms pass tab information along
	    return false;}}
    fdjtUI.Tabs.click=tab_onclick;
    
    function select_tab(tabbar,contentid,shownclass){
	if (!(shownclass)) {
	    shownclass=
		fdjtDOM.findAttrib(tabbar,"shownclass","http://fdjt.org/")||
		"fdjtshown";}
	var tabseen=false;
	var tabs=fdjtDOM.getChildren(tabbar,".tab");
	var i=0; while (i<tabs.length) {
	    var tab=tabs[i++];
	    if ((tab.getAttribute("contentid"))===contentid) {
		addClass(tab,shownclass); tabseen=true;}
	    else if (hasClass(tab,shownclass)) {
		dropClass(tab,shownclass);
		var cid=fdjtDOM.getAttrib(tab,"contentid");
		var content=(cid)&&fdjtID(cid);
		if (!(content))
		    fdjtWarn("No reference for tab content %o",cid);
		else dropClass(content,shownclass);}
	    else dropClass(tab,shownclass);}
	if (fdjtID(contentid)) {
	    if (tabseen) addClass(contentid,shownclass);
	    else fdjtLog.warn("a tab for %s was not found in %o",
			      contentid,tabbar);}
	else fdjtLog.warn("No reference for tab content %o",contentid);}
    fdjtUI.Tabs.selectTab=select_tab;
    
    function setupTabs(elt){
	if (!(elt)) elt=fdjtDOM.$(".tabs[tabstate]");
	else if (typeof elt === 'string') elt=fdjtID(elt);
	if ((!(elt))||(!(elt.getAttribute("tabstate")))) return;
	var tabstate=elt.getAttribute("tabstate");
	var content_id=false;
	if (tabstate==='#') {
	    content_id=document.location.hash;
	    if (content_id[0]==='#') content_id=content_id.slice(1);
	    var content=((content_id)&&(fdjtID(content_id)));
	    if (!(content)) return;
	    var ss={}; fdjtUI.scrollSave(ss);
	    window.scrollTo(0,0);
	    if (!(fdjtDOM.isVisible(content)))
		fdjtUI.scrollRestore(ss);}
	else content_id=fdjtState.getQuery(tabstate)||
	    fdjtState.getCookie(tabstate);
	if (!(content_id)) return;
	if (content_id[0]==='#') content_id=content_id.slice(1);
	if (content_id) select_tab(elt,content_id);}
    fdjtUI.Tabs.setup=setupTabs;
    
    function selected_tab(tabbar){
	var tabs=fdjtDOM.getChildren(tabbar,".tab");
	var i=0; while (i<tabs.length) {
	    var tab=tabs[i++];
	    if (hasClass(tag,"shown"))
		return tag.getAttribute("contentid");}
	return false;}
    fdjtUI.Tabs.getSelected=selected_tab;}());


/* Delays */

(function(){
    var timeouts={};
    
    fdjtUI.Delay=function(interval,name,fcn){
	window.setTimeout(fcn,interval);};}());

/* Expansion */

fdjtUI.Expansion.toggle=function(evt,spec,exspec){
  evt=evt||event;
    var target=fdjtUI.T(evt);
    var wrapper=fdjtDOM.getParent(target,spec||".fdjtexpands");
    if (wrapper) fdjtDOM.toggleClass(wrapper,exspec||"fdjtexpanded");};
fdjtUI.Expansion.onclick=fdjtUI.Expansion.toggle;

fdjtUI.Collapsible.click=function(evt){
  evt=evt||event;
  var target=fdjtUI.T(evt);
  if (fdjtUI.isDefaultClickable(target)) return;
  var wrapper=fdjtDOM.getParent(target,".collapsible");
  if (wrapper) {
    fdjtUI.cancel(evt);
    fdjtDOM.toggleClass(wrapper,"expanded");};};

fdjtUI.Collapsible.focus=function(evt){
  evt=evt||event;
  var target=fdjtUI.T(evt);
  var wrapper=fdjtDOM.getParent(target,".collapsible");
  if (wrapper) {
    fdjtDOM.toggleClass(wrapper,"expanded");};};

/* Temporary Scrolling */

(function(){
    var saved_scroll=false;
    var use_native_scroll=false;
    var preview_elt=false;

    function scroll_discard(ss){
	if (ss) {
	    ss.scrollX=false; ss.scrollY=false;}
	else saved_scroll=false;}

    function scroll_save(ss){
	if (ss) {
	    ss.scrollX=window.scrollX; ss.scrollY=window.scrollY;}
	else {
	    if (!(saved_scroll)) saved_scroll={};
	    saved_scroll.scrollX=window.scrollX;
	    saved_scroll.scrollY=window.scrollY;}}
    
    function scroll_offset(wleft,eleft,eright,wright){
	var result;
	if ((eleft>wleft) && (eright<wright)) return wleft;
	else if ((eright-eleft)<(wright-wleft)) 
	    return eleft-Math.floor(((wright-wleft)-(eright-eleft))/2);
	else return eleft;}

    function scroll_into_view(elt,topedge){
	if ((topedge!==0) && (!topedge) && (fdjtDOM.isVisible(elt)))
	    return;
	else if ((use_native_scroll) && (elt.scrollIntoView)) {
	    elt.scrollIntoView(top);
	    if ((topedge!==0) && (!topedge) && (fdjtDOM.isVisible(elt,true)))
		return;}
	else {
	    var top = elt.offsetTop;
	    var left = elt.offsetLeft;
	    var width = elt.offsetWidth;
	    var height = elt.offsetHeight;
	    var winx=(window.pageXOffset||document.documentElement.scrollLeft||0);
	    var winy=(window.pageYOffset||document.documentElement.scrollTop||0);
	    var winxedge=winx+(document.documentElement.clientWidth);
	    var winyedge=winy+(document.documentElement.clientHeight);
	    
	    while(elt.offsetParent) {
		elt = elt.offsetParent;
		top += elt.offsetTop;
		left += elt.offsetLeft;}
	    
	    var targetx=scroll_offset(winx,left,left+width,winxedge);
	    var targety=
		(((topedge)||(topedge===0)) ?
		 ((typeof topedge === "number") ? (top+topedge) : (top)) :
		 (scroll_offset(winy,top,top+height,winyedge)));
	    
	    var vh=fdjtDOM.viewHeight();
	    var x=0; var y;
	    var y_target=top+(height/3);
	    if ((2*(height/3))<((vh/2)-50))
		y=y_target-vh/2;
	    else if ((height)<(vh-100))
		y=top-(50+(height/2));
	    else y=top-50;

	    window.scrollTo(x,y);}}

    fdjtUI.scrollTo=function(target,id,context,discard,topedge){
	scroll_discard(discard);
	if (id) document.location.hash=id;
	if (context) {
	    setTimeout(function() {
		scroll_into_view(context,topedge);
		if (!(fdjtDOM.isVisible(target))) {
		    scroll_into_view(target,topedge);}},
		       100);}
	else setTimeout(function() {scroll_into_view(target,topedge);},100);};

    function scroll_preview(target,context,delta){
	/* Stop the current preview */
	if (!(target)) {
	    stop_preview(); return;}
	/* Already previewing */
	if (target===preview_elt) return;
	if (!(saved_scroll)) scroll_save();
	if (typeof target === 'number')
	    window.scrollTo(((typeof context === 'number')&&(context))||0,target);
	else scroll_into_view(target,delta);
	preview_elt=target;}

    function scroll_restore(ss){
	if (preview_elt) {
	    preview_elt=false;}
	if ((ss) && (typeof ss.scrollX === "number")) {
	    // fdjtLog("Restoring scroll to %d,%d",ss.scrollX,ss.scrollY);    
	    window.scrollTo(ss.scrollX,ss.scrollY);
	    return true;}
	else if ((saved_scroll) &&
		 ((typeof saved_scroll.scrollY === "number") ||
		  (typeof saved_scroll.scrollX === "number"))) {
	    // fdjtLog("Restoring scroll to %o",_fdjt_saved_scroll);
	    window.scrollTo(saved_scroll.scrollX,saved_scroll.scrollY);
	    saved_scroll=false;
	    return true;}
	else return false;}

    function stop_preview(){
	fdjtDOM.dropClass(document.body,"preview");
	if ((preview_elt) && (preview_elt.className))
	    fdjtDOM.dropClass(preview_elt,"previewing");
	preview_elt=false;}

    fdjtUI.scrollSave=scroll_save;
    fdjtUI.scrollRestore=scroll_restore;
    fdjtUI.scrollIntoView=scroll_into_view;
    fdjtUI.scrollPreview=scroll_preview;
    fdjtUI.scrollRestore=scroll_restore;}());

(function(){
    function dosubmit(evt){
	evt=evt||event;
	var target=fdjtUI.T(evt);
	var form=fdjtDOM.getParent(target,"FORM");
	var submit_event = document.createEvent("HTMLEvents");
	submit_event.initEvent('submit',false,true);
	form.dispatchEvent(submit_event);
	form.submit();}
    fdjtUI.dosubmit=dosubmit;}());

(function(){
    var addClass=fdjtDOM.addClass;
    var dropClass=fdjtDOM.dropClass;
    var getGeometry=fdjtDOM.getGeometry;
    var getInsideBounds=fdjtDOM.getInsideBounds;
    function checkOverflow(node){
	var geom=getGeometry(node);
	var inside=getInsideBounds(node);
	if (inside.bottom>geom.bottom) addClass(node,"overflow");
	else dropClass(node,"overflow");}
    fdjtUI.Overflow=checkOverflow;}());

(function(){
    var hasClass=fdjtDOM.hasClass;
    
    fdjtUI.T=function(evt) {
	evt=evt||event; return (evt.target)||(evt.srcElement);};

    fdjtUI.nodefault=function(evt){
	evt=evt||event;
	if (evt.preventDefault) evt.preventDefault();
	else evt.returnValue=false;
	return false;};

    fdjtUI.isClickable=function(target){
	if (target instanceof Event) target=fdjtUI.T(target);
	while (target) {
	    if (((target.tagName==='A')&&(target.href))||
		(target.tagName==="INPUT") ||
		(target.tagName==="TEXTAREA") ||
		(target.tagName==="SELECT") ||
		(target.tagName==="OPTION") ||
		(hasClass(target,"checkspan"))||
		(hasClass(target,"clickable"))||
		(hasClass(target,"isclickable")))
		return true;
	    else if (target.onclick)
	      return true;
	    else target=target.parentNode;}
	return false;};

    fdjtUI.isDefaultClickable=function(target){
	if (target instanceof Event) target=fdjtUI.T(target);
	while (target) {
	    if (((target.tagName==='A')&&(target.href))||
		(target.tagName==="INPUT") ||
		(target.tagName==="TEXTAREA") ||
		(target.tagName==="SELECT") ||
		(target.tagName==="OPTION") ||
		(hasClass(target,"isclickable")))
		return true;
	    else target=target.parentNode;}
	return false;};

    fdjtUI.cancel=function(evt){
	evt=evt||event;
	if (evt.preventDefault) evt.preventDefault();
	else evt.returnValue=false;
	evt.cancelBubble=true;
	return false;};
    fdjtUI.nobubble=function(evt){
	evt=evt||event;
	evt.cancelBubble=true;};

    function submitEvent(arg){
	var form=((arg.nodeType)?(arg):(fdjtUI.T(arg)));
	while (form) {
	    if (form.tagName==='FORM') break;
	    else form=form.parentNode;}
	if (!(form)) return;
	var submit_evt = document.createEvent("HTMLEvents");
	submit_evt.initEvent("submit", true, true);
	form.dispatchEvent(submit_evt);
	return;}
    fdjtUI.submitEvent=submitEvent;

    function focusEvent(arg){
	var elt=((arg.nodeType)?(arg):(fdjtUI.T(arg)));
	var focus_evt = document.createEvent("HTMLEvents");
	focus_evt.initEvent("focus", true, true);
	elt.dispatchEvent(focus_evt);
	return;}
    fdjtUI.focusEvent=focusEvent;


}());

/* Emacs local variables
   ;;;  Local variables: ***
   ;;;  compile-command: "make; if test -f ../makefile; then cd ..; make; fi" ***
   ;;;  End: ***
*/
/* -*- Mode: Javascript; -*- */

/* Copyright (C) 2009-2012 beingmeta, inc.
   This file is a part of the FDJT web toolkit (www.fdjt.org)
   This file provides extended Javascript utility functions
   of various kinds.

   This program comes with absolutely NO WARRANTY, including implied
   warranties of merchantability or fitness for any particular
   purpose.

   Use, modification, and redistribution of this program is permitted
   under either the GNU General Public License (GPL) Version 2 (or
   any later version) or under the GNU Lesser General Public License
   (version 3 or later).

   These licenses may be found at www.gnu.org, particularly:
   http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
   http://www.gnu.org/licenses/lgpl-3.0-standalone.html

*/

var fdjtUI=((typeof fdjtUI === 'undefined')?{}:(fdjtUI));
fdjtUI.TapHold=(function(){
    var trace_taps=false;
    var debug_taps=false;
    var window_setup=false;
    
    var touched=false;
    var pressed=false;
    var th_target=false;
    var th_timer=false;
    var mouse_down=false;
    var shift_down=false;
    var touch_x=false;
    var touch_y=false;
    
    var getGeometry=fdjtDOM.getGeometry;

    function getTarget(evt){
	if ((evt.changedTouches)&&((evt.changedTouches.length)))
	    return evt.changedTouches[evt.changedTouches.length-1].target;
	else return fdjtUI.T(evt);}

    function getClientX(evt){
	if (typeof evt.offsetX === "number") return evt.offsetX;
	else if ((evt.touches)&&(evt.touches.length)) {
	    var touch=evt.touches[0];
	    return touch.clientX;}
	else if (typeof evt.clientX === "number") return evt.clientX;
	else return false;}
    function getClientY(evt){
	if (typeof evt.offsetY === "number") return evt.offsetY;
	else if ((evt.touches)&&(evt.touches.length)) {
	    var touch=evt.touches[0];
	    return touch.clientY;}
	else if (typeof evt.clientY === "number") return evt.clientY;
	else return false;}
    
    function fakeEvent(target,etype,orig){
	var evt = document.createEvent("UIEvent");
	if (trace_taps)
	    fdjtLog("Synthesizing %s on %o @%d,%d",etype,target,
		    touch_x,touch_y);
	evt.initEvent(etype, true, true);
	evt.clientX=touch_x; evt.clientY=touch_y;
	// If the target no longer has a parent, it's been removed
	//  from the DOM, so we use the event target if there is one
	if ((orig)&&(!(target.parentNode))) target=fdjtUI.T(orig);
	// Does dispatchEvent set this?
	// evt.target=target;
	if (orig) fdjtUI.cancel(orig);
	target.dispatchEvent(evt);}

    function tap_handler(evt){
	var target=fdjtUI.T(evt);
	var msgelt=fdjtID("TAPHOLDMESSAGE");
	if (msgelt) msgelt.innerHTML=fdjtString("Tapped %o",target);
	fdjtLog("Tapped %o",target);}
    function hold_handler(evt){
	var target=fdjtUI.T(evt);
	var msgelt=fdjtID("TAPHOLDMESSAGE");
	if (msgelt) msgelt.innerHTML=fdjtString("Held %o",target);
	fdjtLog("Held %o",target);}
    function release_handler(evt){
	var target=fdjtUI.T(evt);
	var msgelt=fdjtID("TAPHOLDMESSAGE");
	if (msgelt) msgelt.innerHTML=fdjtString("Released %o",target);
	fdjtLog("Released %o",target);}

    function tapped(target,evt){return fakeEvent(target,"tap",evt);}
    function held(target,evt){return fakeEvent(target,"hold",evt);}
    function released(target,evt){return fakeEvent(target,"release",evt);}
    function slipped(target,evt){return fakeEvent(target,"slip",evt);}

    function TapHold(elt){
	elt=elt||window;
	fdjtDOM.addListener(elt,"mousemove",mousemove);
	fdjtDOM.addListener(elt,"touchmove",mousemove);
	fdjtDOM.addListener(elt,"touchstart",mousedown);
	fdjtDOM.addListener(elt,"mousedown",mousedown);
	fdjtDOM.addListener(elt,"mouseup",mouseup);
    	fdjtDOM.addListener(elt,"touchend",mouseup);
	if (!(window_setup)) {
	    fdjtDOM.addListener(document,"keydown",keydown);
	    fdjtDOM.addListener(document,"keyup",keyup);
	    window_setup=window;}

	if (debug_taps) {
	    fdjtDOM.addListener(elt,"tap",tap_handler);
	    fdjtDOM.addListener(elt,"hold",hold_handler);
	    fdjtDOM.addListener(elt,"release",release_handler);}}

    function startpress(evt){
	if (touched) return;
	if (pressed) return;
	if (th_timer) return;
	touched=th_target; pressed=false
	if (trace_taps) fdjtLog("startpress %o",evt);
	th_timer=setTimeout((function(evt){
	    if (trace_taps) fdjtLog("startpress/timeout %o",evt);
	    pressed=th_target;
	    held(th_target,evt);
	    th_timer=false;
	    touched=false;}),TapHold.interval||300);}
    function endpress(evt){
	if (th_timer) {
	    if (trace_taps) fdjtLog("endpress %o t=%o p=%o",evt,th_target,pressed);
	    clearTimeout(th_timer); th_timer=false;
	    if (th_target===touched) tapped(th_target,evt);}
	else if (pressed) {released(pressed,evt);}
	touched=false; pressed=false;}
    function abortpress(evt){
	if (th_timer) {
	    clearTimeout(th_timer); th_timer=false;}
	else if (pressed) {released(pressed,evt);}
	touched=false; pressed=false;}

    function mousemove(evt){
	evt=evt||event;
	var target=fdjtUI.T(evt);
	// if (target!==th_target) fdjtLog("New target %o",target);
	th_target=target;
	touch_x=evt.clientX||getClientX(evt);
	touch_y=evt.clientY||getClientY(evt);
	if (evt.touches) th_target=document.elementFromPoint(touch_x,touch_y);
	if ((evt.touches)&&(evt.touches.length)&&
	    (evt.touches.length>1))
	    return;
	// else fdjtUI.cancel(evt);
	if ((pressed)&&(th_target!==pressed)) {
	    if (trace_taps)
		fdjtLog("move %o %o %d,%d",th_target,th_target.name,touch_x,touch_y);
	    slipped(pressed);
	    pressed=th_target;
	    held(pressed);}}

    function keydown(evt){
	evt=evt||event;
	if (evt.keyCode===16) {
	    shift_down=true;
	    if ((evt.ctrlKey)||(evt.altKey)) return;
	    if (!(touched)) startpress(th_target);}}
    TapHold.keydown=keydown;
    function mousedown(evt){
	evt=evt||event;
	mouse_down=true;
	if (trace_taps) fdjtLog("down %o",evt);
	th_target=fdjtUI.T(evt);
	touch_x=evt.clientX||getClientX(evt);
	touch_y=evt.clientY||getClientY(evt);
	if (evt.ctrlKey) return;
	if ((evt.touches)&&(evt.touches.length)&&
	    (evt.touches.length>1))
	    return;
	if (fdjtUI.isClickable(evt)) return;
	if (!(touched)) startpress(th_target,evt);}
    TapHold.mousedown=mousedown;
    
    function keyup(evt){
	evt=evt||event;
	if (evt.keyCode===16) {
	    shift_down=false;
	    if ((evt.ctrlKey)||(evt.altKey)) return;
	    if ((!(shift_down))&&(!(mouse_down))) endpress();}}
    TapHold.keyup=keyup;
    function mouseup(evt){
	evt=evt||event;
	mouse_down=false;
	if (trace_taps) fdjtLog("up %o",evt);
	if ((evt.touches)&&(evt.touches.length)&&
	    (evt.touches.length>1))
	    return;
	touch_x=evt.clientX||getClientX(evt);
	touch_y=evt.clientY||getClientY(evt);
	if (fdjtUI.isClickable(evt)) return;
	if ((!(shift_down))&&(!(mouse_down))) endpress(evt);}
    TapHold.mouseup=mouseup;

    TapHold.ispressed=function(){
	return (pressed);}

    return TapHold;})();


/* Emacs local variables
   ;;;  Local variables: ***
   ;;;  compile-command: "make; if test -f ../makefile; then cd ..; make; fi" ***
   ;;;  End: ***
*/
/* -*- Mode: Javascript; -*- */

/* Copyright (C) 2009-2012 beingmeta, inc.
   This file is a part of the FDJT web toolkit (www.fdjt.org)
   This file provides extended Javascript utility functions
   of various kinds.

   This program comes with absolutely NO WARRANTY, including implied
   warranties of merchantability or fitness for any particular
   purpose.

   Use, modification, and redistribution of this program is permitted
   under either the GNU General Public License (GPL) Version 2 (or
   any later version) or under the GNU Lesser General Public License
   (version 3 or later).

   These licenses may be found at www.gnu.org, particularly:
   http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
   http://www.gnu.org/licenses/lgpl-3.0-standalone.html

*/

var fdjtUI=((typeof fdjtUI === 'undefined')?{}:(fdjtUI));
(function(){
    var serial=0;

    /* Constants */
    // Always set to distinguish no options from false
    var FDJT_COMPLETE_OPTIONS=1;
    // Whether the completion element is a cloud (made of spans)
    var FDJT_COMPLETE_CLOUD=2;
    // Whether to require that completion match an initial segment
    var FDJT_COMPLETE_ANYWORD=4;
    // Whether to match case in keys to completions
    var FDJT_COMPLETE_MATCHCASE=8;
    // Whether to an enter character always picks a completion
    var FDJT_COMPLETE_EAGER=16;
    // Whether the key fields may contain disjoins (e.g. (dog|friend))
    // to be accomodated in matching
    var FDJT_COMPLETE_DISJOINS=32;
    // Default options
    var default_options=FDJT_COMPLETE_OPTIONS;
    // Max number of completions to show
    var maxcomplete=50;
    // Milliseconds to wait for auto complete
    var complete_delay=100;

    var hasClass=fdjtDOM.hasClass;
    var addClass=fdjtDOM.addClass;
    var dropClass=fdjtDOM.dropClass;
    var getChildren=fdjtDOM.getChildren;
    var getParent=fdjtDOM.getParent;
    var position=fdjtKB.position;

    var isEmpty=fdjtString.isEmpty;
    var hasPrefix=fdjtString.hasPrefix;
    var prefixAdd=fdjtString.prefixAdd;
    var prefixFind=fdjtString.prefixFind;
    var commonPrefix=fdjtString.commonPrefix;

    fdjtUI.FDJT_COMPLETE_OPTIONS=FDJT_COMPLETE_OPTIONS;
    fdjtUI.FDJT_COMPLETE_CLOUD=FDJT_COMPLETE_CLOUD;
    fdjtUI.FDJT_COMPLETE_ANYWORD=FDJT_COMPLETE_ANYWORD;
    fdjtUI.FDJT_COMPLETE_MATCHCASE=FDJT_COMPLETE_MATCHCASE;
    fdjtUI.FDJT_COMPLETE_EAGER=FDJT_COMPLETE_EAGER;

    function Completions(dom,input,options) {
	this.dom=dom||false; this.input=input||false;
	this.options=options||default_options;
	this.nodes=[]; this.values=[]; this.serial=++serial;
	this.cues=[]; this.displayed=[];
	this.prefixtree={strings: []};
	this.bykey={}; this.byvalue=new fdjtKB.Map();
	if (!(options&FDJT_COMPLETE_MATCHCASE)) this.stringmap={};
	this.initialized=false;
	return this;}
    Completions.probe=function(arg){
	if (arg.tagName==='INPUT') {
	    var cid=arg.getAttribute('COMPLETIONS');
	    arg=fdjtID(cid);
	    if (arg) completions.get(arg);
	    else return false;}
	else return completions.get(arg);};

    function getKey(node){
	return node.key||(node.getAttribute("key"))||(node.value)||
	    (node.getAttribute("value"))||
	    ((hasClass(node,"variation"))&&(fdjtDOM.textify(node)))||
	    ((hasClass(node,"completion"))&&(completionText(node,"")));}
    Completions.getKey=getKey;
    function completionText(node,sofar){
	if (hasClass(node,"variation")) return sofar;
	else if (node.nodeType===3) return sofar+node.nodeValue;
	else if ((node.nodeType===1)&&(node.childNodes)) {
	    var children=node.childNodes;
	    var i=0; var lim=children.length;
	    while (i<lim) {
		var child=children[i++];
		if (child.nodeType===3) sofar=sofar+child.nodeValue;
		else if (child.nodeType===1)
		    sofar=completionText(child,sofar);
		else {}}
	    return sofar;}
	else return sofar;}

    function getValue(node){
	if (!(hasClass(node,"completions")))
	    node=getParent(node,".completions");
	var completions=((node)&&(Completions.probe(node)));
	if (completions)
	    return completions.getValue(node);
	else return false;}
    Completions.getValue=getValue;

    function addNodeKey(node,keystring,ptree,bykey,anywhere){
	var keys=((anywhere)?(keystring.split(/\W/g)):[]).concat(keystring);
	var i=0; var lim=keys.length;
	while (i<lim) {
	    var key=keys[i++];
	    prefixAdd(ptree,key,0);
	    if ((bykey[key])&&(bykey.hasOwnProperty(key)))
		bykey[key].push(node);
	    else bykey[key]=new Array(node);
	    bykey._count++;}}

    function getNodes(string,ptree,bykey,matchcase){
	var result=[]; var direct=[]; var variations=[];
	var keystring=stdspace(string);
	if (isEmpty(keystring)) return [];
	if (!(matchcase)) keystring=string.toLowerCase();
	var strings=prefixFind(ptree,keystring,0);
	var prefix=false;
	var exact=[]; var exactheads=[]; var keys=[];
	var i=0; var lim=strings.length;
	while (i<lim) {
	    var string=strings[i++];
	    var isexact=(string===keystring);
	    if (prefix) prefix=commonPrefix(prefix,string);
	    else prefix=string;
	    var completions=bykey[string];
	    if (completions) {
		var j=0; var jlim=completions.length;
		while (j<jlim) {
		    var c=completions[j++];
		    if (hasClass(c,"hidden")) {}
		    else if (hasClass(c,"completion")) {
			if (isexact) {exactheads.push(c); exact.push(c);}
			result.push(c); keys.push(string); direct.push(c);}
		    else {
			var head=getParent(c,".completion");
			if ((head)&&(hasClass(head,"hidden"))) {}
			else if (head) {
			    if (isexact) exact.push(head);
			    result.push(head); keys.push(string);
			    variations.push(c);}}}}}
	if (exact.length) result.exact=exact;
	if (exactheads.length) result.exactheads=exactheads;
	result.prefix=prefix;
	result.strings=strings;
	result.matches=direct.concat(variations);
	return result;}

    function addCompletion(c,completion,key,value) {
	if (!(key)) key=completion.key||getKey(completion);
	if (!(value))
	    value=(completion.value)||(completion.getAttribute('value'))||key;
	var pos=position(c.nodes,completion);
	if (pos<0) {
	    c.nodes.push(completion); c.values.push(value);
	    c.byvalue.add(value,completion);}
	else return;
	var opts=c.options;
	var container=c.dom;
	var ptree=c.prefixtree;
	var bykey=c.bykey;
	var smap=c.stringmap;
	var stdkey=stdspace(key);
	var matchcase=(opts&FDJT_COMPLETE_MATCHCASE);
	var anyword=(opts&FDJT_COMPLETE_ANYWORD);
	if (!(matchcase)) {
	    var lower=stdkey.toLowerCase();
	    smap[lower]=stdkey;
	    stdkey=lower;}
	if (!(getParent(completion,container)))
	    fdjtDOM.append(container,completion," ");
	addNodeKey(completion,stdkey,ptree,bykey,anyword);
	if (hasClass(completion,"cue")) c.cues.push(completion);
	var variations=getChildren(completion,".variation");
	var i=0; var lim=variations.length;
	while (i<lim) {
	    var variation=variations[i++];
	    var vkey=stdspace(variation.key||getKey(variation));
	    addNodeKey(variation,vkey,ptree,bykey,anyword);}}

    function initCompletions(c){
	var completions=getChildren(c.dom,".completion");
	var i=0; var lim=completions.length;
	while (i<lim) addCompletion(c,completions[i++]);
	c.initialized=true;}

    Completions.prototype.addCompletion=function(completion) {
	if (!(this.initialized)) initCompletions(this);
	addCompletion(this,completion);};

    function updateDisplay(c,todisplay){
	var displayed=c.displayed;
	if (displayed) {
	    var i=0; var lim=displayed.length;
	    while (i<lim) dropClass(displayed[i++],"displayed");
	    c.displayed=displayed=[];}
	else c.displayed=displayed=[];
	if (todisplay) {
	    var i=0; var lim=todisplay.length;
	    while (i<lim) {
		var node=todisplay[i++];
		if (hasClass(node,"completion")) {
		    addClass(node,"displayed");
		    displayed.push(node);}
		else {
		    var head=getParent(node,".completion");
		    if ((head)&&(!(hasClass(head,"displayed")))) {
			displayed.push(node); displayed.push(head);
			addClass(head,"displayed");
			addClass(node,"displayed");}}}}}


    Completions.prototype.getCompletions=function(string) {
	if ((string===this.curstring)||(string===this.maxstring)||
	    ((this.curstring)&&(this.maxstring)&&
	     (hasPrefix(string,this.curstring))&&
	     (hasPrefix(this.maxstring,string))))
	    return this.result;
	else {
	    var result;
	    if (!(this.initialized)) initCompletions(this);
	    if (isEmpty(string)) {
		result=[]; result.prefix=""; result.matches=[];
		if (this.dom) addClass(this.dom,"noinput");}
	    else {
		result=getNodes(string,this.prefixtree,this.bykey);
		if (this.dom) dropClass(this.dom,"noinput");
		updateDisplay(this,result.matches);}
	    if ((this.stringmap)&&(this.strings)) {
		var stringmap=this.stringmap;
		var strings=this.strings;
		var i=0; var lim=strings.length;
		while (i<lim) {
		    var s=strings[i]; var m=stringmap[s];
		    if (m) strings[i++]=m;
		    else i++;}}
	    this.curstring=string;
	    this.maxstring=result.prefix||string;
	    this.result=result;
	    return result;}};

    Completions.prototype.getValue=function(completion) {
	if (completion.value) return completion.value;
	else if (completion.getAttribute("value"))
	    return completion.getAttribute("value");
	var pos=position(this.nodes,completion);
	if (pos<0) return false;
	else return this.values[pos];};
    Completions.prototype.getKey=function(completion) {
	if (completion.key) return completion.value;
	else if (completion.getAttribute("key"))
	    return completion.getAttribute("key");
	var pos=position(this.nodes,completion);
	if (pos<0) return false;
	else return this.values[pos];};

    Completions.prototype.complete=function(string){
	if (!(this.initialized)) initCompletions(this);
	// fdjtLog("Completing on %o",string);
	if ((!(string))&&(string!==""))
	    string=((this.getText)?(this.getText(this.input)):
		    (hasClass(this.input,"isempty"))?(""):
		    (this.input.value));
	if (isEmpty(string)) {
	    if (this.displayed) updateDisplay(this,false);
	    addClass(this.dom,"noinput");
	    dropClass(this.dom,"noresults");
	    return [];}
	var result=this.getCompletions(string);
	if ((!(result))||(result.length===0)) {
	    updateDisplay(this,false);
	    dropClass(this.dom,"noinput");
	    addClass(this.dom,"noresults");
	    return [];}
	else {
	    updateDisplay(this,result.matches);
	    dropClass(this.dom,"noinput");
	    dropClass(this.dom,"noresults");}
	return result;};

    Completions.prototype.getByValue=function(values,spec){
	if (!(this.initialized)) initCompletions(this);
	var result=[];
	var byvalue=this.byvalue;
	if (spec) spec=new fdjtDOM.Selector(spec);
	if (!(values instanceof Array)) values=[values];
	var i=0; var lim=values.length;
	while (i<lim) {
	    var value=values[i++];
	    var completions=byvalue.get(value);
	    if (completions) {
		if (spec) {
		    var j=0; var jlim=completions.length;
		    while (j<jlim) {
			if (spec.match(completions[j]))
			    result.push(completions[j++]);
			else j++;}}
		else result=result.concat(completions);}}
	return result;};
    Completions.prototype.getByKey=function(keys,spec){
	if (!(this.initialized)) initCompletions(this);
	var result=[];
	var byvalue=this.bykey;
	if (spec) spec=new fdjtDOM.Selector(spec);
	if (!(keys instanceof Array)) keys=[keys];
	var i=0; var lim=keys.length;
	while (i<lim) {
	    var key=keys[i++];
	    var completions=bykey.get(key);
	    if (completions) {
		if (spec) {
		    var j=0; var jlim=completions.length;
		    while (j<jlim) {
			if (spec.match(completions[j]))
			    result.push(completions[j++]);
			else j++;}}
		else result=result.concat(completions);}}
	return result;};

    Completions.prototype.setCues=function(values,cueclass){
	if (!(this.initialized)) initCompletions(this);
	if (!(cueclass)) cueclass="cue";
	var cues=[];
	var byvalue=this.byvalue;
	var i=0; var lim=values.length;
	while (i<lim) {
	    var value=values[i++];
	    var completions=byvalue.get(value);
	    if (completions) {
		var j=0; var jlim=completions.length;
		while (j<jlim) {
		    var c=completions[j++];
		    if (hasClass(c,cueclass)) continue;
		    addClass(c,cueclass);
		    cues.push(c);}}}
	return cues;};

    Completions.prototype.setClass=function(values,classname){
	if (!(this.initialized)) initCompletions(this);
	var drop=fdjtDOM.getChildren(this.dom,".completion."+classname);
	if ((drop)&&(drop.length))
	    dropClass(fdjtDOM.Array(drop),"hidden");
	var changed=[];
	var byvalue=this.byvalue;
	var i=0; var lim=values.length;
	while (i<lim) {
	    var value=values[i++];
	    var completions=byvalue.get(value);
	    if (completions) {
		var j=0; var jlim=completions.length;
		while (j<jlim) {
		    var c=completions[j++];
		    if (hasClass(c,classname)) continue;
		    addClass(c,classname);
		    changed.push(c);}}}
	return changed;}
    Completions.prototype.extendClass=function(values,classname){
	if (!(this.initialized)) initCompletions(this);
	var changed=[];
	var byvalue=this.byvalue;
	var i=0; var lim=values.length;
	while (i<lim) {
	    var value=values[i++];
	    var completions=byvalue.get(value);
	    if (completions) {
		var j=0; var jlim=completions.length;
		while (j<jlim) {
		    var c=completions[j++];
		    if (hasClass(c,classname)) continue;
		    addClass(c,classname);
		    changed.push(c);}}}
	return changed;};
    
    Completions.prototype.dropClass=function(classname){
	var drop=fdjtDOM.getChildren(this.dom,".completion."+classname);
	if ((drop)&&(drop.length))
	    dropClass(fdjtDOM.Array(drop),classname);};

    Completions.prototype.docomplete=function(input,callback){
	if (!(this.initialized)) initCompletions(this);
	if (!(input)) input=this.input;
	var delay=this.complete_delay||complete_delay;
	var that=this;
	if (this.timer) {
	    clearTimeout(that.timer);
	    that.timer=false;}
	this.timer=setTimeout(
	    function(){
		if (!(input)) input=that.input;
		var completions=that.complete(input.value);
		if (callback) callback(completions);},
	    delay);}

    function stdspace(string){
	return string.replace(/\s+/," ").replace(/(^\s)|(\s$)/,"");}

    fdjtUI.Completions=Completions;

    var cached_completions={};

    var default_options=
	FDJT_COMPLETE_OPTIONS|
	FDJT_COMPLETE_CLOUD|
	FDJT_COMPLETE_ANYWORD;

    function onkey(evt){
	evt=evt||event;
	var target=fdjtUI.T(evt);
	var name=target.name;
	var completions=cached_completions[name];
	var compid=fdjtDOM.getAttrib(target,"completions");
	var dom=((compid)&&(fdjtID(compid)));
	if (!(dom)) return;
	if (!((completions)&&(completions.dom===dom))) {
	    completions=new Completions(dom,target,default_options);
	    cached_completions[name]=completions;}
	if (!(completions)) return;
	completions.docomplete(target);}
    fdjtUI.Completions.onkey=onkey;}());

/*! syze v1.1.1 MIT/GPL2 @rezitech */
(function (win, docEl) {
	// syze variables
	var
	_sizes = [],
	_names = {},
	_from = 'browser',
	_debounceRate = 50,
	_callback;
	// add window event
	function addWinEvent(type, fn) {
		if (win.addEventListener) addEventListener(type, fn, false); else attachEvent('on' + type, fn);
	}
	// debouncer
	function debounce(fn) {
		var timeout;
		return function () {
			var obj = this, args = arguments;
			function delayed () {
				fn.apply(obj, args);
				timeout = null;
			}
			if (timeout) clearTimeout(timeout);
			timeout = setTimeout(delayed, _debounceRate); 
		};
	}
	// resizer
	function onResize() {
		var
		currentSize = 
			/^device$/i.test(String(_from)) ? !win.orientation || orientation == 180 ? screen.width : screen.height
			: /^browser$/i.test(String(_from)) ? docEl.clientWidth
			: (_from instanceof String) ? Function('return ' + _from)()
			: parseInt(_from, 10) || 0,
		docElClassNames = docEl.className.replace(/^\s+|(^|\s)(gt|is|lt)[^\s]+|\s+$/g, '').split(/\s+/),
		classNames = [], i = -1, e, arr = _sizes, len = arr.length;
		//
		arr.sort(function (a, b) { return(a - b); });
		//
		while (++i < len) if (currentSize < arr[i]) break;
		currentSize = arr[Math.max(Math.min(--i, len - 1), 0)];
		//
		i = -1;
		while (++i < len) {
			classNames.push((currentSize > arr[i] ? 'gt' : currentSize < arr[i] ? 'lt' : 'is') + (_names[arr[i]] || arr[i]));
		}
		//
		docEl.className = (!docElClassNames[0] ? [] : docElClassNames).concat(classNames).join(' ');
		//
		if (_callback) _callback(currentSize);
	}
	// syze controls
	win.syze = {
		sizes: function (val) { _sizes = [].concat.apply([], arguments); onResize(); return this; },
		names: function (val) { if (val instanceof Object) { _names = val; onResize(); } return this; },
		from: function (val) { _from = val; onResize(); return this; },
		debounceRate: function (val) { _debounceRate = parseInt(val, 10) || 0; onResize(); return this; },
		callback: function (val) { if (val instanceof Function) { _callback = val; onResize(); } return this; }
	};
	// start syze
	addWinEvent('resize', debounce(onResize));
	addWinEvent('orientationchange', onResize);
	onResize();
}(this, document.documentElement));
