(function(){window['HYPE_344'] = (function HYPE() {

	///////////////////////////////////////////////////////////////
	// constants
	
	var _hype = this;
	
	_hype['name'] = "hype main object";
	_hype.version = 344;

	_hype.kActionNone = 0;
	_hype.kActionJumpToScene = 1;
	_hype.kActionPresentMedia = 2;
	_hype.kActionStartTimeline = 3;
	_hype.kActionRunJavascript = 4;
	_hype.kActionVisitURL = 5;
	_hype.kActionComposeEmail = 6;
	_hype.kActionPauseTimeline = 7;
	_hype.kActionContinueTimeline = 8;
	_hype.kActionGoToTimeInTimeline = 9;
	_hype.kActionControlTimeline = 10;
	_hype.kActionControlElementPosition = 11;
	_hype.kActionPlaySound = 12;
	_hype.kActionPauseSound = 13;
	_hype.kJumpToSceneOther = 0;
	_hype.kJumpToSceneNext = 1;
	_hype.kJumpToScenePrevious = 2;
	_hype.kJumpToSceneFirst = 3;
	_hype.kJumpToSceneLast = 4;
	_hype.kSceneTransitionNone = 0;
	_hype.kSceneTransitionInstant = 1;
	_hype.kSceneTransitionCrossfade = 2;
	_hype.kSceneTransitionSwap = 3;
	_hype.kSceneTransitionPushLeftToRight = 4;
	_hype.kSceneTransitionPushRightToLeft = 5;
	_hype.kSceneTransitionPushBottomToTop = 6;
	_hype.kSceneTransitionPushTopToBottom = 7;
	_hype.kAnimationTypeStandardKeyframe = 0;
	_hype.kAnimationTypeVideo = 1;
	_hype.kAnimationTypeTimelineAction = 2;
	_hype.kResourcePreloadTypeNone = 0;
	_hype.kResourcePreloadTypeImage = 1;
	_hype.kResourcePreloadTypeAudio = 2;
	_hype.kTimelineDefaultIdentifier = "kTimelineDefaultIdentifier";
	_hype.kDirectionForward = 0;
	_hype.kDirectionReverse = 1;
	_hype.NSViewNotSizable = 0;
	_hype.NSViewMinXMargin = 1;
	_hype.NSViewWidthSizable = 2;
	_hype.NSViewMaxXMargin = 4;
	_hype.NSViewMinYMargin = 8;
	_hype.NSViewHeightSizable = 16;
	_hype.NSViewMaxYMargin = 32;
	_hype.kHypeViewScaleShrink = 64;
	_hype.kHypeViewScaleExpand = 128;

	///////////////////////////////////////////////////////////////
	// module-wide variables

	_hype.timelineRuns = Array();
	_hype.activeTimelineRuns = Array();
	_hype.timelineRunOwnershipOfPropertiesByElement = {};
	_hype.dragOwnershipOfPropertiesByElement = {};
	_hype.animationFrameRequested = false;	
	_hype.fps = 60;
	_hype.nextKeyHandlers = Array();
	_hype.resourceGroups = {};
	_hype.resourceIDsToPreload = Array();
	_hype.isPreloadNextResourceQueued = false;
	_hype.currentValues = Array();
	_hype.mouseOverElementFunctions = Array();
	_hype.currentSceneActionHandlers = {};
//	_hype.timelineCompletionOverrideCallback = null;
//	_hype.timelineIdentifierForCompletionOverrideCallback = null;
	_hype.idMapping = {}; // hype element oids to DOM ids
	_hype.idReverseMapping = {}; // DOM ids to hype element oids
	_hype.idActualMapping = {}; // User proposed DOM ids to the actual DOM ids we set
	_hype.imageResourceIDToDataURLMapping = {};
	_hype.documentUID = 0;
	_hype.inSceneTransition = false;
	_hype.eventHandlers = Array();
	_hype.motionPaths = {};
	_hype.hasShownInitialScene = false;

	_hype.currentTimeForVideoExport = 0;

	_hype.originalSceneSize = {};
	_hype.usesFlexibleLayout = false;
	_hype.shouldQueueFrameUpdates = false;
	_hype.elementsNeedingFrameUpdates = {}; // element id to element
	_hype.browserReportedSizeCache = {};

	///////////////////////////////////////////////////////////////
	// substitutions for file size optimization

	_hype.kSizeOptimizationHexAlphabet = "0123456789ABCDEF";
	_hype.kSizeOptimizationWebKitPrefix = "-webkit-";


	///////////////////////////////////////////////////////////////
	// gesture handling
	_hype.kPhaseStart = "start";
	_hype.kPhaseMove = "move";
	_hype.kPhaseEnd = "end";
	_hype.kPhaseCancel = "cancel";
	// there is a dependency on the left, right, up, down constants remaining the same when creating the Swipe Type for the faux event
	_hype.kLeft = "left";
	_hype.kRight = "right";
	_hype.kUp = "up";
	_hype.kDown = "down";
	_hype.kGestureDrag = "drag";
	_hype.kGestureSwipe = "swipe";
	_hype.kGestureTap = "tap";
	_hype.kCancelEventName = 'touchcancel';
	_hype.currentlyHandlingDragGesture = false;
	_hype.receivedGestureMove = 0;
	_hype.firedMouseClickActionAfterGesture = false;
	_hype.touchEventCoordinates = Array();

	///////////////////////////////////////////////////////////////
	// set from the DocumentLoader

	_hype['z_n'] = function (documentName) {
		_hype.documentName = documentName;
	}
	_hype['z_b'] = function (resources) {
		_hype.resources = resources;
	}
	_hype['z_c'] = function (headAdditions) {
		_hype.headAdditions = headAdditions;
	}
	_hype['z_h'] = function (resourcesFolderName) {
		_hype.resourcesFolderName = resourcesFolderName;
	}
	_hype['z_g'] = function (mainContentContainerID) {
		_hype.mainContentContainerID = mainContentContainerID;
	}		
	_hype['z_a'] = function (attributeTransformerMapping) {
		_hype.attributeTransformerMapping = attributeTransformerMapping;
	}
	_hype['z_d'] = function (scenes) {
		_hype.scenes = scenes;
	}
	_hype['z_q'] = function (originalSceneWidth, originalSceneHeight) {
		_hype.originalSceneSize = { "width" : originalSceneWidth, "height" : originalSceneHeight };
	}
	_hype['z_e'] = function (javascriptMapping) {
		_hype.javascriptMapping = javascriptMapping;
	}
	_hype['z_i'] = function (showHypeBuiltWatermark) {
		_hype.showHypeBuiltWatermark = showHypeBuiltWatermark;
	}							
	_hype['z_j'] = function (showLoadingPage) {
		_hype.showLoadingPage = showLoadingPage;
	}							
	_hype['z_k'] = function (drawSceneBackgrounds) {
		_hype.drawSceneBackgrounds = drawSceneBackgrounds;
	}							
	_hype['z_i'] = function (showHypeBuiltWatermark) {
		_hype.showHypeBuiltWatermark = showHypeBuiltWatermark;
	}
	_hype['z_f'] = function (currentSceneIndex) {
		_hype.currentSceneIndex = currentSceneIndex;
	}
	_hype['z_l'] = function (graphicsAcceleration) {
		// do not use graphics acceleration on chrome 37:
		// https://code.google.com/p/chromium/issues/detail?id=412414
		if(parseInt(_hype.browserInfo.chrome) == 37) {
			_hype.graphicsAcceleration = false;
		} else {
			_hype.graphicsAcceleration = graphicsAcceleration;
		}
	}
	_hype['z_m'] = function (useTouchEvents) {
		_hype.useTouchEvents = useTouchEvents;
	}
	
	///////////////////////////////////////////////////////////////
	// methods

	_hype.log = function (value) {
		if (window.console && console.log) {
			console.log(value);
		}
	}
	
	_hype['z_o'] = function (e) {
		window.mycounter = 0;
		if (_hype.documentUID == 0) {
			// Generate a random unique ID for this document.
			_hype.documentUID = _hype.randomUID();
		}
	
		if(_hype.showHypeBuiltWatermark == true) {
			_hype.addHypeBuiltWatermark();
		}
		
		if(_hype.showLoadingPage == true) {
			_hype.overlayLoadingPage();
		}
		
		var contentContainer = document.getElementById(_hype.mainContentContainerID);
		if(contentContainer.style.width.indexOf("px") == -1 || contentContainer.style.height.indexOf("px") == -1) {
			_hype.usesFlexibleLayout = true;
		}

		_hype.cleanupInternalDataRepresentation();
		_hype.addGeneratedCSS();
		_hype.addHeadAdditions();
		_hype.createSceneDivs();
		_hype.createMotionPaths();

		_hype.createResourceGroups();
		_hype.preloadResources();
		
		if(window.addEventListener) {
			window.addEventListener("resize", _hype.relayoutIfNecessary);
			window.addEventListener("pageshow", (function () { window.setTimeout(_hype.relayoutIfNecessary, 1); })); // workaround for Safari not getting new window size when hitting back button #5918
		} else if(window.attachEvent) {
			window.attachEvent("onresize", _hype.relayoutIfNecessary);
		}
		if (_hype.useTouchEvents) {
			contentContainer.style["-webkit-tap-highlight-color"] = "rgba(0,0,0,0)";
			if (contentContainer.addEventListener) {
				contentContainer.addEventListener("click", _hype.contentContainerClickHandler, true);
				contentContainer.addEventListener("mouseup", _hype.contentContainerClickHandler, true);
				contentContainer.addEventListener("mousedown", _hype.contentContainerClickHandler, true);
				contentContainer.addEventListener("mouseover", _hype.contentContainerClickHandler, true);
			}
		}
	}
	
	_hype.randomUID = function(length) {
		var result = "";
		var chars = _hype.kSizeOptimizationHexAlphabet + "GHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; /* we know _hype.kSizeOptimizationHexAlphabet is "0123456789ABCDEF" */
		if (length == undefined) {
			length = 10;
		}
		for (var i = 0; i < length; i++) {
			var r = Math.floor(Math.random() * chars.length);
			result += chars.substring(r, r+1);
		}
		return result;
	}

	_hype.addHypeBuiltWatermark = function () {
		var contentContainer = document.getElementById(_hype.mainContentContainerID);
		var watermarkDiv = document.createElement("div");
		var watermarkDivStyle = watermarkDiv.style;
		watermarkDivStyle.position = "absolute";
		watermarkDivStyle.bottom = "1px";
		watermarkDivStyle.right = "1px";
		watermarkDivStyle.zIndex = "100000";
		watermarkDivStyle.textAlign = "right";
		watermarkDivStyle.fontSize = "9px";
		watermarkDivStyle.fontFamily = "Helvetica,Arial,Sans-Serif";
		watermarkDiv.innerHTML = "<b><a style='color:#777;text-decoration:none;opacity:.75;'href='http://tumult.com/hype/?utm_source=export&utm_medium=web&utm_campaign=BuiltWithHype'target='_blank'>Built with Hype</a></b>";
		contentContainer.appendChild(watermarkDiv);
	}
	
	_hype.overlayLoadingPage = function () {
		var loadingPageID = _hype.mainContentContainerID + "_loading";
		var loadingDiv = document.getElementById(loadingPageID);
		if(loadingDiv == null) {	
			var contentContainer = document.getElementById(_hype.mainContentContainerID);
			loadingDiv = document.createElement("div");
			loadingDiv.id = loadingPageID;
			loadingDiv.style.cssText = "overflow:hidden;position:absolute;width:150px;top:40%;left:0;right:0;margin:auto;padding:2px;border:3px solid #BBB;background-color:#EEE;border-radius:10px;-moz-border-radius:10px;-webkit-border-radius:10px;text-align:center;font-family:'Lucida Grande',Helvetica,Sans-Serif;font-size:13px;font-weight:bold;color:#AAA;z-index:100000;";
			loadingDiv.innerHTML = "Loading";
			contentContainer.appendChild(loadingDiv);
		}
		
		loadingDiv.style.display = "block";
	}
	
	_hype.hideLoadingPage = function () {
		var loadingDiv = document.getElementById(_hype.mainContentContainerID + "_loading");
		loadingDiv.style.display = "none";
	}
	
	_hype.createIdMapping = function (hypeOid, proposedDomId, suggestedPrefix) {
		var domId = proposedDomId;
	
		if(domId == null || document.getElementById(domId) != null) {
			do {
				domId = suggestedPrefix + _hype.randomUID(16);
			} while(document.getElementById(domId) != null);
		}

		_hype.idMapping[hypeOid] = domId;
		_hype.idReverseMapping[domId] = hypeOid;
		_hype.idActualMapping[(proposedDomId != null) ? proposedDomId : domId] = domId;
		
		return domId;
	}
	
	_hype.getElementById = function (id) { // public, do not change signature without wrapping in _hype['API']!
		var domId = _hype.idActualMapping[id];
		if(domId == null) {
			domId = id;
		}
		return document.getElementById(domId);
	}
	
	_hype.beginFrameUpdateQueue = function () {
		_hype.shouldQueueFrameUpdates = true;
	}
	
	_hype.drainFrameUpdateQueue = function () {
		_hype.shouldQueueFrameUpdates = false;
		for(var elementID in _hype.elementsNeedingFrameUpdates) {
			if(_hype.elementsNeedingFrameUpdates.hasOwnProperty(elementID)) {
				var element = _hype.elementsNeedingFrameUpdates[elementID];
				_hype.Apply.UpdateFrame(element);
			}
		}
		_hype.elementsNeedingFrameUpdates = {};
	}
	
	// null as elementOid means the main content container
	_hype.currentSizeForElementOid = function (elementOid) {
		if(elementOid == null) {
			return _hype.originalSceneSize;
		}
		
		var width = _hype.currentWidthForElementWithOid(elementOid);
		var height = _hype.currentHeightForElementWithOid(elementOid);
		return { "width" : width, "height" : height };
	}
	
	// null as elementOid means the main content container
	_hype.browserReportedSizeForElementOid = function (elementOid) {
		var elementID = _hype.mainContentContainerID; // default value
		if(elementOid != null) {
			elementID = _hype.idMapping[elementOid];
		}
		
		var size = _hype.browserReportedSizeCache[elementID];
		if(size == null) {
			var element = document.getElementById(elementID);
			size = { "width" : element.offsetWidth, "height" : element.offsetHeight };
			_hype.browserReportedSizeCache[elementID] = size;
		}
		return size;
	}
	
	_hype.invalidateBrowserReportedSizeCacheForElement = function (element) {
		_hype.browserReportedSizeCache[element.id] = null;
	}
	
	_hype.relayoutIfNecessary = function () { // public, do not change signature without wrapping in _hype['API']!
		if(_hype.usesFlexibleLayout == false) {
			return;
		}
		_hype.relayoutIfNecessaryUsingParentOid(null);
	}
	
	_hype.relayoutIfNecessaryUsingParentOid = function (parentOid) {
		if(_hype.usesFlexibleLayout == false) {
			return;
		}
		
		_hype.browserReportedSizeCache = {};

		var children = _hype.childrenOidsForElementOid(parentOid);
		for(var i = 0; i < children.length; i++) {
			var childOid = children[i];
			var elementElement = document.getElementById(_hype.idMapping[childOid]);
			_hype.Apply.UpdateFrame(elementElement);
			_hype.relayoutIfNecessaryUsingParentOid(childOid);
		}
	}
	
	_hype.parentOidForElementOid = function (childOid) {
		var element = _hype.scenes[_hype.currentSceneIndex]["v"][childOid];
		if(element == null) {
			return null;
		}
		return element["bF"];
	}
	
	_hype.childrenOidsForElementOid = function (parentOid) {
		var children = [];
		var elements = _hype.scenes[_hype.currentSceneIndex]["v"];
		for(var key in elements) {
			if(elements.hasOwnProperty(key) == false) {
				continue;
			}
			
			var element = elements[key];
			if(element["bF"] == parentOid) {
				children.push(key);
			}
		}
		return children;
	}
	
	_hype.createSceneDivs = function () {
		var contentContainer = document.getElementById(_hype.mainContentContainerID);
	
		for(var i = 0; i < _hype.scenes.length; i++) {
			var scene = _hype.scenes[i];
			var sceneDiv = document.createElement("div");
			var sceneDivStyle = sceneDiv.style;
			sceneDiv.className = "HYPE_scene";
			sceneDiv.id = _hype.createIdMapping(scene["o"], null, "hype-scene-");
			sceneDiv.setAttribute("HYPE_scene_index", scene["x"]);
			if(_hype.drawSceneBackgrounds != false) {
				sceneDivStyle.backgroundColor = scene["c"];
			}
			sceneDivStyle.display = "none";
			sceneDivStyle.overflow = "hidden";
			sceneDivStyle.position = "absolute";
			sceneDivStyle.width = contentContainer.style.width;
			sceneDivStyle.height = contentContainer.style.height;
			if(_hype.browserInfo.ie >= 10) { // ie 3d support, doesn't use containers since there is no pointer-events in IE (see #3554)
				sceneDivStyle["perspective"] = scene["p"];
			}
			contentContainer.appendChild(sceneDiv);
			
			var elements = scene["v"];
			for(var key in elements) {
				if(elements.hasOwnProperty(key) == false) {
					continue;
				}
				
				var element = elements[key];
				var elementElement = document.createElement(element['k']);
				elementElement.className = "HYPE_element";
				elementElement.id = _hype.createIdMapping(key, element['i'], "hype-obj-");
								
				if(element['w'] != null) {
					elementElement.innerHTML = element['w'];
				}

				if(_hype.browserInfo.webkit != null || _hype.browserInfo.ff >= 10) { // webkit 3d support
					var elementContainerDiv = document.createElement("div");
					var elementContainerDivStyle = elementContainerDiv.style;
					elementContainerDiv.className = "HYPE_element_container";
					elementContainerDivStyle.position = "absolute";
					elementContainerDivStyle.top = "0";
					elementContainerDivStyle.left = "0";
					elementContainerDivStyle.width = "100%";
					elementContainerDivStyle.height = "100%";
					
					if(element['bR'] != null || element['aY'] != null) { // x or y
						// partial workaround for <rdar://problem/9973514> Hardware compositing breaks full screen video in Safari 5.1
						elementContainerDivStyle[_hype.kSizeOptimizationWebKitPrefix + "perspective"] = scene["p"];
						elementContainerDivStyle["MozPerspective"] = scene["p"];
						elementContainerDivStyle["perspective"] = scene["p"];
					} else if(_hype.browserInfo.webkit != null && (element['f'] != null || element['bL'] != null || element['bP'] != null)) { // z
						// workaround for <rdar://problem/10346853> REGRESSION: webkit-transform:rotate causes other css properties to not change
						// also workaround for #3642 Element animation issues with WebKit browsers when filter effects are applied
						elementElement.setAttribute("HYP_c", "1");
					}
					
					elementContainerDivStyle["pointerEvents"] = "none";
					elementContainerDivStyle["pointer-events"] = "none";
					elementElement.style["pointerEvents"] = "auto";
					elementElement.style["pointer-events"] = "auto";
					
					elementContainerDiv.appendChild(elementElement);
					sceneDiv.appendChild(elementContainerDiv);
				} else {
					sceneDiv.appendChild(elementElement);
				}
			}
		}
	}

	_hype.distanceBetweenPoints = function(p1, p2) {
		return Math.sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y));
	}

	_hype.linearInterpolationBetweenPoints = function(p1, p2, time) {
		return {
			x: (p1.x + (p2.x - p1.x) * time),
			y: (p1.y + (p2.y - p1.y) * time),
			rotationAngle: 0
		};
	}

	_hype.pointAtPercentForBezierCurve = function(curve, percent, rotationAngle) {
		var linearInterpolationBetweenPointsFunction = _hype.linearInterpolationBetweenPoints;
		var ab = linearInterpolationBetweenPointsFunction(curve.startPoint, curve.startControlPoint, percent);
		var bc = linearInterpolationBetweenPointsFunction(curve.startControlPoint, curve.endControlPoint, percent);
		var cd = linearInterpolationBetweenPointsFunction(curve.endControlPoint, curve.endPoint, percent);
		var abbc = linearInterpolationBetweenPointsFunction(ab, bc, percent);
		var bccd = linearInterpolationBetweenPointsFunction(bc, cd, percent);
		var result = linearInterpolationBetweenPointsFunction(abbc, bccd, percent);
		var flip = false;
		var comparisonPoint = bccd;
		if (bccd.x == result.x && bccd.y == result.y) {
			if (ab.x == result.x && ab.y == result.y) {
				comparisonPoint = cd;
			} else {
				comparisonPoint = ab;
				flip = true;
			}
		}

		if (comparisonPoint.x == result.x) {
			result.rotationAngle = (comparisonPoint.y >= result.y) ? 90 : -90;
		} else {
			result.rotationAngle = Math.atan((comparisonPoint.y - result.y) / (comparisonPoint.x - result.x)) * 180 / Math.PI;
		}
		if (comparisonPoint.x < result.x) {
			result.rotationAngle += 180;
		} 
		if (flip == true) {
			result.rotationAngle += 180;
		}

		return result;
	}

	_hype.calculateInterpolationPointsForMotionPath = function(motionPath) {
		var totalLength = 0;
		var bezierCurves = motionPath.bezierCurves;
		for (var index = 0; index < bezierCurves.length; index++) {
			var bezierCurve = bezierCurves[index];
			var totalCurveLength = 0;
			var previousPoint = bezierCurve.startPoint;
			for (var i = 0; i < 100; i++) {
				var t = i / (100 - 1.0);
				var location = _hype.pointAtPercentForBezierCurve(bezierCurve, t);
				var length = _hype.distanceBetweenPoints(location, previousPoint);
				var bezierPoint = { location: location, length: length };
				bezierCurve.interpolationPoints.push(bezierPoint);
				totalCurveLength += length;
				previousPoint = location;
			}
			totalLength += totalCurveLength;
			bezierCurve.length = totalCurveLength;
		}
		motionPath["length"] = totalLength;
	}

	_hype.createMotionPaths = function() {
		for (var sceneIndex = 0; sceneIndex < _hype.scenes.length; sceneIndex++) {
			var scene = _hype.scenes[sceneIndex];
			for (var key in scene["T"]) {
				var timeline = scene["T"][key];
				for (var motionPathOid in timeline["j"]) {
					var pathData = timeline["j"][motionPathOid];
					var motionPath = {};
					var bezierCurves = Array();
					for (var i = 0; i < pathData.length; i++) {
						var bezierCurveData = pathData[i];
						bezierCurves.push({
							startPoint: {x: bezierCurveData["b"], y: bezierCurveData["c"]},
							startControlPoint: {x: bezierCurveData["d"], y: bezierCurveData["e"]},
							endControlPoint: {x: bezierCurveData["f"], y: bezierCurveData["g"]},
							endPoint: {x: bezierCurveData["h"], y: bezierCurveData["i"]},
							length: 0,
							interpolationPoints: Array()
						});
					}
					motionPath.bezierCurves = bezierCurves;
					_hype.calculateInterpolationPointsForMotionPath(motionPath);
					_hype.motionPaths[motionPathOid] = motionPath;
				}
			}
		}
	}
	
	_hype.decodeTime = function(time, timeline) {
		var framesPerSecond = timeline["f"];
		var seconds = Math.floor(time);
		var frames = (time - seconds) * 100 /  framesPerSecond;
		return _hype.quantizeTimeWithFramesPerSecond(seconds + frames, framesPerSecond);
	}

	_hype.cleanupInternalDataRepresentation = function () {
		// we encode times as seconds.frame so we need to decode this to regular numbers
		// also need to set default values we didn't export for size reasons for later use
		for (var sceneIndex = 0; sceneIndex < _hype.scenes.length; sceneIndex++) {
			var scene = _hype.scenes[sceneIndex];
			for (var key in scene["T"]) {
				var timeline = scene["T"][key];
				timeline["d"] = _hype.decodeTime(timeline["z"], timeline);
				for (var animationIndex = 0; animationIndex < timeline["a"].length; animationIndex++) {
					var animation = timeline["a"][animationIndex];
					animation["t"] = _hype.decodeTime(animation["y"], timeline);
					animation["d"] = _hype.decodeTime(animation["z"], timeline);
					if(animation["p"] == null) {
						animation["p"] = _hype.kAnimationTypeStandardKeyframe;
					} else if (animation["p"] == _hype.kAnimationTypeTimelineAction) {
						animation["d"] = 0;
					}
					if(animation["r"] == null) {
						animation["r"] = false;
					}
					animation['originalIndex'] = animationIndex;
				}
			}
		}
	}
	
	_hype.addHeadAdditions = function () {
		// this does not look for duplicates, which could be an issue with multiple hype documents on one page(?)
		for(var i = 0; i < _hype.headAdditions.length; i++) {
			var headAddition = _hype.headAdditions[i];
			var element = document.createElement("div");
			element.innerHTML = headAddition;
			_hype.addElementToHead(element);
			

		}
		
		// head additions might mean custom fonts. If so, force a relayout after a delay (have no current visibility into font tracking
		window.setTimeout(_hype.relayoutIfNecessary, 120); // hopefully enough time to read from cache
		window.setTimeout(_hype.relayoutIfNecessary, 1200); // hopefully enough time to read from network
	}
	
	_hype.addGeneratedCSS = function () {
		var additionalCSS = ".HYPE_element{\
				-webkit-transform:rotateY(0);\
			}\
			\
			video.HYPE_element{\
				-webkit-transform:none;\
			}\
			\
			^{\
				color:#000;\
				\
				`size:16px;\
				`weight:normal;\
				`family:Helvetica,Arial,Sans-Serif;\
				`weight:normal;\
				`style:normal;\
				`variant:normal;\
				\
				text-decoration:none;\
				text-align:left;\
				text-transform:none;\
				text-indent:0;\
				text-shadow:none;\
				\
				line-height:normal;\
				letter-spacing:normal;\
				white-space:normal;\
				word-spacing:normal;\
				@:baseline;\
				\
				border:none;\
				background-color:transparent;\
				background-image:none;\
				\
				-webkit-`smoothing:antialiased;\
				-moz-backface-visibility:hidden;\
			}\
			\
			^div,^span,^applet,^object,^iframe,\
			^h1,^h2,^h3,^h4,^h5,^h6,^p,^blockquote,^pre,\
			^a,^abbr,^acronym,^address,^big,^cite,^code,\
			^del,^dfn,^em,^img,^ins,^kbd,^q,^s,^samp,\
			^small,^strike,^strong,^sub,^sup,^tt,^var,\
			^b,^u,^i,^center,\
			^dl,^dt,^dd,^ol,^ul,^li,\
			^fieldset,^form,^label,^legend,\
			^table,^caption,^tbody,^tfoot,^thead,^tr,^th,^td,\
			^article,^aside,^canvas,^details,^embed,\
			^figure,^figcaption,^footer,^header,^hgroup,\
			^menu,^nav,^output,^ruby,^section,^summary,\
			^time,^mark,^audio,^video{\
			\
				color:?;\
				\
				`size:?;\
				`weight:?;\
				`family:?;\
				`weight:?;\
				`style:?;\
				`variant:?;\
				\
				text-decoration:?;\
				text-align:?;\
				text-transform:?;\
				text-indent:?;\
				text-shadow:?;\
				\
				line-height:?;\
				letter-spacing:?;\
				white-space:?;\
				word-spacing:?;\
				@:?;\
				\
				border:none;\
				background-color:transparent;\
				background-image:none;\
				\
				padding:0;\
			}\
			\
			^p{\
				display:block;\
				$:1em 0;\
			}\
			\
			^div,^layer{\
				display:block;\
			}\
			\
			^article,^aside,^footer,^header,^hgroup,^nav,^section{\
				display:block;\
			}\
			\
			^blockquote{\
				display:block;\
				$:1em 40px;\
			}\
			\
			^figcaption{\
				display:block;\
			}\
			\
			^figure{\
				display:block;\
				$:1em 40px;\
			}\
			\
			^q{\
				display:inline;\
			}\
			\
			^q:before{\
				content:open-quote;\
			}\
			\
			^q:after{\
				content:close-quote;\
			}\
			\
			^center{\
				display:block;\
				text-align:center;\
			}\
			\
			^hr{\
				display:block;\
				$:.5em auto;\
				border-style:inset;\
				border-width:1px;\
			}\
			\
			^h1,^h2,^h3,^h4,^h5,^h6{\
				display:block;\
				$-left:0;\
				$-right:0;\
				`weight:bold;\
			}\
			\
			^h1{\
				`size:2em;\
				$-top:.67em;\
				$|:.67em;\
			}\
			\
			^h2{\
				`size:1.5em;\
				$-top:.83em;\
				$|:.83em;\
			}\
			\
			^h3{\
				`size:1.17em;\
				$-top:1em;\
				$|:1em;\
			}\
			\
			^h4{\
				$-top:1.33em;\
				$|:1.33em;\
			}\
			\
			^h5{\
				`size:.83em;\
				$-top:1.67em;\
				$|:1.67em;\
			}\
			\
			^h6{\
				`size:.67em;\
				$-top:2.33em;\
				$|:2.33em;\
			}\
			\
			^table{\
				display:table;\
				border-collapse:separate;\
				border-spacing:2px;\
				border-color:gray;\
			}\
			\
			^thead{\
				display:table-header-group;\
				@:middle;\
				border-color:?;\
			}\
			\
			^tbody{\
				display:table-row-group;\
				@:middle;\
				border-color:?;\
			}\
			\
			^tfoot{\
				display:table-footer-group;\
				@:middle;\
				border-color:?;\
			}\
			\
			^col{\
				display:table-column;\
			}\
			\
			^colgroup{\
				display:table-column-group;\
			}\
			\
			^tr{\
				display:table-row;\
				@:?;\
				border-color:?;\
			}\
			\
			^td,^th{\
				display:table-cell;\
				@:?;\
			}\
			\
			^th{\
				`weight:bold;\
			}\
			\
			^caption{\
				display:table-caption;\
				text-align:center;\
			}\
			\
			^ul,^menu,^dir{\
				display:block;\
				list-style-type:disc;\
				$:1em 0;\
				padding-left:40px;\
			}\
			\
			^ol{\
				display:block;\
				list-style-type:decimal;\
				$:1em 0;\
				padding-left:40px;\
			}\
			\
			^li{\
				display:list-item;\
				$:0;\
			}\
			\
			^ul ul,^ol ul{\
				list-style-type:circle;\
			}\
			\
			^ol ol ul,^ol ul ul,^ul ol ul,^ul ul ul{\
				list-style-type:square;\
			}\
			\
			^dd{\
				display:block;\
				$-left:40px;\
			}\
			\
			^dl{\
				display:block;\
				$:1em 0;\
			}\
			\
			^dt{\
				display:block;\
			}\
			\
			^ol ul,^ul ol,^ul ul,^ol ol{\
				$-top:0;\
				$|:0;\
			}\
			\
			^u,^ins{\
				text-decoration:underline;\
			}\
			\
			^strong,^b{\
				`weight:bolder;\
			}\
			\
			^i,^cite,^em,^var,^address{\
				`style:italic;\
			}\
			\
			^tt,^code,^kbd,^samp{\
				`family:monospace;\
			}\
			\
			^pre,^xmp,^plaintext,^listing{\
				display:block;\
				`family:monospace;\
				white-space:pre;\
				$:1em 0;\
			}\
			\
			^mark{\
				background-color:yellow;\
				color:black;\
			}\
			\
			^big{\
				`size:larger;\
			}\
			\
			^small{\
				`size:smaller;\
			}\
			\
			^s,^strike,^del{\
				text-decoration:line-through;\
			}\
			\
			^sub{\
				@:sub;\
				`size:smaller;\
			}\
			\
			^sup{\
				@:super;\
				`size:smaller;\
			}\
			\
			^nobr{\
				white-space:nowrap;\
			}\
				\
			^a{\
				color:blue;\
				text-decoration:underline;\
				cursor:pointer;\
			}\
			\
			^a:active{\
				color:red;\
			}\
			\
			^noframes{\
				display:none;\
			}\
			\
			^frameset,^frame{\
				display:block;\
			}\
			\
			^frameset{\
				border-color:?;\
			}\
			\
			^iframe{\
				border:0;\
			}\
			\
			^details{\
				display:block;\
			}\
			\
			^summary{\
				display:block;\
			}\
			";
		// should be in the reverse order from the minification script to avoid any stomping
		additionalCSS = additionalCSS.replace(/\|/g, "-bottom").replace(/\`/g, "font-").replace(/\@/g, "vertical-align").replace(/\?/g, "inherit").replace(/\$/g, "margin").replace(/\^/g, ".HYPE_scene ");
		
		if(_hype.graphicsAcceleration == false) {
			additionalCSS = additionalCSS.replace(/rotateY\(0\)/g, "none");
		}
		
		_hype.addAdditionalCSS(additionalCSS);
	}
		
	// adapted from David Morrissey's answer in:
	// http://stackoverflow.com/questions/2710284/controlling-css-with-javascript-works-with-mozilla-but-not-with-webkit-based-brow
	_hype.addAdditionalCSS = function (additionalCSS) {
		var css = document.createElement('div');
		var spaces = (document.xmlEncoding == null) ? "&nbsp;" : "";
		css.innerHTML = spaces + '<style id="" type="text/css">'+additionalCSS+'</style>' + spaces;
		_hype.addElementToHead(css);
	}
	
	_hype.addElementToHead = function (element) {
		var head = document.getElementsByTagName("head")[0];
		if (!head) { // SAFARI EARLY VERSIONS HACK! - Some safari versions don't find the "head" tag
			head = document.createElement('div');
			document.body.appendChild(head);
		}
		head.appendChild(element);
	}

	_hype.notifyEvent = function (event, element) {
		var eventListeners = window['HYPE_eventListeners'];
		if(eventListeners == null) {
			return;
		}
		for(var i = 0; i < eventListeners.length; i++) {
			if(eventListeners[i]['type'] == event['type'] && eventListeners[i]['callback'] != null) {
				if(eventListeners[i]['callback'](_hype['API'], element, event) === false) {
					return false;
				}
			}
		}
		return true;
	}
	
	_hype.sceneNames = function () { // public, do not change signature without wrapping in _hype['API']!
		var sceneNames = Array();		
		for(var i = 0; i < _hype.scenes.length; i++) {
			sceneNames.push(_hype.scenes[i]["n"]);
		}		
		return sceneNames;
	}
	
	_hype.currentSceneName = function () { // public, do not change signature without wrapping in _hype['API']!
		return _hype.scenes[_hype.currentSceneIndex]["n"];
	}

	_hype.showNextScene = function (transition, duration) { // public, do not change signature without wrapping in _hype['API']!
		_hype.showScene(_hype.currentSceneIndex + 1, transition, duration);
	}

	_hype.showPreviousScene = function (transition, duration) { // public, do not change signature without wrapping in _hype['API']!
		_hype.showScene(_hype.currentSceneIndex - 1, transition, duration);
	}
	
	_hype.showSceneNamed = function (sceneName, transition, duration) { // public, do not change signature without wrapping in _hype['API']!
		var sceneIndex = myIndexOf(_hype.sceneNames(), sceneName);
		_hype.showScene(sceneIndex, transition, duration);
	}
	
	_hype.showScene = function (sceneNumber, transition, duration) {
		// sanity check to make sure there's a scene with this number
		if(_hype.identifierOfSceneAtIndex(sceneNumber) == null || _hype.inSceneTransition == true) {
			return;
		}
		
		// sanity check for duration
		if(duration == null) {
			duration = 1.1;
		}
		duration = _hype.quantizeTimeWithFramesPerSecond(duration, _hype.fps);
		
		_hype.inSceneTransition = true;
	
		var finishShowScene = (function() {
			// remove timeline runs
			_hype.timelineRuns = Array();

			var currentSceneContainer = _hype.currentSceneElement();

			// do actual loading of scene
			_hype.loadScene(_hype.identifierOfSceneAtIndex(sceneNumber));
			
			var nextSceneContainer = _hype.currentSceneElement();
			
			if(transition == _hype.kSceneTransitionCrossfade) {
				_hype.showSceneWithCrossfade(currentSceneContainer, nextSceneContainer, duration);
			} else if(transition == _hype.kSceneTransitionSwap && (_hype.browserInfo.webkit != null || _hype.browserInfo.ff >= 10 || _hype.browserInfo.ie >= 10)) {
				_hype.showSceneWithSwap(currentSceneContainer, nextSceneContainer, duration);
			} else if(transition == _hype.kSceneTransitionPushLeftToRight || transition == _hype.kSceneTransitionPushRightToLeft || transition == _hype.kSceneTransitionPushBottomToTop || transition == _hype.kSceneTransitionPushTopToBottom) {
				_hype.showSceneWithPush(currentSceneContainer, nextSceneContainer, transition, duration);
			} else { // including (transition == null || transition == _hype.kSceneTransitionInstant)
				_hype.showSceneWithInstant(currentSceneContainer, nextSceneContainer);
			}
			
			// call relayout here because it will not work unless the other scene's display is block/visible
			_hype.relayoutIfNecessary();
		});
	

		// handle scene unload event
		var hasUnloadTimelineRun = false;
		var currentSceneContainer = _hype.currentSceneElement();

		if(_hype.hasShownInitialScene == true && currentSceneContainer != null) {

				var iframe = document.createElement("IFRAME");
				iframe.setAttribute("src", "js-frame:sceneUnload");
				document.documentElement.appendChild(iframe);
				iframe.parentNode.removeChild(iframe);
				iframe = null;

			var sceneIndex = _hype.indexOfSceneWithIdentifier(_hype.currentSceneIdentifier());
			
			// trigger event handlers
			var sceneUnloadFauxEvent = {"type" : "HypeSceneUnload"};
			var eventResult = _hype.notifyEvent(sceneUnloadFauxEvent, null);
			
			if(eventResult !== false) {		
				var onSceneUnloadData = _hype.scenes[sceneIndex]["B"];
				if(onSceneUnloadData != null) {
					var onUnloadActions = onSceneUnloadData["a"];
					var onUnloadFunction = _hype.Apply.CreateActionHandler(onUnloadActions, null);
					
					for(var i = 0; i < onUnloadActions.length; i++) {
						var action = onUnloadActions[i];
						var onSceneUnloadTimelineIdentifier = action["b"];
						if(action["p"] == _hype.kActionStartTimeline && onSceneUnloadTimelineIdentifier != null) {
							hasUnloadTimelineRun = true;
							_hype.timelineIdentifierForCompletionOverrideCallback = onSceneUnloadTimelineIdentifier;
							_hype.timelineCompletionOverrideCallback = finishShowScene;
						}
					}
					
					onUnloadFunction(sceneUnloadFauxEvent);
				}
			}
			
			// remove keyboard handlers
			for(var eventName in _hype.currentSceneActionHandlers) {
				if(_hype.currentSceneActionHandlers.hasOwnProperty(eventName) == false) {
					continue;
				}
				if (document.removeEventListener) {
					document.removeEventListener(eventName, _hype.currentSceneActionHandlers[eventName], false);
				} else if (document.detachEvent) {
					document.detachEvent("on" + eventName, _hype.currentSceneActionHandlers[eventName]);
				}
			}
			_hype.currentSceneActionHandlers = {};
			
			// remove all eventHandlers
			for (var i = 0; i < _hype.eventHandlers.length; i++) {
				var eventHandler = _hype.eventHandlers[i];
				_hype.removeEventHandler(eventHandler['eventType'], eventHandler['handler'], eventHandler['element']);
			}
			_hype.eventHandlers = Array();
		}
		_hype.hasShownInitialScene = true;
	
		if(hasUnloadTimelineRun == false) {
			finishShowScene();
		}
	}

	_hype.showSceneWithInstant = function (currentSceneContainer, nextSceneContainer) {
		_hype.completeSceneTransition(currentSceneContainer, nextSceneContainer, null);
	}

	_hype.showSceneWithCrossfade = function (currentSceneContainer, nextSceneContainer, duration) {
		var animations = Array();
		if(currentSceneContainer != null) {
			currentSceneContainer.style.zIndex = 0;
			
			if(_hype.drawSceneBackgrounds == false) {
				var currentSceneOid = _hype.idReverseMapping[currentSceneContainer.id];
				animations.push(_hype.createTransitionAnimation("e", 0, duration, "1", currentSceneOid, "1", "0"));
			}
		}

		nextSceneContainer.style.zIndex = 1;
		var nextSceneOid = _hype.idReverseMapping[nextSceneContainer.id];
		animations.push(_hype.createTransitionAnimation("e", 0, duration, "1", nextSceneOid, "0" ,"1"));
		
		_hype.applyValue(nextSceneContainer, "e", "0", true);
		_hype.addAndRunSceneTransitionTimeline("HYP_0", duration, animations, currentSceneContainer, nextSceneContainer);
		
		nextSceneContainer.style.display = "block";
	}

	_hype.showSceneWithSwap = function (currentSceneContainer, nextSceneContainer, duration) {
		// function replacement for minification
		var createTransitionAnimationFunction = _hype.createTransitionAnimation;
		var applyValueFunction = _hype.applyValue;
	
		//!! this (and corresponding clear in completeSceneTransition is a workaround for x-bug://1
		var mainContentContainer = document.getElementById(_hype.mainContentContainerID);
		var mainContentContainerStyle = mainContentContainer.style;
		mainContentContainerStyle[_hype.kSizeOptimizationWebKitPrefix + "perspective"] = "600px";
		mainContentContainerStyle["MozPerspective"] = "600px";
		mainContentContainerStyle["perspective"] = "600px";
		mainContentContainerStyle[_hype.kSizeOptimizationWebKitPrefix + "transform-style"] = "preserve-3d";
		mainContentContainerStyle["MozTransformStyle"] = "preserve-3d";
		mainContentContainerStyle["transform-style"] = "preserve-3d";

		var mainContainerContentSize = _hype.browserReportedSizeForElementOid(null);
		var width = mainContainerContentSize.width;
		var height = mainContainerContentSize.height;
		var widthByFour = (width / 4.0) + "px";
		var widthByTwo = (width / 2.0) + "px";
		var negativeWidthByFour = "-" + widthByFour;
		var negativeWidthByTwo = "-" + widthByTwo;
		var durationByTwo = (duration / 2);
		
		var animations = Array();
		if(currentSceneContainer != null) {
			var currentSceneOid = _hype.idReverseMapping[currentSceneContainer.id];
			currentSceneContainer.setAttribute("HYP_c", "1");
			applyValueFunction(currentSceneContainer, "z", "1", true);
			applyValueFunction(currentSceneContainer, "bQ", "0px", true);
			applyValueFunction(currentSceneContainer, "c", width, true);
			applyValueFunction(currentSceneContainer, "d", height, true);

			animations.push(createTransitionAnimationFunction("a", 0, durationByTwo, "3", currentSceneOid, "0px", widthByTwo));
			animations.push(createTransitionAnimationFunction("bQ", 0, durationByTwo, "3", currentSceneOid, "0px", "-300px"));
			animations.push(createTransitionAnimationFunction("aY", 0, durationByTwo, "3", currentSceneOid, "0deg", "-20deg"));
			animations.push(createTransitionAnimationFunction("z", 0, durationByTwo, "0", currentSceneOid, "1", "0"));
		}

		var nextSceneOid = _hype.idReverseMapping[nextSceneContainer.id];
		nextSceneContainer.setAttribute("HYP_c", "1");
		
		animations.push(createTransitionAnimationFunction("a", 0, durationByTwo, "3", nextSceneOid, negativeWidthByFour, negativeWidthByTwo));
		animations.push(createTransitionAnimationFunction("bQ", 0, durationByTwo, "3", nextSceneOid, "-600px", "-300px"));
		animations.push(createTransitionAnimationFunction("aY", 0, durationByTwo, "3", nextSceneOid, "50deg", "40deg"));
		animations.push(createTransitionAnimationFunction("z", 0, durationByTwo, "0", nextSceneOid, "0", "1"));

		
		if(currentSceneContainer != null) {
			animations.push(createTransitionAnimationFunction("a", durationByTwo, durationByTwo, "4", currentSceneOid, widthByTwo, widthByFour));
			animations.push(createTransitionAnimationFunction("bQ", durationByTwo, durationByTwo, "4", currentSceneOid, "-300px", "-600px"));
			animations.push(createTransitionAnimationFunction("aY", durationByTwo, durationByTwo, "4", currentSceneOid, "-20deg", "-50deg"));
		}

		animations.push(createTransitionAnimationFunction("a", durationByTwo, durationByTwo, "4", nextSceneOid, negativeWidthByTwo, "0px"));
		animations.push(createTransitionAnimationFunction("bQ", durationByTwo, durationByTwo, "4", nextSceneOid, "-300px", "0px"));
		animations.push(createTransitionAnimationFunction("aY", durationByTwo, durationByTwo, "4", nextSceneOid, "40deg", "0"));
		
		
		applyValueFunction(nextSceneContainer, "a", widthByFour, true);
		applyValueFunction(nextSceneContainer, "bQ", "-600px", true);
		applyValueFunction(nextSceneContainer, "aY", "50deg", true);
		applyValueFunction(nextSceneContainer, "z", "0", true);
		applyValueFunction(nextSceneContainer, "c", width, true);
		applyValueFunction(nextSceneContainer, "d", height, true);
		
		_hype.addAndRunSceneTransitionTimeline("HYP_ab", duration, animations, currentSceneContainer, nextSceneContainer);
		
		nextSceneContainer.style.display = "block";
	}

	_hype.showSceneWithPush = function (currentSceneContainer, nextSceneContainer, direction, duration) {
		var mainContainerContentSize = _hype.browserReportedSizeForElementOid(null);

		var width = mainContainerContentSize.width;
		var negativeWidth = "-" + width + "px";
		var height = mainContainerContentSize.height;
		var negativeHeight = "-" + height + "px";
		
		var currentSceneStartValue = "0px";
		
		// kSceneTransitionPushLeftToRight as the default
		var identifier = "a";
		var currentSceneEndValue = width;
		var nextSceneStartValue = negativeWidth;
		
		if(direction == _hype.kSceneTransitionPushRightToLeft) {
			currentSceneEndValue = negativeWidth;
			nextSceneStartValue = width;
		} else if(direction == _hype.kSceneTransitionPushBottomToTop) {
			identifier = "b";
			currentSceneEndValue = negativeHeight;
			nextSceneStartValue = height;
		} else if(direction == _hype.kSceneTransitionPushTopToBottom) {
			identifier = "b";
			currentSceneEndValue = height;
			nextSceneStartValue = negativeHeight;
		}
		
		var animations = Array();
		if(currentSceneContainer != null) {
			var currentSceneOid = _hype.idReverseMapping[currentSceneContainer.id];
			_hype.applyValue(currentSceneContainer, "c", width, true);
			_hype.applyValue(currentSceneContainer, "d", height, true);
			animations.push(_hype.createTransitionAnimation(identifier, 0, duration, "2", currentSceneOid, currentSceneStartValue, currentSceneEndValue));
		}

		var nextSceneOid = _hype.idReverseMapping[nextSceneContainer.id];
		animations.push(_hype.createTransitionAnimation(identifier, 0, duration, "2", nextSceneOid, nextSceneStartValue ,currentSceneStartValue));
		
		_hype.applyValue(nextSceneContainer, "c", width, true);
		_hype.applyValue(nextSceneContainer, "d", height, true);
		_hype.applyValue(nextSceneContainer, identifier, nextSceneStartValue, true);
		_hype.addAndRunSceneTransitionTimeline("HYP_aa", duration, animations, currentSceneContainer, nextSceneContainer);
		
		nextSceneContainer.style.display = "block";
	}
	
	_hype.addAndRunSceneTransitionTimeline = function (timelineIdentifier, duration, animations, currentSceneContainer, nextSceneContainer) {
		var timeline = {"i" : timelineIdentifier, "n" : timelineIdentifier, "f" : 30, "d" : duration, "a" : animations };
		
		_hype.scenes[_hype.currentSceneIndex]["T"][timelineIdentifier] = timeline;
		_hype.createTimelineRun(timelineIdentifier, animations);
		_hype.timelineIdentifierForCompletionOverrideCallback = timelineIdentifier;
		_hype.timelineCompletionOverrideCallback = (function() { _hype.completeSceneTransition(currentSceneContainer, nextSceneContainer, timelineIdentifier); });
		_hype.startTimelineRun(timelineIdentifier, null, false);	
	}
	
	_hype.createTransitionAnimation = function (identifier, startTime, duration, timingFunction, oid, startValue, endValue) {
		return { "i" : identifier, "t" : startTime, "d" : duration, "f" : timingFunction, "o" : oid, "s" : startValue, "e" : endValue };
	}

	function getElementsByClassName(className, elm) {
		if(elm.getElementsByClassName) {
			return elm.getElementsByClassName(className);
		} else {
			var testClass = new RegExp("(^|\\s)" + className + "(\\s|$)");
			var tag = "*";
			elm = elm || document;
			var elements = (tag == "*" && elm.all)? elm.all : elm.getElementsByTagName(tag);
			var returnElements = [];
			var current;
			var length = elements.length;
			for(var i=0; i<length; i++){
				current = elements[i];
				if(testClass.test(current.className)){
					returnElements.push(current);
				}
			}
			return returnElements;
		}
	}

	_hype.currentSceneElement = function () {
		return document.getElementById(_hype.idMapping[_hype.currentSceneIdentifier()]);
	}

	_hype.currentSceneIdentifier = function () {
		var scene = _hype.scenes[_hype.currentSceneIndex];
		if(scene == null) {
			return null;
		}
		return scene['o'];
	}

	_hype.identifierOfSceneAtIndex = function (sceneIndex) {
		var scene = _hype.scenes[sceneIndex];
		if(scene == null) {
			return null;
		}
		return scene['o'];
	}
	
	_hype.indexOfSceneWithIdentifier = function (sceneIdentifier) {
		for(var i = 0; i < _hype.scenes.length; i++) {
			if(_hype.scenes[i]['o'] == sceneIdentifier) {
				return i;
			}
		}
		return -1;
	}
	
	_hype.createResourceGroups = function () {
		for(var resourceID in _hype.resources) {
			if(_hype.resources.hasOwnProperty(resourceID) == false) {
				continue;
			}
			
			var resource = _hype.resources[resourceID];
			var groupOid = resource['g'];
			if(groupOid != null) {
				var resourceGroup = _hype.resourceGroups[groupOid];
				if(resourceGroup == null) {
					resourceGroup = Array();
					_hype.resourceGroups[groupOid] = resourceGroup;
				}
				resourceGroup.push(resourceID);
			}
		}
	}

	_hype.resourcePathForResourceID = function (resourceID) {
		var resource = _hype.resources[resourceID];
		var isURLReference = resource['r'];
		var resourceName = resource['n'];
		if(resourceName == null) {
			return null;
		}
		
		if(isURLReference == true) {
			return "" + resourceName; // empty string makes sure browser tries to load and continues preload cycle
		}
		return "" + _hype.resourcesFolderName + "/" + resourceName;
	}
	
	_hype.resourcePathForBlankGif = function () {
		return "" + _hype.resourcesFolderName + "/blank.gif";
	}


	///////////////////////////////////////////////////////////////
	// PRELOADING

	_hype.preloadFinished = function () {
		if(_hype.showLoadingPage == true) {
			_hype.hideLoadingPage();
		}
		
		if(_hype.notifyEvent({"type":"HypeDocumentLoad"}, document.getElementById(_hype.mainContentContainerID)) === false) {
			return;
		}
		
		_hype.showScene(_hype.currentSceneIndex);
	}

	_hype.preloadResources = function () {
		for(var resourceID in _hype.resources) {
			if(_hype.resources.hasOwnProperty(resourceID) == false) {
				continue;
			}
			var resource = _hype.resources[resourceID];
			var preloadType = resource['p'];
			if(preloadType != null) {
				if(myIndexOf(_hype.resourceIDsToPreload, resourceID) == -1) {
					_hype.resourceIDsToPreload.push(resourceID);
				}
			}
		}

		var localCopyOfResourceIDsToPreload = _hype.resourceIDsToPreload.slice(0); // clone array as it may be mutated during iteration in some cases
		
		// handle case of no images
		if(localCopyOfResourceIDsToPreload.length == 0) {
			_hype.preloadFinished();
			return;
		}


			for(var index = 0; index < localCopyOfResourceIDsToPreload.length; index++) {
				_hype.preloadResource(localCopyOfResourceIDsToPreload[index], false);
			}

	}
	
	_hype.preloadResource = function (resourceID, shouldPreloadSerially) {
		var preloadType = _hype.resources[resourceID]['p'];
		if(preloadType == _hype.kResourcePreloadTypeImage) {
			_hype.preloadImageResource(resourceID, shouldPreloadSerially);
		} else if(preloadType == _hype.kResourcePreloadTypeAudio) {
			_hype.preloadAudioResource(resourceID, shouldPreloadSerially);
		}
	}


	
	_hype.didPreloadResource = function (resourceID, shouldPreloadSerially) {
		var index = myIndexOf(_hype.resourceIDsToPreload, resourceID);
		if(index != -1) {
			_hype.resourceIDsToPreload.splice(index, 1);
		}
	
		if(_hype.resourceIDsToPreload.length <= 0) {
			_hype.preloadFinished();
		} else if(shouldPreloadSerially == true && _hype.isPreloadNextResourceQueued == false) {
			// window.setTimeout is a workaround for the stack overflow line 0 issue
			_hype.isPreloadNextResourceQueued = true;
			window.setTimeout((function() { _hype.preloadNextResource(); }), 1);
		}
	}
	
	_hype.preloadImageResource = function (resourceID, shouldPreloadSerially) {
		var resource = _hype.resources[resourceID];
		var groupOid = resource['g'];
		if(groupOid != null) {
			var bestResourceID = _hype.bestImageResourceIDForResourceGroupOid(groupOid);
			if(bestResourceID != resourceID) {
				_hype.didPreloadResource(resourceID, shouldPreloadSerially);
				return;
			}
		}
	
		var img = new Image();
		var completionHandler = function(e) {
			img = this;
			// use data url mechanism for chrome
			if((_hype.browserInfo.chrome != null || (_hype.browserInfo.safari != null && _hype.browserInfo.ios == null)) && e.type == "load" && _hype.resourcesFolderName.indexOf("dropbox.com") != -1 && img.src.indexOf(".gif") == -1) {
				try {
					var buffer = document.createElement('canvas');
					buffer.width = img.width;
					buffer.height = img.height;
					buffer.getContext("2d").drawImage(img, 0, 0);
					_hype.imageResourceIDToDataURLMapping[img.resourceID] = buffer.toDataURL();
				} catch (err) { }
			}
		
			_hype.didPreloadResource(resourceID, shouldPreloadSerially);
		}	
		
		var resourcePath = _hype.resourcePathForResourceID(resourceID);
		img.onload = completionHandler;
		img.onerror = completionHandler;
		img.onabort = completionHandler;
		img.src = resourcePath;
		img.resourceID = resourceID;
	}
	
	_hype.bestImageResourceIDForResourceGroupOid = function (resourceGroupOid) {
		var resourceIDs = _hype.resourceGroups[resourceGroupOid];
		if(resourceIDs == null) {
			return;
		}
		
		var isRetinaDisplay = (window['devicePixelRatio'] > 1);
		var isSlowConnection = (_hype.browserInfo.iphone != null || _hype.browserInfo.android != null); // assume for now any iphone or android is on a slow connection
		var foundStandardImageResource = false;
		var bestResourceID = null;
		
		for(var i = 0; i < resourceIDs.length; i++) {
			var resourceID = resourceIDs[i];
			var resource = _hype.resources[resourceID];
			var groupingType = resource["t"];
			var isAutoResized = resource["o"];
			var prefersRetina = (isRetinaDisplay == true && !(isSlowConnection == true && isAutoResized == true));
			
			if(groupingType == "@1x") {
				bestResourceID = resourceID;
				foundStandardImageResource = true;
			} else if(groupingType == "@2x") {
				if(foundStandardImageResource == false || prefersRetina == true) {
					bestResourceID = resourceID;
				}
				if(prefersRetina == true) {
					break;
				}
			} else if(foundStandardImageResource == false) {
				bestResourceID = resourceID;
			}
		}

		return bestResourceID;
	}
	
	_hype.preloadAudioResource = function (resourceID, shouldPreloadSerially) {
		var completionHandler = (function(e) {
			var resourceGroupOid = _hype.resourceGroupOidForResourceID(resourceID);
			var resourceIDs = _hype.resourceGroups[resourceGroupOid];
			for(var i = 0; i < resourceIDs.length; i++) {
				var matchingResourceID = resourceIDs[i];
				var resource = _hype.resources[matchingResourceID];
				var preloadType = resource['p'];
				if(preloadType != null) {
					_hype.didPreloadResource(matchingResourceID, shouldPreloadSerially);
				}
			}
		});
		
		var resourceGroupOid = _hype.resourceGroupOidForResourceID(resourceID);
		var audio = _hype.hypeAudioForResourceGroupOid(resourceGroupOid);
		
		// workaround iOS 5 bug where preloading does not work, and don't preload for quicktime
		if((_hype.browserInfo.ios != null && parseFloat(_hype.browserInfo.webkit) < 536.26) || _hype.browserInfo.ie < 9) {
			window.setTimeout(completionHandler, 1); // needs to defer otherwise
		} else {
			audio.load(completionHandler, completionHandler);
		}
	}


	///////////////////////////////////////////////////////////////
	// AUDIO

	
	_hype.playAudioResourceGroupOid = function (resourceGroupOid, loop) {
		var audio = _hype.hypeAudioForResourceGroupOid(resourceGroupOid);
		audio.loop = loop;
		audio.play();
	}

	_hype.pauseAudioResourceGroupOid = function (resourceGroupOid) {
		var audio = _hype.hypeAudioForResourceGroupOid(resourceGroupOid);
		audio.pause();
	}
	
	_hype.hypeAudioForResourceGroupOid = function (resourceGroupOid) {
		var resourceIDs = _hype.resourceGroups[resourceGroupOid];
		var sourceURLsByMimeType = {};
		for(var i = 0; i < resourceIDs.length; i++) {
			var resourceID = resourceIDs[i];
			var mimeType = _hype.resources[resourceID]["t"];
			if(_hype.browserInfo.android != null && mimeType == "audio/ogg") {
				// android has horrible ogg support, it will generally only play once
				continue;
			}
			sourceURLsByMimeType[mimeType] = _hype.resourcePathForResourceID(resourceIDs[i]);
		}
		
		// determine proper API
		var audioMethodAPI;
		var iBooksWorkaround = _hype.resourcesFolderName.indexOf("x-ibooks-th://") != -1 && _hype.browserInfo.ios != null && parseFloat(_hype.browserInfo.webkit) >= 537; /* See #5028 and rdar://problem/15060383 */
		if((typeof AudioContext !== "undefined" || typeof webkitAudioContext !== "undefined") && _hype.resourcesFolderName.indexOf("file://") == -1 && _hype.resourcesFolderName.indexOf("ibooksimg://") == -1 && iBooksWorkaround == false  && navigator.onLine != false) {
			audioMethodAPI = HypeAudio_WebAudioAPI;
		}

		else {
			audioMethodAPI = HypeAudio_HTML5;
		}
		
		var options = {};
		options.startAheadOfPlayback = (_hype.browserInfo.ios != null);
		
		return HypeAudio(audioMethodAPI, resourceGroupOid, sourceURLsByMimeType, options);
	}

	_hype.resourceGroupOidForResourceID = function (resourceID) {
		var resource = _hype.resources[resourceID];
		return resource['g'];
	}

	///////////////////////////////////////////////////////////////
	// MULTI-TOUCH
	
	
	_hype.createFingerData = function () {
		var fingerData=[];
		for (var i=0; i<=5; i++) {
			fingerData.push({
				start:{ x: 0, y: 0 },
				end:{ x: 0, y: 0 },
				lastPosition:{ x: 0, y: 0 }
			});
		}
		
		return fingerData;
	}
	
	// reminder: for web positive y is down, so using top and left position 90 degrees is down (instead of up for normal math)
	_hype.calculateAngle = function(startPoint, endPoint) {
		var x = endPoint.x - startPoint.x;
		var y = endPoint.y - startPoint.y;
		var r = Math.atan2(y, x); //radians
		var angle = Math.round(r * 180 / Math.PI); //degrees
		return angle;
	}
	
	_hype.calculateDirectionAlongAxis = function (startPoint, endPoint, touchStateOptions) {
		var angle = _hype.calculateAngle(startPoint, endPoint);
		if (touchStateOptions["c"] == 0) {
			if ((angle >= 90) || (angle <= -90)) {
				return _hype.kLeft;
			} else {
				return _hype.kRight;
			}
		} else {
			if ((angle <= 0) && (angle >= -180)) {
				return _hype.kUp;
			} else {
				return _hype.kDown;
			} 
		}
	}

	_hype.calculateDirection = function (startPoint, endPoint, duration) {
		var angle = _hype.calculateAngle(startPoint, endPoint);
		var offset = Math.min(45, 45 * duration / 2000);

		if ((angle >= 135 + offset) || (angle <= -135 - offset)) {
			return _hype.kLeft;
		} else if ((angle <= 45 - offset) && (angle >= -45 + offset)) {
			return _hype.kRight;
		} else if ((angle < -45 - offset) && (angle > -135 + offset)) {
			return _hype.kUp;
		} else if ((angle > 45 + offset) && (angle < 135 - offset)) {
			return _hype.kDown;
		} else {
			return undefined;
		}
	}

	_hype.updateMinOrMaxPointForGesture = function(touchState, point) {
		var position = ((touchState.options.direction == _hype.kRight) || (touchState.options.direction == _hype.kLeft)) ? point.x : point.y;
		if (touchState.minOrMaxForGesture == undefined) {
			touchState.minOrMaxForGesture = position;
		} else {
			if ((touchState.options.direction == _hype.kRight) || (touchState.options.direction == _hype.kDown)) {
				touchState.minOrMaxForGesture = Math.max(touchState.minOrMaxForGesture, position);
			} else {
				touchState.minOrMaxForGesture = Math.min(touchState.minOrMaxForGesture, position);
			}
		}
	}

	_hype.directionForDrag = function(touchStateOptions) {
		var forwardDirection;
		if (touchStateOptions["c"] == 0) {
			if (touchStateOptions["A"] == 0) {
				forwardDirection = _hype.kRight;
			} else if (touchStateOptions["A"] == 1) {
				forwardDirection = _hype.kLeft;
			}
		} else if (touchStateOptions["c"] == 1) {
			if (touchStateOptions["A"] == 0) {
				forwardDirection = _hype.kUp;
			} else if (touchStateOptions["A"] == 1) {
				forwardDirection = _hype.kDown;
			}	
		}
		return forwardDirection;
	}
	
	_hype.pagePosition = function (event) {
		var position = new Object();
		if(event.pageX || event.pageY) {
			position.x = event.pageX;
			position.y = event.pageY;
		} else if (event.clientX || event.clientY) {
			position.x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
			position.y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
		}
		return position;
	}

	_hype.addEventHandler = function (eventType, handler, element, manageHandlerRemoval) {
		var eventHandler = {'eventType':eventType, 'handler':handler, 'element':element};
		if (manageHandlerRemoval) {
			_hype.eventHandlers.push(eventHandler);
		}
		if (element.addEventListener) {
			element.addEventListener(eventType, handler, false);
		} else if (element.attachEvent) {
			element.attachEvent('on' + eventType, handler);
		}
	}
	
	_hype.removeEventHandler = function (eventType, handler, element) {
		if (element.removeEventListener) {
			element.removeEventListener(eventType, handler, false);
		} else if (element.detachEvent) {
			element.detachEvent('on' + eventType, handler);
		}
	}

	_hype.sendDragGestureUpdates = function (event, touchState) {
		if (touchState.options.continuousUpdates == true) {
			event['hypeGesturePhase'] = touchState.phase;
			event['hypeGestureXPosition'] = touchState.fingerData[0].end.x;
			event['hypeGestureYPosition'] = touchState.fingerData[0].end.y;
			touchState.updateFunction(event);
		}
	}

	_hype.removeTouchEventHandlers = function(touchState) {
		if (touchState.hasRemovedEventHandlers == true) {
			return;
		}

		touchState.hasRemovedEventHandlers = true;
		_hype.dragOwnershipOfPropertiesByElement = {};
		var touchElement = touchState.isTouchEvent ? touchState.elementForEvents : document;
		if(touchElement.releaseCapture) { touchElement.releaseCapture(); }
		_hype.removeEventHandler(_hype.moveEventType(touchState), touchState.touchMove, touchElement);
		_hype.removeEventHandler(_hype.endEventName(touchState), touchState.touchEnd, touchElement);
		if (touchState.isTouchEvent) {
			_hype.removeEventHandler(_hype.kCancelEventName, touchState.touchCancel, touchElement);
		} else {
			_hype.removeEventHandler('mouseout', touchState.touchCancel, touchElement);
		}
		if (touchState.options.gestureType == _hype.kGestureDrag) _hype.currentlyHandlingDragGesture = false;
		if (touchState.hasReceivedMove == true) {
			_hype.receivedGestureMove--;
		}
	}
		
	_hype.makeTouchCancel = function (touchState) {
		return function (event) {
			_hype.removeTouchEventHandlers(touchState);
			touchState.phase = _hype.kPhaseCancel;
			_hype.sendDragGestureUpdates(event, touchState);
		};
	}

	_hype.moveEventType = function (touchState) {
		return touchState.isTouchEvent ? 'touchmove' : 'mousemove';
	}

	_hype.endEventName = function (touchState) {
		return touchState.isTouchEvent ? 'touchend' : 'mouseup';
	}

	_hype.makeTouchStart = function (touchState) {
		return function (event) {
			touchState.isTouchEvent = (event.type == "touchstart");

			if (touchState.isTouchEvent) {
				if (event.touches.length > 0 && touchState.options.gestureType == _hype.kGestureTap) {
					_hype.preventMouseEventsForTouch(event.touches[0]);
				}
				touchState.fingerCount = event.touches.length;
			} else {
				if (event.preventDefault) {
					event.preventDefault();
				}
			}

			if (touchState.isTouchEvent == false || touchState.fingerCount === 1) {
				touchState.phase = _hype.kPhaseStart;
				touchState.distance = 0;
				touchState.gestureDirection = null;
				touchState.fingerData = _hype.createFingerData();
				touchState.minOrMaxForGesture = undefined;
				touchState.hasRemovedEventHandlers = false;

				var ret;
				var mainEvent = touchState.isTouchEvent ? event.touches[0] : event;

				var touchCancelHandler = _hype.makeTouchCancel(touchState);
				touchState.touchCancel = touchCancelHandler;
				var elementForEvents = touchState.isTouchEvent ? touchState.elementForEvents : document;

				if (touchState.isTouchEvent) {
					_hype.addEventHandler(_hype.kCancelEventName, touchState.touchCancel, elementForEvents, false);
				} else if (window.self != window.top) {
					// iframes need to cancel the drag when the mouse leaves the frame because we will no longer receive mouse events
					var mouseOutHandler = function (event) {
						// cancel if we have moved outside of the document element, IE uses toElement
						if (event.relatedTarget === document.documentElement || event.toElement === null) {
							touchCancelHandler(event);
						}
					}
					touchState.touchCancel = mouseOutHandler;
					_hype.addEventHandler('mouseout', mouseOutHandler, elementForEvents, false);
				}

				var position = _hype.pagePosition(mainEvent);
				var firstFingerData = touchState.fingerData[0];
				firstFingerData.start.x = position.x;
				firstFingerData.end.x = position.x;
				firstFingerData.lastPosition.x = position.x;
				firstFingerData.start.y = position.y;
				firstFingerData.end.y = position.y;
				firstFingerData.lastPosition.y = position.y;
				_hype.updateMinOrMaxPointForGesture(touchState, position);

				touchState.touchMove = _hype.makeTouchMove(touchState);
				_hype.addEventHandler(_hype.moveEventType(touchState), touchState.touchMove, elementForEvents, false);
				touchState.touchEnd = _hype.makeTouchEnd(touchState);
				_hype.addEventHandler(_hype.endEventName(touchState), touchState.touchEnd, elementForEvents, false);

				// older version of ie need this to receive drag events when the mouse is outside the window
				if(elementForEvents.setCapture) { elementForEvents.setCapture(); }
				
				touchState.gestureStartTime = new Date().getTime();
				touchState.lastTimePositionWasSet = touchState.gestureStartTime;
				touchState.hasReceivedMove = false;

				_hype.firedMouseClickActionAfterGesture = false;
				_hype.currentlyHandlingDragGesture = false;
			}
		};
	}

	_hype.makeTouchMove = function (touchState) {
		return function (event) {
			if (touchState.phase === _hype.kPhaseEnd || touchState.phase === _hype.kPhaseCancel) {
				return;
			}
			if (_hype.firedMouseClickActionAfterGesture == true) {
				touchState.touchCancel(event);
				return;
			}

			if (touchState.hasReceivedMove == false) {
				_hype.receivedGestureMove++;
				touchState.hasReceivedMove = true;
				if (touchState.options.gestureType == _hype.kGestureDrag) {
					_hype.currentlyHandlingDragGesture = true;
					if (touchState.options.timelinesToControl != null) {
						for (var i = 0; i < touchState.options.timelinesToControl.length; i++) {
							var timelineIdentifier = touchState.options.timelinesToControl[i]["b"];
							var timelineRun = _hype.timelineRunForIdentifier(timelineIdentifier);
							_hype.pauseTimelineWithIdentifier(timelineIdentifier, null);
							_hype.setTimelineRunIsReversed(timelineRun, false);
							touchState.timelineStartTime = _hype.currentTimeInTimelineWithIdentifier(timelineRun.timelineIdentifier, false);
							var animations = timelineRun.animations;
							for(var j = 0; j < animations.length; j++) {
								var oid = animations[j]["o"];
								var identifier = animations[j]["i"];
								if (_hype.dragOwnershipOfPropertiesByElement[oid] === undefined) {
									_hype.dragOwnershipOfPropertiesByElement[oid] = {};
								}
								_hype.dragOwnershipOfPropertiesByElement[oid][identifier] = timelineRun.timelineIdentifier;
							}
						}
					}
					if (touchState.options.elementToControl != null) {
						var elementOid = _hype.idReverseMapping[touchState.options.elementToControl.id];
						var startLeft = _hype.currentValues[elementOid]['a'];
						touchState.elementStartLeft = _hype.untransformValue(startLeft, 0);
						var startTop = _hype.currentValues[elementOid]['b'];
						touchState.elementStartTop = _hype.untransformValue(startTop, 0);
						if(_hype.timelineRunOwnershipOfPropertiesByElement[elementOid] != undefined) {		
							_hype.timelineRunOwnershipOfPropertiesByElement[elementOid]["a"] = null;
							_hype.timelineRunOwnershipOfPropertiesByElement[elementOid]["b"] = null;
							_hype.stopActiveAnimationsWithoutOwnershipExcludingTimelineRun(null);
						}
						if (_hype.dragOwnershipOfPropertiesByElement[elementOid] === undefined) {
							_hype.dragOwnershipOfPropertiesByElement[elementOid] = {};
						}
						_hype.dragOwnershipOfPropertiesByElement[elementOid]["a"] = "element";
						_hype.dragOwnershipOfPropertiesByElement[elementOid]["b"] = "element";
					}

					_hype.sendDragGestureUpdates(event, touchState);
				}
			}

			var ret;
			var mainEvent = touchState.isTouchEvent ? event.touches[0] : event;
			//Save the first finger data
			var position = _hype.pagePosition(mainEvent);
			var firstFingerData = touchState.fingerData[0];
			firstFingerData.lastPosition.x = firstFingerData.end.x;
			firstFingerData.lastPosition.y = firstFingerData.end.y;
			touchState.lastTimePositionWasSet = touchState.previousTime;
			firstFingerData.end.x = touchState.isTouchEvent ? event.touches[0].pageX : position.x;
			firstFingerData.end.y = touchState.isTouchEvent ? event.touches[0].pageY : position.y;
			touchState.previousTime = new Date().getTime();

			var currentGestureTime = (new Date().getTime() - touchState.gestureStartTime);
			
			touchState.gestureDirection = _hype.calculateDirection(firstFingerData.start, firstFingerData.end, currentGestureTime);
			if (touchState.isTouchEvent) {
				touchState.fingerCount = event.touches.length;
			}

			touchState.phase = _hype.kPhaseMove;
			//Distance and duration are all off the main finger
			touchState.distance = _hype.distanceBetweenPoints(firstFingerData.start, firstFingerData.end);
			touchState.velocity = touchState.distance / currentGestureTime;
			if (touchState.options.gestureType == _hype.kGestureDrag) {
				if (event.preventDefault) {
					event.preventDefault();
				}
				// we need to prevent drag and drop of images in IE since IE does not support preventDefault
				document.ondragstart = function () { return false; };

				if (touchState.options.timelinesToControl != null) {
					for (var i = 0; i < touchState.options.timelinesToControl.length; i++) {
						var timelineIdentifier = touchState.options.timelinesToControl[i]["b"];
						var timelineRun = _hype.timelineRunForIdentifier(timelineIdentifier);
						if (timelineRun) {
							var timelineDuration = _hype.timelineDuration(timelineRun);
							var direction = _hype.directionForDrag(touchState.options.timelinesToControl[i]);
							var translation;
							if (direction == _hype.kRight) {
								translation = firstFingerData.end.x - firstFingerData.start.x;
							} else if (direction == _hype.kLeft) {
								translation = firstFingerData.start.x - firstFingerData.end.x;
							} else if (direction == _hype.kUp) {
								translation = firstFingerData.start.y - firstFingerData.end.y;
							} else if (direction == _hype.kDown) {
								translation = firstFingerData.end.y - firstFingerData.start.y;
							}
							var time = translation / 150;
							
							time = time + touchState.timelineStartTime;
							time = Math.min(timelineDuration, time);
							time = Math.max(0, time);
							_hype.goToTimeInTimelineWithIdentifier(time, timelineIdentifier);
						} else {
							touchState.touchCancel(event);						
						}
					}
				}

				if (touchState.options.elementToControl != null) {
					var left = touchState.elementStartLeft + firstFingerData.end.x - firstFingerData.start.x;
					var top = touchState.elementStartTop + firstFingerData.end.y - firstFingerData.start.y;
					if (_hype.usesFlexibleLayout == false) {
						var elementOid = _hype.idReverseMapping[touchState.options.elementToControl.id];
						var parentOid = _hype.parentOidForElementOid(elementOid);
						var leftOffset = 0;
						var topOffset = 0;
						while (parentOid != null && parentOid != _hype.mainContentContainerID) {
							leftOffset += _hype.untransformValue(_hype.currentValues[parentOid]['a'], 0);
							topOffset += _hype.untransformValue(_hype.currentValues[parentOid]['b'], 0);
							parentOid = _hype.parentOidForElementOid(parentOid);
						}

						var currentContainerSize = _hype.browserReportedSizeForElementOid(parentOid);
						var elementWidth = _hype.currentWidthForElementWithOid(elementOid);
						left = Math.max(left, -elementWidth / 2 - leftOffset);
						left = Math.min(left, currentContainerSize.width - (elementWidth / 2) - leftOffset);
						var elementHeight = _hype.currentHeightForElementWithOid(elementOid);
						top = Math.max(top, -elementHeight / 2 - topOffset);
						top = Math.min(top, currentContainerSize.height - (elementHeight / 2) - topOffset);
					}
					_hype.applyValue(touchState.options.elementToControl, 'a', left, true);
					_hype.applyValue(touchState.options.elementToControl, 'b', top, true);
					
				}
				_hype.sendDragGestureUpdates(event, touchState);
			} else if (touchState.options.gestureType == _hype.kGestureSwipe) {
				if (touchState.fingerCount > 1) {
					touchState.touchCancel(event);
				} else {
					if (event.preventDefault && (touchState.gestureDirection == touchState.options.direction)) {
						event.preventDefault();
					}
					var swipePosition = ((touchState.options.direction == _hype.kRight) || (touchState.options.direction == _hype.kLeft)) ? firstFingerData.end.x : firstFingerData.end.y;
					var wrongWay = false;
					if ((touchState.options.direction == _hype.kRight) || (touchState.options.direction == _hype.kDown)) {
						wrongWay = ((touchState.minOrMaxForGesture - swipePosition) > 20);
					} else {
						wrongWay = ((swipePosition - touchState.minOrMaxForGesture) > 20);
					}
					var isCorrectDirection = ((touchState.gestureDirection === touchState.options.direction) || (currentGestureTime < 100));
					if (wrongWay || (isCorrectDirection == false) || _hype.currentlyHandlingDragGesture == true) {
						touchState.touchCancel(event);
					}
				}
			} else if (touchState.options.gestureType == _hype.kGestureTap) {
				touchState.touchCancel(event);
			}
			_hype.updateMinOrMaxPointForGesture(touchState, position);
		};
	}

	_hype.makeTouchEnd = function (touchState) {
		return function (event) {
			if (touchState.phase === _hype.kPhaseEnd || touchState.phase === _hype.kPhaseCancel) {
				return;
			}
		
			//If we are still in a touch another finger is down, then dont cancel
			if(event.touches && event.touches.length > 0) {
				return;
			}

			if (_hype.firedMouseClickActionAfterGesture == true && _hype.receivedGestureMove > 0) {
				touchState.touchCancel(event);
				return;
			}

			if (event.type == 'touchend') {
				if (event.changedTouches.length > 0 && touchState.options.type == _hype.kGestureTap) {
					_hype.preventMouseEventsForTouch(event.changedTouches[0]);
				}
			}
			
			var currentGestureTime = (new Date().getTime() - touchState.gestureStartTime);
			var firstFingerData = touchState.fingerData[0];
			touchState.distance = _hype.distanceBetweenPoints(firstFingerData.start, firstFingerData.end);
			touchState.gestureDirection = _hype.calculateDirection(firstFingerData.start, firstFingerData.end, currentGestureTime);
			touchState.velocity = _hype.distanceBetweenPoints(firstFingerData.lastPosition, firstFingerData.end) / (new Date().getTime() - touchState.lastTimePositionWasSet);
			touchState.phase = _hype.kPhaseEnd;
			
			//The number of fingers we want were matched, or on desktop we ignore
			var hasCorrectFingerCount = (touchState.fingerCount === 1 || !touchState.isTouchEvent);
			//We have an end value for the finger
			var hasEndPoint = firstFingerData.end.x !== 0;
			if (touchState.options.gestureType == _hype.kGestureDrag) {
				if (touchState.hasReceivedMove == false) {
					_hype.removeTouchEventHandlers(touchState);
					return;
				}
				_hype.dragOwnershipOfPropertiesByElement = {};
				if (touchState.options.timelinesToControl != null) {
					for (var i = 0; i < touchState.options.timelinesToControl.length; i++) {
						var timelineIdentifier = touchState.options.timelinesToControl[i]["b"];
						var timelineRun = _hype.timelineRunForIdentifier(timelineIdentifier);
						var continuePlaying = touchState.options.timelinesToControl[i]["y"];
						if (timelineRun) {
							var timelineDuration = _hype.timelineDuration(timelineRun);
							var translation;
							var forwardDirection = _hype.directionForDrag(touchState.options.timelinesToControl[i]);
							var translation;
							if (forwardDirection == _hype.kRight) {
								translation = firstFingerData.end.x - firstFingerData.start.x;
							} else if (forwardDirection == _hype.kLeft) {
								translation = firstFingerData.start.x - firstFingerData.end.x;
							} else if (forwardDirection == _hype.kUp) {
								translation = firstFingerData.start.y - firstFingerData.end.y;
							} else if (forwardDirection == _hype.kDown) {
								translation = firstFingerData.end.y - firstFingerData.start.y;
							}

							var time = translation / 150;
							
							time = time + touchState.timelineStartTime;
							time = Math.min(timelineDuration, time);
							time = Math.max(0, time);
							var continueTimeline = false;
							var reversed = false;
							var playScale = 1;
							if (continuePlaying == true) {
								if (touchState.velocity > 0.02) {
									continueTimeline = true;
									var lastDirection = _hype.calculateDirectionAlongAxis(firstFingerData.lastPosition, firstFingerData.end, touchState.options.timelinesToControl[i]);
									reversed = (lastDirection != forwardDirection);
									playScale = (touchState.velocity + 1) * 3;
								} else {
									// too slow to accurately predict direction find the nearest endpoint or timeline action with a pause and go in that direction
									var currentTimeInTimeline = _hype.currentTimeInTimelineWithIdentifier(timelineRun.timelineIdentifier, false);
									var distanceFromCurrentTime = currentTimeInTimeline;
									var timeToContinueTo = 0;
									for (var j = 0; j < timelineRun.animations.length; j++) {
										var animation = timelineRun.animations[j];
										if (animation["p"] == _hype.kAnimationTypeTimelineAction) {
											var actions = animation["s"]["a"];
											var containsPauseAction = false;
											for (var k = 0; k < actions.length; k++) {
												var action = actions[k];
												if (action["p"] == _hype.kActionPauseTimeline && action["b"] != null && action["b"] == timelineIdentifier) {
													 containsPauseAction = true;
													 break;
												}
											}
											if (containsPauseAction == true) {
												var startTime = animation["t"];
												var distance = Math.abs(startTime - currentTimeInTimeline);
												if (distance < distanceFromCurrentTime) {
													distanceFromCurrentTime = distance;
													timeToContinueTo = startTime;
												}
											}
										}
									}
									if (timelineDuration - currentTimeInTimeline < distanceFromCurrentTime) {
										timeToContinueTo = timelineDuration;
										distanceFromCurrentTime = timelineDuration - currentTimeInTimeline;
									}
									if (distanceFromCurrentTime != 0) {
										continueTimeline = true;
										reversed = (timeToContinueTo < currentTimeInTimeline);
										playScale = 3;
									}
								}
							}

							if (continueTimeline == true) {
								_hype.setTimelineRunPlayScale(timelineRun, playScale);
								_hype.continueTimelineWithIdentifier(timelineIdentifier, reversed, false);
							} else {
								_hype.setTimelineRunIsReversed(timelineRun, false);
							}
						}
					}
				}
				_hype.sendDragGestureUpdates(event, touchState);
			} else if (touchState.options.gestureType == _hype.kGestureSwipe) {
				var isCorrectDirection = (touchState.gestureDirection === touchState.options.direction);
				var isSwipe = (hasCorrectFingerCount && hasEndPoint && isCorrectDirection);

				if (isSwipe && (touchState.distance > touchState.options.threshold)) {
					var swipeType = "HypeSwipe" + touchState.options.direction.charAt(0).toUpperCase() + touchState.options.direction.slice(1) + "Action";
					var swipeActionFauxEvent = {"type" : swipeType};
					touchState.updateFunction(swipeActionFauxEvent);
				} else {
					touchState.touchCancel(event);
				}
			} else if (touchState.options.gestureType == _hype.kGestureTap) {
				if (currentGestureTime < 1500) {
					touchState.updateFunction(event);
				}
			} else {
				touchState.touchCancel(event);
			}
			_hype.removeTouchEventHandlers(touchState);
		};
	}

	_hype.addDragGestureHandler = function(value, element) {
		var options = { fingers : 1, continuousUpdates : true, gestureType : _hype.kGestureDrag };

		// add any timelines or elements to control to the options
		var actions = value["a"];
		var allActionsAreNone = true;
		for(var i = 0; i < actions.length; i++) {
			var action = actions[i];
			var type = action["p"];
			if(type != _hype.kActionNone) {
				allActionsAreNone = false;
			}
			if(type == _hype.kActionControlTimeline && action["b"] != null) {
				if (options.timelinesToControl == null) {
					options.timelinesToControl = Array();
				}
				var timelineControlOptions = {"b" : action["b"], "c" : action["c"], "A" : action["A"], "y" : action["y"]};
				options.timelinesToControl.push(timelineControlOptions);
			} else if (type == _hype.kActionControlElementPosition) {
				options.elementToControl = element;
			}
		}
		if (allActionsAreNone == false) {
			_hype.addGestureHandler(options, element, _hype.Apply.CreateActionHandler(actions, element));
		}
	}
	
	_hype.addGestureHandler = function (options, element, func) {
		var touchState = {
							distance : 0,
							gestureDirection : null,
							phase : _hype.kPhaseEnd,
							fingerCount : 0,
							fingerData : null,
							gestureStartTime : 0,
							timelineStartTime : 0,
							velocity : 0,
							lastTimePositionWasSet : 0,
							previousTime : 0,
							elementStartLeft : 0,
							elementStartTop : 0,
							touchMove : null,
							touchEnd : null,
							touchCancel : null,
							isTouchEvent : false,
							hasRemovedEventHandlers : false,

							options : options,
							updateFunction : func,
							elementForEvents : element
						};
		_hype.addEventHandler('touchstart', _hype.makeTouchStart(touchState), element, true);
		if (options.gestureType == _hype.kGestureDrag || options.gestureType == _hype.kGestureSwipe) {
			var elementToSetStyle = (element == document) ? document.getElementById(_hype.mainContentContainerID) : element;
			elementToSetStyle.style.msTouchAction = 'none';
			elementToSetStyle.style.touchAction = 'none';
		}
		// we do not want to add the mousedown event for tap because we will use the regular click event instead
		if (options.gestureType != _hype.kGestureTap) {
			_hype.addEventHandler('mousedown', _hype.makeTouchStart(touchState), element, true);
		}
	}
		
	_hype.addActionHandler = function (eventName, element, func, interactsWithDrag) {
		_hype._addActionHandler(eventName, element, func, interactsWithDrag);
		
		// need to make dummy to set _hype.mouseOverElement correctly
		if(eventName == "mouseover") {
			_hype._addActionHandler("mouseout", element, (function (e) { return; }), interactsWithDrag);
		} else if(eventName == "mouseout") {
			_hype._addActionHandler("mouseover", element, (function (e) { return; }), interactsWithDrag);
		}
	}
	
	_hype._addActionHandler = function (eventName, element, func, interactsWithDrag) {
		if(eventName == "click" || eventName == "mouseup") {
			// Click and mouseup events triggered by touch on the Surface don't work if there are drag or swipe action handlers because mousemove is always triggered, so use pointerup events on the Surface
			if (window.navigator.pointerEnabled) {
				eventName = "pointerup";
			} else if (window.navigator.msPointerEnabled) {				
				eventName = "MSPointerUp";
			} else {
				// convert click events to mouse up events because in the case of buttons, the innerhtml change can squash the click event
				eventName = "mouseup";
			}
		}

		var handler = (function (e) {
			e = (e) ? e : window.event;
			
			// fixes behavior where inner elements cause bubbled mouseovers/mouseouts
			if(e.type == "mouseover") {
				var mouseOverAlreadyBeganForFunc = false;
				for(var i = 0; i < _hype.mouseOverElementFunctions.length; i++) {
				    if (_hype.mouseOverElementFunctions[i] == func) {
				        mouseOverAlreadyBeganForFunc = true;
				        break;
				    }
				}
				if(mouseOverAlreadyBeganForFunc == true) {
					// we shouldn't accept other mouse overs
					return;	
				} else {
					// we're really over something, and continue with this method
					_hype.mouseOverElementFunctions.push(func);
				}
			} else if(e.type == "mouseout") {
				// make sure the mouseout element is on the actual scene
				if(_hype.scenes[_hype.currentSceneIndex]["v"][_hype.idReverseMapping[element.id]] == null) {
					return;
				}
			
				// get position and make sure it is really outside the element
				// code adapted from http://www.quirksmode.org/js/events_properties.html
				if(!e) e = window.event;
				var position = _hype.pagePosition(e);
				var posx = position.x;
				var posy = position.y;

				var mouseOutElement = document.elementFromPoint(posx, posy);
				var searchElement = this;												
				var currentElement = mouseOutElement;
				while(currentElement != null && currentElement != searchElement && currentElement.nodeName != 'BODY') {
					currentElement = currentElement.parentNode;
				}
				
				if(currentElement == searchElement) {
					// we're still inside
					return;
				} else {
					// set that we're really out of something, and continue with the method
					_hype.mouseOverElementFunctions = Array();
				}
			} else if (e.type == "touchstart") {
				if (e.touches.length > 0) {
					_hype.preventMouseEventsForTouch(e.touches[0]);
				}
			} else if (e.type == "touchend") {
				if (e.changedTouches.length > 0) {
					_hype.preventMouseEventsForTouch(e.changedTouches[0]);
				}
			}

			if (interactsWithDrag == true && (e.type == "mouseclick" || e.type == "mouseup" || e.type == "touchend")) {
				if (_hype.receivedGestureMove > 0) {
					return;
				}
				_hype.firedMouseClickActionAfterGesture = true;
			}

			func(e);
		});
		_hype.addEventHandler(eventName, handler, element, true);
	}

	_hype.contentContainerClickHandler = function (event) {
		for (var i = 0; i < _hype.touchEventCoordinates.length; i++) {
			var position = _hype.pagePosition(event);
			if (Math.abs(position.x - _hype.touchEventCoordinates[i].x) < 25 && Math.abs(position.y - _hype.touchEventCoordinates[i].y) < 25) {
				event.stopPropagation();
				event.preventDefault();
			}
		}
	}

	_hype.preventMouseEventsForTouch = function (touch) {
		var position = _hype.pagePosition(touch);
		_hype.touchEventCoordinates.push(position);
		window.setTimeout(_hype.removeCoordinates, 2500);
	}

	_hype.removeCoordinates = function () {
		_hype.touchEventCoordinates.splice(0, 1);
	}
		
	_hype.loadScene = function (sceneIdentifier) {
		_hype.pauseVideos();
		_hype.stopAllTimelineRuns();
		_hype.mouseOverElementFunctions = Array();
		
		_hype.currentSceneIndex = _hype.indexOfSceneWithIdentifier(sceneIdentifier);
		
		var scene = document.getElementById(_hype.idMapping[sceneIdentifier]);
		var sceneIndex = scene.getAttribute("HYPE_scene_index");
		var domElements = getElementsByClassName("HYPE_element", scene);
		var domElementIds = []; // need this because simply iterating over domElements will be wrong if the dom hierarchy changes
		
		_hype.currentValues[sceneIdentifier] = Array();
		
		for(var i = 0; i < domElements.length; i++) {
			domElementIds.push(domElements[i].id);
		}


		_hype.beginFrameUpdateQueue();


		
		for(var i = 0; i < domElementIds.length; i++) {
			var domElementId = domElementIds[i];
			var domElement = document.getElementById(domElementId);
			var initialValues = _hype.scenes[sceneIndex]["v"][_hype.idReverseMapping[domElementId]];
			if(initialValues == null) {
				continue;
			}
			
			// create an array to hold all current values (used for relative keyframes)
			_hype.currentValues[_hype.idReverseMapping[domElementId]] = Array();
			
			// always set flexible layout options first
			_hype.applyValue(domElement, 'bS', initialValues['bS'], true);

			// always set dimensions next
			//!! this is a bit of a hack for IE, since I might rewrite elements... the correct fix would be to lookup the elementbyid each time or to make outer divs that don't change
			_hype.applyValue(domElement, 'b', initialValues['b'], true);
			_hype.applyValue(domElement, 'a', initialValues['a'], true);
			_hype.applyValue(domElement, 'c', initialValues['c'], true);
			_hype.applyValue(domElement, 'd', initialValues['d'], true);
			_hype.applyValue(domElement, 'j', initialValues['j'], true);

			// apply all other attributes
			for(var attributeIdentifier in initialValues) {
				if(initialValues.hasOwnProperty(attributeIdentifier) == false) {
					continue;
				}
				var initialValue = initialValues[attributeIdentifier];
				_hype.applyValue(domElement, attributeIdentifier, initialValue, true);
			}
			

		}


		_hype.drainFrameUpdateQueue();


		var timelines = _hype.scenes[sceneIndex]["T"];
		for (var timelineIdentifier in timelines) {
			var animations = _hype.scenes[_hype.currentSceneIndex]["T"][timelineIdentifier]["a"];
			_hype.createTimelineRun(timelineIdentifier, animations);
		}

		var keyHandlerNames = {"C" : "keydown", "D" : "keyup", "E" : "keypress"};
		for(var handlerName in keyHandlerNames) {
			if(keyHandlerNames.hasOwnProperty(handlerName) == false || _hype.scenes[sceneIndex][handlerName] == null) {
				continue;
			}
				
			var keyFunction = _hype.Apply.CreateActionHandler(_hype.scenes[sceneIndex][handlerName]["a"], document);
			var eventName = keyHandlerNames[handlerName];
			_hype.currentSceneActionHandlers[eventName] = keyFunction;
			if (document.addEventListener) {
				document.addEventListener(eventName, keyFunction, false);
			} else if (document.attachEvent) {
				document.attachEvent("on" + eventName, keyFunction);
			}
		}
		var swipeActionNames = {"G" : _hype.kLeft, "H" : _hype.kRight, "I" : _hype.kUp, "J" : _hype.kDown};
		for (var swipeName in swipeActionNames) {
			var swipeHandler = _hype.scenes[sceneIndex][swipeName];
			if (swipeHandler) {
				var swipeActions = swipeHandler["a"];
				var allActionsAreNone = true;
				for(var i = 0; i < swipeActions.length; i++) {
					var action = swipeActions[i];
					var type = action["p"];
					if(type != _hype.kActionNone) {
						allActionsAreNone = false;
						break;
					}
				}
				if (allActionsAreNone == false) {
					 var swipeFunction = _hype.Apply.CreateActionHandler(swipeActions, document);
					 var options = {
										fingers : 1,
										direction : swipeActionNames[swipeName],
										threshold : 30,
										gestureType : _hype.kGestureSwipe
									};
					_hype.addGestureHandler(options, scene, swipeFunction);
				}
			}
		}
		var dragHandler = _hype.scenes[sceneIndex]["K"];
		if (dragHandler) {
			_hype.addDragGestureHandler(dragHandler, document);
		}
	}

	_hype.completeSceneTransition = function (currentSceneContainer, nextSceneContainer, timelineIdentifier) {
		_hype.inSceneTransition = false;
	
		if(timelineIdentifier != null) {
			delete _hype.scenes[_hype.currentSceneIndex]["T"][timelineIdentifier];
		}
	
		nextSceneContainer.style.display = "block";
		nextSceneContainer.removeAttribute("HYP_ah");
		nextSceneContainer.removeAttribute("HYP_e");
		_hype.applyValue(nextSceneContainer, "b", "0", true);
		_hype.applyValue(nextSceneContainer, "a", "0", true);
		_hype.applyValue(nextSceneContainer, "z", "1", true);
		_hype.applyValue(nextSceneContainer, "e", "1", true);
		nextSceneContainer.style.width = "100%"; // use direct since applyValue doesn't think width/height should be percents
		nextSceneContainer.style.height = "100%"; // use direct since applyValue doesn't think width/height should be percents
	
		// cleanup after transition
		if(currentSceneContainer != null && currentSceneContainer != nextSceneContainer) {
			currentSceneContainer.style.display = "none";
			currentSceneContainer.removeAttribute("HYP_ah");
			currentSceneContainer.removeAttribute("HYP_e");
			_hype.applyValue(currentSceneContainer, "b", "0", true);
			_hype.applyValue(currentSceneContainer, "a", "0", true);
			_hype.applyValue(currentSceneContainer, "z", "0", true);
			_hype.applyValue(currentSceneContainer, "e", "1", true);
			currentSceneContainer.style.width = "100%"; // use direct since applyValue doesn't think width/height should be percents
			currentSceneContainer.style.height = "100%"; // use direct since applyValue doesn't think width/height should be percents
		}
		
		var mainContentContainer = document.getElementById(_hype.mainContentContainerID);
		mainContentContainer.style[_hype.kSizeOptimizationWebKitPrefix + "perspective"] = null;
		mainContentContainer.style[_hype.kSizeOptimizationWebKitPrefix + "transform-style"] = "flat";
		mainContentContainer.style["-moz-perspective"] = null;
		mainContentContainer.style["perspective"] = null;
		

		
		// begin any animations
		_hype.startTimelineRun(_hype.kTimelineDefaultIdentifier, null, false);
		
		// trigger event handlers
		var sceneLoadFauxEvent = {"type" : "HypeSceneLoad"};
		var eventResult = _hype.notifyEvent(sceneLoadFauxEvent, null);

		if(eventResult !== false) {
			// scene load event (happens after main timeline starts so it can be paused if need be)
			var sceneIndex = _hype.indexOfSceneWithIdentifier(_hype.idReverseMapping[nextSceneContainer.id]);
			var onSceneLoadData = _hype.scenes[sceneIndex]["A"];
			if(onSceneLoadData != null) {
				var onSceneLoadActions = onSceneLoadData["a"];
				var onLoadFunction = _hype.Apply.CreateActionHandler(onSceneLoadActions, null);
				onLoadFunction(sceneLoadFauxEvent);
			}
		}
	}

	_hype.untransformValue = function (value, transformerClassType) {
		if(transformerClassType == 0 || transformerClassType == 1) {
			return parseInt(value, 10);
		} else if(transformerClassType == 2) {
			return parseFloat(value);
		} else if(transformerClassType == 3) {
			return parseFloat(value);
		} else if(transformerClassType == 4) {
			return (parseFloat(value) / 100.0);
		} else if(transformerClassType == 5) {
			return hex2num(value);
		}
		return value;
	}

	_hype.transformValue = function (value, transformerClassType) {
		if(transformerClassType == 0 || transformerClassType == 1) {
			return "" + (Math.round(value)) + "px";
		} else if(transformerClassType == 2) {
			return "" + value + "deg";
		} else if(transformerClassType == 3) {
			return "" + value + "";
		} else if(transformerClassType == 4) {
			return "" + (parseFloat(value) * 100.0) + "%";
		} else if(transformerClassType == 5) {
			return num2hex(value);
		}
		
		return value;
	}

	// Color methods from http://www.openjs.com/scripts/graphics/hex_color_rbg_value_converter.php

	//Convert a hex value to its decimal value - the inputted hex must be in the
	//	format of a hex triplet - the kind we use for HTML colours. The function
	//	will return an array with three values.
	function hex2num(hex) {
		if(hex.charAt(0) == "#") hex = hex.slice(1); //Remove the '#' char - if there is one.
		hex = hex.toUpperCase();
		var hex_alphabets = _hype.kSizeOptimizationHexAlphabet;
		var value = new Array(3);
		var k = 0;
		var int1,int2;
		for(var i=0;i<6;i+=2) {
			int1 = hex_alphabets.indexOf(hex.charAt(i));
			int2 = hex_alphabets.indexOf(hex.charAt(i+1)); 
			value[k] = (int1 * 16) + int2;
			k++;
		}
		return(value);
	}

	//Give a array with three values as the argument and the function will return
	//	the corresponding hex triplet.
	function num2hex(triplet) {
		var hex_alphabets = _hype.kSizeOptimizationHexAlphabet;
		var hex = "#";
		var int1,int2;
		for(var i=0;i<3;i++) {
			int1 = triplet[i] / 16;
			int2 = triplet[i] % 16;

			hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2); 
		}
		return(hex);
	}

	_hype.applyValue = function (element, attributeIdentifier, value, shouldCache) {
		if(typeof value == "undefined") {
			return;
		}
		if(typeof value == "number" && _hype.attributeTransformerMapping[attributeIdentifier] == 0) {
			value = "" + value + "px";
		}
	
		try {
			if(shouldCache == true) {
				_hype.currentValues[_hype.idReverseMapping[element.id]][attributeIdentifier] = value;
			}
			_hype.Apply[attributeIdentifier](value, element);
		} catch(err) {
			_hype.log("APPLY ERR " + attributeIdentifier + "=" + value + " : " + err);
		}
	}
	
	_hype.pauseVideos = function () {
		var currentSceneContainer = _hype.currentSceneElement();
		if(currentSceneContainer != null) {
			var videos = currentSceneContainer.getElementsByTagName("video");
			for(var i = 0; i < videos.length; i++) {
				if(videos[i].pause) {
					videos[i].pause();
				}
			}
			var embeds = currentSceneContainer.getElementsByTagName("embed");
			for(var i = 0; i < embeds.length; i++) {
				if(embeds[i].id.indexOf("embedobj_") == 0) {
					embeds[i].parentElement.innerHTML = "";
				}
			}
		}
	}

	_hype.applyButtonFromTimelineRun = function (event, timelineIdentifier, triggeringObjectIdentifier) {
		var timeline = _hype.scenes[_hype.currentSceneIndex]["T"][timelineIdentifier];
		if(timeline == null) {
			return;
		}
		
		var animations = timeline["a"];
		var element = document.getElementById(_hype.idMapping[triggeringObjectIdentifier]);
		
		// clear any previous button states first
		var buttonHoverValue = _hype.scenes[_hype.currentSceneIndex]["v"][triggeringObjectIdentifier]['aM'];
		if(buttonHoverValue != null && buttonHoverValue != timelineIdentifier) {
			_hype.resetButtonFromTimelineRun(event, buttonHoverValue, triggeringObjectIdentifier);
		}

		var buttonPressValue = _hype.scenes[_hype.currentSceneIndex]["v"][triggeringObjectIdentifier]['aN'];
		event = (event) ? event : window.event;		
		if(event.type != "mouseup" && buttonPressValue != null && buttonPressValue != timelineIdentifier && buttonHoverValue != timelineIdentifier) {
			_hype.resetButtonFromTimelineRun(event, buttonPressValue, triggeringObjectIdentifier);
		}
		
		for(var i = 0; i < animations.length; i++) {
			var animation = animations[i];
			var oid = animation["o"];
			if(oid != triggeringObjectIdentifier) {
				// should always be equal
				continue;
			}
			
			var identifier = animation["i"];
			var startValue = animation["e"];
						
			_hype.applyValue(element, identifier, startValue, false);
		}
	}
	
	_hype.resetButtonFromTimelineRun = function (event, timelineIdentifier, triggeringObjectIdentifier) {
		var timeline = _hype.scenes[_hype.currentSceneIndex]["T"][timelineIdentifier];
		if(timeline == null) {
			return;
		}
		var animations = timeline["a"];
		var element = document.getElementById(_hype.idMapping[triggeringObjectIdentifier]);
		
		for(var i = 0; i < animations.length; i++) {
			var animation = animations[i];
			var oid = animation["o"];
			if(oid != triggeringObjectIdentifier) { // sanity check, should always be equal
				continue;
			}
			
			var identifier = animation["i"];
			_hype.applyValue(element, identifier, _hype.currentValues[triggeringObjectIdentifier][identifier], false);
		}
		
		event = (event) ? event : window.event;
		
		// if this was a mouse click and there's hover values, reinstate those
		if(event.type == "mouseup") {
			var buttonHoverValue = _hype.scenes[_hype.currentSceneIndex]["v"][triggeringObjectIdentifier]['aM'];
			if(buttonHoverValue != null) {
				_hype.applyButtonFromTimelineRun(event, buttonHoverValue, triggeringObjectIdentifier);
			}
		}
	}
		
	_hype.startTimelineNamed = function (timelineName, direction) { // public, do not change signature without wrapping in _hype['API']!
		var timelineIdentifier = _hype.timelineIdentifierForTimelineNamed(timelineName);
		if(timelineIdentifier == null) {	
			timelineIdentifier = _hype.kTimelineDefaultIdentifier;
		}
		if (direction == undefined) {
			direction = _hype.kDirectionForward;
		}
		var reversed = (direction == _hype.kDirectionReverse) ? true : false;
		_hype.startTimelineRun(timelineIdentifier, null, reversed);
	}

	_hype.pauseTimelineNamed = function (timelineName) { // public, do not change signature without wrapping in _hype['API']!
		var timelineIdentifier = _hype.timelineIdentifierForTimelineNamed(timelineName);
		if(timelineIdentifier == null) {	
			timelineIdentifier = _hype.kTimelineDefaultIdentifier;
		}
		_hype.pauseTimelineWithIdentifier(timelineIdentifier, null);
	}

	_hype.continueTimelineNamed = function (timelineName, direction) { // public, do not change signature without wrapping in _hype['API']!
		var timelineIdentifier = _hype.timelineIdentifierForTimelineNamed(timelineName);
		if(timelineIdentifier == null) {	
			timelineIdentifier = _hype.kTimelineDefaultIdentifier;
		}
		if (direction == undefined) {
			direction = _hype.kDirectionForward;
		}
		var reversed = (direction == _hype.kDirectionReverse) ? true : false;
		_hype.continueTimelineWithIdentifier(timelineIdentifier, reversed, true);
	}
	
	_hype.goToTimeInTimelineNamed = function (time, timelineName) { // public, do not change signature without wrapping in _hype['API']!
		var timelineIdentifier = _hype.timelineIdentifierForTimelineNamed(timelineName);
		if(timelineIdentifier == null) {	
			timelineIdentifier = _hype.kTimelineDefaultIdentifier;
		}		
		_hype.goToTimeInTimelineWithIdentifier(time, timelineIdentifier);
	}

	_hype.currentTimeInTimelineNamed = function (timelineName) { // public, do not change signature without wrapping in _hype['API']!
		var timelineIdentifier = _hype.timelineIdentifierForTimelineNamed(timelineName);
		if(timelineIdentifier == null) {	
			timelineIdentifier = _hype.kTimelineDefaultIdentifier;
		}
		return _hype.currentTimeInTimelineWithIdentifier(timelineIdentifier, true);
	}

	_hype.durationForTimelineNamed = function (timelineName) { // public, do not change signature without wrapping in _hype['API']!
		var timelineIdentifier = _hype.timelineIdentifierForTimelineNamed(timelineName);
		if(timelineIdentifier == null) {	
			timelineIdentifier = _hype.kTimelineDefaultIdentifier;
		}
		var timelineRun = _hype.timelineRunForIdentifier(timelineIdentifier);
		return _hype.timelineDuration(timelineRun);
	}

	_hype.isPlayingTimelineNamed = function (timelineName) { // public, do not change signature without wrapping in _hype['API']!
		var timelineIdentifier = _hype.timelineIdentifierForTimelineNamed(timelineName);
		if(timelineIdentifier == null) {	
			timelineIdentifier = _hype.kTimelineDefaultIdentifier;
		}
		var timelineRun = _hype.timelineRunForIdentifier(timelineIdentifier);
		return timelineRun.isPlaying;
	}

	_hype.currentDirectionForTimelineNamed = function (timelineName) { // public, do not change signature without wrapping in _hype['API']!
		var timelineIdentifier = _hype.timelineIdentifierForTimelineNamed(timelineName);
		if(timelineIdentifier == null) {	
			timelineIdentifier = _hype.kTimelineDefaultIdentifier;
		}
		var timelineRun = _hype.timelineRunForIdentifier(timelineIdentifier);
		return (timelineRun.isReversed) ? _hype.kDirectionReverse : _hype.kDirectionForward;
	}

	_hype.resetTimelineStateAfterScaleOrDirectionChange = function (timelineRun, time) {
		if (timelineRun.animationStartTime != null) {

			timelineRun.animationStartTime = _hype.currentTimeForVideoExport - (time / timelineRun.playScale);


		}
	}

	_hype.setTimelineRunPlayScale = function (timelineRun, playScale) {
		if (timelineRun.playScale != playScale) {
			var currentTime = _hype.currentTimeInTimelineWithIdentifier(timelineRun.timelineIdentifier, false);
			timelineRun.playScale = playScale;
			_hype.resetTimelineStateAfterScaleOrDirectionChange(timelineRun, currentTime);
		}
	}
	
	_hype.setTimelineRunIsReversed = function (timelineRun, reversed) {
		var currentTime = _hype.currentTimeInTimelineWithIdentifier(timelineRun.timelineIdentifier, false);
		if (reversed != timelineRun.isReversed) {
			timelineRun.isReversed = reversed;
			if (reversed == true && timelineRun.reversedAnimations == null) {
				// copy the array
				timelineRun.reversedAnimations = timelineRun.animations.slice(0);
				// sort the animations in reverse order
				timelineRun.reversedAnimations.sort(function(a,b) {
					var aStartTime = _hype.startTimeForAnimation(a, timelineRun);
					var bStartTime = _hype.startTimeForAnimation(b, timelineRun);
					if (aStartTime == bStartTime) {
						return b['originalIndex'] - a['originalIndex'];
					} else {
						return (aStartTime - bStartTime);
					}
				});
			}

			var timelineDuration = _hype.timelineDuration(timelineRun);
			timelineRun.activeAnimations = Array();
			timelineRun.nextAnimationIndex = 0;
			timelineRun.pauseTime = timelineDuration - timelineRun.pauseTime;

			var currentTime = timelineDuration - currentTime;
			_hype.resetTimelineStateAfterScaleOrDirectionChange(timelineRun, currentTime);
			if (timelineRun.animationStartTime != null) {

				var timeToGoTo = _hype.quantizeTime(_hype.currentTimeForVideoExport - timelineRun.animationStartTime, timelineRun);


				_hype.heartbeatGoToTimeForTimelineRun(timeToGoTo, timelineRun, false, false);
			}
		}
	}

	_hype.createTimelineRun = function (timelineIdentifier, animations) {
		var timelineRun = {
							nextAnimationIndex: 0,
							activeAnimations: Array(),
							animations: animations,
							timelineIdentifier: timelineIdentifier,
							//triggeringObjectIdentifier: null,
							isPlaying: false,
							pauseTime: 0,
							playScale : 1,
							isReversed : false,
							//reversedAnimations : null,
							relativeMotionPaths : {}
						};
		_hype.timelineRuns.push(timelineRun);
	}

	_hype.timelineIdentifierForTimelineNamed = function (timelineName) {
		var timelines = _hype.scenes[_hype.currentSceneIndex]["T"];
		for(var timelineIdentifier in timelines) {
			if(timelines.hasOwnProperty(timelineIdentifier) == false) {
				continue;
			}
			if(timelines[timelineIdentifier]["n"] == timelineName) {
				return timelineIdentifier;
			}
		}
		return null;
	}
	
	_hype.timelineRunForIdentifier = function (timelineIdentifier) {
		for(var i = 0; i < _hype.timelineRuns.length; i++) {
			var checkTimelineRun = _hype.timelineRuns[i];
			if(checkTimelineRun.timelineIdentifier == timelineIdentifier) {
				return checkTimelineRun;
			}
		}
		return null;
	}

	_hype.setupRelativeMotionPath = function (timelineRun, animation) {
		var oid = animation["o"];
		var identifier = animation["i"];

		var motionPathOid = animation["a"];
		if (motionPathOid != undefined) {
			var motionPath = _hype.motionPaths[motionPathOid];
			var motionPathCopy = {};
			var bezierCurves = Array();
			var xDifference = 0;
			var yDifference = 0;
			for (var i = 0; i < motionPath.bezierCurves.length; i++) {
				var bezierCurve = motionPath.bezierCurves[i];
				if (i == 0) {
					var width = _hype.currentWidthForElementWithOid(oid);
					var height = _hype.currentHeightForElementWithOid(oid);
					var transformerClassType = _hype.attributeTransformerMapping["c"];
					var left = _hype.untransformValue(_hype.currentValues[oid]['a'], transformerClassType);
					var top = _hype.untransformValue(_hype.currentValues[oid]['b'], transformerClassType);
					xDifference = (left + width / 2) - bezierCurve.startPoint.x;
					yDifference = (top + height / 2) - bezierCurve.startPoint.y;
				}
				bezierCurves.push({
					startPoint: {x: bezierCurve.startPoint.x + xDifference, y: bezierCurve.startPoint.y + yDifference},
					startControlPoint: {x: bezierCurve.startControlPoint.x + xDifference, y: bezierCurve.startControlPoint.y + yDifference},
					endControlPoint: {x: bezierCurve.endControlPoint.x, y: bezierCurve.endControlPoint.y},
					endPoint: {x: bezierCurve.endPoint.x, y: bezierCurve.endPoint.y},
					length: 0,
					interpolationPoints: Array()
				});
				xDifference = 0;
				yDifference = 0;
			}
			motionPathCopy.bezierCurves = bezierCurves;
			_hype.calculateInterpolationPointsForMotionPath(motionPathCopy);
			timelineRun.relativeMotionPaths[motionPathOid] = motionPathCopy;
			var element = document.getElementById(_hype.idMapping[oid]);
			if (element.getAttribute("HYP_ae") == "YES") {
				var motionPathRotationAnimation;
				for(var j = 0; j < timelineRun.animations.length; j++) {
					var rotationAnimation = timelineRun.animations[j];
					if (rotationAnimation["i"] == "bO") {
						motionPathRotationAnimation = rotationAnimation;
						break;
					}
				}
				if (motionPathRotationAnimation != null) {
					var point = _hype.topLeftPointForMotionPath(motionPathCopy, 0, oid);
					motionPathRotationAnimation["b"] = point.rotationAngle;
				}
			}

		}
	}
	
	_hype.setStartValueForRelativeKeyframes = function (timelineRun, replaceExisting) {
		// set the start value to be used for relative animations
		for(var j = 0; j < timelineRun.animations.length; j++) {
			var oid = timelineRun.animations[j]["o"];
			var identifier = timelineRun.animations[j]["i"];
			var isRelative = timelineRun.animations[j]["r"];
			try { // try as a guard against an animation referencing an object that doesn't exist
				if(isRelative == true && _hype.currentValues[oid][identifier] != null) {
					if (replaceExisting || timelineRun.animations[j]["b"] == null) {
						timelineRun.animations[j]["b"] = _hype.currentValues[oid][identifier];
					}
				}
			} catch(err) { }
		}
	}

	_hype.startTimelineRun = function (timelineIdentifier, triggeringObjectIdentifier, reversed) {
		// reset the timeline to the initial values
		for(var i = 0; i < _hype.timelineRuns.length; i++) {
			var checkTimelineRun = _hype.timelineRuns[i];
			if(checkTimelineRun.timelineIdentifier == timelineIdentifier) {
				//!! I'm just going to stop it instead of return, this could cause behaviors where the timeline never really finishes
				_hype.stopTimelineRun(checkTimelineRun);
			}
		}
		
		var timelineRun = _hype.timelineRunForIdentifier(timelineIdentifier);
		if (timelineRun == null) {
			return;
		}
		
		timelineRun.triggeringObjectIdentifier = triggeringObjectIdentifier;
		_hype.activeTimelineRuns.push(timelineRun);
		
		if(_hype.scenes[_hype.currentSceneIndex]["T"][timelineRun.timelineIdentifier] == null) {
			_hype.timelineRunCompleteCallback(timelineRun);
			return null;
		}

		var replaceExistingStartValues = (reversed == false);
		_hype.setStartValueForRelativeKeyframes(timelineRun, replaceExistingStartValues);

		_hype.setTimelineRunIsReversed(timelineRun, reversed);
		_hype.stopIntersectingTimelinesFromTime(timelineRun, 0);
		_hype.applyFirstValuesInTimelineFromTime(timelineRun, 0);
		timelineRun.isPlaying = true;
		timelineRun.nextAnimationIndex = 0;
		timelineRun.activeAnimations = Array();
		timelineRun.relativeMotionPaths = {};

		timelineRun.animationStartTime = _hype.currentTimeForVideoExport;



		if(timelineRun.animations.length > 0) {
			// I just use requestHeartbeat instead of the AtTime variant due to what looks like a timing issue in firefox with transitions that start at time 0
			_hype.requestHeartbeat();
		}
		
		// create a separate callback for when the timeline run is complete (probably should be replaced with events later)

		return timelineRun;
	}
		
	_hype.stopIntersectingTimelinesFromTime = function (timelineRun, time) {
		var animations = timelineRun.animations;
		
		// put any items in this timelineRun into the timelineRunOwnershipOfPropertiesByElement listing
		for(var i = 0; i < animations.length; i++) {
			var animation = animations[i];
			var oid = animation["o"];
			var identifier = animation["i"];
			var startTime = _hype.startTimeForAnimation(animation, timelineRun);
			var duration = animation["d"];
            var endTime = _hype.quantizeTime(startTime + duration, timelineRun);
			
			// we expect the array for oid to be definied even if the timeline does not have ownership
			if(_hype.timelineRunOwnershipOfPropertiesByElement[oid] === undefined) {
				_hype.timelineRunOwnershipOfPropertiesByElement[oid] = {};
			}
			
			if(endTime >= time) {
				if (_hype.dragOwnershipOfPropertiesByElement[oid] === undefined || _hype.dragOwnershipOfPropertiesByElement[oid][identifier] === undefined || _hype.dragOwnershipOfPropertiesByElement[oid][identifier] == timelineRun.timelineIdentifier) {
					_hype.timelineRunOwnershipOfPropertiesByElement[oid][identifier] = timelineRun.timelineIdentifier;
				}
			}
		}
		
		_hype.stopActiveAnimationsWithoutOwnershipExcludingTimelineRun(timelineRun);
	}

	_hype.stopActiveAnimationsWithoutOwnershipExcludingTimelineRun = function (timelineRun) {
		// stop any active animations using these elements/properties
		for(var i = 0; i < _hype.activeTimelineRuns.length; i++) {
			var checkTimelineRun = _hype.activeTimelineRuns[i];
			if(timelineRun != null && checkTimelineRun.timelineIdentifier == timelineRun.timelineIdentifier) {
				continue;
			}
		
			for(var j = 0; j < checkTimelineRun.activeAnimations.length; j++) {
				var oid = checkTimelineRun.activeAnimations[j]["o"];
				var identifier = checkTimelineRun.activeAnimations[j]["i"];
				if(_hype.timelineRunOwnershipOfPropertiesByElement[oid][identifier] != checkTimelineRun.timelineIdentifier) {
					checkTimelineRun.activeAnimations.splice(j, 1);
					j--;
				}
			}
		}
	}
	
	_hype.stopTimelineRun = function (timelineRun) {
		// clear values
		window.clearTimeout(timelineRun.animationCompletionTimeout);
		timelineRun.nextAnimationIndex = 0;
		timelineRun.activeAnimations = Array();
		timelineRun.triggeringObjectIdentifier = null;
		timelineRun.animationStartTime = null;
		timelineRun.isPlaying = false;
		timelineRun.playScale = 1;
		timelineRun.pauseTime = _hype.timelineDuration(timelineRun);
		
		var timelineIndex = myIndexOf(_hype.activeTimelineRuns, timelineRun);
		if (timelineIndex != -1) {
			_hype.activeTimelineRuns.splice(timelineIndex, 1);
		}
	}
	
	_hype.stopAllTimelineRuns = function () {
		// stop traditional timeline runs
		while(_hype.activeTimelineRuns.length > 0) {
			_hype.stopTimelineRun(_hype.activeTimelineRuns[0]);
		}
	}
	
	_hype.requestAnimFrame = function () {
		var timerBasedFunction = 	(function(callback, element) {
										window.setTimeout(callback, ((1 * 1000.0) / _hype.fps));
									});

		// workaround for <rdar://problem/12363449> webkitRequestAnimationFrame does not work between pages on Mobile Safari
		//!! remove when fixed!
		if(_hype.browserInfo.ios != null) {
			return timerBasedFunction;
		}

		return  window['requestAnimationFrame']			|| 
				window['webkitRequestAnimationFrame']	|| 
				window['mozRequestAnimationFrame']		|| 
				timerBasedFunction;
	}
		
	_hype.requestHeartbeat = function () {
		if(_hype.animationFrameRequested === true) {
			return;
		}
		_hype.animationFrameRequested = true;
		_hype.requestAnimFrame()((function () { _hype.animationFrameRequested = false; _hype.heartbeat(); }));
	}

	_hype.requestHeartbeatAtTime = function (nextAnimationTick) {
		window.setTimeout((function () { _hype.animationFrameRequested = false; _hype.heartbeat(); }), (nextAnimationTick * 1000.0));
	}
	
	_hype.timelineDuration = function (timelineRun) {
        var duration = _hype.scenes[_hype.currentSceneIndex]["T"][timelineRun.timelineIdentifier]["d"];
		return _hype.quantizeTime(duration, timelineRun);
	}
			
	_hype.quantizeTimeWithFramesPerSecond = function (time, framesPerSecond) {
	    var seconds = Math.floor(time);
		var frames = Math.round(((time - seconds) * framesPerSecond)) / framesPerSecond;
	    return (seconds + frames);
	}
							 
	_hype.quantizeTime = function (time, timelineRun) {
		var framesPerSecond = _hype.scenes[_hype.currentSceneIndex]["T"][timelineRun.timelineIdentifier]["f"];
		return _hype.quantizeTimeWithFramesPerSecond(time, framesPerSecond);
	}
	
	_hype.timelineRunCompleteCallback = function (timelineRun) {
		// make sure animations at the end are triggered before we remove the timeline
		var significantChangeDuringHeartbeat = _hype.heartbeatGoToTimeForTimelineRun(_hype.timelineDuration(timelineRun), timelineRun, true, true);
		if (significantChangeDuringHeartbeat) {
			return;
		}

		_hype.stopTimelineRun(timelineRun);
		
		if(_hype.timelineCompletionOverrideCallback != null && _hype.timelineIdentifierForCompletionOverrideCallback == timelineRun.timelineIdentifier) {
			// save the callback first so we can call it last, as the call may set the timelineCompletionOverrideCallback itself (in the case of scene transitions, #5074)
			var callback = _hype.timelineCompletionOverrideCallback;
			_hype.timelineCompletionOverrideCallback = null;
			_hype.timelineIdentifierForCompletionOverrideCallback = null;
			callback();
		} else {
			// trigger event handlers
			var timelines = _hype.scenes[_hype.currentSceneIndex]["T"];
			var timelineCompleteFauxEvent = {"type" : "HypeTimelineComplete", "timelineName" : timelines[timelineRun.timelineIdentifier]["n"]};
			var eventResult = _hype.notifyEvent(timelineCompleteFauxEvent, null);
			
			if(eventResult !== false) {				
				// perform action if exists
				var currentSceneContainer = _hype.currentSceneElement();
				if(currentSceneContainer != null) {
					var sceneIndex = _hype.indexOfSceneWithIdentifier(_hype.currentSceneIdentifier());
					var onSceneTimelineCompleteData = _hype.scenes[sceneIndex]["F"];
					if(onSceneTimelineCompleteData != null) {
						var onSceneTimelineCompleteActions = onSceneTimelineCompleteData["a"];
						var onSceneTimelineCompleteFunction = _hype.Apply.CreateActionHandler(onSceneTimelineCompleteActions, currentSceneContainer);
						onSceneTimelineCompleteFunction(timelineCompleteFauxEvent);
					}
				}
			}
		}
	}
	
	_hype.pauseTimelineWithIdentifier = function (timelineIdentifier, pauseTime) {
		var timelineRun = _hype.timelineRunForIdentifier(timelineIdentifier);
		if(timelineRun != null && timelineRun.isPlaying == true) {
			_hype.setTimelineRunPlayScale(timelineRun, 1.0);
			if (pauseTime != null) {
				timelineRun.pauseTime = pauseTime;
			} else {

				timelineRun.pauseTime = _hype.currentTimeForVideoExport - timelineRun.animationStartTime;

	
			}
			timelineRun.isPlaying = false;
			window.clearTimeout(timelineRun.animationCompletionTimeout);
		}
	}
	
	_hype.continueTimelineWithIdentifier = function (timelineIdentifier, reversed, resetScale) {
		var timelineRun = _hype.timelineRunForIdentifier(timelineIdentifier);

		if (resetScale) {
			_hype.setTimelineRunPlayScale(timelineRun, 1.0);
		}
		_hype.setTimelineRunIsReversed(timelineRun, reversed);
		
		if(timelineRun != null) {
			if (timelineRun.isPlaying == false) {
				if (timelineRun.animationStartTime != null) {
					timelineRun.isPlaying = true;

					timelineRun.animationStartTime = _hype.currentTimeForVideoExport - (timelineRun.pauseTime / timelineRun.playScale);


					_hype.stopIntersectingTimelinesFromTime(timelineRun, timelineRun.pauseTime);
					_hype.requestHeartbeat();
				} else {
					_hype.startTimelineRun(timelineIdentifier, null, reversed);
				}
			}
		}
	}

	_hype.applyFirstValuesInTimelineFromTime = function (timelineRun, time) {
		var hasAppliedPropertyByElement = {};
		var animations = (timelineRun.isReversed) ? timelineRun.reversedAnimations : timelineRun.animations;
		for(var i = 0; i < animations.length; i++) {
			var animation = animations[i];

			var type = animation["p"];
			if(type != _hype.kAnimationTypeStandardKeyframe) {
				continue;
			}
			var oid = animation["o"];
			var identifier = animation["i"];
			if(_hype.timelineRunOwnershipOfPropertiesByElement[oid][identifier] != timelineRun.timelineIdentifier) {
				continue;
			}
			var startTime = _hype.startTimeForAnimation(animation, timelineRun);
			var startValue = _hype.startValueForAnimation(animation, timelineRun);
			var isRelative = (timelineRun.isReversed) ? false : animation["r"];
			
			// we apply the startValue of first non-relative keyframe with startTime >= currentTime after calling go to time (in case we jump to a time before any keyframes)
			// if we picked a keyframe too far in the future it doesn't matter because heartbeatGoToTimeForTimelineRun is called after this and will end up overwriting this change
			if(startTime >= time) {
				if(hasAppliedPropertyByElement[oid] == null) {
					hasAppliedPropertyByElement[oid] = {};
				}
				if(isRelative != true && hasAppliedPropertyByElement[oid][identifier] == null) {
					var element = document.getElementById(_hype.idMapping[oid]);
					if (startValue == undefined && timelineRun.isReversed == true && animation["d"] == 0) {
						startValue = animation["s"];
					}
					_hype.applyValue(element, identifier, startValue, true);
				}
				hasAppliedPropertyByElement[oid][identifier] = true;
			}
		}
	}

	_hype.currentTimeInTimelineWithIdentifier = function (timelineIdentifier, reverseResult) {
		var timelineRun = _hype.timelineRunForIdentifier(timelineIdentifier);
		var currentTime = 0;
		if (timelineRun.isPlaying == true) {

			currentTime = _hype.currentTimeForVideoExport - timelineRun.animationStartTime;


			currentTime = currentTime * timelineRun.playScale;		
		} else {
			currentTime = timelineRun.pauseTime;
		}
		var timelineDuration = _hype.timelineDuration(timelineRun);
		if (reverseResult == true && timelineRun.isReversed == true) {
			currentTime = timelineDuration - currentTime;
		}
		currentTime = Math.max(0, currentTime);
		currentTime = Math.min(timelineDuration, currentTime);
		return currentTime;
	}

	// go to time always uses a time on the forward scale
	_hype.goToTimeInTimelineWithIdentifier = function (time, timelineIdentifier) {							 
		var timelineRun = _hype.timelineRunForIdentifier(timelineIdentifier);
		_hype.setTimelineRunPlayScale(timelineRun, 1.0);

		if (myIndexOf(_hype.activeTimelineRuns, timelineRun) == -1) {
			_hype.pauseTimelineWithIdentifier(timelineIdentifier);
			_hype.activeTimelineRuns.push(timelineRun);
		}
		
		time = _hype.quantizeTime(time, _hype.timelineRunForIdentifier(timelineIdentifier));
		
		// make sure the time is in bounds
		var duration = _hype.timelineDuration(timelineRun);
		if(time > duration) {
			time = duration;
		}

		if (timelineRun.isReversed == true) {
			time = _hype.quantizeTime(duration - time, _hype.timelineRunForIdentifier(timelineIdentifier));
		}

		// reset items back to the beginning
		timelineRun.nextAnimationIndex = 0;
		timelineRun.activeAnimations = Array();

		timelineRun.animationStartTime = _hype.currentTimeForVideoExport - time;
		timelineRun.pauseTime = _hype.currentTimeForVideoExport - timelineRun.animationStartTime;




		
		_hype.setStartValueForRelativeKeyframes(timelineRun, false);

		// go to time takes ownership of all animated properties on the timeline so that if you jump into the middle of a timeline all the earlier state is reflected as well
		_hype.stopIntersectingTimelinesFromTime(timelineRun, 0);
		_hype.applyFirstValuesInTimelineFromTime(timelineRun, time);
		_hype.heartbeatGoToTimeForTimelineRun(time, timelineRun, false, true);
	}


	_hype.requestAnimationFrameCallback = function () {		
		_hype.requestAnimFrame()(function () {
			var iframe = document.createElement("IFRAME");
			iframe.setAttribute("src", "js-frame:requestAnimationFrameCallback");
			document.documentElement.appendChild(iframe);
			iframe.parentNode.removeChild(iframe);
			iframe = null;
		});
	}

	_hype.heartbeatForVideoExport = function (currentTime) {
		var nextAnimationFrameRequestTick;
		var quantizedTime = _hype.quantizeTimeWithFramesPerSecond(currentTime, 30);
		_hype.currentTimeForVideoExport = quantizedTime;

		for (var i = 0; i < _hype.activeTimelineRuns.length; i++) {
			var timelineRun = _hype.activeTimelineRuns[i];
				
			if(myIndexOf(_hype.activeTimelineRuns, timelineRun) == -1 || timelineRun.animationStartTime == null || timelineRun.isPlaying == false) {
				continue;
			}
			var animations = (timelineRun.isReversed) ? timelineRun.reversedAnimations : timelineRun.animations;
			_hype.heartbeatGoToTimeForTimelineRun(_hype.quantizeTime(quantizedTime - timelineRun.animationStartTime, timelineRun), timelineRun, true, true);
		}
		for (var i = 0; i < _hype.activeTimelineRuns.length; i++) {
			var timelineRun = _hype.activeTimelineRuns[i];
			if (timelineRun.isPlaying == true && _hype.quantizeTime(_hype.timelineDuration(timelineRun) + timelineRun.animationStartTime, timelineRun) < quantizedTime) {
				_hype.timelineRunCompleteCallback(timelineRun);
			}
		}

		_hype.requestAnimFrame()(function () { 
			var iframe = document.createElement("IFRAME");
			iframe.setAttribute("src", "js-frame:finishedHeartbeat");
			document.documentElement.appendChild(iframe);
			iframe.parentNode.removeChild(iframe);
			iframe = null;
		});

	}

	
	_hype.heartbeat = function () {

		return;

		var nextAnimationFrameRequestTick;

		for(var i = 0; i < _hype.activeTimelineRuns.length; i++) {
			var timelineRun = _hype.activeTimelineRuns[i];
				
			if(myIndexOf(_hype.activeTimelineRuns, timelineRun) == -1 || timelineRun.animationStartTime == null || timelineRun.isPlaying == false) {
				continue;
			}
			
			var currentTime = (((new Date).getTime()) / 1000.0) - timelineRun.animationStartTime;
			var animations = (timelineRun.isReversed) ? timelineRun.reversedAnimations : timelineRun.animations;
			var significantChangeDuringHeartbeat = _hype.heartbeatGoToTimeForTimelineRun(currentTime, timelineRun, true, true);
			if(significantChangeDuringHeartbeat) {
				currentTime = (((new Date).getTime()) / 1000.0) - timelineRun.animationStartTime;
			}
			if(myIndexOf(_hype.activeTimelineRuns, timelineRun) == -1) { // recheck, I might not be active anymore due to a scene change from a timeline action
				continue;
			}

			var timelineDuration = _hype.timelineDuration(timelineRun);
			if(currentTime <= timelineDuration) {
				if(timelineRun.activeAnimations.length > 0) {
					nextAnimationFrameRequestTick = 0;
				} else if(timelineRun.nextAnimationIndex < animations.length) {
					var proposedTick = _hype.quantizeTime((_hype.startTimeForAnimation(animations[timelineRun.nextAnimationIndex], timelineRun) - (currentTime * timelineRun.playScale)) / timelineRun.playScale, timelineRun);
					if(nextAnimationFrameRequestTick == null || proposedTick < nextAnimationFrameRequestTick) {
						nextAnimationFrameRequestTick = proposedTick;
					}
				}
			}
		}
		
		if(nextAnimationFrameRequestTick === 0) {
			_hype.requestHeartbeat();
		} else if(nextAnimationFrameRequestTick != null) {
			_hype.requestHeartbeatAtTime(nextAnimationFrameRequestTick);
		}		
	}
	
	_hype.startTimeForAnimation = function (animation, timelineRun) {
		return (timelineRun.isReversed) ? _hype.quantizeTime((_hype.timelineDuration(timelineRun) - animation["d"] - animation["t"]), timelineRun) : animation["t"];
	}
	
	_hype.endValueForAnimation = function (animation, timelineRun) {
		return (timelineRun.isReversed) ? animation["s"] : animation["e"];
	}
	
	_hype.startValueForAnimation = function (animation, timelineRun) {
		return (timelineRun.isReversed) ? animation["e"] : animation["s"];
	}
	
	// worker method returns true if a significant change happens as the result of an action
	_hype.heartbeatGoToTimeForTimelineRun = function (currentTime, timelineRun, runPreviousActions, runActions) {
		_hype.beginFrameUpdateQueue();
	
		var animationStartTime = timelineRun.animationStartTime;
		var animations = (timelineRun.isReversed) ? timelineRun.reversedAnimations : timelineRun.animations;
		var actionAnimationArray = Array();
		var realTime = currentTime;
		var scaledTime = Math.min(currentTime * timelineRun.playScale, _hype.timelineDuration(timelineRun));
		for(var i = timelineRun.nextAnimationIndex; i < animations.length; i++) {
			var animation = animations[i];
			var startTime = _hype.startTimeForAnimation(animation, timelineRun);
			if (animation["p"] == _hype.kAnimationTypeTimelineAction && timelineRun.isPlaying == true && scaledTime >= startTime) {
				actionAnimationArray.push(animation);
			}
		}
		var significantTimeChange = false;
		if(actionAnimationArray.length == 0 || runActions == false) {
			// if there are no actions just run all the other animations
			_hype.runAnimationsForTimelineRun(scaledTime, timelineRun, animations);
		} else {
			var timeOfSignificantChange = 0;
			var alreadyRanAnimationsAtTime = 0;
			// loop through actions running animations up to the action startTime before firing the action
			for(var i = 0; i < actionAnimationArray.length; i++) {
				var actionAnimation = actionAnimationArray[i];
			    var startTime = _hype.startTimeForAnimation(actionAnimation, timelineRun);
				
				// if there is a significantTimeChange play any actions that occurred at the same time as the significantTimeChange but skip any actions that occur after
				if (significantTimeChange == true && timeOfSignificantChange < startTime) {
					break;
				}
				// don't run actions before the current time if runPreviousActions is false, this is so that go to time doesn't run all the previous actions
				if (runPreviousActions == false && startTime != scaledTime) {
					continue;
				}

				// only run the other animations if we haven't already run them at this time (this would occur for stacked actions)
				if (alreadyRanAnimationsAtTime == 0 || alreadyRanAnimationsAtTime != startTime) {
					_hype.runAnimationsForTimelineRun(startTime, timelineRun, animations);
					alreadyRanAnimationsAtTime = startTime;
				}
				var actions = actionAnimation["s"]["a"];
				var actionsToUse = [];
				for (var actionIndex = 0; actionIndex < actions.length; actionIndex++) {
						var action = actions[actionIndex];
						if (action["p"] == _hype.kActionPauseTimeline && action["b"] == timelineRun.timelineIdentifier) {
							action["r"] = startTime;
						}
						// don't play start timeline actions at time 0 because this is just an infinite loop (and a common thing people try with reversal)
						if (startTime == 0 && action["p"] == _hype.kActionStartTimeline && action["b"] == timelineRun.timelineIdentifier) {
							continue;
						}
						actionsToUse.push(action);
				}
				
				if (actionsToUse.length != 0) {
					var actionFunction = _hype.Apply.CreateActionHandler(actionsToUse, null);
					var timelines = _hype.scenes[_hype.currentSceneIndex]["T"];
					actionFunction({"type" : "HypeTimelineAction", "timelineName" : timelines[timelineRun.timelineIdentifier]["n"]});
				}

				if(animationStartTime != timelineRun.animationStartTime || myIndexOf(_hype.activeTimelineRuns, timelineRun) == -1 || timelineRun.animationStartTime == null || timelineRun.isPlaying == false) {
					significantTimeChange = true;
					timeOfSignificantChange = startTime;
				}
			}
			// if none of the actions caused significant time changes run all animations up to the current time
			if (significantTimeChange == false) {
				_hype.runAnimationsForTimelineRun(scaledTime, timelineRun, animations);
			}
		}
		
		_hype.drainFrameUpdateQueue();
		
		return significantTimeChange;
	}
	
	_hype.runAnimationsForTimelineRun = function (currentTime, timelineRun, animations) {
		for(var j = timelineRun.nextAnimationIndex; j < animations.length; j++) {
			var animation = animations[j];
			var startTime = _hype.startTimeForAnimation(animation, timelineRun);
			var oid = animation["o"];
			var identifier = animation["i"];
			
			if(currentTime >= startTime) {
				timelineRun.nextAnimationIndex = j + 1;

				if(_hype.timelineRunOwnershipOfPropertiesByElement[oid][identifier] != timelineRun.timelineIdentifier) {
					continue;
				}
				

				if(animation["p"] == _hype.kAnimationTypeVideo) {
					timelineRun.activeAnimations.push(animation);


				} else if(animation["p"] != _hype.kAnimationTypeTimelineAction) {
					// push onto the manually managed array
					timelineRun.activeAnimations.push(animation);
				}
			} else {
				break;
			}
		}
		
		var topAndLeftAnimations = Array();
		for(var j = 0; j < timelineRun.activeAnimations.length; j++) {
			var activeAnimation = timelineRun.activeAnimations[j];
			var startTime = _hype.startTimeForAnimation(activeAnimation, timelineRun);
			var duration = activeAnimation["d"];
            var endTime = _hype.quantizeTime(startTime + duration, timelineRun);
			if(currentTime < endTime) {

				if (activeAnimation["p"] == _hype.kAnimationTypeVideo) {
					var oid = activeAnimation["o"];
					var element = document.getElementById(_hype.idMapping[oid]);
					if (element.readyState == 4) {
						var videoTime = Math.max(0, _hype.quantizeTime(currentTime - startTime, timelineRun));
						element.controls = false;
						element.currentTime = videoTime;
					} else {
						var iframe = document.createElement("IFRAME");
						iframe.setAttribute("src", "js-frame:videoNotReady:" + oid);
						document.documentElement.appendChild(iframe);
						iframe.parentNode.removeChild(iframe);
						iframe = null;
						
						var loadCompleteMaker = (function (animationOid) {
							return function (event) {
								var loadCompleteiframe = document.createElement("IFRAME");
								loadCompleteiframe.setAttribute("src", "js-frame:videoReady:" + animationOid);
								document.documentElement.appendChild(loadCompleteiframe);
								loadCompleteiframe.parentNode.removeChild(loadCompleteiframe);
								loadCompleteiframe = null;
							};
						});
						
						element.addEventListener("canplaythrough", loadCompleteMaker(oid), true);
					}
					continue;
				}

				// apply value
				if (activeAnimation["i"] == "a" || activeAnimation["i"] == "b") {
					// top and left animations may be using a motion path which is dependent on the width, height, padding, and margins
					// we need to wait to do top and left until after all these animations have been applied
					topAndLeftAnimations.push(activeAnimation);
				} else {
					_hype.applyAnimation(activeAnimation, currentTime, timelineRun);
				}
			} else {

				if (activeAnimation["p"] == _hype.kAnimationTypeVideo) {
					var oid = activeAnimation["o"];
					var element = document.getElementById(_hype.idMapping[oid]);
					if (element.readyState == 4) {
						element.currentTime = duration;
					}
				} else {

					// set value to end value and remove from the list
					var elementOid = activeAnimation["o"];
					var element = document.getElementById(_hype.idMapping[elementOid]);
					var endValue;
					var relativeEndValue = (timelineRun.isReversed == true) ? activeAnimation["b"] : null;
					endValue = (relativeEndValue == null) ? _hype.endValueForAnimation(activeAnimation, timelineRun) : relativeEndValue;
					_hype.applyValue(element, activeAnimation["i"], endValue, true);

				}

				timelineRun.activeAnimations.splice(j, 1);
				j--;
			}
		}
		for (var j = 0; j < topAndLeftAnimations.length; j++) {
			_hype.applyAnimation(topAndLeftAnimations[j], currentTime, timelineRun);
		}
	}
		
	_hype.percentBasedOnArcLength = function (curve, percentComplete)
	{
		var targetLength = curve.length * percentComplete;
		var closestPointSmallerThanTarget = curve.interpolationPoints[0];
		var closestIndex = 0;
		var totalLength = 0;
		for (var i = 0; i < curve.interpolationPoints.length; i++) {
			var point = curve.interpolationPoints[i];
			if ((totalLength + point.length) <= targetLength) {
				totalLength += point.length;
				closestPointSmallerThanTarget = point;
				closestIndex = i;
			} else {
				break;
			}
		}
		var closestPointLargerThanTarget = (closestIndex + 1 < curve.interpolationPoints.length) ? curve.interpolationPoints[closestIndex + 1] : curve.interpolationPoints[curve.interpolationPoints.length - 1];
		var fractionLeftover = (targetLength - totalLength) / closestPointLargerThanTarget.length;
		return (closestIndex + fractionLeftover) / (curve.interpolationPoints.length - 1);
	}

	// returns the coordinates of the elements center and the rotation angle
	_hype.centerPointAtPercentCompleteForAnimation = function (motionPath, percentComplete)
	{
		var totalLength = motionPath["length"];
		var lengthThusFar = 0;
		var bezierCurveForAnimation;
		var segmentPercentComplete = 0;
		if (totalLength == 0) {
			return {x: motionPath.bezierCurves[0].startPoint.x, y: motionPath.bezierCurves[0].startPoint.y, rotationAngle: 0};
		} if (percentComplete == 1.0) {
			var bezierCurves = motionPath.bezierCurves;
			bezierCurveForAnimation = bezierCurves[bezierCurves.length - 1];
			segmentPercentComplete = 1.0;
		} else {
			for (var i = 0; i < motionPath.bezierCurves.length; i++) {
				bezierCurve = motionPath.bezierCurves[i];
				if (((bezierCurve.length + lengthThusFar) / totalLength) > percentComplete) {
					bezierCurveForAnimation = bezierCurve;
					break;
				}
				lengthThusFar += bezierCurve.length;
			}
			// convert to a value between 0 and 1 for that segment
			segmentPercentComplete = (percentComplete - (lengthThusFar / totalLength)) / (bezierCurveForAnimation.length / totalLength);
			segmentPercentComplete = _hype.percentBasedOnArcLength(bezierCurveForAnimation, segmentPercentComplete);
		}
		return _hype.pointAtPercentForBezierCurve(bezierCurveForAnimation, segmentPercentComplete);
	}

	_hype.currentWidthForElementWithOid = function(elementOid) 
	{
		var transformerClassType = _hype.attributeTransformerMapping["c"];
		var element = document.getElementById(_hype.idMapping[elementOid]);
		var currentValuesForElement = _hype.currentValues[elementOid];
		if(currentValuesForElement == null) {
			return 0;
		}
		var width = (currentValuesForElement['c'] != undefined) ? _hype.untransformValue(currentValuesForElement['c'], transformerClassType) : 0;
		var padding = 0;
		if (width == 0) {
			width = element.clientWidth;
			padding = 0;
		} else {
			var paddingLeft = (currentValuesForElement['aT'] != undefined) ? _hype.untransformValue(currentValuesForElement['aT'], transformerClassType) : 0;
			var paddingRight = (currentValuesForElement['aU'] != undefined) ? _hype.untransformValue(currentValuesForElement['aU'], transformerClassType) : 0;
			padding = paddingLeft + paddingRight;
		}
		var borderLeft = (currentValuesForElement['N'] != undefined) ? _hype.untransformValue(currentValuesForElement['N'], transformerClassType) : 0;
		var borderRight = (currentValuesForElement['O'] != undefined) ? _hype.untransformValue(currentValuesForElement['O'], transformerClassType) : 0;
		var border = borderRight + borderLeft;
		return (width + padding + border);
	}

	_hype.currentHeightForElementWithOid = function(elementOid)
	{
		var transformerClassType = _hype.attributeTransformerMapping["d"];
		var element = document.getElementById(_hype.idMapping[elementOid]);
		var currentValuesForElement = _hype.currentValues[elementOid];
		if(currentValuesForElement == null) {
			return 0;
		}
		var height = (currentValuesForElement['d'] != undefined) ? _hype.untransformValue(currentValuesForElement['d'], transformerClassType) : 0;
		var padding = 0;
		if (height == 0) {
			height = element.clientHeight;
			padding = 0;
		} else {
			var paddingTop = (currentValuesForElement['aV'] != undefined) ? _hype.untransformValue(currentValuesForElement['aV'], transformerClassType) : 0;
			var paddingBottom = (currentValuesForElement['aS'] != undefined) ? _hype.untransformValue(currentValuesForElement['aS'], transformerClassType) : 0;
			padding = paddingTop + paddingBottom;
		}
		var borderTop = (currentValuesForElement['P'] != undefined) ? _hype.untransformValue(currentValuesForElement['P'], transformerClassType) : 0;
		var borderBottom = (currentValuesForElement['M'] != undefined) ? _hype.untransformValue(currentValuesForElement['M'], transformerClassType) : 0;
		var border = borderTop + borderBottom;
		return (height + padding + border);
	}

	// returns the coordinates of the left and top of the element and the rotation angle
	_hype.topLeftPointForMotionPath = function (motionPath, percentComplete, elementOid) {
		var transformerClassType = _hype.attributeTransformerMapping["a"];
		var point = _hype.centerPointAtPercentCompleteForAnimation(motionPath, percentComplete);
		point.x = _hype.transformValue(point.x - (_hype.currentWidthForElementWithOid(elementOid) / 2), transformerClassType);
		point.y = _hype.transformValue(point.y - (_hype.currentHeightForElementWithOid(elementOid) / 2), transformerClassType);
		return point;
	}
	
	_hype.applyAnimation = function (animation, currentTime, timelineRun) {
		var startTime = _hype.startTimeForAnimation(animation, timelineRun);
		var duration = animation["d"];
		var identifier = animation["i"];
		var oid = animation["o"];
		var relativeStartValue = (timelineRun.isReversed == false) ? animation["b"] : null;
		var startValue = (relativeStartValue == null) ? _hype.startValueForAnimation(animation, timelineRun) : relativeStartValue;
		var relativeEndValue = (timelineRun.isReversed == true) ? animation["b"] : null;
		var endValue = (relativeEndValue == null) ? _hype.endValueForAnimation(animation, timelineRun) : relativeEndValue;
		var timingFunction = animation["f"];	

		var transformerClassType = _hype.attributeTransformerMapping[identifier];
		
		var element = document.getElementById(_hype.idMapping[oid]);
		
		var percentComplete = (currentTime - startTime) / duration;
		if (timelineRun.isReversed == true) {
			percentComplete = 1.0 - percentComplete;
		}
		var adjustedPercentComplete = 1.0;
		
		if(timingFunction == "0") {
			adjustedPercentComplete = (percentComplete == 1.0) ? 1.0 : 0.0;
		} else if(timingFunction == "1") {
			adjustedPercentComplete = percentComplete;
		} else if(timingFunction == "3") {
			adjustedPercentComplete = solveCubicBezierFunction(0.42, 0.0, 1.0, 1.0, percentComplete, 1.0);
		} else if(timingFunction == "4") {
			adjustedPercentComplete = solveCubicBezierFunction(0.0, 0.0, 0.58, 1.0, percentComplete, 1.0);
		} else if(timingFunction == "2") {
			adjustedPercentComplete = solveCubicBezierFunction(0.42, 0.0, 0.58, 1.0, percentComplete, 1.0);
		} else if(timingFunction == "5") {
			var t = percentComplete;
			if (percentComplete < (1/2.75)) {
				adjustedPercentComplete = 7.5625 * t * t;
			} else if (percentComplete < (2/2.75)) {
				adjustedPercentComplete = 7.5625 * (t-=(1.5/2.75)) * t + .75;
			} else if (percentComplete < (2.5/2.75)) {
				adjustedPercentComplete = 7.5625 * (t-=(2.25/2.75)) * t + .9375;
			} else {
				adjustedPercentComplete = 7.5625 * (t-=(2.625/2.75)) * t + .984375;
			}
		}
		if (timelineRun.isReversed == true) {
			adjustedPercentComplete = 1.0 - adjustedPercentComplete;
		}
		
		var motionPathOid = animation["a"];
		if (motionPathOid != undefined) {
			var motionPath = _hype.motionPaths[motionPathOid];
			if (animation["b"] != null) {
				if (timelineRun.relativeMotionPaths[motionPathOid] == null) {
					_hype.setupRelativeMotionPath(timelineRun, animation);
				} 
				motionPath = timelineRun.relativeMotionPaths[motionPathOid];
			}
			var currentValue;
			if (timelineRun.isReversed == true) adjustedPercentComplete = 1 - adjustedPercentComplete;
			var point = _hype.topLeftPointForMotionPath(motionPath, adjustedPercentComplete, oid);
			if (identifier == "a") {
				currentValue = point.x;
				if (element.getAttribute("HYP_ae") == "YES") {
					_hype.applyValue(element, "bO", _hype.transformValue(point.rotationAngle, 2), true);
				}
			} else if (identifier == "b") {
				currentValue = point.y;		
			}

			_hype.applyValue(element, identifier, currentValue, true);
		} else {
			if(transformerClassType == 0 || transformerClassType == 1 || transformerClassType == 2 || transformerClassType == 3 || transformerClassType == 4) {
				var untransformedStart = _hype.untransformValue(startValue, transformerClassType);
				var untransformedEnd = _hype.untransformValue(endValue, transformerClassType);
				var untransformedCurrent = untransformedStart + ((untransformedEnd - untransformedStart) * adjustedPercentComplete);
				var currentValue = _hype.transformValue(untransformedCurrent, transformerClassType);		
				_hype.applyValue(element, identifier, currentValue, true);
			} else if(transformerClassType == 5) {
				var untransformedStart = _hype.untransformValue(startValue, transformerClassType);
				var untransformedEnd = _hype.untransformValue(endValue, transformerClassType);

				var untransformedCurrentRed = untransformedStart[0] + ((untransformedEnd[0] - untransformedStart[0]) * adjustedPercentComplete);
				var untransformedCurrentGreen = untransformedStart[1] + ((untransformedEnd[1] - untransformedStart[1]) * adjustedPercentComplete);
				var untransformedCurrentBlue = untransformedStart[2] + ((untransformedEnd[2] - untransformedStart[2]) * adjustedPercentComplete);
				
				var currentValue = _hype.transformValue([untransformedCurrentRed, untransformedCurrentGreen, untransformedCurrentBlue], transformerClassType);
				_hype.applyValue(element, identifier, currentValue, true);
			} else {
				if(adjustedPercentComplete == 1.0) {
					_hype.applyValue(element, identifier, endValue, true);
				}
			}
		}
	}

	function solveEpsilon(duration) {
		return 1.0 / (200.0 * duration);
	}

	function solveCubicBezierFunction(p1x, p1y, p2x, p2y, t, duration) {
		// Convert from input time to parametric value in curve, then from
		// that to output time.
		var bezier = new UnitBezier(p1x, p1y, p2x, p2y);
		return bezier.solve(t, solveEpsilon(duration));
	}
	
	function myIndexOf(haystack, needle) {
		if(haystack.indexOf) {
			return haystack.indexOf(needle);
		} else {
			for(var i = 0; i < haystack.length; i++) {
				if(haystack[i] == needle) {
					return i;
				}
			}
		}
		return -1;
	}
	
	function escapeURLForCSS(str) {
		return str.replace(/'/g, "%27");
	}

	_hype.urlByReplacingCommonUserErrors = function(url) {
		if(url != null && url.indexOf("www.") === 0) {
			url = "http://" + url;
		}
		return url;
	}

	
	// modified from source found at: http://frugalcoder.us/post/2010/09/13/browser-detection.aspx
	_hype.browserInfo = (function () {
		var b = {};
	
		if (!navigator) return b;

		//browsermatch method...
		function bm(re) {
			var m = (navigator && navigator.userAgent && navigator.userAgent.match(re));
			return (m && m[1]);
		}

		//setup browser detection
		b.ie = parseFloat(bm(/MSIE (\d+\.\d+)/)) || null;
		b.gecko = parseFloat(bm(/Gecko\/(\d+)/)) || null;
		b.ff = parseFloat(bm(/Firefox\/(\d+\.\d+)/)) || null;
		b.webkit = parseFloat(bm(/AppleWebKit\/(\d+\.\d+)/));
		b.chrome = parseFloat(b.webkit && bm(/Chrome\/(\d+\.\d+)/)) || null;
		b.safari = parseFloat(b.webkit && bm(/Safari\/(\d+\.\d+)/) && bm(/Version\/(\d+\.\d+)/)) || null;
		b.opera = parseFloat(bm(/Opera\/(\d+\.\d+)/) && bm(/Version\/(\d+\.\d+)/)) || bm(/Opera\/(\d+\.\d+)/) || parseFloat(b.webkit && bm(/OPR\/(\d+\.\d+)/)) || null;
		b.android = (navigator.userAgent.search("Android") > -1) || null;
		b.ipad = (navigator.userAgent.search("iPad") > -1) || null;
		b.iphone = (navigator.userAgent.search("iPhone") > -1) || null;
		b.ipod = (navigator.userAgent.search("iPod") > -1) || null;
		b.ios = b.ipad || b.iphone || b.ipod || null;
		b.quirksmode = (document.compatMode == "BackCompat") || null;

		//delete empty values
		for (var x in b) {
			if (b[x] === null || isNaN(b[x]))
				delete b[x];
		}

		//disable IE matching for older Opera versions.
		if (b.opera && b.ie) delete b.ie;
		
		return b;
	}());


	
	function UnitBezier(p1x, p1y, p2x, p2y) {
		var _ub = this;
		// Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
		_ub.cx = 3.0 * p1x;
		_ub.bx = 3.0 * (p2x - p1x) - _ub.cx;
		_ub.ax = 1.0 - _ub.cx -_ub.bx;
		 
		_ub.cy = 3.0 * p1y;
		_ub.by = 3.0 * (p2y - p1y) - _ub.cy;
		_ub.ay = 1.0 - _ub.cy - _ub.by;	
	}

	UnitBezier.prototype.sampleCurveX = function(t) {
		var _ub = this;
		// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
		return ((_ub.ax * t + _ub.bx) * t + _ub.cx) * t;
	};

	UnitBezier.prototype.sampleCurveY = function(t) {
		var _ub = this;
		return ((_ub.ay * t + _ub.by) * t + _ub.cy) * t;
	};

	UnitBezier.prototype.sampleCurveDerivativeX = function(t) {
		var _ub = this;
		return (3.0 * _ub.ax * t + 2.0 * _ub.bx) * t + _ub.cx;
	};

	// Given an x value, find a parametric value it came from.
	UnitBezier.prototype.solveCurveX = function(x, epsilon) {
		var _ub = this;
		var t0;
		var t1;
		var t2;
		var x2;
		var d2;
		var i;

		// First try a few iterations of Newton's method -- normally very fast.
		for (t2 = x, i = 0; i < 8; i++) {
			x2 = _ub.sampleCurveX(t2) - x;
			if (Math.abs(x2) < epsilon)
				return t2;
			d2 = _ub.sampleCurveDerivativeX(t2);
			if (Math.abs(d2) < 1e-6)
				break;
			t2 = t2 - x2 / d2;
		}

		// Fall back to the bisection method for reliability.
		t0 = 0.0;
		t1 = 1.0;
		t2 = x;

		if (t2 < t0)
			return t0;
		if (t2 > t1)
			return t1;

		while (t0 < t1) {
			x2 = _ub.sampleCurveX(t2);
			if (Math.abs(x2 - x) < epsilon)
				return t2;
			if (x > x2)
				t0 = t2;
			else
				t1 = t2;
			t2 = (t1 - t0) * 0.5 + t0;
		}

		// Failure.
		return t2;
	};

	UnitBezier.prototype.solve = function(x, epsilon) {
		var _ub = this;
		return _ub.sampleCurveY(this.solveCurveX(x, epsilon));
	};

	
	
	_hype.Apply = ({
			'i' : function (value, element) {
				// no-op, dom ids are set in createSceneDivs
			},

			'b' : function (value, element) {
				element.setAttribute("HYP_a", value);
				_hype.Apply.UpdateFrame(element);
			},

			'a' : function (value, element) {
				element.setAttribute("HYP_b", value);
				_hype.Apply.UpdateFrame(element);
			},
			
			'c' : function (value, element) {
				element.setAttribute("HYP_am", value);
				_hype.invalidateBrowserReportedSizeCacheForElement(element);
				_hype.Apply.UpdateFrame(element);
			},

			'd' : function (value, element) {
				element.setAttribute("HYP_an", value);
				_hype.invalidateBrowserReportedSizeCacheForElement(element);
				_hype.Apply.UpdateFrame(element);
			},
			
			UpdateFrame : function (element) {
				try {
					_hype.Apply._UpdateFrame(element);
				} catch(err) { }
			},
			
			_UpdateFrame : function (element) {
				if(_hype.shouldQueueFrameUpdates == true) {
					_hype.elementsNeedingFrameUpdates[element.id] = element;
					return;
				}
			
				var autoresizingMask = parseInt(element.getAttribute("HYP_ar"));
				if(isNaN(autoresizingMask)) {
					autoresizingMask = 0;
				}
				var useTranslates = (_hype.browserInfo.webkit != null && element.getAttribute("HYP_c") == "1");
				
				var elementOid = _hype.idReverseMapping[element.id];
				var parentOid = _hype.parentOidForElementOid(elementOid);
				
				var originalContainerSize = _hype.currentSizeForElementOid(parentOid);
				var currentContainerSize = _hype.browserReportedSizeForElementOid(parentOid);
				var currentElementSize = _hype.currentSizeForElementOid(elementOid);
				
				var leftValue = element.getAttribute("HYP_b");
				var topValue = element.getAttribute("HYP_a");
				var widthValue = element.getAttribute("HYP_am");
				var heightValue = element.getAttribute("HYP_an");

				leftValue = (leftValue != null) ? parseFloat(leftValue) : 0;
				topValue = (topValue != null) ? parseFloat(topValue) : 0;
				
				// if there's been no width/height is likely a text box without explicit dimensions. We still need to know what it is, but should not set it.
				var shouldSetWidth = true;
				var shouldSetHeight = true;

				if(widthValue != null) {
					widthValue = parseFloat(widthValue);
				} else {
					shouldSetWidth = false;
					widthValue = currentElementSize.width;
				}
				if(heightValue != null) {
					heightValue = parseFloat(heightValue);
				} else {
					shouldSetHeight = false;
					heightValue = currentElementSize.height;
				}

				var fullWidth = _hype.currentWidthForElementWithOid(elementOid);
				var fullHeight = _hype.currentHeightForElementWithOid(elementOid);
				var widthDelta = fullWidth - widthValue;
				var heightDelta = fullHeight - heightValue;

				var calculatedLeft = leftValue;
				var calculatedTop = topValue;
				var calculatedWidth = widthValue;
				var calculatedHeight = heightValue;
				
				if(_hype.usesFlexibleLayout == true && autoresizingMask != 0 && autoresizingMask != 36) {
					var isMinXMargin = (autoresizingMask & _hype.NSViewMinXMargin) != 0;
					var isMaxXMargin = (autoresizingMask & _hype.NSViewMaxXMargin) != 0;
					var isWidthSizable = (autoresizingMask & _hype.NSViewWidthSizable) != 0;
					var isMinYMargin = (autoresizingMask & _hype.NSViewMinYMargin) != 0;
					var isMaxYMargin = (autoresizingMask & _hype.NSViewMaxYMargin) != 0;
					var isHeightSizable = (autoresizingMask & _hype.NSViewHeightSizable) != 0;
					var isScaleShrink = ((autoresizingMask & _hype.kHypeViewScaleShrink) != 0) && (isWidthSizable == true) && (isHeightSizable == true);
					var isScaleExpand = ((autoresizingMask & _hype.kHypeViewScaleExpand) != 0) && (isWidthSizable == true) && (isHeightSizable == true);
					var shouldPreserveProportions = (isScaleShrink == true || isScaleExpand == true);

					var widthResizeRatio = (currentContainerSize.width / originalContainerSize.width);
					var heightResizeRatio = (currentContainerSize.height / originalContainerSize.height);
					
					if(isMinXMargin == true && isMaxXMargin == false && isWidthSizable == false) {
						calculatedLeft = (currentContainerSize.width - originalContainerSize.width) + leftValue;
					} else if(isMinXMargin == true && isMaxXMargin == true && isWidthSizable == false) {
						var centerPoint = (leftValue + (fullWidth / 2));
						calculatedLeft = (centerPoint * widthResizeRatio) - (fullWidth / 2);
					} else if(isMinXMargin == true && isMaxXMargin == true && isWidthSizable == true) {
						calculatedWidth = (fullWidth + (currentContainerSize.width - originalContainerSize.width)) - widthDelta;
					} else if(isWidthSizable == true) {
						calculatedWidth = Math.ceil((fullWidth * widthResizeRatio) - widthDelta);
						if(isMinXMargin == true && isMaxXMargin == false) {
							calculatedLeft = (currentContainerSize.width - originalContainerSize.width) + leftValue - (calculatedWidth - widthValue);
						}
					}
					
					if(isMinYMargin == true && isMaxYMargin == false && isHeightSizable == false) {
						calculatedTop = (currentContainerSize.height - originalContainerSize.height) + topValue;
					} else if(isMinYMargin == true && isMaxYMargin == true && isHeightSizable == false) {
						var centerPoint = (topValue + (fullHeight / 2));
						calculatedTop = (centerPoint * heightResizeRatio) - (fullHeight / 2);
					} else if(isMinYMargin == true && isMaxYMargin == true && isHeightSizable == true) {
						calculatedHeight = (fullHeight + (currentContainerSize.height - originalContainerSize.height)) - heightDelta;
					} else if(isHeightSizable == true) {
						calculatedHeight = Math.ceil((fullHeight * heightResizeRatio) - heightDelta);
						if(isMinYMargin == true && isMaxYMargin == false) {
							calculatedTop = (currentContainerSize.height - originalContainerSize.height) + topValue - (calculatedHeight - heightValue);
						}
					}
					
					if(shouldPreserveProportions == true) {
						var previouslyCalculatedCalculatedWidth = calculatedWidth;
						var previouslyCalculatedCalculatedHeight = calculatedHeight;
						var proportionalWidthResizeRatio = (calculatedWidth / widthValue);
						var proportionalHeightResizeRatio = (calculatedHeight / heightValue);
						var proportionalResizeRatio = proportionalWidthResizeRatio;
						
						if(isScaleShrink == true) { // choose largest
							proportionalResizeRatio = (proportionalWidthResizeRatio <= proportionalHeightResizeRatio) ? proportionalWidthResizeRatio : proportionalHeightResizeRatio;
						} else if(isScaleExpand == true) { // choose smallest
							proportionalResizeRatio = (proportionalWidthResizeRatio >= proportionalHeightResizeRatio) ? proportionalWidthResizeRatio : proportionalHeightResizeRatio;
						}
						
						calculatedWidth = widthValue * proportionalResizeRatio;
						calculatedHeight = heightValue * proportionalResizeRatio;
						
						var proportionalWidthDelta = previouslyCalculatedCalculatedWidth - calculatedWidth;
						var proportionalHeightDelta = previouslyCalculatedCalculatedHeight - calculatedHeight;
						
						if(isMinXMargin == true && isMaxXMargin == false) { // need to bottom-align
							calculatedLeft += proportionalWidthDelta
						} else if(isMinXMargin == true && isMaxXMargin == true) { // need to center
							calculatedLeft += (proportionalWidthDelta / 2);
						}
						
						if(isMinYMargin == true && isMaxYMargin == false) { // need to bottom-align
							calculatedTop += proportionalHeightDelta;
						} else if(isMinYMargin == true && isMaxYMargin == true) { // need to center
							calculatedTop += (proportionalHeightDelta / 2);
									
						}
					}
				}
		
				if(shouldSetWidth == true) {
					element.style.width = "" + (Math.max(0, calculatedWidth)) + "px";
				} else {
					element.style.width = "";
				}
				
				if(shouldSetHeight == true) {
					element.style.height = "" + (Math.max(0, calculatedHeight)) + "px";
				} else {
					element.style.height = "";
				}
				

				
				if(useTranslates == false) {

					
					element.style.top = "" + calculatedTop + "px";
					element.style.left = "" + calculatedLeft + "px";
				} else {
					element.setAttribute("HYP_ao", ("" + calculatedTop + "px"));
					element.setAttribute("HYP_ap", ("" + calculatedLeft + "px"));
					_hype.Apply.ApplyTransforms(element);
				}
			},
			
			'bQ' : function (value, element) {
				if(_hype.browserInfo.webkit != null && element.getAttribute("HYP_c") == "1") {
					element.setAttribute("HYP_ah", value);
					_hype.Apply.ApplyTransforms(element);
				}
			},

			ApplyTransforms : function (element) {
				var leftValue = element.getAttribute("HYP_ap");
				var topValue = element.getAttribute("HYP_ao");
				var depthValue = element.getAttribute("HYP_ah");
				var rotateAngleXValue = element.getAttribute("HYPE_ak");
				var rotateAngleYValue = element.getAttribute("HYP_e");
				var rotateAngleZValue = element.getAttribute("HYP_d");
				var motionPathRotation = element.getAttribute("HYP_ad");
				var transformOriginXOffset = element.getAttribute("HYP_tX");
				var transformOriginYOffset = element.getAttribute("HYP_tY");
				var transformString = "";
				
				if(leftValue != null) {
					transformString += " translateX(" + leftValue + ") ";
				}
				if(topValue != null) {
					transformString += " translateY(" + topValue + ") ";
				}
				if(depthValue != null) {
					transformString += " translateZ(" + depthValue + ") ";
				}
				if(motionPathRotation != null) {
				   if (element.getAttribute("HYP_ae") == "YES") {
						transformString += " rotate(" + motionPathRotation + ") ";
					}
				}
				if(rotateAngleZValue != null) {
					transformString += " rotate(" + rotateAngleZValue + ") ";
				}
				if(!(_hype.browserInfo.ie < 10)) {
					if(rotateAngleYValue != null) {
						transformString += " rotateY(" + rotateAngleYValue + ") ";
					} else if(_hype.graphicsAcceleration == true) {
						transformString += " rotateY(0) ";
					}
					if(rotateAngleXValue != null) {
						transformString += " rotateX(" + rotateAngleXValue + ") ";
					}
				}

				var transformOriginXOffsetString = "50%";
				if(transformOriginXOffset != null) {
					transformOriginXOffsetString = transformOriginXOffset;
				}
				var transformOriginYOffsetString = "50%";
				if(transformOriginYOffset != null) {
					transformOriginYOffsetString = transformOriginYOffset;
				}
				element.style[_hype.kSizeOptimizationWebKitPrefix + "transform-origin"] = transformOriginXOffsetString + " " + transformOriginYOffsetString;
				element.style["MozTransformOrigin"] = transformOriginXOffsetString + " " + transformOriginYOffsetString;
				element.style["OTransformOrigin"] = transformOriginXOffsetString + " " + transformOriginYOffsetString;
				element.style["-ms-transform-origin"] = transformOriginXOffsetString + " " + transformOriginYOffsetString;

				element.style[_hype.kSizeOptimizationWebKitPrefix + "transform"] = transformString;
				element.style["MozTransform"] = transformString;
				element.style["OTransform"] = transformString;
				element.style["-ms-transform"] = transformString;
			},
			
			'bS' : function (value, element) {
				element.setAttribute("HYP_ar", value);
				_hype.Apply.UpdateFrame(element);
			},
			
			't' : function (value, element) {
				element.style.fontSize = value;
			},
			
			's' : function (value, element) {
				element.style.fontFamily = value;
			},
			
			'v' : function (value, element) {
				element.style.fontWeight = value;
			},
			
			'u' : function (value, element) {
				element.style.fontStyle = value;
			},
			
			'H' : function (value, element) {
				element.style.textDecoration = value;
			},
			
			'F' : function (value, element) {
				element.style.textAlign = value;
			},
			
			'G' : function (value, element) {
				element.style.color = value;
			},
			
			'X' : function (value, element) {
				element.style.letterSpacing = value;
			},
			
			'E' : function (value, element) {
				element.style.wordSpacing = value;
			},
			
			'Y' : function (value, element) {
				element.style.lineHeight = value;
			},

			'aX' : function (value, element) {
				element.setAttribute("HYP_f", value);

				var reflectionDepth = element.getAttribute("HYP_g");
				if(reflectionDepth != null) {
					_hype.Apply['aW'](reflectionDepth, element);
				}
			},

			'aW' : function (value, element) {
				element.setAttribute("HYP_g", value);
				
				var reflectionOffset = element.getAttribute("HYP_f");
				if(reflectionOffset == null) {
					//TODO: make it so this value comes from the same place for HypeAttribute and the javascript
					reflectionOffset = "8px";
				}
				
				if(_hype.browserInfo.android == null) {
					if((1.0 - value) == 1.0 && element.style.removeProperty != null) {
						element.style.removeProperty(_hype.kSizeOptimizationWebKitPrefix + "box-reflect");
					} else {
						element.style[_hype.kSizeOptimizationWebKitPrefix + "box-reflect"] = "below " + reflectionOffset + " -webkit-gradient(linear,left top,left bottom,from(transparent),color-stop(" + (1.0 - value) + ",transparent),to(rgba(255,255,255,.5)))";
					}
				}
			},

			'f' : function (value, element) {
				element.setAttribute("HYP_d", value);


					_hype.Apply.ApplyTransforms(element);

			},

			'bR' : function (value, element) {
				element.setAttribute("HYPE_ak", value);				
				_hype.Apply.ApplyTransforms(element);
			},
			
			'aY' : function (value, element) {
				element.setAttribute("HYP_e", value);
				_hype.Apply.ApplyTransforms(element);
			},

			'tX' : function (value, element) {
				element.setAttribute("HYP_tX", value);

					_hype.Apply.ApplyTransforms(element);

			},

			'tY' : function (value, element) {
				element.setAttribute("HYP_tY", value);

					_hype.Apply.ApplyTransforms(element);

			},

			'bO' : function (value, element) {
				element.setAttribute("HYP_ad", value);

					_hype.Apply.ApplyTransforms(element);

			},

			'bP' : function (value, element) {
				element.setAttribute("HYP_ae", value);
			},



			'e' : function (value, element) {

			
				// others
				element.style.opacity = value;
				element.style.MozOpacity = value;
			},
			
			'aP' : function (value, element) {
				element.style.cursor = value;
			},
			
			'bD' : function (value, element) {
				// webkit
				element.style[_hype.kSizeOptimizationWebKitPrefix + "user-select"] = value;
				
				// mozilla
				element.style["MozUserSelect"] = value;
				
				// ie
				element["onselectstart"] = (function() { return false; });
			},

			'N' : function (value, element) {
				element.style.borderLeftWidth = value;
				_hype.invalidateBrowserReportedSizeCacheForElement(element);
				_hype.Apply.UpdateFrame(element);
			},

			'O' : function (value, element) {
				element.style.borderRightWidth = value;
				_hype.invalidateBrowserReportedSizeCacheForElement(element);
				_hype.Apply.UpdateFrame(element);
			},

			'P' : function (value, element) {
				element.style.borderTopWidth = value;
				_hype.invalidateBrowserReportedSizeCacheForElement(element);
				_hype.Apply.UpdateFrame(element);
			},

			'M' : function (value, element) {
				element.style.borderBottomWidth = value;
				_hype.invalidateBrowserReportedSizeCacheForElement(element);
				_hype.Apply.UpdateFrame(element);
			},

			'B' : function (value, element) {
				element.style.borderLeftColor = value;
			},

			'C' : function (value, element) {
				element.style.borderRightColor = value;
			},

			'D' : function (value, element) {
				element.style.borderTopColor = value;
			},

			'A' : function (value, element) {
				element.style.borderBottomColor = value;
			},

			'J' : function (value, element) {
				element.style.borderLeftStyle = value;
			},

			'K' : function (value, element) {
				element.style.borderRightStyle = value;
			},

			'L' : function (value, element) {
				element.style.borderTopStyle = value;
			},

			'I' : function (value, element) {
				element.style.borderBottomStyle = value;
			},


			
			'aK' : function (value, element) {
				element.style['borderTopLeftRadius'] = value;
				
				// firefox 3.6
				element.style["MozBorderRadiusTopleft"] = value;
				

			},
			
			'aL' : function (value, element) {
				element.style['borderTopRightRadius'] = value;

				// firefox 3.6
				element.style["MozBorderRadiusTopright"] = value;


			},
			
			'aI' : function (value, element) {
				element.style['borderBottomLeftRadius'] = value;
				
				// firefox 3.6
				element.style["MozBorderRadiusBottomleft"] = value;


			},
			
			'aJ' : function (value, element) {
				element.style['borderBottomRightRadius'] = value;

				// firefox 3.6
				element.style["MozBorderRadiusBottomright"] = value;
				

			},
			
			'bE' : function (value, element) {
				element.innerHTML = "";
			
				var objectEmbedSource;
				
				var resourceIDs = _hype.resourceGroups[value];
				if(resourceIDs == null) {
					return;
				}
				
				for(var i = 0; i < resourceIDs.length; i++) {
					var resourceID = resourceIDs[i];
					var resource = _hype.resources[resourceID];
					var mimeType = resource["t"];
					var resourcePath = _hype.resourcePathForResourceID(resourceID);
					
					var sourceElement = element.ownerDocument.createElement("source");
					sourceElement.src = resourcePath;
					if(mimeType != null && (_hype.browserInfo.chrome == null || mimeType != "video/quicktime")) { // quicktime check for 4741: Quicktime videos do not play in Chrome
						sourceElement.setAttribute("type", mimeType);
					}
					
					if(objectEmbedSource == null && mimeType != "video/ogg" && mimeType != "video/webm") {
						objectEmbedSource = resourcePath;
					}
					
					element.appendChild(sourceElement);
				}


			},
			
			// this is internal for IE support
			'Play' : function (value, embedobj) {
                try {
                    if(value == true) {
						embedobj['Play']();
					} else {
						embedobj['Stop']();
					}
                } catch (err) {
                    window.setTimeout(function() { _hype.Apply['Play'](value, embedobj) }, 100);
                }
			},

			'aH' : function (value, element) {
				// autoplay is now used by Hype's animation system
				element.autoplay = false;// (value != 0);
			},

			'aO' : function (value, element) {

				value = false;

				element.setAttribute("controls", value);
				element.controls = (value != 0);


			},

			'aR' : function (value, element) {
				element.setAttribute("mute", value);
				element.volume = (value != 0) ? "0.0" : "1.0";


			},

			'aQ' : function (value, element) {
				element.setAttribute("loop", value);
				element.loop = (value != 0);

			},

			'j' : function (value, element) {
				element.style.position = value;
			},

			'r' : function (value, element) {
				element.style.display = value;
			},
			
			'aG' : function (value, element) {
				element.setAttribute("title", value);
				element.setAttribute("alt", value);
			},

			'g' : function (value, element) {
				element.style.backgroundColor = value;
			},

			'n' : function (value, element) {
				element.setAttribute("HYP_p", value);
				_hype.Apply.BackgroundGradient(element);
			},
			
			'm' : function (value, element) {
				element.setAttribute("HYP_q", value);
				_hype.Apply.BackgroundGradient(element);
			},
			
			'l' : function (value, element) {
				element.setAttribute("HYP_r", value);
				_hype.Apply.BackgroundGradient(element);
			},
			
			BackgroundGradient : function (element) {
				var backgroundGradientStartColor = element.getAttribute("HYP_p");
				if(backgroundGradientStartColor == null || backgroundGradientStartColor == "") {
					return;
				}
				var backgroundGradientEndColor = element.getAttribute("HYP_q");
				if(backgroundGradientEndColor == null || backgroundGradientEndColor == "") {
					return;
				}
				var backgroundGradientAngle = element.getAttribute("HYP_r");
				if(backgroundGradientAngle == null) {
					return;
				}

				function generateGradientString(prefix, isSpecRotation) {
					var angle = (isSpecRotation == true) ? ((parseFloat(Math.abs(parseFloat(backgroundGradientAngle))) + 180) % 360) : ((parseFloat(360 - Math.abs(parseFloat(backgroundGradientAngle))) + 270) % 360);
					return "" + prefix + "linear-gradient(" + angle + "deg," + backgroundGradientStartColor + "," + backgroundGradientEndColor + ")";
				}
				
				if(_hype.browserInfo.webkit != null) {
					element.style.backgroundImage = generateGradientString(_hype.kSizeOptimizationWebKitPrefix, false);
				} else if(_hype.browserInfo.gecko != null) {
					element.style.backgroundImage = generateGradientString("-moz-", false);
				}

				else if(_hype.browserInfo.opera < 15) {
					element.style.backgroundImage = generateGradientString("-o-", false);
				} else {
					element.style.backgroundImage = generateGradientString("", true);
				}
			},

			'h' : function (value, element) {
				var resourceID = _hype.bestImageResourceIDForResourceGroupOid(value);
				if(resourceID == null) {
					return;
				}

				var resourceName = _hype.resources[resourceID]['n'];
				var resourcePath = _hype.resourcePathForResourceID(resourceID);

				//!! do not do this for webkit based browsers because they work the old way and this will cause a crash! (need to file)
				if((_hype.browserInfo.ff < 3.6 || _hype.browserInfo.ie < 9) && (element.style.backgroundRepeat == null || element.style.backgroundRepeat == "" || element.style.backgroundRepeat == "no-repeat")) {
					// need to create an img tag because IE8- and FF 3.5- can't scale background images
					var imgId = "img_" + element.id;
					var imgElement = document.getElementById(imgId);
					if(imgElement != null) {
						imgElement.src = resourcePath;
					} else {					
						imgElement = document.createElement("img");
						imgElement.src = resourcePath;
						imgElement.id = imgId;
						imgElement.style.position = "absolute";
						imgElement.style.width = "100%";
						imgElement.style.height = "100%";
						imgElement.style.top = "0";
						imgElement.style.left = "0";
						imgElement.style.zIndex = "-10000";
						element.appendChild(imgElement);
					}
					
					imgElement.setAttribute("HYP_ag", imgElement.src);



					// need to set width/height of image
					_hype.Apply.UpdateFrame(element);
				} else {
					// use data url cached version instead
					var imageDataURL = _hype.imageResourceIDToDataURLMapping[resourceID];
					if(imageDataURL != null) {
						element.style.backgroundImage = "url('" + imageDataURL + "')";
					} else {
						element.style.backgroundImage = "url('" + escapeURLForCSS(resourcePath) + "')";
					}					
				}
			},

			'q' : function (value, element) {
				element.style['backgroundSize'] = value;
				
				// webkit
				element.style[_hype.kSizeOptimizationWebKitPrefix + "background-size"] = value;
				
				// mozilla
				element.style["MozBackgroundSize"] = value;
			},

			'o' : function (value, element) {
				element.style['backgroundOrigin'] = value;
				
				// webkit
				element.style[_hype.kSizeOptimizationWebKitPrefix + "background-origin"] = value;
				
				// mozilla
				element.style["MozBackgroundOrigin"] = value;
			},

			'p' : function (value, element) {
				// remove that extra img tag we added in backgroundImage
				if(value != null && value != "no-repeat") {
					var img = document.getElementById("img_" + element.id);
					if(img != null) {
						element.style.backgroundImage = "url('" + escapeURLForCSS(img.getAttribute("HYP_ag")) + "')";
						img.parentNode.removeChild(img);
					}
				}
			
				element.style.backgroundRepeat = value;
			},

			'x' : function (value, element) {
				element.style.overflow = value;
			},

			'aT' : function (value, element) {
				element.style.paddingLeft = value;
				_hype.invalidateBrowserReportedSizeCacheForElement(element);
				_hype.Apply.UpdateFrame(element);
			},
			
			'aU' : function (value, element) {
				element.style.paddingRight = value;
				_hype.invalidateBrowserReportedSizeCacheForElement(element);
				_hype.Apply.UpdateFrame(element);
			},
			
			'aV' : function (value, element) {
				element.style.paddingTop = value;
				_hype.invalidateBrowserReportedSizeCacheForElement(element);
				_hype.Apply.UpdateFrame(element);
			},
			
			'aS' : function (value, element) {
				element.style.paddingBottom = value;
				_hype.invalidateBrowserReportedSizeCacheForElement(element);
				_hype.Apply.UpdateFrame(element);
			},
			
			'S' : function (value, element) {
				element.setAttribute("HYP_s", value);
				_hype.Apply.BoxShadow(element);
			},

			'T' : function (value, element) {
				element.setAttribute("HYP_t", value);
				_hype.Apply.BoxShadow(element);
			},
			
			'R' : function (value, element) {
				element.setAttribute("HYP_u", value);
				_hype.Apply.BoxShadow(element);
			},
			
			'Q' : function (value, element) {
				element.setAttribute("HYP_v", value);
				_hype.Apply.BoxShadow(element);
			},
						
			BoxShadow : function (element) {
				var boxShadowXOffset = element.getAttribute("HYP_s");
				if(boxShadowXOffset == null) {
					return;
				}
				var boxShadowYOffset = element.getAttribute("HYP_t");
				if(boxShadowYOffset == null) {
					return;
				}
				var boxShadowShadowColor = element.getAttribute("HYP_u");
				if(boxShadowShadowColor == null) {
					return;
				}
				var boxShadowBlurRadius = element.getAttribute("HYP_v");
				if(boxShadowBlurRadius == null) {
					return;
				}
				
				var xOffset = parseFloat(boxShadowXOffset);
				var yOffset = parseFloat(boxShadowYOffset);
				
				var boxShadowProperties = [_hype.kSizeOptimizationWebKitPrefix + "box-shadow" /* webkit */, "MozBoxShadow" /* mozilla */, "boxShadow" /* firefox 4.0+ */, "box-shadow" /* ie/standard */];

				// remove it so that IE can properly rotate
				if(xOffset == 0 && yOffset == 0 && parseFloat(boxShadowBlurRadius) == 0) {
					for(var i = 0; i < boxShadowProperties.length; i++) {
						if(element.style.removeAttribute) { // IE 6-8 does not have css.removeProperty, but removeAttribute will work
							element.style.removeAttribute(boxShadowProperties[i]);
						} else {
							element.style.removeProperty(boxShadowProperties[i]);
						}
					}
					
					return;
				}
				
				// apply
				var boxShadowValue = "" + xOffset + "px " + yOffset + "px " + boxShadowBlurRadius + " " + boxShadowShadowColor;
				for(var i = 0; i < boxShadowProperties.length; i++) {
					element.style[boxShadowProperties[i]] = boxShadowValue;
				}
			},


			'bB' : function (value, element) {
				element.setAttribute("HYP_w", value);
				_hype.Apply.TextShadow(element);
			},

			'bC' : function (value, element) {
				element.setAttribute("HYP_x", value);
				_hype.Apply.TextShadow(element);
			},
			
			'bA' : function (value, element) {
				element.setAttribute("HYP_y", value);
				_hype.Apply.TextShadow(element);
			},
			
			'aZ' : function (value, element) {
				element.setAttribute("HYP_z", value);
				_hype.Apply.TextShadow(element);
			},
			
			TextShadow : function (element) {
				var textShadowXOffset = element.getAttribute("HYP_w");
				if(textShadowXOffset == null) {
					return;
				}
				var textShadowYOffset = element.getAttribute("HYP_x");
				if(textShadowYOffset == null) {
					return;
				}
				var textShadowShadowColor = element.getAttribute("HYP_y");
				if(textShadowShadowColor == null) {
					return;
				}
				var textShadowBlurRadius = element.getAttribute("HYP_z");
				if(textShadowBlurRadius == null) {
					return;
				}
				
				var xOffset = parseFloat(textShadowXOffset);
				var yOffset = parseFloat(textShadowYOffset);
				var blurRadius = parseFloat(textShadowBlurRadius);
				
				if(xOffset == 0 && yOffset == 0 && blurRadius == 0) {
					element.style["textShadow"] = "none";
				} else {
					element.style["textShadow"] = "" + xOffset + "px " + yOffset + "px " + textShadowBlurRadius + " " + textShadowShadowColor;
				}
			},
			
			'bL' : function (value, element) {
				element.setAttribute("HYP_1", value);
				_hype.Apply.FilterEffect(element);
				
				// workaround for #3485/chromium bug 161423
				if(_hype.browserInfo.chrome != null && element.style[_hype.kSizeOptimizationWebKitPrefix + "transform"] == "") {
					element.style[_hype.kSizeOptimizationWebKitPrefix + "transform"] = "none";
				}
			},

			'bG' : function (value, element) {
				element.setAttribute("HYP_2", value);
				_hype.Apply.FilterEffect(element);
			},

			'bH' : function (value, element) {
				element.setAttribute("HYP_3", value);
				_hype.Apply.FilterEffect(element);
			},

			'bI' : function (value, element) {
				element.setAttribute("HYP_4", value);
				_hype.Apply.FilterEffect(element);
			},

			'bJ' : function (value, element) {
				element.setAttribute("HYP_5", value);
				_hype.Apply.FilterEffect(element);
			},

			'bK' : function (value, element) {
				element.setAttribute("HYP_6", value);
				_hype.Apply.FilterEffect(element);
			},
			
			FilterEffect : function (element) {
				var blur = element.getAttribute("HYP_1");
				var sepia = element.getAttribute("HYP_2");
				var hue = element.getAttribute("HYP_3");
				var saturate = element.getAttribute("HYP_4");
				var brightness = element.getAttribute("HYP_5");
				var contrast = element.getAttribute("HYP_6");
				
				var filterString = "";
				
				if(blur != null && parseFloat(blur) != 0) {
					filterString += "blur(" + blur + "px) ";
				}
				if(sepia != null && parseFloat(sepia) != 0) {
					filterString += "sepia(" + sepia + ") ";
				}
				if(hue != null && parseFloat(hue) != 0) {
					filterString += "hue-rotate(" + hue + ") ";
				}
				if(saturate != null && parseFloat(saturate) != 1.0) {
					filterString += "saturate(" + saturate + ") ";
				}
				
				var brightnessAsFloat = parseFloat(brightness);
				if(brightness != null && brightnessAsFloat != 1.0) {
					// older webkits and even Hype misinterpreted the spec (-100%=black,0%=normal,100%=white), thus I add 1.0 to the brightness value for newer browsers
					// see http://code.google.com/p/chromium/issues/detail?id=168004
					if(_hype.browserInfo.safari == 6 || (_hype.browserInfo.ios != null && parseFloat(_hype.browserInfo.webkit) <= 536.26)) {
						brightnessAsFloat -= 1;
						if(brightnessAsFloat > 1.0) { // also older safari did not allow brightness beyond 1.0, which is now allowed with the spec
							brightnessAsFloat = 1;
						}
					}
					filterString += "brightness(" + brightnessAsFloat + ") ";	
				}
				if(contrast != null && parseFloat(contrast) != 1.0) {
					filterString += "contrast(" + contrast + ") ";
				}
				
				if(filterString == "") {
					element.style[_hype.kSizeOptimizationWebKitPrefix + 'filter'] = "none";
				} else {
					element.style[_hype.kSizeOptimizationWebKitPrefix + 'filter'] = filterString;
				}
			},			
			
			'Z' : function (value, element) {
				element.style.wordWrap = value;
			},

			'yy' : function (value, element) {
				element.style['whiteSpace'] = value;
			},

			'y' : function (value, element) {
				element.style['whiteSpaceCollapsing'] = value;
			},
				 
			'z' : function (value, element) {
				element.style.zIndex = value;
				
				if(element.parentNode.className == "HYPE_element_container") { // webkit 3d support
					element.parentNode.style.zIndex = value;
				}
			},

			CreateActionHandler : function (value, element) {
				return function(e) {
					for(var i = 0; i < value.length; i++) {
						var action = value[i];
					
						var type = action["p"];
						if(type == _hype.kActionJumpToScene) {
							var sceneSymbol = action["f"];
							var transition = action["g"];
							var duration = action["d"];
							if(sceneSymbol == null || sceneSymbol == _hype.kJumpToSceneOther) {
								var sceneIndex = _hype.indexOfSceneWithIdentifier(action["e"]);
								_hype.showScene(sceneIndex, transition, duration);
							} else if(sceneSymbol == _hype.kJumpToSceneNext) {
								_hype.showNextScene(transition, duration);
							} else if(sceneSymbol == _hype.kJumpToScenePrevious) {
								_hype.showPreviousScene(transition, duration);
							} else if(sceneSymbol == _hype.kJumpToSceneFirst) {
								_hype.showScene(0, transition, duration);
							} else if(sceneSymbol == _hype.kJumpToSceneLast) {
								_hype.showScene(_hype.scenes.length - 1, transition, duration);
							}
						}/* else if(type == kActionPresentMedia) {
							
						}*/ else if(type == _hype.kActionStartTimeline) {
							if(action["b"] != null) {
								var timelineRun = _hype.timelineRunForIdentifier(action["b"]);
								var playInReverse = (action["z"] != undefined) ? action["z"] : false;
								_hype.startTimelineRun(action["b"], (element != null ? element.id : null), playInReverse);
							}
						} else if(type == _hype.kActionPauseTimeline) {
							if(action["b"] != null) {
								var pauseTime = action["r"];
								_hype.pauseTimelineWithIdentifier(action["b"], pauseTime);
							}
						} else if(type == _hype.kActionContinueTimeline) {
							if(action["b"] != null) {
								var playInReverse = (action["z"] != undefined) ? action["z"] : false;
								_hype.continueTimelineWithIdentifier(action["b"], playInReverse, true);
							}
						} else if(type == _hype.kActionGoToTimeInTimeline) {
							if(action["b"] != null && action["i"] != null) {
								_hype.goToTimeInTimelineWithIdentifier(action["i"], action["b"]);
							}
						} else if(type == _hype.kActionRunJavascript) {
							if(action["h"] != null) {
								var functionName = _hype.javascriptMapping[action["h"]];
								try {
									_hype['z_p'][functionName](_hype['API'], element, e);
								} catch(err) {
									_hype.log("Error in " + functionName + ": " + err);
								}
							}
						} else if(type == _hype.kActionVisitURL) {
							var url = _hype.urlByReplacingCommonUserErrors(action["j"]);
							if(url != null) {						
								var openInNewWindow = (action["k"] == true);

									// button == 1 is set by FF when ctrl clicking on Windows; meta == true is set by FF when command clicking on OS X.
									openInNewWindow = (openInNewWindow || e.button == 1 || e.metaKey == true);

								if(openInNewWindow) {
									if ((_hype.browserInfo.ie < 9 || _hype.browserInfo.quirksmode) || _hype.browserInfo.ff != null) {
										// IE 6 - 8 and IE 9+ in quirks mode don't respect createEvent, initMouseEvent, or dispatchEvent. Firefox reputedly does, but versions before FF6 seem to eat simulated click events; from reading the tea leaves of their bugs, I presume this is purposeful for "security" reasons. FF6-18 (the current Nightly) work in normal mouse click scenarios, but swallow events when the user cmd-clicks. Fall back to naive behavior for these browsers.
										window.open(url, "_blank");
									} else if ((_hype.browserInfo.ie == 9 || _hype.browserInfo.opera < 15) && !(e instanceof MouseEvent)) {
										// window.open must be used on IE 9 and Opera for non-mouse events, as those browsers raise errors when trying to create an artifical mouse event if the user didn't click. (#3782) This logic silently fails on Safari 6 when pop-up blocking is enabled, so we're isolating this case to just IE 9 and Opera.
										window.open(url, "_blank");
									} else {
										// Create an a element in the DOM and virtually click it, providing any current modifier keys. Lets the browser do the right thing w.r.t. opening the link in a new window or tab.
										var temporaryLink = document.createElement('a');
										temporaryLink.setAttribute("href", url);

										// IE>=9 don't repect the middle-mouse button or meta key triggers when handling artifical clicks – they're treated as normal clicks.  We can circumvent that behavior by seting a _blank target if we detect a middle-mouse button click or a meta key click. Safari, Chrome and Opera all do the right thing with or without the _blank target, but the _blank target is necessary to create a new window if the Hype animation is embedded in an iframe
										temporaryLink.setAttribute("target", "_blank");

										document.body.appendChild(temporaryLink);
										var clickEvent = document.createEvent("MouseEvents");
										clickEvent.initMouseEvent("click", e.bubbles, e.cancelable, e.view, e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.button, document.body.parentNode);
										temporaryLink.dispatchEvent(clickEvent);
										document.body.removeChild(temporaryLink);
									}
								} else {
									// This is the only way to open links in the same page which works in desktop browsers, iBooks, and in iOS stand-alone web apps, and when Hype animations are embedded in iframes
									window.top.location = url;
								}
							}
						} else if(type == _hype.kActionComposeEmail) {	
							var toAddress = action["l"];
							var subject = action["m"];
							var body = action["n"];
							var url = "mailto:";
							if (toAddress != null) {
								url += escape(toAddress);
							}	
							if (subject != null || body != null) {
								url += "?";
								var parts = Array();
								if (subject != null) {
									parts.push("subject="+encodeURIComponent(subject));
								}
								if (body != null) {
									parts.push("body="+encodeURIComponent(body));
								}
								url += parts.join("&");
							}
							if (url != "mailto:") {
								window.top.location = url;
							}
						} else if(type == _hype.kActionPlaySound) {
							var resourceGroupOid = action["o"];
							var loop = action["q"];
							if(loop != true) {
								loop = false;
							}
							if(resourceGroupOid != null) {
								_hype.playAudioResourceGroupOid(resourceGroupOid, loop);
							}
						} else if(type == _hype.kActionPauseSound) {
							var resourceGroupOid = action["o"];
							if(resourceGroupOid != null) {
								_hype.pauseAudioResourceGroupOid(resourceGroupOid);
							}
						}
					}
				};
			},

			'aA' : function (value, element) {
				var actionHandler = _hype.Apply.CreateActionHandler(value["a"], element);
				if (_hype.useTouchEvents) {
					var options = { fingers: 1, gestureType:_hype.kGestureTap };
					_hype.addGestureHandler(options, element, actionHandler);
					element.style["-webkit-tap-highlight-color"] = "rgba(0,0,0,0)";
				}
				_hype.addActionHandler("click", element, actionHandler, true);
			},
			
			'aD' : function (value, element) {
				_hype.addActionHandler("mouseover", element, _hype.Apply.CreateActionHandler(value["a"], element), false);
			},
			
			'aC' : function (value, element) {
				_hype.addActionHandler("mouseout", element, _hype.Apply.CreateActionHandler(value["a"], element), false);
			},

			'aE' : function (value, element) {
				var actionHandler = _hype.Apply.CreateActionHandler(value["a"], element);
				if (_hype.useTouchEvents) {
					_hype.addActionHandler("touchend", element, actionHandler, true);
					element.style["-webkit-tap-highlight-color"] = "rgba(0,0,0,0)";
				}
				_hype.addActionHandler("mouseup", element, actionHandler, true);
			},

			'aB' : function (value, element) {
				var actionHandler = _hype.Apply.CreateActionHandler(value["a"], element);
				if (_hype.useTouchEvents) {
					_hype.addActionHandler("touchstart", element, actionHandler, true);
					element.style["-webkit-tap-highlight-color"] = "rgba(0,0,0,0)";
				}
				_hype.addActionHandler("mousedown", element, actionHandler, true);
			},
			
			'bN' : function (value, element) {
				_hype.addDragGestureHandler(value, element);
			},
			
			CreateButtonHandler : function (value, element) {
				return function(e) {
					_hype.applyButtonFromTimelineRun(e, value, _hype.idReverseMapping[element.id]);
				};
			},
			
			CreateButtonResetHandler : function (value, element) {
				return function(e) {
					_hype.resetButtonFromTimelineRun(e, value, _hype.idReverseMapping[element.id]);
				};
			},
			
			'aM' : function (value, element) {
				_hype.addActionHandler("mouseover", element, _hype.Apply.CreateButtonHandler(value, element), false);
				_hype.addActionHandler("mouseout", element, _hype.Apply.CreateButtonResetHandler(value, element), false);
			},
			
			'aN' : function (value, element) {
				if (_hype.useTouchEvents) {
					_hype.addActionHandler("touchstart", element, _hype.Apply.CreateButtonHandler(value, element), false);
					_hype.addActionHandler("touchmove", element, _hype.Apply.CreateButtonResetHandler(value, element), false);
					_hype.addActionHandler("touchend", element, _hype.Apply.CreateButtonResetHandler(value, element), false);
					element.style["-webkit-tap-highlight-color"] = "rgba(0,0,0,0)";
				}
				_hype.addActionHandler("mousedown", element, _hype.Apply.CreateButtonHandler(value, element), false);
				_hype.addActionHandler("mouseup", element, _hype.Apply.CreateButtonResetHandler(value, element), false);	
			},

			'V' : function (value, element) {
				element.setAttribute("HYP_7", value);
				_hype.Apply.IFrameSource(element);
			},

			'U' : function (value, element) {
				element.setAttribute("HYP_8", value);
				_hype.Apply.IFrameSource(element);
			},
			
			'W' : function (value, element) {
				element.setAttribute("HYP_9", value);
				_hype.Apply.IFrameSource(element);
			},
			
			IFrameSource : function (element) {
				var type = element.getAttribute("HYP_7");
				if(type != null) {
					var iframeSrc = "";
					if(type == "1") { // url
						iframeSrc = _hype.urlByReplacingCommonUserErrors(element.getAttribute("HYP_9"));
					} else if(type == "0") { // html
						iframeSrc = "" + _hype.resourcesFolderName + "/" + element.getAttribute("HYP_8");
					}
					
					var iframeInnerHTML = "<iframe frameBorder='0'style='width:100%;height:100%;border:none;'src='" + iframeSrc + "'></iframe>";
					
					// fix for iBooks scrolling issue <rdar://problem/10798491> Cannot scroll iframes in iBooks on iPad
					if(_hype.browserInfo.ios != null) {
						iframeInnerHTML = "<div style='overflow:auto;-webkit-overflow-scrolling:touch;width:100%;height:100%;'>" + iframeInnerHTML + "</div>";
					}
					
					element.innerHTML = iframeInnerHTML;
				}
			},
			
			'w' : function (value, element) {
				var img = document.getElementById("img_" + element.id);
				var imgClone = (img != null) ? img.cloneNode(true) : null;			
				element.innerHTML = value;				

				// execute any embedded <script>...</script> javascripts (those with src= will not work, see #868)
				var scripts = element.getElementsByTagName("script");
				for(var i = 0; i < scripts.length; i++) {
					if(scripts[i].src == "") {
						// better form of eval(scripts[i].text) for IE and Firefox (taken from jquery)
						(window.execScript || function(data) { window["eval"].call(window, data); })(scripts[i].text);
					}
				}

				if(imgClone != null) {
					element.appendChild(imgClone);
				}
			},
			
			'aF' : function (value, element) {
				element.style.cssText += "\n" + value;
			},
			
			'k' : function (value, element) {
				// can't change this after the fact!
			},
			
			'bF' : function (value, element) {
				var childElement = element;
				var childContainer = element.parentNode;
				if(childContainer != null && childContainer.className == "HYPE_element_container") {
					childElement = childContainer;
				}
				var newParentElement = document.getElementById(_hype.idMapping[value]);
				var oldParentElement = childElement.parentNode;
				if(newParentElement == null || newParentElement == childElement || newParentElement == element || newParentElement == oldParentElement) {
					return;
				}
				
				oldParentElement.removeChild(childElement);
				newParentElement.appendChild(childElement);
			}
			
		});


	
	//
//  HypeAudio.js
//  Hype
//
//  Created by Jonathan Deutsch on 7/17/12.
//  Copyright (c) 2012 Tumult Inc. All rights reserved.
//

var HypeAudio_allAudios = {};
var audioContext = null;

function HypeAudio(audioMethodAPI, oid, sourceURLsByMimeType, options) {
	var hypeAudio = HypeAudio_allAudios[oid];
	if(hypeAudio != null) {
		return hypeAudio;
	}
	
	hypeAudio = new audioMethodAPI(oid, sourceURLsByMimeType);

	hypeAudio.oid = oid;
	hypeAudio.sourceURLsByMimeType = sourceURLsByMimeType;
	hypeAudio.options = options;

//  default values:
//	hypeAudio.playWhenFinishedLoading = false;
//	hypeAudio.isLoading = false;
//	hypeAudio.isLoaded = false;
//	hypeAudio.audioBuffer = null;
//	hypeAudio.loop = false;

	hypeAudio.sortedMimeTypes = (function () {
		var mimeTypes = Array();
		for(var mimeType in hypeAudio.sourceURLsByMimeType) {
			if(hypeAudio.sourceURLsByMimeType.hasOwnProperty(mimeType) == false) {
				continue;
			}
			mimeTypes.push(mimeType);
		}
		return mimeTypes.sort();
	});

	return hypeAudio;
}

function HypeAudio_HTML5(oid, sourceURLsByMimeType) {
	var _hypeAudio = this;
	HypeAudio_allAudios[oid] = _hypeAudio;

	_hypeAudio.setupAudioElement = (function (errorCallback) {
		var audioElement = document.createElement("audio");
		_hypeAudio.audioElement = audioElement;
		var sortedMimeTypes = _hypeAudio.sortedMimeTypes();
		for(var i = 0; i < sortedMimeTypes.length; i++) {
			var mimeType = sortedMimeTypes[i];
			var sourceElement = document.createElement("source");
			var sourceURL = _hypeAudio.sourceURLsByMimeType[mimeType];
			sourceElement.setAttribute("src", sourceURL);
			sourceElement.setAttribute("type", mimeType);
			audioElement.appendChild(sourceElement);
		}
		document.body.appendChild(audioElement);
		
		return audioElement;
	});

	_hypeAudio.load = (function (successCallback, errorCallback) {
		if(_hypeAudio.isLoaded == true || _hypeAudio.isLoading == true) {
			return;
		}
		
		var audioElement = _hypeAudio.setupAudioElement(errorCallback);
		
		var loadCompleteFunction = (function() {
			window.clearTimeout(_hypeAudio.watchdog);
			if(_hypeAudio.isLoaded == true) {
				return;
			}
			
			_hypeAudio.isLoading = false;
			_hypeAudio.isLoaded = true;
			if(successCallback != null) {
				successCallback(_hypeAudio);
			}
			if(_hypeAudio.playWhenFinishedLoading == true) {
				_hypeAudio.play();
			}
		});

		var errorFunction = (function() {
			window.clearTimeout(_hypeAudio.watchdog);
			if(_hypeAudio.isLoaded == true) {
				return;
			}
			_hypeAudio.isLoading = false;
			if(errorCallback != null) {
				errorCallback(_hypeAudio);
			}
		});

		audioElement.addEventListener("canplaythrough", loadCompleteFunction, true);
		audioElement.addEventListener("abort", errorFunction, true);
		audioElement.addEventListener("error", errorFunction, true);
		_hypeAudio.watchdog = window.setTimeout(errorFunction, 15000); // watchdog timer to make sure the document still loads after 15 seconds (ex: ogg preload on ios gives no error)
		
		_hypeAudio.isLoading = true;
		if(navigator.onLine == null || navigator.onLine == true) {
			audioElement.load();
		} else {
			loadCompleteFunction();
		}
	});
	
	_hypeAudio.play = (function () {
		if(_hypeAudio.isLoaded != true) {
			if(_hypeAudio.isLoading == true) {
				_hypeAudio.playWhenFinishedLoading = true;
				return;
			}
			_hypeAudio.isLoaded = true;
		}
		
		if(_hypeAudio.audioElement == null) {
			_hypeAudio.setupAudioElement(null);
		}
				
		try {
			_hypeAudio.audioElement.loop = _hypeAudio.loop;
			_hypeAudio.audioElement.currentTime = 0;
		} catch(e) { }
		_hypeAudio.audioElement.play();
	});

	_hypeAudio.pause = (function () {
		if(_hypeAudio.isLoaded != true) {
			_hypeAudio.playWhenFinishedLoading = false;
			return;
		}

		if(_hypeAudio.audioElement != null) {
			_hypeAudio.audioElement.pause();
		}
	});
}

function HypeAudio_WebAudioAPI(oid, sourceURLsByMimeType) {
	var _hypeAudio = this;
	HypeAudio_allAudios[oid] = _hypeAudio;

	if(audioContext == null) {
		if(typeof AudioContext !== "undefined") {
			audioContext = new AudioContext();
		} else if(typeof webkitAudioContext !== "undefined") {
			audioContext = new webkitAudioContext();
		}
	}

	_hypeAudio.load = (function (successCallback, errorCallback) {
		if(_hypeAudio.isLoaded == true || _hypeAudio.isLoading == true) {
			return;
		}
		
		_hypeAudio.isLoading = true;
		
		var decodeArrayBuffer = (function (arrayBuffer) {
			audioContext['decodeAudioData'](arrayBuffer, (function (audioBuffer) {
				_hypeAudio.audioBuffer = audioBuffer;
				_hypeAudio.isLoading = false;
				_hypeAudio.isLoaded = true;

				if(successCallback != null) {
					successCallback(_hypeAudio);
				}
				if(_hypeAudio.playWhenFinishedLoading == true) {
					if(_hypeAudio.source != null && _hypeAudio.options.startAheadOfPlayback == true) {
						_hypeAudio.source['buffer'] = audioBuffer;
					} else {
						_hypeAudio.play();
					}
				}
			}), (function (audioBuffer) {
				_hypeAudio.isLoading = false;
				if(errorCallback != null) {
					errorCallback(_hypeAudio);
				}			
			}));
		});
	
	
		// use a '?' at the end to trigger the NETWORK: variant of the resource when using the cache manifest
		var url = _hypeAudio.validSourceURL();
		if(url.indexOf("?") == -1) {
			url = "" + url + "?";
		}
		
		var request = new XMLHttpRequest();
		request.open("GET", url, true);
		request.responseType = "arraybuffer";		
		
		request.onload = (function () {
			decodeArrayBuffer(request.response);
		});
		
		request.onerror = (function () {
			_hypeAudio.isLoading = false;
			if(errorCallback != null) {
				errorCallback(_hypeAudio);
			}
		});
		
		request.send();
	});
	
	_hypeAudio.play = (function () {
		_hypeAudio._stop();

		_hypeAudio.source = audioContext['createBufferSource']();
		_hypeAudio.source['connect'](audioContext['destination']);
		_hypeAudio.source['loop'] = _hypeAudio.loop;
		
		if(_hypeAudio.options.startAheadOfPlayback == true) {
			_hypeAudio._start();
		}

		if(_hypeAudio.isLoaded != true) {
			_hypeAudio.playWhenFinishedLoading = true;
			_hypeAudio.load();
			return;
		} else {
			_hypeAudio.source['buffer'] = _hypeAudio.audioBuffer;
			if(_hypeAudio.options.startAheadOfPlayback != true) {
				_hypeAudio._start();
			}
		}
	});

	_hypeAudio.pause = (function () {
		if(_hypeAudio.isLoaded != true) {
			_hypeAudio.playWhenFinishedLoading = false;
			return;
		}

		_hypeAudio._stop();
	});
	
	_hypeAudio._start = (function () {
		try {
			if(_hypeAudio.source != null) {
				if(_hypeAudio.source['noteOn'] != null) {
					_hypeAudio.source['noteOn'](0);
				} else {
					_hypeAudio.source['start'](0);
				}
			}
		} catch (e) {	}
	});
	
	_hypeAudio._stop = (function () {
		try {
			if(_hypeAudio.source != null) {
				if(_hypeAudio.source['noteOff'] != null) {
					_hypeAudio.source['noteOff'](0);
				} else {
					_hypeAudio.source['stop'](0);
				}
			}
		} catch (e) {	}
	});
	
	_hypeAudio.validSourceURL = (function () {
		if(_hypeAudio.resolvedValidSourceURL != null) {
			return _hypeAudio.resolvedValidSourceURL;
		}
		
		var dummyAudioElement = document.createElement("audio");
	
		var sortedMimeTypes = _hypeAudio.sortedMimeTypes();
		for(var i = 0; i < sortedMimeTypes.length; i++) {
			var mimeType = sortedMimeTypes[i];
			_hypeAudio.resolvedValidSourceURL = _hypeAudio.sourceURLsByMimeType[mimeType];
			if(dummyAudioElement.canPlayType(mimeType) != "no" && dummyAudioElement.canPlayType(mimeType) != "") {
				break;
			}
		}
		
		return _hypeAudio.resolvedValidSourceURL;
	});
}	



	
	///////////////////////////////////////////////////////////////
	// exported (public) interfaces
	
	_hype['API'] = {
		'kSceneTransitionInstant' : _hype.kSceneTransitionInstant,
		'kSceneTransitionCrossfade' : _hype.kSceneTransitionCrossfade,
		'kSceneTransitionSwap' : _hype.kSceneTransitionSwap,
		'kSceneTransitionPushLeftToRight' : _hype.kSceneTransitionPushLeftToRight,
		'kSceneTransitionPushRightToLeft' : _hype.kSceneTransitionPushRightToLeft,
		'kSceneTransitionPushBottomToTop' : _hype.kSceneTransitionPushBottomToTop,
		'kSceneTransitionPushTopToBottom' : _hype.kSceneTransitionPushTopToBottom,
		'kHypeGesturePhaseStart' : _hype.kPhaseStart,
		'kHypeGesturePhaseMove' : _hype.kPhaseMove,
		'kHypeGesturePhaseEnd' : _hype.kPhaseEnd,
		'kHypeGesturePhaseCancel' : _hype.kPhaseCancel,
		'documentName' : (function () { return _hype.documentName; }),
		'documentId' : (function () { return _hype.mainContentContainerID; }),
		'sceneNames' : _hype.sceneNames,
		'currentSceneName' : _hype.currentSceneName,
		'showSceneNamed' : _hype.showSceneNamed,
		'showNextScene' : _hype.showNextScene,
		'showPreviousScene' : _hype.showPreviousScene,
		'playTimelineNamed' : _hype.startTimelineNamed,
		'startTimelineNamed' : _hype.startTimelineNamed,
		'goToTimeInTimelineNamed' : _hype.goToTimeInTimelineNamed,
		'pauseTimelineNamed' : _hype.pauseTimelineNamed,
		'continueTimelineNamed' : _hype.continueTimelineNamed,
		'getElementById' : _hype.getElementById,
		'functions' : (function() { return _hype['z_p']; }),
		'resourcesFolderURL' : (function () { return _hype.resourcesFolderName; }),
		'relayoutIfNecessary' : _hype.relayoutIfNecessary,
		'kDirectionForward' : _hype.kDirectionForward,
		'kDirectionReverse' : _hype.kDirectionReverse,
		'currentTimeInTimelineNamed' : _hype.currentTimeInTimelineNamed,
		'durationForTimelineNamed' : _hype.durationForTimelineNamed,
		'currentDirectionForTimelineNamed' : _hype.currentDirectionForTimelineNamed,
		'isPlayingTimelineNamed' : _hype.isPlayingTimelineNamed

		 ,
	    'heartbeatForVideoExport' : _hype.heartbeatForVideoExport,
		'requestAnimationFrameCallback' : _hype.requestAnimationFrameCallback

	}

});

if (window['HYPE'] == null) {
	window['HYPE'] = window['HYPE_344'];
	window['HYPE']['documents'] = {};
}

while(window['HYPE_dtl_344'].length > 0) {
	window['HYPE_dtl_344'][0]();
	window['HYPE_dtl_344'].splice(0,1);
}

})();