//if (TVGS) alert('tvgs!');



// test curve unit
// this is intended to be a general path calculation unit, returning a path as an array of coordinate pairs
// returned paths include end point and leave out start point (so: 10 steps means ten steps excluding the start point)
// spatial curvature describes the position of points, taking movement away from a straight line a straight line
// spatial curve should accept pixels ( + or -) or a float between 1 and -1
// temporal curvature describes the distance between consecutive points, making movement slower or faster, total time delta (number of steps) remains the same
// leaving out position parameters defaults them to zero
// leaving out step_total defaults it to 1
// complete = true returns start point as well
// precise = true returns float numbers instead of rounded coordinates
if (TVGS) TVGS.path = function (path_config)
{
	
	// CHECK INPUT
	
	// check input
	path_config = TVGS.path_check(path_config);
	
	// check coordinates
	// note: these may be left at null by the path_check
	if (x_start === null) x_start = 0;
	if (y_start === null) y_start = 0;
	if (x_end === null) x_end = 0;
	if (y_end === null) y_end = 0;
	
	
	// internalize config values as function variables
	
	// general
	var x_start = path_config.x_start;
	var y_start = path_config.y_start;
	var x_end = path_config.x_end;
	var y_end = path_config.y_end;
	var step_total = path_config.step_total;
	
	// curve
	var curve = path_config.curve;
	var curve_size = path_config.curve_size;
	var curve_repeat = path_config.curve_repeat;
	var curve_repeat_alternate = path_config.curve_repeat_alternate;
	var curve_repeat_decay = path_config.curve_repeat_decay;
	var curve_repeat_decay_distance = path_config.curve_repeat_decay_distance;
	
	// path
	var linear_step = path_config.linear_step;
	var reverse = path_config.reverse;
	var mirror = path_config.mirror;
	var mirror_flip = path_config.mirror_flip;
	var rebound = path_config.rebound;
	var rebound_flip = path_config.rebound_flip;
	
	// output
	var complete = path_config.complete;
	var precise = path_config.precise;
	
	
	
	//document.getElementById('output').innerHTML += ' xs:' + x_start + ' ys:' + y_start + ' xe:' + x_end + ' ye:' + y_end + ' st:' + step_total;
	
	
	
	// SET PATH GLOBALS
	
	// get distances
	var x_distance = x_end - x_start;
	var y_distance = y_end - y_start;
	var path_lenght = Math.sqrt((x_distance * x_distance) + (y_distance * y_distance));
	
	// get step size of the straight path
	var x_step_size = x_distance / step_total;
	var y_step_size = y_distance / step_total;
	
	

	// calculate paths
	
	// calculate x path
	var x_step_number = 0;

	
	var x_step_path = [];
	
	while (x_step_number < step_total)
	{
		x_step_path[x_step_number] = x_start + (x_step_size * x_step_number);
		x_step_number++;
	}
	
	// add last step
	x_step_path[x_step_number] = x_end;
	
	
	// calculate y path
	var y_step_number = 0;


	var y_step_path = [];
	
	while (y_step_number < step_total)
	{
		y_step_path[y_step_number] = y_start + (y_step_size * y_step_number);
		y_step_number++;
	}
	
	// add last step
	y_step_path[y_step_number] = y_end;
	
	
	//alert (x_step_path);
	//alert (y_step_path);
	
	

	
	
	// apply spatial curve
	// when path length is zero spatial direction cannot be determined
	if (!(path_lenght == 0 || curve == 0 || curve_size == 0))
	{
			
			
			// calculate straight curve offset trajectory
			//var curve_offset = curve_size;
			//var steps = step_total + 1;
			
			var curve_offset_path = [];
			
			var x = 0; // ramp must start at zero
			//var y = 0;
			
			var relative_step_distance = 2 / (step_total);
			
			var x_position = 0;
			var y_position = 0;
			
			// get the step path inside this segment, excluding and that are equal to start or end point of this segment
			
			// quick fix, just get them into an array until segments are implemented properly
			var relative_segment_step = [];
			
			
			var half_pi = Math.PI / 2;
			
			while (x <= step_total)
			{
				var relative_step_x_offset = -1 + (x * relative_step_distance);
				
				
				if (linear_step == false)
				{
					// correct offset to hit evenly spaced step on semicircle for curve = 1
					relative_step_x_offset = Math.cos(half_pi - (relative_step_x_offset * half_pi));
				}
				
				relative_segment_step.push(relative_step_x_offset);
			
				x++;
			}
			
			
			
			
			for (var i in relative_segment_step){ if (TVGS.key(i)){
				
				// calculate y path based on curve
				// y deviate is maximum
				
				// get half circle y offset multiplier for a given number of steps
				// trajectory x length = 2
				// trajectory y offset maximum is 1
				

				
				// get y offset
				y_position = Math.sqrt(1 - (relative_segment_step[i] * relative_segment_step[i]));
				
				// apply curve factor
				y_position = y_position * curve;
				
				
				// offset in units
				y_position = y_position * curve_size;
				

				
				curve_offset_path.push(y_position);
				//curve_offset_path[curve_offset_path.length] = y_position;
			
			}}
			
			
			
			/*
			// note: this skips the last step which always lands at x = 2, y = 0
			while (x < step_total)
			{
				
				// calculate y path based on curve
				// y deviate is maximum
				
				// get half circle y offset multiplier for a given number of steps
				// trajectory x length = 2
				// trajectory y offset maximum is 1
				
				// current x position, count from -1
				x_position = -1 + (x * step_distance);
				
				// get y offset
				y_position = Math.sqrt(1 - (x_position * x_position));
				
				// apply curve factor
				y_position = y_position * curve;
				
				
				// offset in units
				y_position = y_position * curve_size;
				
				
				
				//calc_output.innerHTML += y_position + ' ';
				
				curve_offset_path[curve_offset_path.length] = y_position;
			
				x++;
			}
			*/
			//alert (curve);
			//alert (curve_offset_path);
			
			
			
			
			
			// adding the deviation
			// this adds a chain of +90 or -90 degrees offsets to coordinates on the path
			// the offsets are a set of positive or negative numbers, as many as there are coordinates on the path
			
			// get the direction of the straight path as distance multipliers per length unit
			var x_unit_multiplier = x_distance / path_lenght;
			var y_unit_multiplier = y_distance / path_lenght;
			
			
			// set multipliers for path direction at a straight angle
			// note: used for translating curve coordinates when the angle of the base path changes
			// note: positive distance at a straight angle from the base path is clockwise
			var x_angle_multiplier = -1 * y_unit_multiplier;
			var y_angle_multiplier = x_unit_multiplier;
			
		
			
			// apply offsets of curve to real path
			for (var i in relative_segment_step){ if (TVGS.key(i)){
			
				x_step_path[i] = x_start + (((relative_segment_step[i] + 1) / 2) * x_distance) + (curve_offset_path[i] * x_angle_multiplier);
				y_step_path[i] = y_start + (((relative_segment_step[i] + 1) / 2) * y_distance) + (curve_offset_path[i] * y_angle_multiplier);
				
				
				//x_step_path[i] = x_step_path[i] + (curve_offset_path[i] * x_angle_multiplier);
				//y_step_path[i] = y_step_path[i] + (curve_offset_path[i] * y_angle_multiplier);
			
			}}
			
			//alert (x_step_path);
			//alert (y_step_path);
			
		}
		
		
		// round final coordinates
		if (precise == false)
		{
			for (var i in x_step_path){ if (TVGS.key(i)){
			
				x_step_path[i] = Math.round(x_step_path[i]);
				y_step_path[i] = Math.round(y_step_path[i]);
			
			}}
		}
		//alert (x_step_path);
		//alert (y_step_path);
		
		
		// gather in single coordinate array
		var path_coordinate = [];
		for (var i in x_step_path){ if (TVGS.key(i)){
		
			path_coordinate[i] = {
				x: x_step_path[i],
				y: y_step_path[i]
			};
			
			//document.getElementById('output').innerHTML += ' ==> finalstep ' + parseInt(x_step_path[i]) + ',' + parseInt(y_step_path[i]);
		
		}}
		
		// remove first step
		if (complete == false) path_coordinate.shift();
			
		
		//alert (path_coordinate);
		
		return path_coordinate;

}


// check path config
// when input is faulty defaults are used
// note: without input this returns a path config consisting completely of default values: the path moves to its default position in one step
if (TVGS) TVGS.path_check = function (path_config)
{
	
	// setup clean path config
	// note: any properties of the input that are not in this checked path model will be ignored and left out
	var checked_path_config = {
	
		// general
		x_start: null, // float, starting x coordinate
		y_start: null, // float, starting y coordinate
		x_end: null, // float, ending x coordinate
		y_end: null, // float, ending y coordinate
		step_total: 1, // number of points on the path, excluding the start point
		
		// curve
		curve: 0, // float, positive or negative determines direction, 0 is a straight line, 1 is a semicircle, -1 is a semicircle
		curve_size: null, // float, 0 or higher, determines the size of the curve in the same units as the start and end coordinates, 0 is a straight line, null is ignore
		curve_repeat: 1, // integer, 1 or higher, how often to repeat the curve as a path segment
		curve_repeat_alternate: false, // boolean, flip the curve of each consecutive segment between positive and negative
		curve_repeat_decay: 1, // float, 0 to 1, the relative curve of the last segment compared to the first, 1 means no decay
		curve_repeat_decay_distance: 1, // float, 0 to 1, the relative distance of the last segment compared to the first, 1 means no decay
		
		// path
		linear_step: false, // boolean, step coordinates are calculated using distances that follow a straight path from start to end, instead of distances traced along the line of the curve
		reverse: false, // boolean, reverse the entire path
		mirror: false, // boolean, add a mirror of the path to the end of the path
		mirror_flip: false, // boolean, flip the curve values of the mirrored portion between positive and negative
		rebound: false, // boolean, backtrack from the end point to the start point
		rebound_flip: false, // boolean, flip the curve values of the backtracked portion between positive and negative
		
		// output
		complete: false, // boolean, include the start coordinates with the returned path
		precise: false, // boolean, return float coordinates
	
	end : null };
	
	
	// check all path parameters
	// parameters that are found in order are kept, otherwise the default value is left in place
	if(!(path_config instanceof Array) && (path_config !== null) && (typeof(path_config) == 'object'))
	{
		
		// general
		
		//if (typeof path_config.x_start != 'undefined') checked_path_config.x_start = TVGS.convert_to_float(path_config.x_start);
		//if (typeof path_config.y_start != 'undefined') checked_path_config.y_start = TVGS.convert_to_float(path_config.y_start);
		//if (typeof path_config.x_end != 'undefined') checked_path_config.x_end = TVGS.convert_to_float(path_config.x_end);
		//if (typeof path_config.y_end != 'undefined') checked_path_config.y_end = TVGS.convert_to_float(path_config.y_end);
		
		if (!TVGS.nil(path_config.x_start)) checked_path_config.x_start = TVGS.convert_to_float(path_config.x_start);
		if (!TVGS.nil(path_config.y_start)) checked_path_config.y_start = TVGS.convert_to_float(path_config.y_start);
		if (!TVGS.nil(path_config.x_end)) checked_path_config.x_end = TVGS.convert_to_float(path_config.x_end);
		if (!TVGS.nil(path_config.y_end)) checked_path_config.y_end = TVGS.convert_to_float(path_config.y_end);
		
		if (typeof path_config.step_total != 'undefined')
		{
			// get numeric integer value
			var step_total = Math.ceil(TVGS.convert_to_float(path_config.step_total));
			if (step_total > 0) checked_path_config.step_total = step_total;
		}
		
		
		// curve
		
		if (typeof path_config.curve != 'undefined') checked_path_config.curve = TVGS.convert_to_float(path_config.curve);

		if (!TVGS.nil(path_config.curve_size))
		{
			// get positive numeric float value, leave null intact
			//if (path_config.curve_size != null)
			//{
				var curve_size = TVGS.convert_to_float(path_config.curve_size);
				if (curve_size >= 0) checked_path_config.curve_size = curve_size;
			//}
		}
		
		if (typeof path_config.curve_repeat != 'undefined')
		{
			// get numeric integer value
			var curve_repeat = Math.ceil(TVGS.convert_to_float(path_config.curve_repeat))
			if (curve_repeat > 0) checked_path_config.curve_repeat = curve_repeat;
		}
		
		if (typeof path_config.curve_repeat_alternate && path_config.curve_repeat_alternate === true) checked_path_config.curve_repeat_alternate = true;
		
		if (typeof path_config.curve_repeat_decay != 'undefined')
		{
			// get positive numeric float value between 0 and 1
			var curve_repeat_decay = TVGS.convert_to_float(path_config.curve_repeat_decay);
			if (curve_repeat_decay >= 0 && curve_repeat_decay <= 1) checked_path_config.curve_repeat_decay = curve_repeat_decay;
		}
		
		if (typeof path_config.curve_repeat_decay_distance != 'undefined')
		{
			// get positive numeric float value between 0 and 1
			var curve_repeat_decay_distance = TVGS.convert_to_float(path_config.curve_repeat_decay_distance);
			if (curve_repeat_decay_distance >= 0 && curve_repeat_decay_distance <= 1) checked_path_config.curve_repeat_decay_distance = curve_repeat_decay_distance;
		}
		
		
		// path
		
		if (typeof path_config.linear_step != 'undefined' && path_config.linear_step === true) checked_path_config.linear_step = true;
		if (typeof path_config.reverse != 'undefined' && path_config.reverse === true) checked_path_config.reverse = true;
		if (typeof path_config.mirror != 'undefined' && path_config.mirror === true) checked_path_config.mirror = true;
		if (typeof path_config.mirror_flip != 'undefined' && path_config.mirror_flip === true) checked_path_config.mirror_flip = true;
		if (typeof path_config.rebound != 'undefined' && path_config.rebound === true) checked_path_config.rebound = true;
		if (typeof path_config.rebound_flip != 'undefined' && path_config.rebound_flip === true) checked_path_config.rebound_flip = true;
		
		
		// output
		
		if (typeof path_config.complete != 'undefined' && path_config.complete === true) checked_path_config.complete = true;
		if (typeof path_config.precise != 'undefined' && path_config.precise === true) checked_path_config.precise = true;

	}
	
	
	
	// special rules
	
	// curve and unit size
	// note: when a real size is given, curve is converted to 1 or -1 to act only as a directional multiplier to not interfere with the given curve size
	if (checked_path_config.curve_size != null)
	{	
		// a curve of 0 means curve_size is treated as positive
		checked_path_config.curve = (checked_path_config.curve >= 0 ? 1 : -1);
	}
	
	
	
	return checked_path_config;

}



// FAMILY: CHRON CHAIN
// executes relayed chain instigates
if (TVGS) TVGS.chron_chain_instigate_relay = function (instigate_values)
{
	
	switch(instigate_values.relay)
	{
	
		case 1:
		
			// chain relay 1: tvgs_element_waver_by_id(element_id, step_size, step_total, initial_delay, repeat_total, repeat_delay)
			tvgs_element_waver_by_id(instigate_values.chain_config, instigate_values.element_id, instigate_values.step_size, instigate_values.step_total, instigate_values.initial_delay, instigate_values.repeat_total, instigate_values.repeat_delay);
			break;
			
		
		case 2:
		
			// chain relay 2: tvgs_image_swap(element_id, imagepool, background, reverse, interval, initial_delay, repeat_total, repeat_delay)
			tvgs_image_swap(instigate_values.chain_config, instigate_values.element_id, instigate_values.imagepool, instigate_values.background, instigate_values.reverse, instigate_values.interval, instigate_values.initial_delay, instigate_values.repeat_total, instigate_values.repeat_delay);
			break;

		/*		
		case 3:
		
			// relay 3: tvgs_element_resize(chain_config, element_id, interval, time_delta, x_start, x_end, x_distance, x_unit, y_start, y_end, y_distance, y_unit, rebound, initial_delay, repeat_total, repeat_delay, instigate)
			tvgs_element_resize(instigate_values.chain_config, instigate_values.element_id, instigate_values.interval, instigate_values.time_delta, instigate_values.x_start, instigate_values.x_end, instigate_values.x_distance, instigate_values.x_unit, instigate_values.y_start, instigate_values.y_end, instigate_values.y_distance, instigate_values.y_unit, instigate_values.rebound, instigate_values.initial_delay, instigate_values.repeat_total, instigate_values.repeat_delay, instigate_values.instigate);
			break;
		*/
		
		case 3:
		
			// relay 3: tvgs_element_resize(chain_config, element_id, interval, time_delta, x_start, x_end, x_distance, x_unit, y_start, y_end, y_distance, y_unit, rebound, initial_delay, repeat_total, repeat_delay, instigate)
			tvgs_element_resize(instigate_values);
			break;
		
		case 4:
		
			// relay 4: tvgs_element_drop_rise(chain_config)
			tvgs_element_drop_rise(instigate_values);
			break;
			
		case 5:
		
			// relay 5: tvgs_element_move(chain_config)
			tvgs_element_move(instigate_values);
			break;
			
		case 6:
		
			// relay 6: tvgs_element_showhide(chain_config)
			tvgs_element_showhide(instigate_values);
			break;
			
		
		default:
			
			break;
		
	}

	return;

}




// FAMILY: TIMELINE CHAIN
// companion: tvgs_element_set_position

// moves the element

// chain config example
// passing a chain config as the only argument has the following advantages:
// - there is only one argument, which is set up in an easily readible manner, instead of a long single line
// - argument names are always visible with their value providing a practical overview
// - each argument can be deactivated easily by escaping the line it is on
// - parameters that are not recognized as arguments may still be passed in the chain config

// note: the time_delta setting and the step settings (step_size, step_unit and step_total) are mutually exclusive, setting time_delta will ignore all step settings

/*
// create config object
chain_config = {};

// general settings
chain_config.element_id		= 'my_image';	// the element id
chain_config.interval		= 1000;			// the time between each frame in milliseconds, may be omitted, defaults to timeline frame interval
chain_config.time_delta		= 1000;			// the time in which to complete the resize in milliseconds
chain_config.step_size		= 5;			// the size of each move step
chain_config.step_unit		= 'px';			// the unit applied to the step_size, may be omitted, defaults to 'px'
chain_config.step_total		= 10;			// the total number of move steps

// specific settings
chain_config.x_move			= 200;			// the x distance to move to in pixels
chain_config.y_move			= 200;			// the y distance to move to in pixels

// path controls
chain_config.path			= {};			// path options, x_start and y_start will default to to current x and y position of the element when omitted

// chain controls
chain_config.initial_delay	= 500;			// delay before the action starts in milliseconds, may be omitted, defaults to 0
chain_config.repeat_total	= 3;			// number of times the action is run consecutively, may be omitted, defaults to 1 (setting this to 0 means repeat forever)
chain_config.repeat_delay	= 250;			// delay before each repeat of the action in milliseconds, may be omitted, defaults to 0
chain_config.instigate		= [];			// array of any actions (as script or relay format) to launch at the end of this chain, these actions will be deleted as normal when the parent chain is smothered

// pass config object into function
tvgs_element_move(chain_config);			// that's it, the config object now provides all the arguments to the function
*/

function tvgs_element_move(chain_config, element_id, x_move, y_move, time_delta)
{
	
	
	// set ignored arguments
	// note: these variables are not defined by the function parameters and are defined here to keep the inline function writing style as short as possible
	var interval;
	var step_size;
	var step_unit;
	var step_total;
	
	var path;
	
	var initial_delay;
	var repeat_total;
	var repeat_delay;
	var instigate;
	
	
	
	// set chain name
	var name = 'tvgs_element_move';
	
	
	
	// CHECK INPUT
	
	// check for config object
	// note: arguments in a config object will overwrite normal arguments, if normal arguments are missing they will default to null
	if (!TVGS.nil(chain_config))
	{
		// general settings
		if (typeof chain_config.element_id != 'undefined') element_id = chain_config.element_id;
		if (typeof chain_config.interval != 'undefined') interval = chain_config.interval;
		if (typeof chain_config.time_delta != 'undefined') time_delta = chain_config.time_delta;
		if (typeof chain_config.step_size != 'undefined') step_size = chain_config.step_size; // ignored
		if (typeof chain_config.step_unit != 'undefined') step_unit = chain_config.step_unit; // ignored
		if (typeof chain_config.step_total != 'undefined') step_total = chain_config.step_total; // ignored
		
		// specific settings
		if (typeof chain_config.x_move != 'undefined') x_move = chain_config.x_move;
		if (typeof chain_config.y_move != 'undefined') y_move = chain_config.y_move;
		
		// path control
		if (typeof chain_config.path != 'undefined') path = chain_config.path;
		
		// chain control
		if (typeof chain_config.initial_delay != 'undefined') initial_delay = chain_config.initial_delay;
		if (typeof chain_config.repeat_total != 'undefined') repeat_total = chain_config.repeat_total;
		if (typeof chain_config.repeat_delay != 'undefined') repeat_delay = chain_config.repeat_delay;
		if (typeof chain_config.instigate != 'undefined') instigate = chain_config.instigate;
	}
	

	// check general settings

	// get element
	var element = document.getElementById(element_id);

	// check element
	// devnote: if the element does not exist, continue to schedule any instigate?
	if (TVGS.nil(element)) return;
	
	// get element original properties
	// note: this check is always performed, to ensure that before anything else happens the original properties are stored
	if (TVGS.nil(element.tvgs_original_top)) element.tvgs_original_top = TVGS.element_get_current_top(element);
	if (TVGS.nil(element.tvgs_original_left)) element.tvgs_original_left = TVGS.element_get_current_left(element);
	
	// check time delta
	// note: no time delta means the element snaps to the final frame
	if (TVGS.nil(time_delta)) time_delta = 0;

	// check frame interval
	if (TVGS.nil(interval)) interval = TVGS.CHRON.frame_interval; // use timeline's native frame interval

	// set minimum of 1 millisecond
	// note: this prevents rupturing of the space-time continuum
	if (interval < 1) interval = 1;
	
	// check step settings
	// note: no step settings means steps of 1 pixel for 1 second using the timeline's native frame interval
	if (TVGS.nil(step_size)) step_size = 1;
	if (TVGS.nil(step_unit)) step_unit = 'px';
	if (TVGS.nil(step_total)) step_total = Math.round(1000 / TVGS.CHRON.frame_interval);
	
	
	
	// check specific settings
	
	// check x_move
	if (!TVGS.nil(x_move)) x_move = TVGS.convert_to_float(x_move);
	
	// check y_move
	if (!TVGS.nil(y_move)) y_move = TVGS.convert_to_float(y_move);


	
	// check path
	path = TVGS.path_check(path);
	
	
	
	// check chain control
	
	// set initial delay to repeated delay when not specified
	// note: this makes sure the chain is continued smoothly
	// note: to have an initial delay of zero combined with a repeat delay, the initial delay needs to be explicitly set to zero 
	if (TVGS.nil(repeat_delay)) repeat_delay = 0;
	if (TVGS.nil(initial_delay)) initial_delay = repeat_delay;

	// set to 1 when not specified
	if (TVGS.nil(repeat_total)) repeat_total = 1;

	// update repeat total
	// note: zero will be left as is, meaning repeat indefinitely
	if (repeat_total > 1) repeat_total--;
	else if (repeat_total == 1) repeat_total = -1; // repeat 1 time means this is the last one, do not continue
	
	// check instigate
	if (TVGS.nil(instigate)) instigate = [];

	

	// PREPARE CHAIN

	// get current top and left
	var current_x_position = TVGS.element_get_current_left(element);
	var current_y_position = TVGS.element_get_current_top(element);
	

	
	// get movement steps
	
	// divide time delta by current frame rate to get number of steps
	var step_total = Math.round(time_delta / interval);

	// set minimum of 1 step
	// note: this prevents a small time delta from having the only step rounded down to zero
	if (step_total < 1) step_total = 1;

	
	
	// alter path
	
	// remember original path settings
	var original_x_start = path.x_start;
	var original_y_start = path.y_start;
	var original_x_end = path.x_end;
	var original_y_end = path.y_end;
	var original_step_total = path.step_total;
	
	// determine x_start and y_start, when no start is specified the current position is used
	if (path.x_start === null) path.x_start = current_x_position;
	if (path.y_start === null) path.y_start = current_y_position;
	
	// determine x_end
	if (!TVGS.nil(x_move))
	{
		// calculate end from move
		path.x_end = path.x_start + x_move;
	}
	else if (path.x_end === null)
	{
		// set end to current position
		path.x_end = current_x_position;
	}
	
	// determine y_end
	if (!TVGS.nil(y_move))
	{
		// calculate end from move
		path.y_end = path.y_start + y_move;
	}
	else if (path.y_end === null)
	{
		// set end to current position
		path.y_end = current_y_position;
	}
	
	// set step total
	path.step_total = step_total;
	
	// get path from path unit
	var step_path = TVGS.path(path);
	
	// restore original path settings for instigate
	path.x_start = original_x_start;
	path.y_start = original_y_start;
	path.x_end = original_x_end;
	path.y_end = original_y_end;
	path.step_total = original_step_total;
	
	
	//alert (step_path);
   
	
	// set trace string
	var t_a_trace = TVGS.CHRON.trace();
	
	// set chain base time
	var t_a_time = TVGS.CHRON.time() + initial_delay; // includes initial delay, used only once and is not carried on in propagation
		
	// set timeline assignment properties
	var t_a_name = name; // used for instigate eval script
	
	// set values
	var t_a_values = {}; // different for each action
	
	// set script
	var t_a_script = ''; // different for each action

	// set chain
	var t_a_chain = null; // the chain info object to delete when the last frame is processed or when the chain is deleted
	
	// set complete
	var t_a_complete = null; // different on first and last action
	
	// set smother
	var t_a_smother = null; // the chain to delete
	
	// set instigate
	var t_a_instigate = null; // different on last action
	
	
	
	
	
	// set first and last frame
	var current_frame = 0;
	var last_frame = step_path.length;
	
	//alert (step_path.length);
	
	// step through frames
	for (var i in step_path){ if (TVGS.key(i)){
			
		// increase frame number
		// note: first is always frame 1
		current_frame++;
		
		// increase time
		// note: time is increased up front to be one interval after the timestamp to ensure smooth instigates
		t_a_time = t_a_time + interval;
		
		
		
		// set values
		t_a_values = {};
		t_a_values.relay = 6; // tvgs_element_set_position
		t_a_values.element = element;
		t_a_values.x_offset = step_path[i].x;
		t_a_values.y_offset = step_path[i].y;
		
		//document.getElementById('output').innerHTML += '(' + current_frame + ' - x:' + t_a_values.x_offset + ')';
				
		// set complete and smother
		// note: smother is automatic for this chain type on this object in the first frame
		if (current_frame == 1)
		{	
			t_a_complete = 1;
		
			t_a_smother = {};
			t_a_smother.name = name;
			t_a_smother.element = element;
		}
		else
		{
			// set back to null in other frames
			t_a_complete = null;
			t_a_smother = null;
		}
		
		
		
		// set complete and instigate 
		if (current_frame == last_frame)
		{
			
			t_a_complete = 1; // obligatory step to show end result and handle instigates
			
			// see if this chain will continue
			if (repeat_total > -1)
			{
				// setup new instigate to propagate this chain
				var chain_instigate = {};

				// instigate script
				chain_instigate.script = null; 
				
				// instigate relay
				chain_instigate.relay = {
				
					// set chain relay
					relay: 5, // relay 5: tvgs_element_move(chain_config)
	
					// general settings
					element_id: element_id,
					interval: interval,
					time_delta: time_delta,
					step_size: step_size,
					step_unit: step_unit,
					step_total: step_total,
	
					// specific settings
					x_move: x_move,
					y_move: y_move,
					
					// path control
					path: path,
	
					// chain control
					initial_delay: null, // makes for unexpected results when not null, using just the repeat delay will ensure smooth propagation
					repeat_total: repeat_total,
					repeat_delay: repeat_delay,
					
				end : null };
				
				// add to instigate list 
				instigate.push(chain_instigate);
			}
			
			// add instigate list into this frame
			t_a_instigate = instigate;

		}
		
	
		
		// add frame to timeline
		chron_timeline_add_action(t_a_trace, t_a_time, t_a_name, t_a_values, t_a_script, t_a_chain, t_a_complete, t_a_smother, t_a_instigate);
		
	}}
	
	document.getElementById('output').innerHTML += ' => MOVE ';
	//alert('chain created');
	
}







// FAMILY: TIMELINE CHAIN
// companion: tvgs_element_set_opacity

// shows or hides the element

// note: set time_delta to zero and visibility to 1 or 0 to completely show or hide the element in 1 step
// note: the visibility parameter has nothing to do with the css "visibility" statement
// note: while this also controls intermediate levels of opacity, minimum and maximum levels of visibility never actually use the css "opacity" statement, instead setting it to empty and using normal "display" default or none statements
function tvgs_element_showhide(chain_config, element_id, time_delta, showhide_start, showhide_end)
{
	
	// set ignored arguments
	// note: these variables are not defined by the function parameters and are defined here to keep the inline function writing style as short as possible
	var interval;
	var step_size;
	var step_unit;
	var step_total;

	var initial_delay;
	var repeat_total;
	var repeat_delay;
	var instigate;
	


	// set chain name
	var name = 'tvgs_element_showhide';
	
	
	
	// CHECK INPUT
	
	// check for config object
	// note: arguments in a config object will overwrite normal arguments, if normal arguments are missing they will default to null
	if (!TVGS.nil(chain_config))
	{
		// general settings
		if (typeof chain_config.element_id != 'undefined') element_id = chain_config.element_id;
		if (typeof chain_config.interval != 'undefined') interval = chain_config.interval;
		if (typeof chain_config.time_delta != 'undefined') time_delta = chain_config.time_delta;
		if (typeof chain_config.step_size != 'undefined') step_size = chain_config.step_size; // ignored
		if (typeof chain_config.step_unit != 'undefined') step_unit = chain_config.step_unit; // ignored
		if (typeof chain_config.step_total != 'undefined') step_total = chain_config.step_total; // ignored
		
		// specific settings
		if (typeof chain_config.showhide_start != 'undefined') showhide_start = chain_config.showhide_start;
		if (typeof chain_config.showhide_end != 'undefined') showhide_end = chain_config.showhide_end;
		
		// chain control
		if (typeof chain_config.initial_delay != 'undefined') initial_delay = chain_config.initial_delay;
		if (typeof chain_config.repeat_total != 'undefined') repeat_total = chain_config.repeat_total;
		if (typeof chain_config.repeat_delay != 'undefined') repeat_delay = chain_config.repeat_delay;
		if (typeof chain_config.instigate != 'undefined') instigate = chain_config.instigate;
	}
	

	// check general settings

	// get element
	var element = document.getElementById(element_id);

	// check element
	// devnote: if the element does not exist, continue to schedule any instigate?
	if (TVGS.nil(element)) return;
	
	// get element original properties
	// note: this check is always performed, to ensure that before anything else happens the original properties are stored
	if (TVGS.nil(element.tvgs_original_display)) element.tvgs_original_display = TVGS.element_get_current_display(element);
	if (TVGS.nil(element.tvgs_original_opacity)) element.tvgs_original_opacity = TVGS.element_get_current_opacity(element);
	
	// check time delta
	// note: no time delta means the element snaps to the final frame
	if (TVGS.nil(time_delta)) time_delta = 0;

	// check frame interval
	if (TVGS.nil(interval)) interval = TVGS.CHRON.frame_interval; // use timeline's native frame interval

	// set minimum of 1 millisecond
	// note: this prevents rupturing of the space-time continuum
	if (interval < 1) interval = 1;
	
	// check step settings
	// note: no step settings means steps of 1 pixel for 1 second using the timeline's native frame interval
	if (TVGS.nil(step_size)) step_size = 1;
	if (TVGS.nil(step_unit)) step_unit = 'px';
	if (TVGS.nil(step_total)) step_total = Math.round(1000 / TVGS.CHRON.frame_interval);
	
	
	
	// check specific settings
	
	// check showhide_start
	if (!TVGS.nil(showhide_start))
	{
		showhide_start = TVGS.convert_to_float(showhide_start);
		if (showhide_start < 0) showhide_start = 0;
		if (showhide_start > 1) showhide_start = 1;
	}
	
	// check showhide_end
	if (!TVGS.nil(showhide_end))
	{
		showhide_end = TVGS.convert_to_float(showhide_end);
		if (showhide_end < 0) showhide_end = 0;
		if (showhide_end > 1) showhide_end = 1;
	}
	
	
	
	// check chain control
	
	// set initial delay to repeated delay when not specified
	// note: this makes sure the chain is continued smoothly
	// note: to have an initial delay of zero combined with a repeat delay, the initial delay needs to be explicitly set to zero 
	if (TVGS.nil(repeat_delay)) repeat_delay = 0;
	if (TVGS.nil(initial_delay)) initial_delay = repeat_delay;

	// set to 1 when not specified
	if (TVGS.nil(repeat_total)) repeat_total = 1;

	// update repeat total
	// note: zero will be left as is, meaning repeat indefinitely
	if (repeat_total > 1) repeat_total--;
	else if (repeat_total == 1) repeat_total = -1; // repeat 1 time means this is the last one, do not continue
	
	// check instigate
	if (TVGS.nil(instigate)) instigate = [];

	

	// PREPARE CHAIN

	// get current display and opacity
	var current_display = TVGS.element_get_current_display(element);
	var current_opacity = TVGS.element_get_current_opacity(element);
	

	
	// get movement steps
	
	// divide time delta by current frame rate to get number of steps
	var step_total = Math.round(time_delta / interval);

	// set minimum of 1 step
	// note: this prevents a small time delta from having the only step rounded down to zero
	if (step_total < 1) step_total = 1;

	
	
	
	
	// determine real visible opacity based on css display and opacity setting
	var current_showhide;
	if (TVGS.nil(showhide_start))
	{
		if (current_display == 'none')
		{
			// invisible
			current_showhide = 0;
		}
		else
		{
			if (current_opacity == null)
			{
				// no opacity setting, totally visible
				current_showhide = 1;
			}
			else
			{
				current_showhide = current_opacity;
			}
		}
	}
	else
	{
		// use provided setting
		current_showhide = showhide_start;
	}
	

	
	
	
	
	// create opacity ramp
	var showhide_delta = showhide_end - current_showhide;
	var showhide_frames = [];
	var step_counter = 1;
	var step_size = showhide_delta / step_total;
	
	// add intermediate steps
	while (step_counter < step_total)
	{	
		showhide_frames.push(current_showhide + (step_size * step_counter));
		
		step_counter++;
	}
	
	// add last step
	showhide_frames.push(showhide_end);



/*
	// duplicate array, add final step, cut off first
	var opacity_frames_reversed = opacity_frames.slice().reverse();
	opacity_frames_reversed.push(original_opacity); // adds the initial state as the last step
	opacity_frames_reversed.shift(); // prevents an identical step in the middle
	
	// join two halves
	var all_opacity_frames = opacity_frames.concat(opacity_frames_reversed);

   //alert(all_opacity_frames);
   */
   
   
   
   
	// add to timeline
	//var interval = TVGS.CHRON.frame_interval; // use timeline's native frame interval
	//var timestamp = TVGS.CHRON.time();
	
	// set trace string
	var t_a_trace = TVGS.CHRON.trace();
	
	// set chain base time
	var t_a_time = TVGS.CHRON.time() + initial_delay; // includes initial delay, used only once and is not carried on in propagation
	
	
	//var t_a_time = timestamp + initial_delay; // includes initial delay, used only once and is not carried on in propagation
	
	// set timeline assignment properties
	var t_a_name = name; // used for instigate eval script
	
	// set values
	var t_a_values = {}; // different for each action
	
	// set script
	var t_a_script = ''; // different for each action

	// set chain
	var t_a_chain = null; // the chain info object to delete when the last frame is processed or when the chain is deleted
	
	// set complete
	var t_a_complete = null; // different on first and last action
	
	// set smother
	var t_a_smother = null; // the chain to delete
	
	// set propagate
	//var t_a_propagate = null; // different on last action
	
	// set instigate
	var t_a_instigate = null; // different on last action
	
	
	// collect chain info
	// note: properties that are frame specific are not part of the general chain object
//	var chain_info = {};
	//chain_info.name = t_a_name;
	//chain_info.element = element;
	
	
	//document.getElementById('output').innerHTML += t_a_trace + ' make frames<br>';
	
	
	
	
	// set first and last frame
	var current_frame = 0;
	var last_frame = showhide_frames.length;
	
	//alert(showhide_frames);
	
	// step through frames
	for (var i in showhide_frames){ if (TVGS.key(i)){
			
			// increase frame number
			// note: first is always frame 1
			current_frame++;
			
			// increase time
			// note: time is increased up front to be one interval after the timestamp to ensure smooth instigates
			t_a_time = t_a_time + interval;
			
			
			
			// set values
			t_a_values = {};
			t_a_values.relay = 7; // tvgs_element_set_showhide
			t_a_values.element = element;
			t_a_values.showhide = Math.round(showhide_frames[i] * 1000) / 1000; // round to 3 digits
			
			
			
			
			
			// set complete and smother
			// note: smother is automatic for this chain type on this object in the first frame
			if (current_frame == 1)
			{	
				t_a_complete = 1;
			
				t_a_smother = {};
				t_a_smother.name = name;
				t_a_smother.element = element;
			}
			else
			{
				// set back to null in other frames
				t_a_smother = null;
				t_a_complete = null;
			}
			
			
			// set complete and instigate 
			if (current_frame == last_frame)
			{
				
				t_a_complete = 1; // obligatory step to show end result and handle instigates
				
				// see if this chain will continue
				if (repeat_total > -1)
				{
					// setup new instigate to propagate this chain
					var chain_instigate = {};
	
					// instigate script
					chain_instigate.script = null; 
					
					// instigate relay
					chain_instigate.relay = {
					
						// set chain relay
						relay: 6, // relay 6: tvgs_element_showhide(chain_config)
		
						// general settings
						element_id: element_id,
						interval: interval,
						time_delta: time_delta,
						step_size: step_size,
						step_unit: step_unit,
						step_total: step_total,
		
						// specific settings
						showhide_start: showhide_start,
						showhide_end: showhide_end,
		
						// chain control
						initial_delay: null, // makes for unexpected results when not null, using just the repeat delay will ensure smooth propagation
						repeat_total: repeat_total,
						repeat_delay: repeat_delay,
						
					end : null };
					
					// add to instigate list 
					instigate.push(chain_instigate);
				}
				
				// add instigate list into this frame
				t_a_instigate = instigate;
				
			}
			
	
			chron_timeline_add_action(t_a_trace, t_a_time, t_a_name, t_a_values, t_a_script, t_a_chain, t_a_complete, t_a_smother, t_a_instigate);
			
	}}
	
	//document.getElementById('output').innerHTML += ' => SHOWHIDE ';
	//alert('showhide chain spawned');
	
}








// FAMILY: TIMELINE CHAIN
// companion: tvgs_element_set_background_position
// moves the background to a new position using a set time
function tvgs_element_move_background_image(element_id, x_position, y_position, time_delta)
{
	
	// check input
	
	// check if target element exists
	// note: there is no need to check if the target element actually has a background image set, when it is missing setting its position will just have no effect
	if (!document.getElementById(element_id)) return;
	
	// x and y position are required
	if (x_position === null) return;
	if (y_position === null) return;
	
	// no time delta means the background snaps to the new position
	if (time_delta === null) time_delta = 0;



	// set name
	var name = 'tvgs_element_move_background_image';
	
	// get element
	var element = document.getElementById(element_id);
	

	
	// get current background position
	// note: this ignores any values that are not explicitly pixel dimensions
	if (element.style.backgroundPosition && element.style.backgroundPosition.match(/px/gi).length == 2 && element.style.backgroundPosition.replace(/[^0-9- ]/gi, '').split(' ').length == 2)
	{
		var current_position = element.style.backgroundPosition.replace(/[^0-9- ]/gi, '').split(' ');
		
		var current_x_position = current_position[0] * 1; // style values are strings
		var current_y_position = current_position[1] * 1; // style values are strings
	}
	else
	{
		var current_x_position = 0;
		var current_y_position = 0;
	}

	//alert(current_x_position + ' ' + current_y_position);
	

	
	// get movement steps
	
	// divide time delta by current frame rate to get number of steps
	var step_total = Math.round(time_delta / TVGS.CHRON.frame_interval);

	// set minimum of 1 step
	// note: this prevents a small time delta from having the only step rounded down to zero
	if (step_total < 1) step_total = 1;

	
	
	// get distances
	
	// get x distance
	var x_distance = 0;
	
	if (current_x_position > x_position)
	{
		x_distance = current_x_position - x_position;
	}
	else if (current_x_position < x_position)
	{
		x_distance = x_position - current_x_position;
	}
	
	// get y distance
	var y_distance = 0;
	
	if (current_y_position > y_position)
	{
		y_distance = current_y_position - y_position;
	}
	else if (current_y_position < y_position)
	{
		y_distance = y_position - current_y_position;
	}
	
	// no movement, stop here
	if (x_distance == 0 && y_distance == 0) return;
	
	//alert(x_distance + ' ' + y_distance);
	

	// calculate paths
	
	// calculate x path
	var x_step_number = 0;
	var x_step_size = x_distance / step_total; // not rounded
	//var x_step_size = Math.round((x_distance / step_total) * 1000) / 1000; // rounded to 3 digits precision
	var x_step_path = [];
	
	if (current_x_position >= x_position)
	{
		while (x_step_number < step_total)
		{
			x_step_path[x_step_number] = Math.round(current_x_position - (x_step_size * (x_step_number + 1)));
			x_step_number++;
		}
	}
	else if (current_x_position < x_position)
	{
		while (x_step_number < step_total)
		{
			x_step_path[x_step_number] = Math.round(current_x_position + (x_step_size * (x_step_number + 1)));
			x_step_number++;
		}
	}

	// calculate y path
	var y_step_number = 0;
	var y_step_size = y_distance / step_total; // not rounded
	var y_step_size = Math.round((y_distance / step_total) * 1000) / 1000; // rounded to 3 digits precision
	var y_step_path = [];
	
	if (current_y_position >= y_position)
	{
		while (y_step_number < step_total)
		{
			y_step_path[y_step_number] = Math.round(current_y_position - (y_step_size * (y_step_number + 1)));
			y_step_number++;
		}
	}
	else if (current_y_position < y_position)
	{
		while (y_step_number < step_total)
		{
			y_step_path[y_step_number] = Math.round(current_y_position + (y_step_size * (y_step_number + 1)));
			y_step_number++;
		}
	}
	
	//alert(x_step_path + ' ' + y_step_path);
	

   
	// add to timeline
	var interval = TVGS.CHRON.frame_interval; // use timeline's native frame interval
	var timestamp = TVGS.CHRON.time();
	
	// set trace string
	var t_a_trace = TVGS.CHRON.trace();
	
	// set timestamp
	var t_a_time = timestamp;
	
	// set timeline assignment properties
	var t_a_name = name; // used for instigate eval script
	
	// set values
	var t_a_values = {}; // different for each action
	
	// set script
	var t_a_script = null; // different for each action

	// set chain
	var t_a_chain = null; // the chain info object to delete when the last frame is processed or when the chain is deleted
	
	// set complete
	var t_a_complete = null; // different on first and last action
	
	// set smother
	var t_a_smother = null; // the chain to delete
	
	// set instigate
	var t_a_instigate = null; // different on last action
	
	
	
	
	
	// create frames
	
	var current_frame = 0;
	
	// use x step path as handle
	var last_frame = x_step_path.length;
	
	for (var i in x_step_path){ if (TVGS.key(i)){
		
		// increase frame number
		// note: first is always frame 1
		current_frame++;
		
		// increase time
		// note: time is increased up front to be one interval after the timestamp to ensure smooth instigates
		t_a_time = t_a_time + interval;
		
		
		
		// set values
		t_a_values = {};
		t_a_values.relay = 3; // tvgs_element_set_background_position
		t_a_values.element = element;
		t_a_values.x_offset = x_step_path[i];
		t_a_values.y_offset = y_step_path[i];
		
		
				
		// set complete and smother
		// note: smother is automatic for this chain type on this object in the first frame
		if (current_frame == 1)
		{	
			t_a_complete = 1;
		
			t_a_smother = {};
			t_a_smother.name = name;
			t_a_smother.element = element;
		}
		else
		{
			// set back to null in other frames
			t_a_complete = null;
			t_a_smother = null;
		}
		
		
		
		// set complete and instigate 
		if (current_frame == last_frame)
		{
			
			t_a_complete = 1; // obligatory step to show end result
			
		}
		
	
		
		// add frame to timeline
		chron_timeline_add_action(t_a_trace, t_a_time, t_a_name, t_a_values, t_a_script, t_a_chain, t_a_complete, t_a_smother, t_a_instigate);

	}}
	
	//alert('chain created');
	
}




// FAMILY: TIMELINE CHAIN
// name: tvgs_element_drop_rise
// usage: "tvgs_element_drop_rise(chain_config, element_id, interval, time_delta, step_size, step_unit, effect, action, y_offset, opacity, initial_delay, instigate);"
// companion: tvgs_element_set_clipped_appearance

// makes the element appear or disappear in a "drop down" or "rise up" fashion

// chain config example
// passing a chain config as the only argument has the following advantages:
// - there is only one argument, which is set up in an easily readible manner, instead of a long single line
// - argument names are always visible with their value providing a practical overview
// - each argument can be deactivated easily by escaping the line it is on

// note: the time_delta setting and the step settings (step_size, step_unit and step_total) are mutually exclusive, setting time_delta will make the function ignore all the step settings
// note: the initial delay is used only once, when no initial delay is specified the repeat delay will be used as the initial delay, to have an initial delay of zero combined with a repeat delay, the initial delay needs to be explicitly set to zero

/*
// create config object
chain_config = {};

// general settings
chain_config.element_id		= 'my_menu';	// the element id

// animation setup
chain_config.interval		= 40;				// the time between each frame in milliseconds, may be omitted, defaults to timeline frame interval
chain_config.time_delta		= 1000;			// the time in which to complete the effect in milliseconds
chain_config.step_size		= 5;				// the size of each resize step, may be omitted, defaults to 1
chain_config.step_unit		= 'px';			// the unit applied to the step_size, may be omitted, defaults to 'px'
chain_config.step_total		= 10;				// the total number of resize steps, may be omitted, defaults to the number of steps in 1 second based on the frame interval

// specific settings
chain_config.effect			= 'drop';		// determines wether the element appears above or below the starting point, accepts 'drop' for below and 'rise' for above
chain_config.action			= 'close';		// makes the element disappear when visible and appear when invisible, when the element is already in the target state, no visible effect will occur, accepts 'open' to appear and 'close' to disappear
chain_config.y_offset		= 30;				// the vertical offset in pixels from of the starting point, may be omitted, defaults to 0
chain_config.y_noclip		= 105;				// the vertical portion in pixels from the top or bottom of the element (depending on effect setting) that is always visible, may be omitted, defaults to 0
chain_config.opacity			= 0.8;			// the start value of the opacity when transitioning from invisible to visible, accepts a number from 0, for fully transparent, to 1, for fully opaque, may be omitted, defaults to 1

// chain controls
chain_config.initial_delay	= 500;			// delay before the action starts in milliseconds, may be omitted, defaults to 0
chain_config.repeat_total	= 3;				// number of times the action is run consecutively, may be omitted, defaults to 1 (setting this to 0 means repeat forever)
chain_config.repeat_delay	= 250;			// delay before each repeat of the action in milliseconds, may be omitted, defaults to 0
chain_config.instigate		= [];				// array of any actions (as script or relay format) to launch at the end of this chain, these actions will be deleted as normal when the parent chain is smothered

// pass config object into function
tvgs_element_drop_rise(chain_config);		// that's it, the config object "chain_config" now provides all the arguments to the function

// destroy the config object after use
// note: this is optional, but since objects are referenced this is a safe way to avoid possible "weird" behavior when you modify the object outside of the function while it is still in use 
chain_config = null;
*/

function tvgs_element_drop_rise(chain_config, element_id, interval, time_delta, step_size, step_unit, effect, action, y_offset, opacity, initial_delay, instigate)
{
	
	// SET NAME AND ARGUMENTS

	// set chain name
	var name = 'tvgs_element_drop_rise';
	
	// set ignored arguments
	// note: variables set here are not defined by the function parameters but here instead to keep the inline function writing style as short as possible
	
	// general settings
	//var element_id;
	
	// animation setup
	//var interval;
	//var time_delta;
	//var step_size;
	//var step_unit; // not supported, steps are clip steps and as such the unit is always pixels
	var step_total; // not supported, step total is calculated
	
	// specific settings
	//var effect;
	//var action;
	//var y_offset;
	var y_noclip; // advanced option
	//var opacity;
	
	// chain controls
	//var initial_delay;
	var repeat_total; // not supported, action is either open once or close once
	var repeat_delay; // not applicable 
	//var instigate;
	
	
	
	// CHECK INPUT
	
	// check for config object
	// note: arguments in a config object will overwrite normal arguments, if normal arguments are missing they will default to null
	if (TVGS.set(chain_config))
	{
		// general settings
		if (typeof chain_config.element_id != 'undefined') element_id = chain_config.element_id;
		
		// animation setup
		if (typeof chain_config.interval != 'undefined') interval = chain_config.interval;
		if (typeof chain_config.time_delta != 'undefined') time_delta = chain_config.time_delta;
		if (typeof chain_config.step_size != 'undefined') step_size = chain_config.step_size;
		if (typeof chain_config.step_unit != 'undefined') step_unit = chain_config.step_unit;
		if (typeof chain_config.step_total != 'undefined') step_total = chain_config.step_total;
		
		// specific settings
		if (typeof chain_config.effect != 'undefined') effect = chain_config.effect;
		if (typeof chain_config.action != 'undefined') action = chain_config.action;
		if (typeof chain_config.y_offset != 'undefined') y_offset = chain_config.y_offset;
		if (typeof chain_config.y_noclip != 'undefined') y_noclip = chain_config.y_noclip;
		if (typeof chain_config.opacity != 'undefined') opacity = chain_config.opacity;
		
		// chain control
		if (typeof chain_config.initial_delay != 'undefined') initial_delay = chain_config.initial_delay;
		if (typeof chain_config.repeat_total != 'undefined') repeat_total = chain_config.repeat_total;
		if (typeof chain_config.repeat_delay != 'undefined') repeat_delay = chain_config.repeat_delay;
		if (typeof chain_config.instigate != 'undefined') instigate = chain_config.instigate;
	}
	

	
	// check general settings

	// get element
	var element = document.getElementById(element_id);

	// check element
	if (TVGS.nil(element)) return;
	
	// get element original properties
	// note: this check is always performed, to ensure that before anything else happens the original properties are stored
	if (TVGS.nil(element.tvgs_original_width)) element.tvgs_original_width = TVGS.element_get_current_width(element);
	if (TVGS.nil(element.tvgs_original_height)) element.tvgs_original_height = TVGS.element_get_current_height(element);
	if (TVGS.nil(element.tvgs_original_top)) element.tvgs_original_top = TVGS.element_get_current_top(element);
	if (TVGS.nil(element.tvgs_original_left)) element.tvgs_original_left = TVGS.element_get_current_left(element);
	if (TVGS.nil(element.tvgs_original_display)) element.tvgs_original_display = TVGS.element_get_current_display(element);
	if (TVGS.nil(element.tvgs_original_opacity)) element.tvgs_original_opacity = TVGS.element_get_current_opacity(element);
	
	
	
	// check animation setup

	// check frame interval
	if (TVGS.nil(interval)) interval = TVGS.CHRON.frame_interval; // use timeline's native frame interval

	// set minimum of 1 millisecond
	// note: this prevents rupturing of the space-time continuum
	if (interval < 1) interval = 1;

	// check time delta and step settings
	if (TVGS.nil(time_delta) && TVGS.nil(step_size)) 
	{
		// no time delta and no step settings means the element snaps to the final frame
		time_delta = 0;
	}
	else if (TVGS.nil(time_delta)) 
	{
		// no time delta, check step settings
		// note: no step settings means steps of 1 pixel for 1 second using the timeline's native frame interval
		if (TVGS.nil(step_size)) step_size = 1;
		//if (TVGS.nil(step_unit)) step_unit = 'px';
		step_unit = 'px'; // steps are clip steps and as such the unit is always pixels
		if (TVGS.nil(step_total)) step_total = Math.round(1000 / TVGS.CHRON.frame_interval);
	}
	
	
	
	// check specific settings
	
	// return on no effect or action setting
	if (effect != 'drop' && effect != 'rise') return;
	if (action != 'open' && action != 'close') return;
	if (TVGS.nil(y_offset)) y_offset = 0;
	if (TVGS.nil(y_noclip)) y_noclip = 0;
	if (TVGS.nil(opacity)) opacity = null; // set to null instead of 1, meaning "do nothing" instead of "set to 1", as the latter may impact browser rendering

	
	
	// check chain control
	
	// set initial delay to repeat delay when not specified
	// note: this makes sure the chain is continued smoothly
	// note: to have an initial delay of zero combined with a repeat delay, the initial delay needs to be explicitly set to zero
	if (TVGS.nil(repeat_delay)) repeat_delay = 0;
	if (TVGS.nil(initial_delay)) initial_delay = repeat_delay;

	// set to once when not specified
	if (TVGS.nil(repeat_total)) repeat_total = 1;

	// update repeat total
	// note: zero will be left as is, meaning repeat indefinitely
	if (repeat_total > 1) repeat_total--;
	else if (repeat_total == 1) repeat_total = -1; // repeat 1 time means this is the last one, do not continue
	
	// check instigate
	if (TVGS.nil(instigate)) instigate = [];
	


	// PREPARE CHAIN
	
	// calculate step settings from time delta 
	if (TVGS.set(time_delta)) 
	{
		// divide time delta by current frame rate to get number of steps
		step_total = Math.round(time_delta / interval);
	
		// set minimum of 1 step
		// note: this prevents a small time delta from having the only step rounded down to zero
		if (step_total < 1) step_total = 1;
		
		// set step size
		step_size = (element.tvgs_original_height - y_noclip) / step_total; // not rounded		
	}
	
	
	
	// create clip animation ramp for height
	
	// set height frames
	var height_frames = [];
	
	// set work height
	var work_height = element.tvgs_original_height - y_noclip;
	
	// set first frame
	height_frames.push(Math.round(work_height));
	
	// create frames for close action
	while(Math.round(work_height) > 0)
	{
		work_height = work_height - step_size;
		if (work_height < 0) work_height = 0;
		height_frames.push(Math.round(work_height));
	}
	
	// reverse frames for open action
	if (action == 'open')
	{
		height_frames = height_frames.reverse();
	}
	
	

	// get current clip values and visible height
	// note: uses clip property: "clip: rect(10px, 40px, 100px, 5px);"
	// note: clip style usage: top (y1clip), right (from left) (x2clip), bottom (from top) (y2clip), left (x1clip)
	
	// opera, firefox: rect(90px, 212px, 164px, 0px)
	// safari: rect(90px 212px 164px 0px)
	
	// 2010-01-24: updated for safari dom clip format "rect(0px 205px 164px 0px)" without commas
	if (element.style.clip && element.style.clip.replace(/[^0-9 ]/g, '').split(' ').length == 4)
	{
		// get array of 4 clip numbers
		var current_clip_value = element.style.clip;
		var current_clip_numbers = current_clip_value.replace(/[^0-9 ]/g, '').split(' ');
		
		// assign to variables
		var current_x1clip = current_clip_numbers[3] * 1; // style values are strings
		var current_x2clip = current_clip_numbers[1] * 1; // style values are strings
		var current_y1clip = current_clip_numbers[0] * 1; // style values are strings
		var current_y2clip = current_clip_numbers[2] * 1; // style values are strings
	}
	else
	{
		// set clip values to effectively hide element
		var current_x1clip = 0;	
		var current_x2clip = 0;
		var current_y1clip = 0;
		var current_y2clip = 0;
		
		// when it is visible, use visible height
		if (!(element.style.display && element.style.display == 'none'))
		{
			current_x1clip = 0;	
			current_x2clip = element.tvgs_original_width;
			current_y1clip = 0;
			current_y2clip = element.tvgs_original_height;
		}
	}
	
	// get visible height
	var visible_height = current_y2clip - current_y1clip - y_noclip;
	
		

	// set opacity step value per height pixel
	if (TVGS.set(opacity))
	{
		var opacity_step = (1 - opacity) / (element.tvgs_original_height - y_noclip);
	}
	
		
	
	// GENERATE CHAIN
  
	// set trace string
	var t_a_trace = TVGS.CHRON.trace();
	
	// set chain base time
	var t_a_time = TVGS.CHRON.time() + initial_delay; // includes initial delay, used only once and is not carried on in propagation
	
	// set timeline assignment properties
	var t_a_name = name; // used for instigate eval script
	
	// set values
	var t_a_values = {}; // different for each action
	
	// set script
	var t_a_script = ''; // different for each action

	// set chain
	var t_a_chain = null; // the chain info object to delete when the last frame is processed or when the chain is deleted
	
	// set complete
	var t_a_complete = null; // different on first and last action
	
	// set smother
	var t_a_smother = null; // the chain to delete
	
	// set instigate
	var t_a_instigate = null; // different on last action
	
	
	
	// set first and last frame
	var last_frame = height_frames.length;
	var current_frame = 0;
	
	// set smother set flag to track smother setting on first frame, which is variable in number
	var smother_set = false;
	//document.getElementById('output').innerHTML += 'frametotal ' + height_frames.length + ' ';
	for (var i in height_frames){ if (TVGS.key(i)){
		
		// increase frame number
		// note: first is always frame 1
		current_frame++;
		
		
		
		// set values
		t_a_values = {};
		t_a_values.relay = 2; // relay 2: tvgs_element_set_clipped_appearance(element, y1clip, x2clip, y2clip, x1clip, top, left, display, opacity)
		t_a_values.element = element;
		t_a_values.left = null;
		t_a_values.x1clip = 0;
		t_a_values.x2clip = element.tvgs_original_width;

		

		// add frames based on current visible height
		// note: this enables switching direction at any point in the animation
		if ((action == 'close' && (height_frames[i] < visible_height || height_frames[i] == 0)) || (action == 'open' && (height_frames[i] > visible_height || height_frames[i] == element.tvgs_original_height)))
		{
			
			// increase frame time
			// note: time is increased up front to be one interval after the base time to ensure smooth instigates
			t_a_time = t_a_time + interval;



			// set complete and smother
			// note: smother is automatic for this chain type on this object in the first frame
			if (smother_set == false)
			{
				t_a_complete = 1; // obligatory step to execute smother
				
				t_a_smother = {};
				t_a_smother.name = name;
				t_a_smother.element = element;
				
				// update smother set flag
				smother_set = true;
			}
			else
			{
				// set back to null in other frames
				t_a_smother = null;
				t_a_complete = null;
			}
		


			// set y1clip, y2clip and top
			// note: clip style usage: top (y1clip), right (from left) (x2clip), bottom (from top) (y2clip), left (x1clip)
			if (effect == 'drop')
			{
				t_a_values.y1clip = (element.tvgs_original_height - (height_frames[i] + y_noclip)); // cut from top
				t_a_values.y2clip = element.tvgs_original_height; // keep bottom visible
				t_a_values.top = 0 - (element.tvgs_original_height - (height_frames[i] + y_noclip)) + y_offset; // drop from fixed top
			}
			else if (effect == 'rise')
			{
				t_a_values.y1clip = 0; // keep top visible
				t_a_values.y2clip = (height_frames[i] + y_noclip); // cut from bottom
				t_a_values.top = 0 - (height_frames[i] + y_noclip) + y_offset; // rise with moving top
			}
			
			
			
			// make invisible when height is zero
			if (height_frames[i] == 0 && y_noclip == 0)
			{
				t_a_values.display = 'none';
			}
			else
			{
				t_a_values.display = (element.tvgs_original_display != 'none' ? element.tvgs_original_display : '');
			}
			
			

			// set opacity, round at two digits
			if (TVGS.set(opacity))
			{
				t_a_values.opacity = Math.round((opacity + (height_frames[i] * opacity_step)) * 100) / 100;
			}
			else
			{
				t_a_values.opacity = null;
			}
				
		
			
			// always finish
			if (current_frame == last_frame)
			{
				t_a_complete = 1; // obligatory step to show end result
				
				t_a_instigate = instigate;
			}
			


			// add to timeline
			chron_timeline_add_action(t_a_trace, t_a_time, t_a_name, t_a_values, t_a_script, t_a_chain, t_a_complete, t_a_smother, t_a_instigate);
			
		}
		
	}}
	
}





// FAMILY: TIMELINE CHAIN
// companion: tvgs_element_set_opacity
// timeline frame creation test function
// fades and restores an element's opacity in a number of steps
function tvgs_element_waver_by_id(element_id, step_size, step_total, initial_delay, repeat_total, repeat_delay)
{
	
	// check input
	if (!document.getElementById(element_id)) return;
	if (step_size == null || step_size < 1) step_size = 1; else step_size = Math.round(step_size);
	if (step_total == null || step_total < 1) step_total = 1; else step_total = Math.round(step_total);
	
	// set initial delay to repeated delay when not specified
	if (repeat_delay == null) repeat_delay = 0;
	if (initial_delay == null) initial_delay = repeat_delay;
	
	// set to 1 when not specified
	if (repeat_total == null) repeat_total = -1;


	// update repeat total
	// note: 0 will be left as is, meaning repeat indefinitely
	if (repeat_total > 1) repeat_total--;
	else if (repeat_total == 1) repeat_total = -1; // repeat 1 time means this is the last one, do not continue
	




	// set name
	var name = 'tvgs_element_waver_by_id';
	
	// get element
	var element = document.getElementById(element_id);
	

	
	
	
	
	// build action
	
	// get element
	//var element = document.getElementById(element_id);
	
	//document.getElementById('output').innerHTML += element.nodeName + '<br>';
	
	
	// get original opacity
	if (element.style.opacity && element.style.opacity.length > 0)
	{
		
		//alert(element.style.opacity);
		
		var opacity = element.style.opacity * 1; // style values are strings
		opacity = opacity * 100; // opacity style correction
		
	}
	// get original opacity from explorer
	else if (element.style.filter && element.style.filter.length > 0)
	{
		
		//alert(element.style.filter);
		
		// get explorer opacity
		if (element.style.filter.match(/opacity/))
		{

			var opacity = element.style.filter.replace(/[^0-9]/gi, '') * 1; // style values are strings
		
		}

	}
	else
	{
		var opacity = 100;
	}
	
	
	//alert(opacity);
	
	
	// create animation ramp
	var original_opacity = opacity;
	var opacity_frames = [];
	var step_counter = step_total;
	
	while(opacity > 0 && step_counter > 0)
	{
	
		opacity = opacity - step_size;
		step_counter--;
		
		if (opacity < 1) opacity = 0;
	
		opacity_frames.push(opacity);
	
	}


//alert(opacity_frames);


	// duplicate array, add final step, cut off first
	var opacity_frames_reversed = opacity_frames.slice().reverse();
	opacity_frames_reversed.push(original_opacity); // adds the initial state as the last step
	opacity_frames_reversed.shift(); // prevents an identical step in the middle
	
	// join two halves
	var all_opacity_frames = opacity_frames.concat(opacity_frames_reversed);

   //alert(all_opacity_frames);
   
   
	// add to timeline
	var interval = TVGS.CHRON.frame_interval; // use timeline's native frame interval
	var timestamp = TVGS.CHRON.time();
	
	// set trace string
	var t_a_trace = TVGS.CHRON.trace();
	
	var t_a_time = timestamp + initial_delay; // includes initial delay, used only once and is not carried on in propagation
	
	// set timeline assignment properties
	var t_a_name = name; // used for instigate eval script
	
	// set values
	var t_a_values = {}; // different for each action
	
	// set script
	var t_a_script = null; // different for each action

	// set chain
	var t_a_chain = null; // the chain info object to delete when the last frame is processed or when the chain is deleted
	
	// set complete
	var t_a_complete = null; // different on first and last action
	
	// set smother
	var t_a_smother = null; // the chain to delete
	
	// set propagate
	//var t_a_propagate = null; // different on last action
	
	// set instigate
	var t_a_instigate = null; // different on last action
	
	
	// collect chain info
	// note: properties that are frame specific are not part of the general chain object
//	var chain_info = {};
	//chain_info.name = t_a_name;
	//chain_info.element = element;
	
	
	//document.getElementById('output').innerHTML += t_a_trace + ' make frames<br>';
	
	
	
	var last_frame = all_opacity_frames.length;
	var current_frame = 0;
	
	for (var i in all_opacity_frames){ if (TVGS.key(i)){
		
		// increase frame number
		// note: first is always frame 1
		current_frame++;
		
		// increase time
		// note: time is increased up front to be one interval after the timestamp to ensure smooth instigates
		t_a_time = t_a_time + interval;
		
		
		
		// set values
		t_a_values = {};
		//t_a_values.trace = trace;
		//t_a_values.timestamp = timestamp;
		t_a_values.relay = 1; // tvgs_element_set_opacity
		t_a_values.element = element;
		t_a_values.opacity = (all_opacity_frames[i] / 100);
		
		
		// make script
		//t_a_script = 'tvgs_element_set_opacity("' + element_id + '", ' + all_opacity_frames[i] + ');';
		
		
		// set complete and smother
		// note: smother is automatic for this chain type on this object in the first frame
		if (current_frame == 1)
		{	
			t_a_complete = 1;
		
			t_a_smother = {};
			t_a_smother.name = name;
			t_a_smother.element = element;
		}
		else
		{
			// set back to null in other frames
			t_a_smother = null;
			t_a_complete = null;
		}
		
		
		// set complete and instigate 
		if (current_frame == last_frame)
		{
			
			t_a_complete = 1; // obligatory step to repeat
			
		
			//t_a_propagate = 0; // infinite propagations
			
			
			if (repeat_total > -1)
			{
			
				t_a_instigate = {};

				t_a_instigate.values = {};
				t_a_instigate.values.relay = 1; // chain relay 1: tvgs_element_waver_by_id(element_id, step_size, step_total, initial_delay, repeat_total, repeat_delay)
				t_a_instigate.values.element_id = element_id;
				t_a_instigate.values.step_size = step_size;
				t_a_instigate.values.step_total = step_total;
				t_a_instigate.values.initial_delay = null; // makes for unexpected results when not null, using just the repeat delay will ensure smooth propagation
				t_a_instigate.values.repeat_total = repeat_total;
				t_a_instigate.values.repeat_delay = repeat_delay;
				
				//t_a_instigate.script = t_a_name + '("' + element_id + '", ' + step_size + ', ' + step_total + ', ' + repeat_delay + ');';
				
			}
			
		}
		
	

		
	
		chron_timeline_add_action(t_a_trace, t_a_time, t_a_name, t_a_values, t_a_script, t_a_chain, t_a_complete, t_a_smother, t_a_instigate);
		
		
	
	}}
	
	
	
	
	// add chain info to timeline
	//TVGS.CHRON.chains.push(chain_info);
	
	/*
	if (initial_delay == null)
	{ 
		
		for (var g in TVGS.CHRON.actions){ if (TVGS.key(g)){
		
			var readout = '';
			for (var i in TVGS.CHRON.actions[g]){ if (TVGS.key(i)){
			
				readout += ' - ' + TVGS.CHRON.actions[g][i];
				
			}}
			
			document.getElementById('output').innerHTML += readout + '<br>';
		}}
		
	}
	*/
	
}







// FAMILY: TIMELINE CHAIN
// companion: tvgs_element_set_image_source

// swaps the image source using the imagepool
function tvgs_image_swap(element_id, imagepool_name, background, reverse, interval, initial_delay, repeat_total, repeat_delay)
{
	
	// check input

	// check element
	if (!document.getElementById(element_id)) return;
	
	// check if the imagepool exists and set reference
	if (TVGS.config.chron.imagepool && TVGS.config.chron.imagepool.length > 0)
	{	
		var imagepool_exists = false;
		for (var i in TVGS.config.chron.imagepool){ if (TVGS.key(i)){
		
			if (TVGS.config.chron.imagepool[i].name && TVGS.config.chron.imagepool[i].name == imagepool_name)
			{
				var imagepool = TVGS.config.chron.imagepool[i];
				imagepool_exists = true;
				break;
			}
		}}
		if (imagepool_exists == false) return;
	}
	else return;


	// check frame interval
	if (interval == null) interval = TVGS.CHRON.frame_interval; // use timeline's native frame interval

	
	// set initial delay to repeated delay when not specified
	if (repeat_delay == null) repeat_delay = 0;
	if (initial_delay == null) initial_delay = repeat_delay;
	
	// set to 0 when not specified
	if (repeat_total == null) repeat_total = 0;


	// update repeat total
	// note: 0 will be left as is, meaning repeat indefinitely
	if (repeat_total > 1) repeat_total--;
	else if (repeat_total == 1) repeat_total = -1; // repeat 1 time means this is the last one, do not continue
	


	// set name
	var name = 'tvgs_image_swap';
	
	// get element
	var element = document.getElementById(element_id);
	


	// construct all source image links
	var all_source_image_links = [];
	
	for (var i in imagepool.image){ if (TVGS.key(i)){
	
		all_source_image_links[i] = imagepool.directory + imagepool.image[i];
	
	}}

	
   
	// add to timeline
	//var interval = TVGS.CHRON.frame_interval; // use timeline's native frame interval
	var timestamp = TVGS.CHRON.time();
	
	// set trace string
	var t_a_trace = TVGS.CHRON.trace();
	
	// set chain base time
	var t_a_time = timestamp + initial_delay; // includes initial delay, used only once and is not carried on in propagation
	
	// set timeline assignment properties
	var t_a_name = name; // used for instigate eval script
	
	// set values
	var t_a_values = {}; // different for each action
	
	// set script
	var t_a_script = null; // different for each action

	// set chain
	var t_a_chain = null; // the chain info object to delete when the last frame is processed or when the chain is deleted
	
	// set complete
	var t_a_complete = null; // different on first and last action
	
	// set smother
	var t_a_smother = null; // the chain to delete
	
	// set instigate
	var t_a_instigate = null; // different on last action
	
	
	
	var last_frame = all_source_image_links.length;
	var current_frame = 0;

	for (var i in all_source_image_links){ if (TVGS.key(i)){
		
		// increase frame number
		// note: first is always frame 1
		current_frame++;
		
		// increase time
		// note: time is increased up front to be one interval after the timestamp to ensure smooth instigates
		t_a_time = t_a_time + interval;
		
		
		
		// set values
		t_a_values = {};
		t_a_values.relay = 4; // relay 4: tvgs_element_set_image_source(element, background, source)
		t_a_values.element = element;
		t_a_values.background = background;
		t_a_values.source = all_source_image_links[i];
		

		
		// set complete and smother
		// note: smother is automatic for this chain type on this object in the first frame
		if (current_frame == 1)
		{	
			t_a_complete = 1;
		
			t_a_smother = {};
			t_a_smother.name = name;
			t_a_smother.element = element;
		}
		else
		{
			// set back to null in other frames
			t_a_smother = null;
			t_a_complete = null;
		}
		
		
		
		// set complete and instigate 
		if (current_frame == last_frame)
		{
			
			t_a_complete = 1; // obligatory step to repeat
			
			
			if (repeat_total > -1)
			{

				t_a_instigate = {};

				t_a_instigate.values = {};
				t_a_instigate.values.relay = 2; // chain relay 2: tvgs_image_swap(element_id, imagepool, background, reverse, initial_delay, repeat_total, repeat_delay)
				t_a_instigate.values.element_id = element_id;
				t_a_instigate.values.imagepool = imagepool_name;
				t_a_instigate.values.background = background;
				t_a_instigate.values.reverse = reverse;
				t_a_instigate.values.interval = interval;
				t_a_instigate.values.initial_delay = null; // makes for unexpected results when not null, using just the repeat delay will ensure smooth propagation
				t_a_instigate.values.repeat_total = repeat_total;
				t_a_instigate.values.repeat_delay = repeat_delay;

			}
			
		}



		// add action to timeline
		chron_timeline_add_action(t_a_trace, t_a_time, t_a_name, t_a_values, t_a_script, t_a_chain, t_a_complete, t_a_smother, t_a_instigate);

	}}
	
}












// FAMILY: TIMELINE CHAIN
// companion: tvgs_element_set_size

// resizes the element

// chain config example
// passing a chain config as the only argument has the following advantages:
// - there is only one argument, which is set up in an easily readible manner, instead of a long single line
// - argument names are always visible with their value providing a practical overview
// - each argument can be deactivated easily by escaping the line it is on

// note: the time_delta setting and the step settings (step_size, step_unit and step_total) are mutually exclusive, setting time_delta will ignore all step settings

/*
// create config object
chain_config = {};

// general settings
chain_config.element_id		= 'my_image';	// the element id
chain_config.interval		= 1000;			// the time between each frame in milliseconds, may be omitted, defaults to timeline frame interval
chain_config.time_delta		= 1000;			// the time in which to complete the resize in milliseconds
chain_config.step_size		= 5;				// the size of each resize step
chain_config.step_unit		= 'px';			// the unit applied to the step_size, may be omitted, defaults to 'px'
chain_config.step_total		= 10;				// the total number of resize steps

// specific settings
chain_config.x_start			= 200;			// the start width
chain_config.x_end			= ?;			// ?
chain_config.x_distance		= 100;			// the number of units to increase or decrease the width from the starting point
chain_config.x_unit			= 'px';			// the unit applied to the start and end value of the x axis, accepts 'px' for pixels or '%' for percentage of the original image width (when the image has its width defined by a style before the resize is applied, the value of the style will be interpreted as the original width, for example: <img style="width: 200px;" src="image.jpg"> means that regardless of the real image dimensions, 200 pixels is now treated as the original width)
chain_config.y_start			= 300;			// the start height
chain_config.y_end			= ?;			// ?
chain_config.y_distance		= 250;			// the number of units to increase or decrease the height from the starting point
chain_config.y_unit			= 'px';			// the unit applied to the start and end value of the y axis, accepts 'px' for pixels or '%' for percentage of the original image height (when the image has its height defined by a style before the resize is applied, the value of the style will be interpreted as the original height, for example: <img style="height: 200px;" src="image.jpg"> means that regardless of the real image dimensions, 200 pixels is now treated as the original height)
chain_config.rebound			= true;			// adds the reversed action to the end of the original action, effectively restoring the start size, accepts true or false, may be omitted, defaults to false

// chain controls
chain_config.initial_delay	= 500;			// delay before the action starts in milliseconds, may be omitted, defaults to 0
chain_config.repeat_total	= 3;				// number of times the action is run consecutively, may be omitted, defaults to 1 (setting this to 0 means repeat forever)
chain_config.repeat_delay	= 250;			// delay before each repeat of the action in milliseconds, may be omitted, defaults to 0
chain_config.instigate		= [];				// array of any actions (as script or relay format) to launch at the end of this chain, these actions will be deleted as normal when the parent chain is smothered

// pass config object into function
tvgs_element_resize(chain_config);			// that's it, the config object now provides all the arguments to the function
*/

function tvgs_element_resize(chain_config, element_id, interval, time_delta, x_start, x_end, x_distance, x_unit, y_start, y_end, y_distance, y_unit, rebound, initial_delay, repeat_total, repeat_delay, instigate)
{
	
	// set ignored arguments
	// note: these variables are not defined by the function parameters and are defined here to keep the inline function writing style as short as possible
	var step_size;
	var step_unit;
	var step_total;
	
	// specialty parameters
	var all_even; // makes all dimensions into even numbers, for smooth display of centered content
	
	// set chain name
	var name = 'tvgs_element_resize';
	
	
	
	// CHECK INPUT
	
	// check for config object
	// note: arguments in a config object will overwrite normal arguments, if normal arguments are missing they will default to null
	if (!TVGS.nil(chain_config))
	{
		// general settings
		if (typeof chain_config.element_id != 'undefined') element_id = chain_config.element_id;
		if (typeof chain_config.interval != 'undefined') interval = chain_config.interval;
		if (typeof chain_config.time_delta != 'undefined') time_delta = chain_config.time_delta;
		if (typeof chain_config.step_size != 'undefined') step_size = chain_config.step_size; // ignored
		if (typeof chain_config.step_unit != 'undefined') step_unit = chain_config.step_unit; // ignored
		if (typeof chain_config.step_total != 'undefined') step_total = chain_config.step_total; // ignored
		
		// specific settings
		if (typeof chain_config.x_start != 'undefined') x_start = chain_config.x_start;
		if (typeof chain_config.x_end != 'undefined') x_end = chain_config.x_end;
		if (typeof chain_config.x_distance != 'undefined') x_distance = chain_config.x_distance;
		if (typeof chain_config.x_unit != 'undefined') x_unit = chain_config.x_unit;
		if (typeof chain_config.y_start != 'undefined') y_start = chain_config.y_start;
		if (typeof chain_config.y_end != 'undefined') y_end = chain_config.y_end;
		if (typeof chain_config.y_distance != 'undefined') y_distance = chain_config.y_distance;
		if (typeof chain_config.y_unit != 'undefined') y_unit = chain_config.y_unit;
		if (typeof chain_config.rebound != 'undefined') rebound = chain_config.rebound;
		if (typeof chain_config.all_even != 'undefined') all_even = chain_config.all_even;

		// chain control
		if (typeof chain_config.initial_delay != 'undefined') initial_delay = chain_config.initial_delay;
		if (typeof chain_config.repeat_total != 'undefined') repeat_total = chain_config.repeat_total;
		if (typeof chain_config.repeat_delay != 'undefined') repeat_delay = chain_config.repeat_delay;
		if (typeof chain_config.instigate != 'undefined') instigate = chain_config.instigate;
	}
	
//alert('chain_config checked');
	
	// check general settings

	// get element
	var element = document.getElementById(element_id);
//alert('element checked');
	// check element
	if (TVGS.nil(element)) return;
	
	// get element original properties
	// note: this check is always performed, to ensure that before anything else happens the original properties are stored
	if (TVGS.nil(element.tvgs_original_width)) element.tvgs_original_width = TVGS.element_get_current_width(element);
	if (TVGS.nil(element.tvgs_original_height)) element.tvgs_original_height = TVGS.element_get_current_height(element);

	
//	alert('element 2 checked');
	// check time delta
	// note: no time delta means the element snaps to the final frame
	if (TVGS.nil(time_delta)) time_delta = 0;
//	alert('time_delta checked');
	// check frame interval
	if (TVGS.nil(interval)) interval = TVGS.CHRON.frame_interval; // use timeline's native frame interval
//	alert('interval checked');
	// set minimum of 1 millisecond
	// note: this prevents rupturing of the space-time continuum
	if (interval < 1) interval = 1;
	
	// check step settings
	// note: no step settings means steps of 1 pixel for 1 second using the timeline's native frame interval
	if (TVGS.nil(step_size)) step_size = 1;
	if (TVGS.nil(step_unit)) step_unit = 'px';
	if (TVGS.nil(step_total)) step_total = Math.round(1000 / TVGS.CHRON.frame_interval);
	
	
//	alert('general checked');
	// check specific settings
	
	// return on no resize
	//if (((TVGS.nil(x_start) && TVGS.nil(x_distance)) || TVGS.nil(x_distance)) && ((TVGS.nil(y_start) && TVGS.nil(y_distance)) || TVGS.nil(y_distance))) return;
//	if (((x_start === null && x_distance === null) || x_distance === null) && ((y_start === null && y_distance === null) || y_distance === null)) return;
	
	
	
	
	// check x dimensions
	if (TVGS.nil(x_start) && TVGS.nil(x_end) && TVGS.nil(x_distance))
	{
		// nothing set, go from current to original
		x_start = TVGS.element_get_current_width(element);
		x_end = element.tvgs_original_width;
		x_distance = x_end - x_start;
	}
	else if (TVGS.nil(x_start) && TVGS.nil(x_distance))
	{
		// only end set, go from current to specified end
		x_start = TVGS.element_get_current_width(element);
		x_distance = x_end - x_start;
	}
	else if (TVGS.nil(x_end) && TVGS.nil(x_distance))
	{
		// only start set, go from start to original
		x_end = element.tvgs_original_width;
		x_distance = x_end - x_start;
	}
	else if (TVGS.val(x_start) && TVGS.val(x_end))
	{
		// start and end set, go from start to end, ignore distance
		x_distance = x_end - x_start;
	}
	
	if (TVGS.nil(x_start)) x_start = TVGS.element_get_current_width(element);
	if (TVGS.nil(x_end)) x_end = element.tvgs_original_width;
	if (TVGS.nil(x_distance)) x_distance = 0;
	if (TVGS.nil(x_unit)) x_unit = 'px';
	
	//alert(x_start + ' < start x end > ' + x_end);
	//document.getElementById('output').innerHTML += ' x_distance ' + x_distance + ' ';
	
	// check y dimensions
	if (TVGS.nil(y_start) && TVGS.nil(y_end) && TVGS.nil(y_distance))
	{
		// nothing set, go from current to original
		y_start = TVGS.element_get_current_height(element);
		y_end = element.tvgs_original_height;
		y_distance = y_end - y_start;
	}
	else if (TVGS.nil(y_start) && TVGS.nil(y_distance))
	{
		// only end set, go from current to specified end
		y_start = TVGS.element_get_current_height(element);
		y_distance = y_end - y_start;
	}
	else if (TVGS.nil(y_end) && TVGS.nil(y_distance))
	{
		// only start set, go from start to original
		y_end = element.tvgs_original_height;
		y_distance = y_end - y_start;
	}
	else if (TVGS.val(y_start) && TVGS.val(y_end))
	{
		// start and end set, go from start to end, ignore distance
		y_distance = y_end - y_start;
	}
	
	if (TVGS.nil(y_start)) y_start = TVGS.element_get_current_height(element);
	if (TVGS.nil(y_end)) y_end = element.tvgs_original_height;
	if (TVGS.nil(y_distance)) y_distance = 0;
	if (TVGS.nil(y_unit)) y_unit = 'px';
	
	
	
	// return on no resize
	// note: check here after distance is calculated
	
	
	
	// check rebound
	if (TVGS.nil(rebound)) rebound = false;
//	alert(rebound);
//	alert('specific checked');
	
	
	// check all even
	if (all_even !== true) all_even = false;
	
	
	// check chain control
	
	// set initial delay to repeated delay when not specified
	// note: this makes sure the chain is continued smoothly
	// note: to have an initial delay of zero combined with a repeat delay, the initial delay needs to be explicitly set to zero 
	if (TVGS.nil(repeat_delay)) repeat_delay = 0;
	if (TVGS.nil(initial_delay)) initial_delay = repeat_delay;
	//alert(initial_delay);
	// set to zero when not specified
	if (TVGS.nil(repeat_total)) repeat_total = 1;

//document.getElementById('output').innerHTML += ' repeat_total ' + repeat_total + ' ';
	// update repeat total
	// note: zero will be left as is, meaning repeat indefinitely
	if (repeat_total > 1) repeat_total--;
	else if (repeat_total == 1) repeat_total = -1; // repeat 1 time means this is the last one, do not continue
	
	// check instigate
	if (TVGS.nil(instigate)) instigate = [];
	
//	alert('input checked');
	
	//document.getElementById('output').innerHTML += ' repeat_total ' + repeat_total + ' ';
	
	
	
	// PREPARE CHAIN
	
	// set step total
	
	// divide time delta by current frame rate to get number of steps
	var step_total = Math.round(time_delta / interval);

	// set minimum of 1 step
	// note: this prevents a small time delta from having the only step rounded down to zero
	if (step_total < 1) step_total = 1;

	//if (x_distance == 0) document.getElementById('output').innerHTML += ' step_total ' + step_total + ' ';

	// calculate steps
	
	// calculate x path
	var x_step = null;
	var x_step_number = 0;
	var x_step_size = x_distance / step_total; // not rounded
	var x_step_path = [];
	
	while (x_step_number < step_total)
	{
		x_step = Math.round(x_start + (x_step_size * (x_step_number + 1)));
		
		// check for all even, excepting last step
		if (all_even == true && x_step_number != (step_total - 1))
		{
			// even out odd numbers, adjust downwards
			// note: discriminates step direction, up means adjustment is upward, down means adjustment is downward, possibly doubling adjusted steps with the last step, so animation may visibly finish early, but never start late
			if ((x_step + '').match(/[13579]$/)) x_step_size >= 0 ? x_step++ : x_step--;
		}
		
		x_step_path[x_step_number] = x_step;

		x_step_number++;
	}
	
	
	
	//if (x_distance == 0) document.getElementById('output').innerHTML += ' x_step_path ' + x_step_path + ' ';
	//alert(x_step_path);
	
	// attach reversed x steps
	if (rebound)
	{
		// duplicate array, add final step, cut off first
		var x_step_path_reversed = x_step_path.slice().reverse();
		x_step_path_reversed.push(x_start); // adds the initial state as the last step
		x_step_path_reversed.shift(); // prevents an identical step in the middle
		
		// join two halves
		x_step_path = x_step_path.concat(x_step_path_reversed);
	}
	
	// calculate y path
	var y_step = null;
	var y_step_number = 0;
	var y_step_size = y_distance / step_total; // not rounded
	var y_step_path = [];
	
	while (y_step_number < step_total)
	{
		y_step = Math.round(y_start + (y_step_size * (y_step_number + 1)));
		
		// check for all even, excepting last step
		if (all_even == true && y_step_number != (step_total - 1))
		{
			// even out odd numbers, adjust downwards
			// note: discriminates step direction, up means adjustment is upward, down means adjustment is downward, possibly doubling adjusted steps with the last step, so animation may visibly finish early, but never start late
			if ((y_step + '').match(/[13579]$/)) y_step_size >= 0 ? y_step++ : y_step--;
		}
		
		y_step_path[y_step_number] = y_step;
		
		y_step_number++;
	}
	
	// attach reversed y steps
	if (rebound)
	{
		// duplicate array, add final step, cut off first
		var y_step_path_reversed = y_step_path.slice().reverse();
		y_step_path_reversed.push(y_start); // adds the initial state as the last step
		y_step_path_reversed.shift(); // prevents an identical step in the middle
		
		// join two halves
		y_step_path = y_step_path.concat(y_step_path_reversed);
	}
	
	
	// GENERATE CHAIN

	// set trace string
	var t_a_trace = TVGS.CHRON.trace();
	
	// set chain base time
	var t_a_time = TVGS.CHRON.time() + initial_delay; // includes initial delay, used only once and is not carried on in propagation
	
	// set timeline assignment properties
	var t_a_name = name; // used for instigate eval script
	
	// set values
	var t_a_values = {}; // different for each action
	
	// set script
	var t_a_script = ''; // different for each action

	// set chain
	var t_a_chain = null; // the chain info object to delete when the last frame is processed or when the chain is deleted
	
	// set complete
	var t_a_complete = null; // different on first and last action
	
	// set smother
	var t_a_smother = null; // the chain to delete
	
	// set instigate
	var t_a_instigate = null; // different on last action
	
	
	
	// set first and last frame
	var current_frame = 0;
	// note: this does not use step total because frame number may be altered through rebound setting
	var last_frame = x_step_path.length;
	
	

	// step through frames
	for (var i in x_step_path){ if (TVGS.key(i)){
		
		if(TVGS.key(i)) // only numeric keys
		{
			
			// increase frame number
			// note: first is always frame 1
			current_frame++;
			//document.getElementById('output').innerHTML += ' resize addframe ' + current_frame + ' ';
			// increase frame time
			// note: time is increased up front to be one interval after the base time to ensure smooth instigates
			t_a_time = t_a_time + interval;
			
		//	document.getElementById('output').innerHTML += ' frame ' + current_frame + ' a ';
			
			// set values
			t_a_values = {};
			t_a_values.relay = 5; // relay 5: tvgs_element_set_size(element, width, height)
			t_a_values.element = element;
			t_a_values.width = x_step_path[i] + x_unit;
			t_a_values.height = y_step_path[i] + y_unit;
			
		//	document.getElementById('output').innerHTML += 'b ';
		//	t_a_script = "document.getElementById('output').innerHTML += ' resize " + x_step_path[i] + " ';";
	
			
			// set complete and smother
			// note: smother is automatic for this chain type on this object in the first frame
			if (current_frame == 1)
			{	
				t_a_complete = 1;
			
				t_a_smother = {};
				t_a_smother.name = name;
				t_a_smother.element = element;
			}
			else
			{
				// set back to null in other frames
				t_a_smother = null;
				t_a_complete = null;
			}
			
	//		document.getElementById('output').innerHTML += 'c ';
			
			// set complete and instigate 
			if (current_frame == last_frame)
			{
				
				t_a_complete = 1; // obligatory step to repeat
				
		//		document.getElementById('output').innerHTML += 'c1 ';
				// see if this chain will continue
				if (repeat_total > -1)
				{
			//		document.getElementById('output').innerHTML += 'c2 ';
					// setup new instigate to propagate this chain
					var chain_instigate = {};
	
					// instigate script
					chain_instigate.script = null; 
					
					// instigate relay
					chain_instigate.relay = {};
					
					// set chain relay, always fixed fot this chain type
					chain_instigate.relay.relay = 3; // chain relay 3: tvgs_element_resize(element_id, imagepool, background, reverse, initial_delay, repeat_total, repeat_delay)
		//			document.getElementById('output').innerHTML += 'c3 ';
					// general settings
					chain_instigate.relay.element_id = element_id;
					chain_instigate.relay.interval = interval;
					chain_instigate.relay.time_delta = time_delta;
					chain_instigate.relay.step_size = step_size;
					chain_instigate.relay.step_unit = step_unit;
					chain_instigate.relay.step_total = step_total;
		//			document.getElementById('output').innerHTML += 'c4 ';
					// specific settings
					chain_instigate.relay.x_start = x_start;
					chain_instigate.relay.x_end = x_end;
					chain_instigate.relay.x_distance = x_distance;
					chain_instigate.relay.x_unit = x_unit;
					chain_instigate.relay.y_start = y_start;
					chain_instigate.relay.y_end = y_end;
					chain_instigate.relay.y_distance = y_distance;
					chain_instigate.relay.y_unit = y_unit;
					chain_instigate.relay.rebound = rebound;
		//			document.getElementById('output').innerHTML += 'c5 ';
					// chain control
					chain_instigate.relay.initial_delay = null; // makes for unexpected results when not null, using just the repeat delay will ensure smooth propagation
					chain_instigate.relay.repeat_total = repeat_total;
					chain_instigate.relay.repeat_delay = repeat_delay;
					
					// pass instigate??? update trigger countdown??? use base time offset to schedule instigate out of this chain??
					//t_a_instigate.values.instigate = instigate;
		//			alert('chain insitgate 2');
					// add to instigate list 
					instigate[instigate.length] = chain_instigate;
					//document.getElementById('output').innerHTML += ' instigate ' + instigate + ' ';
	
				}
				
				// add instigate list into this frame
				t_a_instigate = instigate;
				
			//	alert('chain insitgate 0');
				
			}
	
	//		document.getElementById('output').innerHTML += 'd ';
			
			//if (x_distance == 0) document.getElementById('output').innerHTML += ' cf ' + current_frame + ' ';
			
			// add action to timeline
			chron_timeline_add_action(t_a_trace, t_a_time, t_a_name, t_a_values, t_a_script, t_a_chain, t_a_complete, t_a_smother, t_a_instigate);
		
		}
	
	}}
	
//	alert('chain created');
	
}







/*
// reworked into above chain generator, exept for the part where the element is hidden when size is zero

function tvgs_img_pop(img_id, step_size, axis)
{
	
	// check if element exists
	if (document.getElementById(img_id))
	{
	
		var image = document.getElementById(img_id);
		
		// check if its an image
		if (image.nodeName == 'IMG')
		{
			
			// get original height
			if (image.style.height != '')
			{
				var image_height = image.style.height.replace(/px/, '') * 1; // style values are strings
			}
			else
			{
				var image_height = image.height; // properties are integers
			}
			
			
			// get original width
			if (image.style.width != '')
			{
				var image_width = image.style.width.replace(/px/, '') * 1; // style values are strings
			}
			else
			{
				var image_width = image.width; // properties are integers
			}
			
			
			// set pixel step size	
			if (step_size == null || step_size < 1)
			{
				var img_pop_step_size = 1;
			}
			else
			{
				var img_pop_step_size = Math.round(step_size);
			}
			
			
			// set direction
			if (axis == 'horizontal' || axis == 'x') axis = 'h';
			if (axis == 'vertical' || axis == 'y') axis = 'v';
			if (axis == 'both' || axis == 'xy') axis = null;
		

			// set pop parameters
			// grow or shrink is based on visibility
			if (image.style.display != 'none')
			{

				var pop_action = 'shrink';
				
				// shrink from image width
				var adjusted_width = image_width;
				var adjusted_height = image_height;
			
			}
			else
			{

				var pop_action = 'grow';
				
				// grow from 0 pixels
				var adjusted_width = 0;
				var adjusted_height = 0;
				
				// inverted adjustment for growing from axis
				if (axis == 'h') adjusted_height = image_height;
				if (axis == 'v') adjusted_width = image_width;
			
			}

			
			// repeater
			function img_pop_step()
			{
				
				if (pop_action == 'shrink')
				{
					
					// shrink
					if (axis == 'h' || axis == null) adjusted_width = adjusted_width - img_pop_step_size;
					if (axis == 'v' || axis == null) adjusted_height = adjusted_height - img_pop_step_size;
					
					if(adjusted_width < 1 || adjusted_height < 1)
					{

						// hide image and restore size
						image.style.display = 'none';
						image.style.width = image_width + 'px';
						image.style.height = image_height + 'px';
						
						clearInterval(img_pop_interval_id);
						
						return;
					
					}
					else
					{
					
						// shrink width
						image.style.width = adjusted_width + 'px';
						image.style.height = adjusted_height + 'px';
						
					}
					
				}
				else
				{
					
					// grow
					if (axis == 'h' || axis == null) adjusted_width = adjusted_width + img_pop_step_size;
					if (axis == 'v' || axis == null) adjusted_height = adjusted_height + img_pop_step_size;
					
					if (adjusted_width >= image_width) adjusted_width = image_width;
					if (adjusted_height >= image_height) adjusted_height = image_height;
					
					if(adjusted_width == image_width && adjusted_height == image_height)
					{
					
						// end growing
						image.style.width = image_width + 'px';
						image.style.height = image_height + 'px';
						
						clearInterval(img_pop_interval_id);
						
						return;
					
					}
					else
					{
						
						// grow width
						image.style.width = adjusted_width + 'px';
						image.style.height = adjusted_height + 'px';
						
						// make image visible
						if (image.style.display == 'none')
						{
							 image.style.display = '';
						}
						
					}
				
				}
			
			}
			
			
			// start animation
			img_pop_interval_id = setInterval(img_pop_step, 25);
			
		}
		
	}
	
}
*/

