
	//// BEGIN HEADER ////////////////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////////////////////////////////
	////                                                                                          ////
	////    MOTIONHANDLER CLASS. V2.0                                                             ////
	////                                                                                          ////
	////    Copyright 2008, Jose Cao-Garcia                                                       ////
	////                                                                                          ////
	////    This software is licensed under the Creative Commons                                  ////
	////    Attribution-ShareAlike 2.5 License:                                                   ////
	////    <http://creativecommons.org/licenses/by-sa/2.5/legalcode>                             ////
	////                                                                                          ////
	////    HELP/INFO/DEVELOPER CONTACT: jose@jcao.com, http://jcao.com                           ////
	////                                                                                          ////
	////    You are free to use and distribute this code as long as this header remains intact.   ////
	////    If you create a derivative work from this, or modify this script significantly,       ////
	////    please note your changes clearly and obviously below this header.                     ////
	////                                                                                          ////
	//////////////////////////////////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////////////////////////////////
	////                                                                                          ////
	////                                                                                          ////
	////    This is a general purpose, css-based, unit-independent animation object creator. It   ////
	////    provides a lightweight, efficient, and smooth/fast animation tool for the concurrent  ////
	////    animation of multiple document elements and element properties with ease. Some of     ////
	////    the key features built into this creator are as follows:                              ////
	////                                                                                          ////
	////    UNIT-INDEPENDENT ANIMATION:                                                           ////
	////    Most of the javascript based animation libraries that I have seen on the web require  ////
	////    that you use an absolute unit (px) to specify element metrics. If you have built a    ////
	////    scalable or resolution-independent website with EM-based scaling, using an animation  ////
	////    script that depends upon px as a unit for moving properties will damage the           ////
	////    scalability of your site. This script allows you to use any valid CSS unit you want   ////
	////    for styling your animated elements, so you can use it on EM-scaled sites.             ////
	////                                                                                          ////
	////    JOB-QUEUE BASED ANIMATION:                                                            ////
	////    Uses a job-queue and a single timer to animate multiple elements and element          ////
	////    properties concurrently, to keep memory footprint and cpu demand as low as possible.  ////
	////    Job Queue permits sequential linking of multiple linear animations as one.            ////
	////                                                                                          ////
	////    BALANCES LOAD BETWEEN RENDERING ENGINE AND JAVASCRIPT:                                ////
	////    Uses a combination of css rendering techniques to balance the load between            ////
	////    the javascript engine and the rendering engine.                                       ////
	////        - An element with fewer animating css properties will be rendered via a method    ////
	////          that is less javascript intense, but would otherwise be more demanding of the   ////
	////          rendering engine if there were more animating properties.                       ////
	////        - An element with more animating css properties will be renderd via a a method    ////
	////          that is more javascript-intensive, but allows all css properties involved to    ////
	////          be rendered in one redraw (rather than triggering a redraw for each property).  ////
	////                                                                                          ////
	////    ADJUSTABLE FRAME-RATE AND SCOPE PER INSTANCE:                                         ////
	////    This is an object creator fucntion, and as such, you can use it to create multiple    ////
	////    instances of the object it yeilds. This allows you to fine-tune the animations on     ////
	////    your web page to further balance smoothness and cpu/rendering engine load. You can    ////
	////    create one object to manage animation of all the elements in one part of your page,   ////
	////    at 24 frames per second for low-cpu-imact animation, and another object to manage     ////
	////    animation of all the elements in another part of your page, at 80 frames per          ////
	////    second for extra-smooth animation.                                                    ////
	////                                                                                          ////
	////    SUPPORT FOR SHORTHAND CSS PROPERTIES:                                                 ////
	////    Allows you to specify animations with css shorthand, rather than having to enter      ////
	////    multiple longhand properties for complex animations.                                  ////
	////                                                                                          ////
	////    SIMPLE DOM ELEMENT-METHOD BASED IMPLEMENTATION:                                       ////
	////    Hooks into the animation methods are added to elements in the DOM, making it easy     ////
	////    to target elements for animation.                                                     ////
	////                                                                                          ////
	////    FOR MORE INFO VISIT: http://jcao.com/scripts/motionHandler/                           ////
	////                                                                                          ////
	//////////////////////////////////////////////////////////////////////////////////////////////////
	//// END HEADER //////////////////////////////////////////////////////////////////////////////////



	//// GENERAL-PURPOSE RUNTIME-BASED ANIMATION CLASS
		function motionHandler(fps, scope) {
	////////////////////////////////////////////////////////////////////////////////////
	//// overall setup properties
	////////////////////////////////////////////////////////////////////////////////////
			var root              = this;                                    // <!-- for creator-based namespacing
				root.defaults     = {
								  fps      : 30,                             // <!-- object default        - frame rate
								  duration : 0.25                            // <!-- animation job default - duration
				}
				root.biasTrip     = 2;                                       // <!-- Adjusts load distribution between javascript and rendering engine. Shouldn't be set below two. And I wouldn't go higher than four. Smaler number - better rendering engine performance. Higher number - better javascript performance.
				root.scope        = scope || document.body;                  // <!-- HTML element, parent element restraining scope of influence over the dom
				root.elements     = root.scope.getElementsByTagName('*');    // <!-- nodelist, contains all html elements in scope
				root.queue        = [];                                      // <!-- queue, holds all animation jobs that have yet to complete
				root.timer        = null;                                    // <!-- placeholder for main que processor timeout
				root.fps          = fps || root.defaults.fps;                // <!-- frames per second. specified, or default
				root.interval     = (1000/root.fps);                         // <!-- sets animation frame interval by fps. could be more subtle to compensate for browser performance/load inconsistencies.	////////////////////////////////////////////////////////////////////////////////////
				root.isPaused     = false;                                   // <!-- determines whether or not the queue is paused. False to begin with.
				root.onQueueStart = null;                                    // <!-- event can have function assigned, will fire when queue starts up again
				root.onQueueClear = null;                                    // <!-- event can have function assigned, will fire when all jobs in queue are complete
	////////////////////////////////////////////////////////////////////////////////////
	//// methods manage the queue.
	////////////////////////////////////////////////////////////////////////////////////
		// STARTS AND LOOPS THROUGH QUEUE
			root.start = function() {
				if (!root.isPaused) {
				// process each job in the queue
					for (var i in root.queue) {
						var jobNode = root.queue[i];
					// boolean determines if job can be processed
						var jobNodeReady = (
							jobNode.element &&
							typeof(jobNode.element.nodeName) != 'undefined' &&
							!jobNode.isComplete()
						);
					// if job is processable, process. otherwise delete
						if (jobNodeReady) {
							if (!jobNode.isPaused) {
								jobNode.process();
								jobNode.render();
							}
						} else {
						// remove all references to the job
							if (i == 0) {
								root.queue.shift();
							} else {
								root.queue.splice(i, i);
							}
						}
					}
				// restart the queue if jobs remain, stop if queue is clear
					if (root.queue.length > 0) {
						root.timer = setTimeout(root.start, root.interval);
					} else {
						root.timer = clearTimeout(root.timer);
						root.timer = null;
						if (root.onQueueClear && typeof(root.onQueueClear) == 'function') { root.onQueueClear(); }
					}
				}
			}
		// PAUSES THE ENTIRE QUEUE
			root.pauseQueue = function() {
				root.isPaused = true;
			}
		// PLAYS ENTIRE QUEUE
			root.playQueue  = function() {
				root.isPaused = false;
				root.start();
			}
	////////////////////////////////////////////////////////////////////////////////////
	//// methods manage insertion/deletion of methods to elements in the dom scope
	////////////////////////////////////////////////////////////////////////////////////
		// METHOD DETERMINES IF ELEMENT IS WITHIN SCOPE. RETURNS OBJ ELEMENT OR FALSE.
			root.inScope          = function(obj) {
				var childObj = obj;
				if (root.scope != document.body) {
				// crawl parents for scope element if scope is reduced
					while (obj != document.body) {
						if (obj == root.scope) { return childObj; }
						obj = obj.parentNode;
					}
				// finally, return false if not found.
					return false;
				} else {
				// no test if scope is entire body
					return childObj
				}
			}
		// ADDS MOTIONHANDLER METHODS TO AN ELEMENT AND ITS CHILDREN
			root.setMethods       = function(childScope) {
			// do not permit handling of beyond-scope elements
				var scope  = childScope || root.scope;
				if (!root.inScope(scope)) { throw('motionHandler.setMethods - cannot influence elements beyond scope of influence set for object.'); }
				var elements = scope.getElementsByTagName('*');
			// loop through images
				for (var i = 0; i < elements.length; i++) {
					var thisObj = elements[i];
					if(thisObj.id != 'book.swf') { // Added by Grayloon to fix book.swf javascript errors
						if(!thisObj.getAttribute('ignore')) {
							thisObj.move      = root.move;
							thisObj.stop      = root.stop;
							thisObj.reverse   = root.reverse;
							thisObj.end       = root.end;
							thisObj.pause     = root.pause;
							thisObj.play      = root.play;
							thisObj.playPause = root.playPause;
							thisObj.restart   = root.restart;
							thisObj.sequence  = root.sequence;
						}
					}
					
				}
			}
		// CLEARS MOTIONHANDLER METHODS FROM AN ELEMENT AND ITS CHILDREN
			root.cutMethods       = function(childScope) {
			// do not permit handling of beyond-scope elements
				var scope  = childScope || root.scope;
				if (!root.inScope(scope)) { throw('motionHandler.cutMethods - cannot influence elements beyond scope of influence set for object.'); }
				var elements = scope.getElementsByTagName('*');
				for (var i = 0; i < elements.length; i++) {
					var thisObj = elements[i];
					if(thisObj.id != 'book.swf') { // Added by Grayloon to fix book.swf javascript errors
					thisObj.setAttribute('ignore', 'true');
						thisObj.move      = null;
						thisObj.stop      = null;
						thisObj.reverse   = null;
						thisObj.end       = null;
						thisObj.pause     = null;
						thisObj.play      = null;
						thisObj.playPause = null;
						thisObj.restart   = null;
						thisObj.sequence  = null;
					}
				}
			}
	////////////////////////////////////////////////////////////////////////////////////
	//// these methods act upon the queue jobs, performing tween math and rendering
	////////////////////////////////////////////////////////////////////////////////////
		// PROCESSES MATHEMATICS FOR A SINGLE JOB FROM THE ANIMATION QUEUE
			root.jobProc          = function() {
			// process all properties managed by job
				for (var i in this.tweening) {
					var jobProp  = this.tweening[i];
					var propName = i;
				// dont waste processor cycles on inert/complete properties
					if (jobProp.current == jobProp.target) { continue; }
				// do the math - this is probably excessively complicated ... we should redo it when we have time.
					var difference  = parseFloat(Math.abs(Math.min(jobProp.target, jobProp.origin) - Math.max(jobProp.target, jobProp.origin)));
					var stepLength  = parseFloat(difference / Math.round(this.duration*root.fps));
					var newValue    = parseFloat((Math.max(jobProp.target, jobProp.origin) == jobProp.target) ? parseFloat((jobProp.current + stepLength).toPrecision(9)) : parseFloat((jobProp.current - stepLength).toPrecision(9)));
					var tripTotal   = parseFloat(Math.abs(Math.min(newValue, jobProp.origin) - Math.max(newValue, jobProp.origin)));
					var tripLeft    = parseFloat(difference - tripTotal);
					var finalValue  = (tripTotal >= difference || tripLeft < stepLength) ? jobProp.target : newValue; //<-- adjusts for possibility that remaining steps are smaller than increment, jump to final step.
					if (jobProp.unit == 'px') { finalValue = Math.round(finalValue) };
				// set to target, and stop property if we have reached the end of this animation
					jobProp.current = finalValue;
				}
			// if there is an onMove event defined, run it
				if (this.element.onMove) {
					this.element.onMove();
				}
			// if there is a completion function assigned, and motion has run its course, call it.
				if (this.isComplete()) {
					if (this.sequence)   {
						var thisElement  = this.element;
						var thisSequence = this.sequence
						var delay = function() { thisElement.sequence(thisSequence); }
						setTimeout(delay, root.interval);
					}
					if (this.onComplete) { setTimeout(this.onComplete, root.interval); }
				}
			}
		// METHOD RENDERS JOB STATE INTO ELEMENT CSS - FAVORS JAVASCRIPT PERFORMANCE
			root.renderFastScript = function() {
				for (var i in this.tweening) {
					if(i == 'opacity' && client.engine == 'msie') {
						this.element.style.filter = 'alpha(opacity=' + Math.round(this.tweening[i].current * 100) + ')'; // support microsofts jacked up transparency "filter"
					} else {
						var currentValue = (this.tweening[i].units == 'px') ? Math.round(this.tweening[i].current) : this.tweening[i].current;
						this.element.style[i] = currentValue + this.tweening[i].units;
					}
				}
			}
		// METHOD RENDERS JOB STATE INTO ELEMENT CSS - FAVORS RENDERING ENGINE PERFORMANCE
			root.renderFastRedraw = function() {
			// basic setup
				var styleStr = '';
			// if there are inline styles, strip tweened properties
			 	var statStyles = this.element.getAttribute('style') || '';
				if (statStyles && statStyles != '') {
					statStyles = statStyles.split(/;\s*/);
					for (var i in statStyles) {
						if (typeof(this.tweening[statStyles[i].split(/:\s*/)[0]]) != 'undefined') {
							statStyles[i] = '';
						}
					}
					statStyles  = statStyles.join('; ');
				}
			// add tweened properties to inline style string
				for (var i in this.tweening) {
				// convert property syntax to css-style
					var prefix = i.match(/^[a-z]+(?=[A-Z])/);     // left-side
					var suffix = i.match(/[A-Z][a-z]+/);          // right-side
					var styleName = (prefix && suffix) ? prefix[0] + '-' + suffix[0].toLowerCase() : i;
				// stitch string
					var currentValue = (this.tweening[i].units == 'px') ? Math.round(this.tweening[i].current) : this.tweening[i].current;
					statStyles += styleName + ': ' + currentValue + this.tweening[i].units + ';';
				}
			// apply in attribute
				this.element.setAttribute('style', statStyles);
			}
		// METHOD RETURNS BOOLEAN FOR IS JOB COMPLETE
			root.isComplete       = function() {
				for (var i in this.tweening) {
					if (this.tweening[i].current != this.tweening[i].target) { return false; }
				}
			// return response
				return true;
			}
	////////////////////////////////////////////////////////////////////////////////////
	//// these methods alter the motion characteristics/behaviour of an element
	////////////////////////////////////////////////////////////////////////////////////
		// SETS AN ELEMENT IN MOTION
			root.move             = function(motionProps, duration, onComplete) {
				// preprocessor motionProps to convert shorthand properties to longhand
					motionProps = root.preProcess(motionProps);
				// some general validation
					var propCount = 0;
					for (var i in motionProps) {
						propCount++;
						if (typeof(motionProps[i].to) == 'undefined')   { throw(this+'.move() - you must specify a "to" property for each css property'); }
						if (root.getUnits(motionProps[i].to) != root.getUnits(motionProps[i].from)) { this+'.move() - the units in your "to" and "from" properties for a given css property must match' }
					}
					if (!motionProps)                                   { throw(this+'.move() - you must specify a motionProps object');                 }
					if (propCount == 0)                                 { throw(this+'.move() - you must specify at least one css property to animate'); }
					if (onComplete && typeof(onComplete) != 'function') { throw(this+'.move() - onComplete must be a function if specified');            }
				// boolean determines perfornamce bias towards rendering engine or javascript. (standards-based browsers only)
					var renderBias = (propCount >= root.biasTrip && client.engine != 'msie');
				// if element is allready represented in queue, remove it
					this.stop();
				// make new element for queue
					var newMotionJob = {
						element    : this,
						isPaused     : false,
						duration   : duration || root.defaults.duration,
						tweening   : { },
						render     : (renderBias) ? root.renderFastRedraw : root.renderFastScript,
						isComplete : root.isComplete,
						process    : root.jobProc,
						onComplete : onComplete || null
					}
				// add an entry for each moving style property
					for (var i in motionProps) {
						newMotionJob.tweening[i] = {
							units  : root.getUnits(motionProps[i].to),
							origin : root.getValue(motionProps[i].from) || root.getValue(this.style[i])       || 0,
							current: root.getValue(this.style[i])       || root.getValue(motionProps[i].from) || 0,
							target : root.getValue(motionProps[i].to)
						}
					}
				// add it to queue and start if stopped
					root.queue.push(newMotionJob);
					if (root.queue.length == 1) {
						if (root.onQueueStart && typeof(root.onQueueStart) == 'function') { root.onQueueStart(); }
						root.start();
					}
			}
		// REVERSES AN ELEMENTS MOTION
			root.reverse          = function() {
				for (var i in root.queue) {
					var thisMotion = root.queue[i];
					if (thisMotion.element === this) {
						for (var ii in thisMotion.tweening) {
						// reverse sequence order if it exists
							if (typeof(thisMotion.sequence) != 'undefined') {
								thisMotion.sequence.reverse();
							}
						// store old values temporarily
							var oldTarget                = thisMotion.tweening[ii].target;
							var oldOrigin                = thisMotion.tweening[ii].origin;
						// invert properties with old values
							thisMotion.tweening[ii].target   = oldOrigin;
							thisMotion.tweening[ii].origin   = oldTarget;
						}
					}
				}
			}
		// stops an elements motion job by removing it from the queue
			root.stop             = function(executeOnComplete) {
				for (var i in root.queue) {
					var thisMotion = root.queue[i];
				// isolate the current node
					if (thisMotion.element === this) {
					// remove the job from the queue
						if (i == 0) {
							root.queue.shift();
						} else {
							root.queue.splice(i, i);
						}
					}
				}
			}
		// PAUSES AN ELEMENTS MOTION
			root.pause            = function() {
				for (var i in root.queue) {
					var thisMotion = root.queue[i];
					if (thisMotion.element === this) {
						thisMotion.isPaused = true;
					}
				}
			}
		// CONTINUES A PAUSED ELEMENTS MOTION
			root.play             = function() {
				for (var i in root.queue) {
					var thisMotion = root.queue[i];
					if (thisMotion.element === this) {
						thisMotion.isPaused = false;
					}
				}
			}
		// TOGGLES AN ELEMENTS PAUSE/PLAY STATE
			root.playPause        = function() {
				for (var i in root.queue) {
					var thisMotion = root.queue[i];
					if (thisMotion.element === this) {
						thisMotion.isPaused = (thisMotion.isPaused) ? false : true;
					}
				}
			}
		// SENDS AN ELEMENT TO ITS DESTINATION POSITION, IMMEDIATELY
			root.end              = function() {
				for (var i in root.queue) {
					var thisMotion = root.queue[i];
					if (thisMotion.element === this) {
						for (var ii in thisMotion.tweening) {
						// set current to target
							thisMotion.tweening[ii].current   = thisMotion.tweening[ii].target;
						// process and render
							thisMotion.process();
							thisMotion.render();
						}
					}
				}
			}
		// SETS A SEQUENCE OF ANIMATIONS, TO RUN ONE AFTER ANOTHER
			root.sequence         = function(sequence, onComplete) {
			// extract arguments for current job
				var thisJob = sequence.shift();
				var motionProps = thisJob[0];
				var duration    = thisJob[1] || root.defaults.duration;
				var onComplete  = thisJob[2] || null;
			// set motion of current job
				this.move(motionProps, duration, onComplete);
			// save sequence of upcoming jobs to object
				if (sequence.length > 0) {
					for (var i in root.queue) {
						var thisMotion = root.queue[i];
						if (thisMotion.element === this) {
							thisMotion.sequence = sequence;
						}
					}
				}
			}
	////////////////////////////////////////////////////////////////////////////////////
	//// these are utility functions to help parse CSS values.
	////////////////////////////////////////////////////////////////////////////////////
		// RETURNS VALUE ONLY FOR A CSS VALUE
			root.getValue         = function(str) { 
				try {
					return parseFloat(str.toString().match(/^[^a-zA-z]+/).toString());
				} catch (e) { return null; }
			}
		// RETURNS UNITS ONLY FOR A CSS VALUE
			root.getUnits         = function(str) { 
				try {
					return str.toString().match(/[a-zA-z]+$/).toString();
				} catch (e) { return ''; }
			}
		// PRE-PROCESSES CSS VALUES, CONVERTING SHORTHAND TO LONGHAND.
			root.preProcess       = function(motionProps) {
			// convert shorthand properties to constituent longhand properties
				for (var i in motionProps) {
				// skip non-shorthand properties
					if (typeof(motionProps[i].to) != 'string' || motionProps[i].to.indexOf(' ') == -1) { continue; }
				// if it is a shorthand property, convert it
					var propName         = i;
					var propValuesOrigin = motionProps[i].from.split(' ');
					var propValuesTarget = motionProps[i].to.split(' ');
					if (propValuesOrigin.length != propValuesTarget.length) { throw(this+'.move() - when using shorthand properties, the number of values in your from and to properties must match.'); }
				// convert to longhand properties // top right bottom left -- top bottom, left right
					switch (propValuesOrigin.length) {
						case 2:
							motionProps[propName+'Top']   = { from:propValuesOrigin[1], to:propValuesTarget[1] };
							motionProps[propName+'Right'] = { from:propValuesOrigin[0], to:propValuesTarget[0] };
							motionProps[propName+'Bottom']= { from:propValuesOrigin[1], to:propValuesTarget[1] };
							motionProps[propName+'Left']  = { from:propValuesOrigin[0], to:propValuesTarget[0] };
						break;
						case 4:
							motionProps[propName+'Top']   = { from:propValuesOrigin[0], to:propValuesTarget[0] };
							motionProps[propName+'Right'] = { from:propValuesOrigin[1], to:propValuesTarget[1] };
							motionProps[propName+'Bottom']= { from:propValuesOrigin[2], to:propValuesTarget[2] };
							motionProps[propName+'Left']  = { from:propValuesOrigin[3], to:propValuesTarget[3] };
						break;
					}
				// delete old shorthand property
					delete motionProps[i];
				}
				return motionProps;
			}
		}




