// Grid Animation Script v4.0
// Andrew Pettican (July 2009)
//
// Changelog:
// v1.0 - 15/08/2008 - Initial build
// v1.0 - 11/09/2008 - Modified so the grid animation only executes on a mouseover of the grid (not list)
// v2.0 - 16/09/2008 - Made code flexible for use in both the Portfolio and People grids
// v2.0 - 19/09/2008 - Modified to enable grid text to be enabled or disabled
// v3.0 - 06/10/2008 - Rewritten to perform fading animations
// v3.1 - 08/10/2008 - Renamed global variables to avoid possible conflicts with other scripts
// v3.2 - 09/10/2008 - Added ability to show extra portfolio items
// v3.3 - 10/10/2008 - Modified to support the news list as well as the portfolio/people lists
// v3.4 - 16/10/2008 - Modified to perform the grid animation on scroll events
// v3.5 - 21/10/2008 - Modified to support snake and ltr ordering of grid items (NB: ltr = left-to-right)
// v3.6 - 03/11/2008 - Modified to prevent grid animation when mousing over a blank item
// v3.7 - 30/06/2009 - Updated the jQuery triggers to prevent problems in jQuery 1.3.1+
// v3.8 - 29/07/2009 - Optimisated code to prevent floods of animations from triggering
//                     Also modified the lists to prevent dark borders from appearing between default_highlight and hovered list items
//                     Animations now reset when the mouse leaves the window
// v4.0 - 31/07/2009 - Modified for cross site support
//                     Modified to dynamically generate the hover, fade, and text layers
//////////////////////////////////////////////////////////////////

// ---------------------------------------------------------------
// Parameters
// ---------------------------------------------------------------

var ga_list_ids = new Array("awards_list"); // list identifiers
var ga_grid_ids = new Array("awards_grid"); // grid identifiers
var ga_grid_positions = new Array("right", "left");		// grid positions (left or right)
var ga_grid_ordering = new Array("snake", "ltr");		// grid positions (left or right)
var ga_text_id = "grid_text_store";						// text identifier
var ga_fade_time = 500;									// Duration of list/block fades
var ga_list_close_timeout = 100;						// Delay before closing a list block once the mouse has left (Note: users will move quickly across the list)
var ga_grid_close_timeout = 1500;						// Delay before closing a grid block once the mouse has left (Note: users will take longer to move across the grid)
var ga_out_delay = 200;									// The delay before terminating any hovers once the mouse is outside of the gridanim area
var ga_hover_trigger_grid_delay = 100;					// The hover delay before the grid animation is executed (to prevent flashing when the mouse is moved)
var ga_hover_trigger_list_delay = 50;					// The hover delay before the list animation is executed (to prevent flashing when the mouse is moved)
var ga_default_class = "default_highlight";				// Default highlight class for items which will always be active
var ga_above_default_class = "above_default_highlight";	// Class for list items which are above the default highlight item
var ga_list_element_node_type = "li";					// The node type of list element nodes (div, li, etc)
var ga_shakey_mouse_variance = 4;						// The max number of pixels the mouse can move by when starting a grid animation
var ga_grid_dir_normal = "/colour/";					// The sub-directory containing the colour grid images
var ga_grid_dir_fade = "/bw/";							// The sub-directory containing the black and white grid images

// ---------------------------------------------------------------
// Parameters End
// ---------------------------------------------------------------



// ---------------------------------------------------------------
// Global Vars, Do not change anything below this line!
// ---------------------------------------------------------------

var ga_items = new Array();			// An array of gridanim details
var ga_list_prefix = "";			// The prefix on list item id's
var ga_grid_prefix = "";			// The prefix on grid item id's
var ga_grid_text_prefix = "";		// The prefix on grid text item id's
var ga_normal_suffix = "_normal";	// The suffix on normal grid box id's
var ga_fade_suffix = "_fade";		// The suffix on faded grid box id's
var ga_text_suffix = "_text";		// The suffix on text grid box id's
var ga_count = 0;					// Number of items in the list
var ga_list_wrapper_id = "";		// The id of the grid wrapper
var ga_grid_wrapper_id = "";		// The id of the grid wrapper
var ga_grid_block_width = 0;		// The width of a grid block
var ga_grid_block_margin_right = 0;	// The right margin of a grid block
var ga_grid_block_margin_left = 0;	// The left margin of a grid block
var ga_grid_position = "";			// Flag indicating if the grid is positioned on the "left" or "right" of the list
var ga_grid_order = "";				// Flag indicating if the grid items are listed in "ltr" or "snake" ordering
var ga_grid_mode = false;			// Flag indicating if we are operating in grid mode
var ga_list_trigger = "";			// jQuery trigger for list activation
var ga_grid_trigger = "";			// jQuery trigger for grid activation
var ga_entry_classname = "";		// Classname of gridanim list entries
var ga_grid_text_mode = false;		// Will we display text in the grid?
var ga_out_timer = null;			// The gridanim out timer
var ga_hover_trigger_timer = null;	// The hover delay timer
var ga_mouseX = 0;					// The current mouse position (x-coordinate)
var ga_mouseY = 0;					// The current mouse position (y-coordinate)
var ga_scrollX = 0;					// The last scroll position when the mouse was moved (x-coordinate)
var ga_scrollY = 0;					// The last scroll position when the mouse was moved (y-coordinate)
var ga_open_req_id = 0;				// The current request id to open a gridanim item
var ga_mouse_over_list = false;		// Flag indicating if the mouse is over a list element
var ga_blank_block_class = "blank";	// Blank block class

// ---------------------------------------------------------------
// Global Vars End, Do not change anything above this line!
// ---------------------------------------------------------------



// ---------------------------------------------------------------
// JS Events
// ---------------------------------------------------------------

jQuery(document).ready(function()
{
	if(init_gridanim())
	{
		// Keep track of the mouse position
		$().mousemove(function(event) {
			ga_mouseX = event.pageX;
			ga_mouseY = event.pageY;
			ga_scrollX = $(window).scrollLeft();
			ga_scrollY = $(window).scrollTop();
		});
		
		
		// Build our wrapper trigger
		var wrapper_id_triggers = "";
		if(ga_grid_wrapper_id !== "") {
			wrapper_id_triggers += "#"+ga_grid_wrapper_id;
		}
		wrapper_id_triggers += (wrapper_id_triggers !== "") ? ", " : "";
		if(ga_list_wrapper_id !== "") {
			wrapper_id_triggers += "#"+ga_list_wrapper_id+"_wrapper";
		}
		
		
		// Clear the grid if the mouse leaves the screen (or enters a blank block)
		if(wrapper_id_triggers !== "")
		{
			$(document).bind("mouseleave", function(event) {
				// Mouse has left the document, clear mouse position variables
				ga_mouseX = 0;
				ga_mouseY = 0;
				
				// Terminate any hovers after ga_out_delay
				ga_out_timer = setTimeout(function(){ full_delayed_mouse_out_gridanim(); ga_out_timer = null; }, ga_out_delay);
			});
			$(wrapper_id_triggers).bind("mouseleave", function(event) {
				// Terminate any hovers after ga_out_delay
				ga_out_timer = setTimeout(function(){ full_delayed_mouse_out_gridanim(); ga_out_timer = null; }, ga_out_delay);
			});
			$(wrapper_id_triggers).bind("mouseenter", function(event) {
				if(ga_out_timer !== null) {
					clearTimeout(ga_out_timer);
					ga_out_timer = null;
				}
			});
		}
		
		
		// List event triggers
		if(ga_list_trigger !== "") {
			$(ga_list_trigger).bind("mouseenter", function(event) {
				mouse_over_gridanim(this.id, ga_list_prefix);
				ga_mouse_over_list = true;
			});
			$(ga_list_trigger).bind("mouseleave", function(event) {
				mouse_out_gridanim(this.id, ga_list_prefix);
				ga_mouse_over_list = false;
			});
		}
		
		
		// Grid event triggers
		if(ga_grid_trigger !== "") {
			$(ga_grid_trigger).bind("mouseenter", function(event) {
				if($(this).hasClass("blank") === false) {
					if(ga_out_timer !== null) {
						clearTimeout(ga_out_timer);
						ga_out_timer = null;
					}
					mouse_over_gridanim(this.id, ga_grid_prefix);
				} else {
					ga_out_timer = setTimeout(function(){ full_delayed_mouse_out_gridanim(); ga_out_timer = null; }, ga_out_delay);
				}
			});
			$(ga_grid_trigger).bind("mouseleave", function(event) {
				mouse_out_gridanim(this.id, ga_grid_prefix);
			});
		}
		
		
		// Mouse scroll events
		$(window).scroll(function() {
			// Only call may be queued to check_mouse_position()
			check_mouse_position();
		});
	}
});

// ---------------------------------------------------------------
// JS Events End
// ---------------------------------------------------------------



// ---------------------------------------------------------------
// Functions
// ---------------------------------------------------------------

/**
 * Initialises the gridanim items
 */
function init_gridanim() {
	// Get the list div (required)
	var gridanim_list = null;
	for(lid in ga_list_ids) {
	if (ga_list_ids.hasOwnProperty(lid)) {
		gridanim_list = document.getElementById(ga_list_ids[lid]);
		
		// Break the loop once we have found the list
		if(gridanim_list !== null) {
			// Setup variables
			ga_list_wrapper_id = ga_list_ids[lid];
			ga_list_prefix = ga_list_ids[lid]+"_";
			ga_list_trigger = "."+ga_list_ids[lid]+"_entry";
			ga_entry_classname = ga_list_ids[lid]+"_entry";
			break;
		}
	}
	}
	
	// Do we have a gridanim list?
	if(gridanim_list !== null)
	{
		// Get the grid div (optional)
		var gridanim_grid = null;
		for(gid in ga_grid_ids) {
		if (ga_grid_ids.hasOwnProperty(gid)) {
			gridanim_grid = document.getElementById(ga_grid_ids[gid]);
			
			// Break the loop once we have found the grid
			if(gridanim_grid !== null) {
				// Setup variables
				ga_grid_wrapper_id = ga_grid_ids[gid];
				ga_grid_prefix = ga_grid_ids[gid]+"_";
				ga_grid_text_prefix = ga_grid_ids[gid]+"_text_";
				ga_grid_trigger = "."+ga_grid_ids[gid]+"_block";
				ga_grid_block_width = $(ga_grid_trigger).width();
				ga_grid_block_margin_left = parseInt($(ga_grid_trigger).css("margin-left"), 10);
				ga_grid_block_margin_right = parseInt($(ga_grid_trigger).css("margin-right"), 10);
				ga_grid_position = ga_grid_positions[gid];
				ga_grid_order = ga_grid_ordering[gid];
				ga_grid_mode = true;
				break;
			}
		}
		}
		
		// Initialise each item in the gridanim list
		var counter = 0;
		
		
		var children = gridanim_list.getElementsByTagName(ga_list_element_node_type);
		var previous_index = -1;
		var item_position = -1;
		for(i=0; i<children.length; i++) {
			if(children[i].className.indexOf(ga_entry_classname) != -1)
			{
				// Initialise gridanim details
				item_position = parseInt(children[i].id.substr(ga_list_prefix.length), 10);
				ga_items[children[i].id] = new Array();
				
				// List Params
				ga_items[children[i].id]['open'] = false;
				ga_items[children[i].id]['fading_out'] = false;
				ga_items[children[i].id]['fading_in'] = false;
				ga_items[children[i].id]['closing_timer'] = null;
				
				// Grid Params
				ga_items[children[i].id]['grid_fading'] = "";
				ga_items[children[i].id]['ga_grid_mode'] = "normal";
				ga_items[children[i].id]['grid_anim_normal'] = false;
				ga_items[children[i].id]['grid_anim_fade'] = false;
				ga_items[children[i].id]['grid_closing_timer'] = null;
				ga_items[children[i].id]['is_dummy'] = false;
				
				// Build the hover layer div for each list item
				var hover_layer = $("#"+children[i].id+" .normal:first").clone(true);
				$(hover_layer).removeClass("normal").addClass("hover");
				hover_layer.appendTo("#"+children[i].id);
				
				// Build the fade and text layer divs for each grid item
				if(ga_grid_mode) {
					build_grid_item_layers(item_position);
				}
				
				// Default CSS (backup, for IE support)
				$("#"+children[i].id+" .normal").css("opacity", "1");
				$("#"+children[i].id+" .hover").css("opacity", "0");
				
				// If this item is active, and it is not the first item, add the ga_above_default_class class to the list item above it
				if($("#"+children[i].id).hasClass(ga_default_class)) {
					if(counter > 0 && previous_index >= 0) {
						var target_id = children[previous_index].id;
						$("#"+target_id).addClass(ga_above_default_class);
					}
				}
				counter++;
				previous_index = i;
			}
		}
		ga_count = counter;
		
		// The number of list items must be a multiple of 3 (Only when using a grid)
		if(ga_grid_mode)
		{
			var num_extras = 3-(ga_count%3);
			if(num_extras > 2) {
				num_extras = 0;
			}
			
			// Should be between 0 and 2 extras
			for(i=1; i<=num_extras; i++)
			{
				item_position = ga_count+i;
				var dummy_id = int_to_id(item_position, ga_list_prefix);
				
				// Initialise dummy gridanim details
				ga_items[dummy_id] = new Array();
				
				// List Params
				ga_items[dummy_id]['open'] = false;
				ga_items[dummy_id]['fading_out'] = false;
				ga_items[dummy_id]['fading_in'] = false;
				ga_items[dummy_id]['closing_timer'] = null;
				
				// Grid Params
				ga_items[dummy_id]['grid_fading'] = "";
				ga_items[dummy_id]['ga_grid_mode'] = "normal";
				ga_items[dummy_id]['grid_anim_normal'] = false;
				ga_items[dummy_id]['grid_anim_fade'] = false;
				ga_items[dummy_id]['grid_closing_timer'] = null;
				ga_items[dummy_id]['is_dummy'] = true;
				
				// Build the fade and text layer divs for each grid item
				build_grid_item_layers(item_position);
				
				// Default CSS (backup, for IE support)
				$("#"+dummy_id+" .normal").css("opacity", "1");
				$("#"+dummy_id+" .hover").css("opacity", "0");
			}
			
			// Set to true if we will be displaying text in the grid
			ga_grid_text_mode = (document.getElementById(ga_text_id) !== null);
		}
		return true;
	}
	else {
		return false;
	}
}


/**
 * Build the fade and text layers for a grid item
 */
function build_grid_item_layers(item_position)
{
	var grid_block_id = ga_grid_prefix+item_position;
	
	// Ensure we have a valid grid_block_id
	if(document.getElementById(grid_block_id) !== null && $("#"+grid_block_id).hasClass(ga_grid_prefix+"block"))
	{
		// Build the fade layer
		var fade_layer = $("#"+grid_block_id+" .normal:first").clone(true);
		var fade_layer_id = grid_block_id+ga_fade_suffix;
		fade_layer.attr("id", fade_layer_id);
		$(fade_layer).removeClass("normal").addClass("fade");
		
		// Alter the image src from the normal (colour) dir to the fade (bw) dir
		var old_img_src = $(fade_layer).find("img:first").attr("src");
		var new_img_src = old_img_src;
		var img_dir_pos = old_img_src.lastIndexOf(ga_grid_dir_normal);
		if(img_dir_pos != -1) {
			new_img_src = old_img_src.substr(0, img_dir_pos)+ga_grid_dir_fade+old_img_src.substr(img_dir_pos+ga_grid_dir_normal.length);
			
			// Preload this image
			var tmp_image = new Image();
			tmp_image.src = new_img_src;
		}
		$(fade_layer).find("img:first").attr("src", new_img_src);
		fade_layer.appendTo("#"+grid_block_id);
		
		// Build the text layer
		var text_layer = $("#"+grid_block_id+" .normal:first").clone(true);
		var text_layer_id = grid_block_id+ga_text_suffix;
		text_layer.attr("id", text_layer_id);
		$(text_layer).removeClass("normal").addClass("text");
		$(text_layer).html("");
		text_layer.appendTo("#"+grid_block_id);
	}
}


/**
 * Triggered when the mouse goes over a gridanim element.
 * Note, all ID's are in the format: prefixXYZ, where XYZ is an integer.
 * trigger_id	- ID of the triggered element
 * prefix		- ID prefix of the triggered element
 */
function mouse_over_gridanim(trigger_id, prefix)
{
	// Determine if the grid should also be animated
	var animate_grid = false;
	if(prefix == ga_grid_prefix) {
		animate_grid = true;
	}
	
	// Determine trigger position
	var item_position = id_to_int(trigger_id, prefix);
	
	// If this is a grid animation, wait for the mouse to settle first
	if(animate_grid)
	{
		var initial_mouseX = ga_mouseX;
		var initial_mouseY = ga_mouseY;
		ga_hover_trigger_timer = setTimeout(function(){ animate_gridanim_after_mouse_settles(trigger_id, item_position, animate_grid, initial_mouseX, initial_mouseY, ga_hover_trigger_timer); }, ga_hover_trigger_grid_delay);
	}
	
	// Otherwise list animations can load immediately after a small delay
	else
	{
		// Generate a unique request id
		ga_open_req_id = Math.random();
		
		ga_hover_trigger_timer = setTimeout(function(){ animate_gridanim(item_position, animate_grid, ga_open_req_id); }, ga_hover_trigger_list_delay);
	}
}


/**
 * Continually check the mouse position every ga_hover_trigger_grid_delay milliseconds.
 * Only allow the animation to fire if the mouse stays still and over the original trigger node for this duration.
 * This is an attempt to prevent floods of animations from firing when the mouse is kept moving over the list/grid.
 */
function animate_gridanim_after_mouse_settles(trigger_id, item_position, animate_grid, initial_mouseX, initial_mouseY, timer)
{
	// Determine the new mouse position
	var current_mouseX = ga_mouseX;
	var current_mouseY = ga_mouseY;
	
	var trigger_node = document.getElementById(trigger_id);
	var over_trigger_node = false;
	if(trigger_node !== null) {
		over_trigger_node = is_mouseover(current_mouseX, current_mouseY, trigger_node);
	}
	if(over_trigger_node)
	{
		if(current_mouseX > initial_mouseX-ga_shakey_mouse_variance && current_mouseX < initial_mouseX+ga_shakey_mouse_variance && 
		   current_mouseY > initial_mouseY-ga_shakey_mouse_variance && current_mouseY < initial_mouseY+ga_shakey_mouse_variance)
		{
			// The mouse has stayed idle on this item for ga_hover_trigger_grid_delay ms, go go go!
			
			// Generate a unique request id
			ga_open_req_id = Math.random();
			
			// Animate!
			animate_gridanim(item_position, animate_grid, ga_open_req_id);
		}
		else
		{
			// The mouse is still over this item, but it has moved slightly.
			// Update the initial positions and see if the mouse stays still again.
			initial_mouseX = current_mouseX;
			initial_mouseY = current_mouseY;
			timer = setTimeout(function(){ animate_gridanim_after_mouse_settles(trigger_id, item_position, animate_grid, initial_mouseX, initial_mouseY, timer); }, ga_hover_trigger_grid_delay);
		}
	}
}


/**
 * Triggered when the mouse goes out from a gridanim element.
 * Note, all ID's are in the format: prefixXYZ, where XYZ is an integer.
 * trigger_id	- ID of the triggered element
 * prefix		- ID prefix of the triggered element
 * no_delay		- Set to true if we are bypassing the close delay
 */
function mouse_out_gridanim(trigger_id, prefix, no_delay)
{
	// Get the list id for the supplied id
	var list_id = trigger_id;
	if(prefix != ga_list_prefix) {
		list_id = ga_list_prefix + trigger_id.replace(prefix, "");
	}
	
	// Only perform the mouse out animation if the trigger item is open
	// (we can't close something before its open!!!)
	if(ga_items[list_id]['open'] === true)
	{
		// Check if we will be bypassing the closing delay?
		if(typeof(no_delay) != "boolean") {
			no_delay = false;
		}
		
		// Determine if the grid should also be animated
		var animate_grid = false;
		if(prefix == ga_grid_prefix) {
			animate_grid = true;
		}
		
		// Set the appropriate timer
		if(no_delay === false) {
			if(animate_grid) {
				ga_items[list_id]['grid_closing_timer'] = setTimeout(function(){ close_gridanim(list_id, animate_grid); }, ga_grid_close_timeout);
			} else {
				ga_items[list_id]['closing_timer'] = setTimeout(function(){ close_gridanim(list_id, animate_grid); }, ga_list_close_timeout);
			}
		} else {
			// Instant closure
			close_gridanim(list_id, animate_grid);
		}
	}
}


/**
 * Performs animations of the gridanim list and grid
 * item_position	- The numeric position of the gridanim item to be activated
 * animate_grid		- Flag indicating if the grid will be animated (NB: the list is always animated)
 * req_id			- The unique id identifying this request
 */
function animate_gridanim(item_position, animate_grid, req_id)
{
	// 1. Generate ID's
	var target_list_id = int_to_id(item_position, ga_list_prefix);
	var target_grid_id = int_to_id(item_position, ga_grid_prefix);
	var text_source_id = int_to_id(item_position, ga_grid_text_prefix);
	
	// 2. Determine if the mouse is still over this item.
	//    Note: if another request has been received during the delay, we will ignore this request.
	var doanim = false;
	var bad_req = false;
	if(req_id == ga_open_req_id)
	{
		if(animate_grid) {
			var grid_node = document.getElementById(target_grid_id);
			if(grid_node !== null) {
				doanim = is_mouseover(ga_mouseX, ga_mouseY, grid_node);
			}
		} else {
			var list_node = document.getElementById(target_list_id);
			if(list_node !== null) {
				doanim = is_mouseover(ga_mouseX, ga_mouseY, list_node);
			}
		}
	} else {
		bad_req = true;
	}
	
	// Debug
	/*
	var debug_msg = "animate_gridanim request " + target_list_id;
	if(doanim) {
		debug_msg += " GRANTED";
	} else {
		debug_msg += " DENIED! " + bad_req;
	}
	var old = document.getElementById("ga_debug").innerHTML;
	document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
	*/
	
	// 3. Animate?
	if(doanim) {
		if(animate_grid) {
			var text_target_info = calculate_text_position(item_position);
			var text_target_pos = text_target_info['target'];
			animate_ga_grid(target_list_id, target_grid_id, text_source_id, text_target_pos);
		}
		animate_ga_list(target_list_id);
	}
}


/**
 * Performs animations of the gridanim list
 * target_list_id	- The id of the list item to be activated
 */
function animate_ga_list(target_list_id)
{
	// 1. Close any open items (except for target_list_id)
	for(list_node_id in ga_items) {
	if (ga_items.hasOwnProperty(list_node_id)) {
		// Remove highlight from all list nodes except for the target_list_id
		var list_node = document.getElementById(list_node_id);
		if(list_node !== null) {
			if(list_node_id != target_list_id && ga_items[list_node_id]['open'] === true) {
				mouse_out_gridanim(list_node_id, ga_list_prefix);
			}
		}
	}
	}
	
	// Only animate this item if it does not have the ga_default_class
	if($("#"+target_list_id).hasClass(ga_default_class) === false)
	{
		// 2. If target_list_id is on a closing timer, clear it.
		if(ga_items[target_list_id]['closing_timer'] !== null) {
			clearTimeout(ga_items[target_list_id]['closing_timer']);
			ga_items[target_list_id]['closing_timer'] = null;
		}
		
		// 3. If target_list_id is fading out, stop it and fade 'hover' layer in again.
		if(ga_items[target_list_id]['fading_out'] === true)
		{
			// Debug
			/*
			var debug_msg = "reopening " + target_list_id;
			var old = document.getElementById("ga_debug").innerHTML;
			document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
			*/
			
			// Stop the hover layer fading out, and fade it in.
			ga_items[target_list_id]['open'] = true;
			ga_items[target_list_id]['fading_in'] = true;
			ga_items[target_list_id]['fading_out'] = false;
			$("#"+target_list_id+" .hover").stop();
			$("#"+target_list_id+" .hover").animate({opacity:1}, ga_fade_time, function() {
				// Fade complete
				ga_items[target_list_id]['fading_in'] = false;
			});
		}
		
		// 4. If target_list_id is not open fade 'hover' layer in.
		if(ga_items[target_list_id]['open'] !== true)
		{
			// Debug
			/*
			debug_msg = "fresh opening " + target_list_id;
			old = document.getElementById("ga_debug").innerHTML;
			document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
			*/
			
			ga_items[target_list_id]['open'] = true;
			ga_items[target_list_id]['fading_in'] = true;
			$("#"+target_list_id+" .hover").animate({opacity:1}, ga_fade_time, function() {
				// Fade complete
				ga_items[target_list_id]['fading_in'] = false;
				/*
				debug_msg = "opened " + target_list_id;
				old = document.getElementById("ga_debug").innerHTML;
				document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
				*/
			});
		}
	}
}


/**
 * Performs animations of the gridanim grid
 * target_list_id	- The id of the list item associated with the activated grid item
 * target_grid_id	- The id of the grid item to be activated
 * text_source_id	- The id of the text source object, from where the grid text is obtained
 * text_target_pos	- The numeric position of the text target position (NB: this should be to the left/right of the target_grid_id)
 */
function animate_ga_grid(target_list_id, target_grid_id, text_source_id, text_target_pos)
{
	// Debug
	/*
	debug_msg = "fresh opening " + target_list_id;
	old = document.getElementById("ga_debug").innerHTML;
	document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
	*/
	
	// 1. Determine the grid text target position (optional)
	var text_target_grid_id = "";
	var text_target_list_id = "";
	var text_target_id = "";
	if(ga_grid_text_mode) {
		text_target_grid_id = int_to_id(text_target_pos, ga_grid_prefix);
		text_target_list_id = int_to_id(text_target_pos, ga_list_prefix);
		text_target_id = text_target_grid_id + ga_text_suffix;
	}
	
	// 2. Set all blocks to fade except for target and text target
	for(list_node_id in ga_items) {
	if (ga_items.hasOwnProperty(list_node_id)) {
		var grid_node_id = list_node_id.replace(ga_list_prefix, ga_grid_prefix);
		
		if(list_node_id != target_list_id && list_node_id != text_target_list_id) {
			setGridBlockMode("fade", list_node_id, grid_node_id);
		}
		
		// Clear all grid closing timers
		if(ga_items[list_node_id]['grid_closing_timer'] !== null) {
			clearTimeout(ga_items[list_node_id]['grid_closing_timer']);
			ga_items[list_node_id]['grid_closing_timer'] = null;
		}
	}
	}
	
	// 3. Fade the target block from old mode to normal
	setGridBlockMode("normal", target_list_id, target_grid_id);
	
	// 4. Fade the text target block from old mode to text (optional)
	if(ga_grid_text_mode)
	{
		var text_source_node = document.getElementById(text_source_id);
		var text_target_node = document.getElementById(text_target_id);
		
		// Ensure we can find the text target and source
		if(text_source_node !== null && text_target_node !== null)
		{
			// Add the text to the grid
			text_target_node.innerHTML = text_source_node.innerHTML;
			
			// Animate the text block!
			setGridBlockMode("text", text_target_list_id, text_target_grid_id);
		}
	}
}


/**
 * Fully clears any highlights from the grid
 */
function full_delayed_mouse_out_gridanim() {
	for(list_node_id in ga_items) {
	if (ga_items.hasOwnProperty(list_node_id)) {
		if(ga_items[list_node_id]['open'] === true) {
			var grid_node_id = list_node_id.replace(ga_list_prefix, ga_grid_prefix);
			
			// Debug
			/*
			debug_msg = "full clear of " + list_node_id;
			old = document.getElementById("ga_debug").innerHTML;
			document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
			*/
			
			mouse_out_gridanim(grid_node_id, ga_grid_prefix, true);
		}
	}
	}
}


/**
 * Performs closing animations of the gridanim list and grid
 * id			- The list id of the gridanim item to be closed
 * animate_grid	- Flag indicating if the grid will be animated (NB: the list is always animated)
 */
function close_gridanim(id, animate_grid)
{
	// 1. Generate ID's
	var target_list_id = id;
	var target_grid_id = id.replace(ga_list_prefix, ga_grid_prefix);
	
	// 2. Determine if the mouse is still over this item.
	var doanim = false;
	var animate_list = true;
	if(animate_grid) {
		var grid_node = document.getElementById(target_grid_id);
		if(grid_node !== null) {
			doanim = (is_mouseover(ga_mouseX, ga_mouseY, grid_node) === false);
		}
		
		// If the mouse is not over the list, animate the list
		animate_list = (ga_mouse_over_list === false);
	} else {
		var list_node = document.getElementById(target_list_id);
		if(list_node !== null) {
			doanim = (is_mouseover(ga_mouseX, ga_mouseY, list_node) === false);
		}
	}
	
	// Debug
	/*
	var debug_msg = "close request " + target_list_id;
	if(doanim) {
		debug_msg += " GRANTED";
	} else {
		debug_msg += " DENIED!";
	}
	debug_msg += " " + ga_grid_mode + " " + animate_grid;
	var old = document.getElementById("ga_debug").innerHTML;
	document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
	*/
	
	// 3. Animate?
	if(doanim) {
		if(ga_grid_mode && animate_grid) {
			close_ga_grid(target_list_id);
			
			// Debug
			/*
			var debug_msg = "cg " + target_list_id;
			var old = document.getElementById("ga_debug").innerHTML;
			document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
			*/
		}
		// Note, check before closing the list, as it may have been closed on a previous call.
		if(ga_items[id]['open'] === true && animate_list) {
			close_ga_list(target_list_id);
			
			// Debug
			/*
			var debug_msg = "cl " + target_list_id;
			var old = document.getElementById("ga_debug").innerHTML;
			document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
			*/
		}
	}
}


/**
 * Performs closing animations of the gridanim list
 * target_list_id	- The id of the list item to be closed
 */
function close_ga_list(target_list_id) {
	// Is the gridanim item fading in?
	if(ga_items[target_list_id]['fading_in'] === true) {
		// Debug
		/*
		var debug_msg = "reclosing " + target_list_id;
		var old = document.getElementById("ga_debug").innerHTML;
		document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
		*/
		
		// Stop the hover layer fading in, and fade it out.
		ga_items[target_list_id]['open'] = false;
		ga_items[target_list_id]['fading_in'] = false;
		ga_items[target_list_id]['fading_out'] = true;
		$("#"+target_list_id+" .hover").stop();
		$("#"+target_list_id+" .hover").animate({opacity:0}, ga_fade_time, function() {
			// Fade complete
			ga_items[target_list_id]['fading_out'] = false;
			
			// Debug
			/*
			var debug_msg = "reclosed "+target_list_id+" slowly...";
			var old = document.getElementById("ga_debug").innerHTML;
			document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
			*/
		});
	}
	
	// Else, normal operation:
	else {
		// Debug
		/*
		var debug_msg = "close "+target_list_id+" slowly...";
		var old = document.getElementById("ga_debug").innerHTML;
		document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
		*/
		
		// Fade the hover layer out.
		ga_items[target_list_id]['open'] = false;
		ga_items[target_list_id]['fading_in'] = false;
		ga_items[target_list_id]['fading_out'] = true;
		
		$("#"+target_list_id+" .hover").animate({opacity:0}, ga_fade_time, function() {
			// Debug
			/*
			debug_msg = "closed "+target_list_id+" slowly";
			old = document.getElementById("ga_debug").innerHTML;
			document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
			*/
			
			// Fade complete
			ga_items[target_list_id]['fading_out'] = false;
		});
	}
}


/**
 * Performs closing animations of the gridanim grid
 * target_list_id	- The id of the list item associated with the closing grid item
 */
function close_ga_grid(target_list_id) {
	// Debug
	/*
	var debug_msg = "closing " + target_list_id;
	var old = document.getElementById("ga_debug").innerHTML;
	document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
	*/
	
	// 1. Safety check - do not close the grid if the mouse is over the ga_grid_wrapper_id
	var grid_wrapper_node = document.getElementById(ga_grid_wrapper_id);
	var target_grid_id = target_list_id.replace(ga_list_prefix, ga_grid_prefix);
	var over_grid = false;
	if(grid_wrapper_node !== null) {
		over_grid = is_mouseover(ga_mouseX, ga_mouseY, grid_wrapper_node);
	}
	
	// 2. Remember to take account for dummy blocks!
	if(over_grid === true)
	{
		for(list_node_id in ga_items) {
		if (ga_items.hasOwnProperty(list_node_id)) {
			if(ga_items[list_node_id]['is_dummy'] === true)
			{
				// Check if the mouse is over any dummy blocks
				var dummy_grid_id = list_node_id.replace(ga_list_prefix, ga_grid_prefix);
				var dummy_node = document.getElementById(dummy_grid_id);
				if(dummy_node !== null) {
					if(is_mouseover(ga_mouseX, ga_mouseY, dummy_node))
					{
						// The mouse is over a dummy block, make sure you close the grid!
						over_grid = false;
						break;
					}
				}
			}
		}
		}
	}
	
	if(over_grid === false)
	{
		// 3. Fade all blocks back to normal
		for(list_node_id in ga_items) {
		if (ga_items.hasOwnProperty(list_node_id)) {
			var grid_node_id = list_node_id.replace(ga_list_prefix, ga_grid_prefix);
			setGridBlockMode("normal", list_node_id, grid_node_id);
		}
		}
	}
}


/**
 * Changes the mode of a grid block element
 * newmode			- The new mode to change the grid element to (NB: should be one of 'fade', 'text', and 'normal')
 * list_block_id	- The id of the list element related to the grid block
 * grid_block_id	- The id of the grid block being changed
 */
function setGridBlockMode(newmode, list_block_id, grid_block_id)
{
	// 1. Determine the old mode
	var oldmode = ga_items[list_block_id]['ga_grid_mode'];
	
	// Are we currently fading?
	if(ga_items[list_block_id]['grid_fading'] !== "" && newmode == oldmode) {
		// Debug
		/*
		debug_msg = "was fading: "+list_block_id+" from: "+ga_items[list_block_id]['ga_grid_mode']+" -&gt; "+ga_items[list_block_id]['grid_fading'];
		old = document.getElementById("ga_debug").innerHTML;
		document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
		*/
		
		oldmode = ga_items[list_block_id]['grid_fading'];
		ga_items[list_block_id]['grid_fading'] = "";
	}
	
	// 2. Ensure oldmode and newmode is one of: normal, fade, or text
	if(oldmode != "fade" && oldmode != "text") {
		oldmode = "normal";
	}
	if(newmode != "fade" && newmode != "text") {
		newmode = "normal";
	}
	
	// Debug
	/*
	debug_msg = "setGridBlockMode: "+list_block_id+" from: "+oldmode+" -&gt; "+newmode;
	old = document.getElementById("ga_debug").innerHTML;
	document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
	*/
	
	// 3. Initialise CSS and parameters for the animation
	var anim_layer = "";
	var anim_opacity = "";
	switch(oldmode)
	{
	case "normal":
		if(newmode == "fade")
		{
			// Is the normal layer animating?
			if(ga_items[list_block_id]['grid_anim_normal'] === false) {
				$("#"+grid_block_id+" .normal").css("opacity", "1");
			}
			
			// Is the fade layer animating?
			if(ga_items[list_block_id]['grid_anim_fade'] === false) {
				$("#"+grid_block_id+" .fade").css("opacity", "1");
			}
			
			// Set normal opacity to 0
			anim_layer = "normal";
			anim_opacity = 0;
		}
		else if(newmode == "text")
		{
			// Is the normal layer animating?
			if(ga_items[list_block_id]['grid_anim_normal'] === false) {
				$("#"+grid_block_id+" .normal").css("opacity", "1");
			}
			
			// Is the fade layer animating?
			if(ga_items[list_block_id]['grid_anim_fade'] === false) {
				$("#"+grid_block_id+" .fade").css("opacity", "0");
			}
			
			// Set normal opacity to 0
			anim_layer = "normal";
			anim_opacity = 0;
		}
		else if(newmode == "normal")
		{
			// Ensure the layers are set correctly for normal mode (backup if a normal->normal request is received)
			$("#"+grid_block_id+" .normal").css("opacity", "1");
			$("#"+grid_block_id+" .fade").css("opacity", "1");
			$("#"+grid_block_id+" .text").css("opacity", "1");
		}
		break;
	
	case "fade":
		if(newmode == "normal")
		{
			// Is the normal layer animating?
			if(ga_items[list_block_id]['grid_anim_normal'] === false) {
				$("#"+grid_block_id+" .normal").css("opacity", "0");
			}
			
			// Is the fade layer animating?
			if(ga_items[list_block_id]['grid_anim_fade'] === false) {
				$("#"+grid_block_id+" .fade").css("opacity", "1");
			}
			
			// Set normal opacity to 1
			anim_layer = "normal";
			anim_opacity = 1;
		}
		else if(newmode == "text")
		{
			// Is the normal layer animating?
			if(ga_items[list_block_id]['grid_anim_normal'] === false) {
				$("#"+grid_block_id+" .normal").css("opacity", "0");
			}
			
			// Is the fade layer animating?
			if(ga_items[list_block_id]['grid_anim_fade'] === false) {
				$("#"+grid_block_id+" .fade").css("opacity", "1");
			}
			
			// Set fade opacity to 0
			anim_layer = "fade";
			anim_opacity = 0;
		}
		else if(newmode == "fade")
		{
			// Ensure the layers are set correctly for fade mode (backup if a fade->fade request is received)
			$("#"+grid_block_id+" .normal").css("opacity", "0");
			$("#"+grid_block_id+" .fade").css("opacity", "1");
			$("#"+grid_block_id+" .text").css("opacity", "1");
		}
		
		break;
	
	case "text":
		if(newmode == "normal")
		{
			// Is the normal layer animating?
			if(ga_items[list_block_id]['grid_anim_normal'] === false) {
				$("#"+grid_block_id+" .normal").css("opacity", "0");
				/*
				debug_msg = "setGridBlockMode: "+list_block_id+" setting normal to 0 opac!";
				old = document.getElementById("ga_debug").innerHTML;
				document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
				*/
			}
			
			// Is the fade layer animating?
			if(ga_items[list_block_id]['grid_anim_fade'] === false) {
				$("#"+grid_block_id+" .fade").css("opacity", "0");
				/*
				debug_msg = "setGridBlockMode: "+list_block_id+" setting fade to 0 opac!";
				old = document.getElementById("ga_debug").innerHTML;
				document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
				*/
			}
			
			// Set normal opacity to 1
			anim_layer = "normal";
			anim_opacity = 1;
		}
		else if(newmode == "fade")
		{	
			// Is the normal layer animating?
			if(ga_items[list_block_id]['grid_anim_normal'] === false) {
				$("#"+grid_block_id+" .normal").css("opacity", "0");
			}
			
			// Is the fade layer animating?
			if(ga_items[list_block_id]['grid_anim_fade'] === false) {
				$("#"+grid_block_id+" .fade").css("opacity", "0");
			}
			
			// Fade opacity to 1
			anim_layer = "fade";
			anim_opacity = 1;
		}
		else if(newmode == "text")
		{
			// Ensure the layers are set correctly for text mode (backup if a text->text request is received)
			$("#"+grid_block_id+" .normal").css("opacity", "0");
			$("#"+grid_block_id+" .fade").css("opacity", "0");
			$("#"+grid_block_id+" .text").css("opacity", "1");
		}
		break;
	}
	
	// 4. Animate!
	if(anim_layer !== "" || anim_opacity !== "")
	{
		// Set flags
		ga_items[list_block_id]['grid_fading'] = newmode;
		
		// Keep track of which layers are animating
		if(anim_layer == "normal") {
			ga_items[list_block_id]['grid_anim_normal'] = true;
		} else if(anim_layer == "fade") {
			ga_items[list_block_id]['grid_anim_fade'] = true;
		}
		
		// Perform animation
		$("#"+grid_block_id+" ."+anim_layer).animate({opacity:anim_opacity}, ga_fade_time, function()
		{
			// Set flags
			ga_items[list_block_id]['grid_fading'] = "";
			ga_items[list_block_id]['ga_grid_mode'] = newmode;
			
			// Keep track of which layers are animating
			if(anim_layer == "normal") {
				ga_items[list_block_id]['grid_anim_normal'] = false;
			} else if(anim_layer == "fade") {
				ga_items[list_block_id]['grid_anim_fade'] = false;
			}
		});
	} else {
		// Debug
		/*
		debug_msg = "setGridBlockMode: "+list_block_id+" from: "+oldmode+" -&gt; "+newmode+" (BAK!)";
		old = document.getElementById("ga_debug").innerHTML;
		document.getElementById("ga_debug").innerHTML = debug_msg + "<br />\n" + old;
		*/
	}
}


/**
 * Generates an element integer from its id and prefix.
 * e.g. id_to_int(portfolio_list_1, portfolio_list_) would return the integer, 1
 * id		- The id to be parsed
 * prefix	- The prefix of the id
 */
function id_to_int(id, prefix) {
	return parseInt(id.substr(prefix.length), 10);
}


/**
 * Generates an element id from its integer and prefix.
 * e.g. id_to_int(1, portfolio_list_) would return the string, portfolio_list_1
 * int		- The integer to be used
 * prefix	- The prefix of the id
 */
function int_to_id(int, prefix) {
	return prefix + String(int);
}


/**
 * Generates an array containing the position details for the case text.
 * The position returned will either be to the left/right of the source position in the grid.
 * This assumes that grid elements snake through the grid in an S shape.
 * The return array will contain the 'target' number of the text, and also it's 'col' and 'row' in the grid.
 * source_position	- The source position of the activated grid element.
 */
function calculate_text_position(source_position) {
	// Defaults
	var rhs_grid = (ga_grid_position == "right");
	var colNumber = 0;
	var rowNumber = 0;
	var cols = 3;
	var target = 0;
	
	// Calculate column number
	var col_remainder = source_position%(cols*2);
	if(ga_grid_order == "snake")
	{
		// Snake ordering
		if(col_remainder == 1 || col_remainder === 0) {
			colNumber = 1;
		}
		else if(col_remainder == 2 || col_remainder == 5) {
			colNumber = 2;
		}
		else if(col_remainder == 3 || col_remainder == 4) {
			colNumber = 3;
		}
	}
	else {
		// LTR ordering (default)
		if(col_remainder == 1 || col_remainder == 4) {
			colNumber = 1;
		}
		else if(col_remainder == 2 || col_remainder == 5) {
			colNumber = 2;
		}
		else if(col_remainder == 3 || col_remainder === 0) {
			colNumber = 3;
		}
	}
	
	// Calculate row number
	rowNumber = Math.ceil(source_position / 3);
	
	// Left or middle columns, display text to the right
	if(colNumber == 1 || colNumber == 2) {
		if(ga_grid_order == "snake")
		{
			// Snake mode: Targets vary
			if(rowNumber%2 === 0) {
				// Even row, target is position-1
				target = source_position-1;
			}
			else {
				// Odd row, target is position+1
				target = source_position+1;
			}
		}
		else {
			// LTR mode: Targets are always position+1
			target = source_position+1;
		}
	}
	
	// Right column displays text to the left
	if(colNumber == 3) {
		if(ga_grid_order == "snake")
		{
			// Snake mode: Targets vary
			if(rowNumber%2 === 0) {
				// Even row, target is position+1
				target = source_position+1;
			}
			else {
				// Odd row, target is position-1
				target = source_position-1;
			}
		}
		else {
			// LTR mode: Targets are always position-1
			target = source_position-1;
		}
	}
	
	// Build results array
	var result = new Array();
	result['target'] = target;
	result['col'] = colNumber;
	result['row'] = rowNumber;
	return result;
}


/**
 * Checks the mouse position and fires any open/closing functions based on where the mouse is currently.
 * This function takes into account any scrolling which may have taken place since the last mousemove event.
 * NB: This doesnt work reliably in IE6
 */
function check_mouse_position() {
	if(!ie6)
	{
		// 1. Determine how much the window has scrolled by since the mouse last moved.
		var scrollChangeX = $(window).scrollLeft() - ga_scrollX;
		var scrollChangeY = $(window).scrollTop() - ga_scrollY;
		
		// 2. Was the mouse over the ga_list_wrapper_id or ga_grid_wrapper_id
		var list_wrapper_node = document.getElementById(ga_list_wrapper_id);
		var over_list = false;
		if(list_wrapper_node !== null) {
			over_list = is_mouseover(ga_mouseX, ga_mouseY, list_wrapper_node);
		}
		var grid_wrapper_node = document.getElementById(ga_grid_wrapper_id);
		var over_grid = false;
		if(grid_wrapper_node !== null) {
			over_grid = is_mouseover(ga_mouseX, ga_mouseY, grid_wrapper_node);
		}
		
		// 3. Update the global mouse and scroll variables
		ga_mouseX = ga_mouseX + scrollChangeX;
		ga_mouseY = ga_mouseY + scrollChangeY;
		ga_scrollX = $(window).scrollLeft();
		ga_scrollY = $(window).scrollTop();
		
		// 4. If the mouse was over the list/grid, check the current mouse position against all ga_items.
		//    We will fire any open/closing events to reflect the change in mouse position
		if(over_list || over_grid)
		{
			for(list_node_id in ga_items) {
			if (ga_items.hasOwnProperty(list_node_id)) {
			
				// Is the mouse over any list elements?
				if(over_list)
				{
					var list_node = document.getElementById(list_node_id);
					if(list_node !== null) {
						var mouse_over_list = is_mouseover(ga_mouseX, ga_mouseY, list_node);
						
						// Has the mouse left an open item?
						if(ga_items[list_node_id]["open"] === true && mouse_over_list === false) {
							mouse_out_gridanim(list_node_id, ga_list_prefix);
							ga_mouse_over_list = false;
						}
						
						// Has the mouse entered a closed item?
						else if(ga_items[list_node_id]["open"] === false && mouse_over_list === true) {
							mouse_over_gridanim(list_node_id, ga_list_prefix);
							ga_mouse_over_list = true;
						}
					}
				}
				
				// Is the mouse over any grid elements?
				else if(over_grid)
				{
					var grid_node_id = list_node_id.replace(ga_list_prefix, ga_grid_prefix);
					var grid_node = document.getElementById(grid_node_id);
					if(grid_node !== null) {
						var mouse_over_grid = is_mouseover(ga_mouseX, ga_mouseY, grid_node);
						
						// Has the mouse left an open item?
						if(ga_items[list_node_id]["open"] === true && mouse_over_grid === false) {
							if(!ie) {
								// IE browsers have trouble closing grid items
								mouse_out_gridanim(grid_node_id, ga_grid_prefix);
							}
						}
						
						// Has the mouse entered a closed item?
						else if(ga_items[list_node_id]["open"] === false && mouse_over_grid === true) {
							mouse_over_gridanim(grid_node_id, ga_grid_prefix);
						}
					}
				}
			}
			}
		}
	}
}

// ---------------------------------------------------------------
// Functions End
// ---------------------------------------------------------------