/***
 * cookie.js
 * A simple cookie library supporting hash trees
 * Requires Joe Tools for merge_objects() and serialize().
 * 
 * var tree = new CookieTree();
 * tree.set( "foo", "bar" );
 * tree.set( "complex", { hello: "there", array: [1,2,3] } );
 * tree.save();
 * 
 * Copyright (c) 2007 Joseph Huckaby.
 */

if (!window.merge_objects || !window.serialize)
	alert("ERROR: cookie.js requires tools.js.");

function CookieTree(args) {
	// class constructor
	if (args) {
		for (var key in args) this[key] = args[key];
	}
	
	if (!this.expires) {
		var now = new Date();
		now.setFullYear( now.getFullYear() + 10 ); // 10 years from now
		this.expires = now.toGMTString();
	}
	
	this.parse();
}

CookieTree.prototype.domain = location.hostname;
CookieTree.prototype.path = location.pathname;

CookieTree.prototype.parse = function() {
	// parse document.cookie into hash tree
	this.tree = {};
	var cookies = document.cookie.split(/\;\s*/);
	for (var idx = 0, len = cookies.length; idx < len; idx++) {
		var cookie_raw = cookies[idx];
		if (cookie_raw.match(/^CookieTree=(.+)$/)) {
			var cookie = null;
			try {
				eval( "cookie = " + RegExp.$1 + ";" );
			}
			catch (e) { cookie = {}; }
			
			this.tree = merge_objects( this.tree, cookie );
			idx = len;
		}
	}
};

CookieTree.prototype.get = function(key) {
	// get tree branch given value (top level)
	return this.tree[key];
};

CookieTree.prototype.set = function(key, value) {
	// set tree branch to given value (top level)
	this.tree[key] = value;
};

CookieTree.prototype.save = function() {
	// serialize tree and save back into document.cookie
	var cookie_raw = 'CookieTree=' + serialize(this.tree);
	
	cookie_raw += '; expires=' + this.expires;
	cookie_raw += '; domain=' + this.domain;
	cookie_raw += '; path=' + this.path;
	
	document.cookie = cookie_raw;
};

CookieTree.prototype.remove = function() {
	// remove cookie from document
	var cookie_raw = 'CookieTree={}';
	
	var now = new Date();
	now.setFullYear( now.getFullYear() - 1 ); // last year
	cookie_raw += '; expires=' + now.toGMTString();
	
	cookie_raw += '; domain=' + this.domain;
	cookie_raw += '; path=' + this.path;
	
	document.cookie = cookie_raw;
};/***
 * absOrb
 * A game designed by Min Chang.
 * Programmed by Joseph Huckaby.
 * Copyright (c) 2008 Gold Cartridge LLC.
 * http://www.goldcartridge.com
 * All rights reserved.
 ***/

// some globals for easy access to our portal and planes
var port, ui_plane, title_plane, sprite_plane, particle_plane;

// orb color names, and the order in which they come out
var orb_colors = ['blue', 'pink', 'green', 'orange'];

// global session contains things like score, level, lives, orb speed, etc.
var session = {
	version: 'v0.1 Beta',
	cookie: new CookieTree(),
	high_score: 0,
	music: true,
	
	levels: [
		{ /* level 0 doesn't exist */ },
		{ // level 1
			gravity: (new Point()).project( 270, 1 ),
			orb_probability: 0.02,
			orb_color_max: 1,
			behavior: 'standard',
			msg: "Collect orbs that match your ring color!"
		},
		{ // level 2
			gravity: (new Point()).project( 270, 1.2 ),
			orb_probability: 0.03,
			orb_color_max: 2,
			msg: "Press &larr; and &rarr; to change your ring color!"
		},
		{ // level 3
			gravity: (new Point()).project( 270, 1.4 ),
			orb_probability: 0.04,
			orb_color_max: 3
		},
		{ // level 4
			gravity: (new Point()).project( 270, 1.7 ),
			orb_probability: 0.05,
			orb_color_max: 4
		},
		{ // level 5
			gravity: (new Point()).project( 270, 2.0 ),
			orb_probability: 0.06
		},
		{ // level 6
			gravity: (new Point()).project( 270, 2.4 ),
			orb_probability: 0.07,
			behavior: 'wind',
			wind_max_degrees: 20,
			gravity_distance: 2.4,
			msg: "Is it getting windy in here?"
		},
		{ // level 7
			gravity: (new Point()).project( 270, 3.0 ),
			orb_probability: 0.08,
			behavior: 'wind',
			wind_max_degrees: 40,
			gravity_distance: 3.0
		},
		{ // level 8
			gravity: (new Point()).project( 90, 3.5 ),
			orb_probability: 0.09,
			behavior: 'reverse',
			msg: "Reverse!"
		},
		{ // level 9
			gravity: (new Point()).project( 270, 4.0 ),
			orb_probability: 0.12,
			behavior: 'standard'
		},
		{ // level 10
			gravity: (new Point()).project( 270, 5.0 ),
			orb_probability: 0.2,
			behavior: 'standard'
		},
		{ // level 11
			gravity: (new Point()).project( 270, 6.0 ),
			orb_probability: 0.3,
			behavior: 'standard'
		},
		{ // level 12
			gravity: (new Point()).project( 270, 7.0 ),
			orb_probability: 0.4,
			behavior: 'standard'
		},
		{ // level 13
			gravity: (new Point()).project( 270, 8.0 ),
			orb_probability: 0.6,
			behavior: 'standard'
		},
		{ // level 14
			gravity: (new Point()).project( 270, 9.0 ),
			orb_probability: 0.8,
			behavior: 'standard'
		},
		{ // level 15
			gravity: (new Point()).project( 270, 10.0 ),
			orb_probability: 0.9,
			behavior: 'standard'
		},
		{ // level 16 (kill screen)
			gravity: (new Point()).project( 270, 10.0 ),
			orb_probability: 1.0, // thick as possible
			behavior: 'standard'
		}
	]
};

// browser detection
var ua = navigator.userAgent;
var ie = !!ua.match(/MSIE/);
var chrome = !!ua.match(/Chrome/);

// IE and chrome both cannot deal with opacity != 1.0 involving alpha PNG images
var opacity_transitions = (!ie && !chrome);

// decode challenge URL
if (query.a) query.name = rot13(query.a);
if (query.b) query.beat = parseInt( rot13(query.b).substring(1) );
if (query.c) query.email = rot13(query.c);

// first time cookie
if (!session.cookie.get('absorb')) {
	session.cookie.set('absorb', 1);
	session.cookie.set('sound', 1);
	session.cookie.set('music', 1);
	session.cookie.save();
}
else {
	// load things from cookie
	session.high_score = session.cookie.get('high_score');
	if (!session.high_score) session.high_score = 0;
	
	session.last_username = session.cookie.get('username');
	if (!session.last_username) session.last_username = '';
}

gGame.setHandler( 'onInit', 'absorb_init' );

function absorb_init() {
	// IE needs a little more time because it sucks
	setTimeout( "game_setup()", 100 );
}

function absorb_set_music(enabled) {
	// turn music on/off
	if (gAudio.enabled) {
		if (gGame.inGame) {
			if (gGame.state == 'title') gAudio.getTrack('music_title').stop();
			else if (gGame.state == 'game') gAudio.getTrack('music_level').stop();
		}
		
		if (enabled) {
			// enable music
			session.music = true;
			if (gGame.inGame) {
				if (gGame.state == 'title') gAudio.playSound('music_title');
				else if (gGame.state == 'game') gAudio.playSound('music_level');
			}
			document.getElementById('fe_music').checked = true;
		}
		else {
			// disable music
			session.music = false;
			document.getElementById('fe_music').checked = false;
		}
		
		session.cookie.set('music', enabled ? 1 : 0);
		session.cookie.save();
	} // audio is enabled
}

function absorb_set_sound(enabled) {
	// turn sound on/off
	if (enabled) {
		// enable sound
		gAudio.enabled = true;
		document.getElementById('fe_sound').checked = true;
		document.getElementById('fe_music').disabled = false;
		absorb_set_music( session.music );
	}
	else {
		// disable all sound
		gAudio.quiet();
		gAudio.enabled = false;
		document.getElementById('fe_sound').checked = false;
		document.getElementById('fe_music').disabled = true;
	}
	
	session.cookie.set('sound', enabled ? 1 : 0);
	session.cookie.save();
}
	
function game_setup() {
	// setup game definition
	gGame.setGameDef({
		id: 'absorb',
		preload_images: [
			'buttons/button_blue.png',
			'buttons/button_green.png',
			'buttons/button_highlight.png',
			'buttons/button_orange.png',
			'buttons/button_pink.png',
			'buttons/icon_arrow-left.png',
			'buttons/icon_arrow-right.png',
			'buttons/icon_close.png',
			'buttons/icon_pause.png',
			'buttons/icon_play.png',
			'buttons/small-button_blue.png',
			'buttons/small-button_green.png',
			'buttons/small-button_pink.png',
			'buttons/small-button_orange.png',
			'buttons/small-button_highlight.png',
			'environments/canvas-bkgd_merged.png',
			'frames/curlyframe_with-score_shadow.png',
			'logos/absorb_large.png',
			'logos/absorb_medium.png',
			'logos/absorb_small.png',
			'orbs/orb_blue.png',
			'orbs/orb_green.png',
			'orbs/orb_orange.png',
			'orbs/orb_pink.png',
			'orbs/orb_dark.png',
			'orbs/wildcard.png',
			'panels/panel_bottom.png',
			'panels/panel_top.png',
			'panels/panel_bottom_short.png',
			'panels/panel_top_long.png',
			'ring/ring_highlight_blue.png',
			'ring/ring_highlight_green.png',
			'ring/ring_highlight_orange.png',
			'ring/ring_highlight_pink.png',
			'ring/ring_shadow.png',
			'score/life_blue.png',
			'score/life_green.png',
			'score/life_orange.png',
			'score/life_pink.png',
			'score/life_blue_60.png',
			'score/life_green_60.png',
			'score/life_orange_60.png',
			'score/life_pink_60.png',
			'score/life_blue_30.png',
			'score/life_green_30.png',
			'score/life_orange_30.png',
			'score/life_pink_30.png',
			'score/life_glow_blue.png',
			'score/life_glow_green.png',
			'score/life_glow_orange.png',
			'score/life_glow_pink.png',
			'score/score_bkgd_blue.png',
			'score/score_bkgd_green.png',
			'score/score_bkgd_orange.png',
			'score/score_bkgd_pink.png',
			'subheaders/subheader_blue.png',
			'subheaders/subheader_green.png',
			'subheaders/subheader_orange.png',
			'subheaders/subheader_pink.png',
			'title/instructions_graphic_gameplay_blue.png',
			'title/instructions_graphic_gameplay_green.png',
			'title/instructions_graphic_gameplay_orange.png',
			'title/instructions_graphic_gameplay_pink.png',
			'title/instructions_graphic_hud.png',
			'title/text_input.png',
			'title/gold_cartridge_logo.png',
			'title/effect_engine_logo.png',
			'title/high_score_row_bkgnd.png',
			'title/highscores_yourscore_bkgd.png',
			'title/challenge_input.png'
		],
		preload_audio: {
			bad_orb:     { url: 'bad_orb.mp3',      category: 'sfx' },
			game_over:   { url: 'game_over.mp3',    category: 'sfx' },
			good_orb:    { url: 'good_orb.mp3',     category: 'sfx' },
			level_up:    { url: 'level_up.mp3',     category: 'sfx' },
			music_level: { url: 'music_level.mp3',  category: 'music', loop: 1 },
			music_title: { url: 'music_title.mp3',  category: 'music', loop: 1 },
			pause:       { url: 'pause.mp3',        category: 'sfx' },
			start_game:  { url: 'start_game.mp3',   category: 'sfx' },
			switch_color:{ url: 'switch_color.mp3', category: 'sfx' },
			explosion:   { url: 'explosion.mp3',    category: 'sfx' },
			high_score:  { url: 'high_score.mp3',   category: 'sfx' },
			beat_challenge: { url: 'beat_challenge.mp3', category: 'sfx' }
		}
	});
	
	// frame rate settings
	gGame.setTargetFPS( 30 );
	gGame.setSkipFrames( false );

	// create portal
	port = new Portal('d_game');
	port.setSize( 350, 600 );
	port.setVirtualSize( 350, 600 );
	port.setZoomLevel( 1 );
	gGame.attach(port);
	
	// user interface plane
	ui_plane = new SpritePlane('ui');
	ui_plane.zIndex = 1;
	port.attach(ui_plane);

	// sprite plane
	sprite_plane = new SpritePlane('sprites');
	sprite_plane.zIndex = 10;
	sprite_plane.setMinSpriteSize( 32 );
	sprite_plane.setOffscreenDistance( 0.0 );
	port.attach(sprite_plane);
	
	// particle plane
	// (for objects that don't have to hit each other)
	particle_plane = new SpritePlane('particles');
	particle_plane.zIndex = 20;
	particle_plane.setOffscreenDistance( 0.0 );
	port.attach(particle_plane);
	
	// connect game planes together, just so 
	// we don't have to use globals everywhere
	sprite_plane.particle_plane = particle_plane;
	particle_plane.sprite_plane = sprite_plane;
	
	// plane for title screen elements
	title_plane = new SpritePlane('title');
	title_plane.zIndex = 40;
	port.attach( title_plane );

	// set keydown and mousedown handlers
	gGame.setHandler( 'onKeyDown', game_key_down );
	gGame.setHandler( 'onMouseDown', game_mouse_down );
	
	// set mouse move handler for moving ring
	gGame.setHandler( 'onMouseMove', game_mouse_move );
	
	// set mouse button handler on portal
	port.setHandler( 'onMouseDown', port_mouse_down );
	
	// custom handler for checking if high score was achieved
	gGame.setHandler( 'onCheckHighScore', check_high_score );
	
	// define states for game modes, and the handler functions
	gGame.setStateHandler('title', game_title_logic );
	gGame.setStateHandler('game', game_logic );
	gGame.setStateHandler('death', death_logic );
	
	// load common game code and assets
	gGame.load( 'game_core_loaded' );
}

function game_core_loaded() {
	// game is completely loaded, run title screen
	
	// audio settings from cookie
	absorb_set_music( session.cookie.get('music') );
	absorb_set_sound( session.cookie.get('sound') );
	
	if (!session.scores) load_high_scores();
	game_title_screen();
	gGame.run();
	
	// show audio/music controls
	document.getElementById('d_audio_controls').style.display = '';
}

function game_title_screen(page_name) {
	// show title screen
	if (!page_name) page_name = 'main';
	port.style.backgroundColor = '#000';
	// el('d_info').innerHTML = '';
	
	// clear all sprites from all planes
	// (in case we're exiting from a game in progress)
	ui_plane.deleteAll();
	sprite_plane.deleteAll();
	particle_plane.deleteAll();
	title_plane.deleteAll();
	
	// quiet all music tracks
	gAudio.getTrack('music_title').stop();
	gAudio.getTrack('music_level').stop();
	
	// background
	ui_plane.createSprite({
		id: 'bkgnd',
		url: 'environments/canvas-bkgd_merged.png',
		x: 0,
		y: 0,
		zIndex: 1
	});
	
	// two panels
	ui_plane.createSprite({
		id: 'panel_top',
		url: 'panels/panel_top.png',
		x: 0,
		y: (page_name == 'main') ? 0 : -127,
		zIndex: 30
	});
	ui_plane.createSprite({
		id: 'panel_bottom',
		url: 'panels/panel_bottom.png',
		x: 0,
		y: (page_name == 'main') ? 230 : 110,
		zIndex: 30
	});
	
	// absorb logo
	ui_plane.createSprite({
		id: 'logo',
		url: 'logos/absorb_large.png',
		x: 94,
		y: (page_name == 'main') ? 110 : 34,
		zIndex: 31
	});
	
	// set state for title
	gGame.setState('title');
	
	// start transparent, will fade in
	if (opacity_transitions) title_plane.setOpacity(0.0);
	title_page_enter( page_name );
	
	// high score
	// TODO: move this to the high scores screen
	/* ui_plane.createSprite({
		type: CustomSprite,
		width: session.score ? 175 : 350,
		height: 30,
		x: 0,
		y: 540,
		zIndex: 32,
		html: '<div class="hud_label" style="text-align:center">High Score</div>' + 
			'<div class="hud_text" style="text-align:center">' + pad_int(session.high_score, 6) + '</div>'
	});
	if (session.score) {
		// also show last played game score, if applicable
		ui_plane.createSprite({
			type: CustomSprite,
			width: 175,
			height: 30,
			x: 175,
			y: 540,
			zIndex: 32,
			html: '<div class="hud_label" style="text-align:center">Last Game</div>' + 
				'<div class="hud_text" style="text-align:center">' + pad_int(session.score, 6) + '</div>'
		});
	} */
	
	// standard gravity for title
	session.gravity = (new Point()).project( 270, 1 );
	
	// orbs
	for (var idx = 0; idx < 30; idx++) {
		sprite_plane.createSprite({
			type: Orb,
			x: -8 + Math.floor( Math.random() * 317 ),
			y: -50 + Math.floor( Math.random() * 650 ),
			color: rand_array(orb_colors),
			zIndex: 10
		});
	}
	
	// title music
	if (session.music) {
		gAudio.getTrack('music_title').rewind();
		gAudio.playSound( 'music_title' );
	}
}

function game_title_logic() {
	// title screen idle logic, create falling background orbs
	// this is called every logic frame (30 per sec)
	
	if (probably(0.05)) {
		sprite_plane.createSprite({
			type: Orb,
			x: -8 + Math.floor( Math.random() * 317 ),
			y: -50,
			color: rand_array(orb_colors),
			zIndex: 10
		});
	}
}

function title_goto_page(name) {
	// start transition to new title page
	if (session.title_page == 'transition') return; // don't allow during transition
	
	gAudio.playSound('good_orb');
	
	session.title_page = 'transition';
	session.title_dest_page = name;
	
	var common_tween_args = {
		duration: 30,
		mode: 'EaseInOut',
		algorithm: 'Quadratic'
	};
	
	ui_plane.findSprite({ id: 'logo' }).tween(merge_objects(common_tween_args, {
		properties: {
			y: (name == 'main') ? 110 : 34
		},
		onTweenComplete: my_title_tween_complete // only one needs this
	}));
	
	ui_plane.findSprite({ id: 'panel_top' }).tween(merge_objects(common_tween_args, {
		properties: {
			y: (name == 'main') ? 0 : -127
		}
	}));
	
	ui_plane.findSprite({ id: 'panel_bottom' }).tween(merge_objects(common_tween_args, {
		properties: {
			y: (name == 'main') ? 230 : 110
		}
	}));
	
	// fade current title page elements out quickly, then delete them
	if (opacity_transitions) {
		title_plane.tween({
			duration: 15,
			mode: 'EaseOut',
			algorithm: 'Quadratic',
			properties: { opacity: 0.0 },
			onTweenComplete: function() { title_plane.deleteAll(); }
		});
	}
	else {
		title_plane.deleteAll();
	}
}

function my_title_tween_complete(tween) {
	// transition complete, activate new page
	title_page_enter( session.title_dest_page );
}

function title_page_enter(name) {
	// enter title page
	session.title_page = name;
	
	// call function to generate title elements
	window['create_title_page_'+name]();
	
	// fade elements back in
	if (opacity_transitions) {
		title_plane.tween({
			duration: 15,
			mode: 'EaseInOut',
			algorithm: 'Quadratic',
			properties: { opacity: 1.0 }
		});
	}
}

function create_title_page_main() {
	// Main Page
	
	title_plane.createSprite({
		id: 'button_start',
		type: TextButton,
		color: 'blue',
		text: 'START',
		x: 90,
		y: 300,
		onMouseUp: function() {
			if (this.hover) start_new_game();
			return true;
		}
	});
	
	title_plane.createSprite({
		id: 'button_instructions',
		type: TextButton,
		color: 'pink',
		text: 'INSTRUCTIONS',
		x: 90,
		y: 340,
		onMouseUp: function() {
			if (this.hover) {
				title_goto_page('instructions');
			}
			return true;
		}
	});
	
	title_plane.createSprite({
		id: 'button_high_scores',
		type: TextButton,
		color: 'green',
		text: 'HIGH SCORES',
		x: 90,
		y: 380,
		onMouseUp: function() {
			if (this.hover) {
				title_goto_page('high_scores');
			}
			return true;
		}
	});
	
	title_plane.createSprite({
		id: 'button_credits',
		type: TextButton,
		color: 'orange',
		text: 'CREDITS',
		x: 90,
		y: 420,
		onMouseUp: function() {
			if (this.hover) {
				title_goto_page('credits');
			}
			return true;
		}
	});
	
	if (query.beat) {
		// score to beat
		title_plane.createSprite({
			type: CustomSprite,
			width: 350,
			height: 60,
			x: 0,
			y: 500,
			zIndex: 32,
			html: '<div class="hud_label" style="text-align:center">Challenge Score From ' + unescape(query.name) + ':</div>' + 
				'<div class="hud_text" style="text-align:center">' + pad_int(query.beat, 6) + '</div>'
		});
	}
	
	title_plane.createSprite({
		type: CustomSprite,
		width: 330,
		height: 13,
		x: 10,
		y: 585,
		zIndex: 32,
		html: '<span class="copyright" style="float:left">&copy; 2008 GoldCartridge LLC</span>' + 
			'<span class="copyright" style="float:right">'+session.version+'</span>' + '<br clear="all"/>'
	});
}

function create_title_page_instructions() {
	// Instructions Page
	
	title_plane.createSprite({
		type: SubHeader,
		color: 'pink',
		text: 'INSTRUCTIONS',
		x: 14,
		y: 143
	});
	
	title_plane.createSprite({
		type: IconButton,
		icon: 'arrow-left',
		color: 'pink',
		x: 4,
		y: 130,
		onMouseUp: function() {
			if (this.hover) title_goto_page('main');
		}
	});
	
	title_plane.createSprite({
		type: Slideshow,
		width: 75,
		height: 130,
		x: 60,
		y: 195,
		images: [
			'title/instructions_graphic_gameplay_blue.png',
			'title/instructions_graphic_gameplay_green.png',
			'title/instructions_graphic_gameplay_orange.png',
			'title/instructions_graphic_gameplay_pink.png'
		],
		delay: 90
	});
	
	title_plane.createSprite({
		type: CustomSprite,
		x: 140,
		y: 202,
		width: 160,
		height: 130,
		className: 'title_text',
		html: 'The basic point of absOrb is to collect as many orbs as possible.<br/><br/>' + 
			'You control a ring of color to collect the orbs.  Simply <b>move your ' + 
			'mouse to where you want the ring to go.</b>'
	});
	
	title_plane.createSprite({
		type: CustomSprite,
		x: 68,
		y: 330,
		width: 240,
		height: 140,
		className: 'title_text',
		html: 'As the game progresses, different colored orbs will be released (you\'ll know ' + 
			'because the screen will flash).  To toggle the ring through the colors you have ' + 
			'available, simply <b>press the left or right arrow keys</b> on your keyboard.<br/><br/>' + 
			'Flashing orbs give you an extra life, but stay away from the dark orbs!'
			// 'Finally, your dashboard is at the bottom of the screen:'
	});
	
	title_plane.createSprite({
		url: 'title/instructions_graphic_hud.png',
		x: 60,
		y: 480
	});
}

function create_title_page_high_scores() {
	// High Scores Page
	
	title_plane.createSprite({
		type: SubHeader,
		color: 'green',
		text: 'HIGH SCORES',
		x: 14,
		y: 143
	});
	
	title_plane.createSprite({
		type: IconButton,
		icon: 'arrow-left',
		color: 'green',
		x: 4,
		y: 130,
		onMouseUp: function() {
			if (this.hover) title_goto_page('main');
		}
	});
	
	// show current player's local high score
	if (session.high_score) {
		if (!session.last_username) session.last_username = '';
		
		title_plane.createSprite({
			type: CustomSprite,
			x: 31,
			y: 470,
			width: 288,
			height: 24,
			className: 'high_score_row',
			html: '<div class="rank">&nbsp;</div>' + 
				'<div class="username">' + (session.last_username || '&nbsp;') + '</div>' + 
				'<div class="score">' + session.high_score + '</div>' + 
				'<br clear="all"/>'
		}).setBackground('title/highscores_yourscore_bkgd.png');
	
		title_plane.createSprite({
			type: TextButton,
			color: 'green',
			text: 'CHALLENGE...',
			x: 90,
			y: 500,
			onMouseUp: function() {
				if (this.hover) {
					title_goto_page('challenge');
				}
				return true;
			}
		});
	}
	
	// load current high score list
	load_high_scores();
}

function load_high_scores(force, callback) {
	// load high score list
	if (!callback) callback = 'receive_high_scores';
	if (!force && session.scores && session.scores.length) {
		window[ callback ]();
		return;
	}
	
	session.load_high_scores_callback = callback;
	load_script( '/effect/api/view/games/absorb/scores/?format=js&callback=load_high_scores_2' + (force ? ('&random='+Math.random()) : '') );
}

function load_high_scores_2(response) {
	session.scores = [];
	if (response.Data && response.Data.Scores && response.Data.Scores.Score) {
		if (response.Data.Scores.Score.length) session.scores = response.Data.Scores.Score;
		else session.scores = [ response.Data.Scores.Score ];
	}
	
	// fill empty space with scores
	while (session.scores.length < 10) {
		session.scores.push({ score: 0, username: 'AAA' });
	}
	
	window[ session.load_high_scores_callback ]();
}

function receive_high_scores() {
	// render high score list
	
	// make sure we are still on the title screen and on the high scores page before drawing
	if ((gGame.state == 'title') && (session.title_page == 'high_scores')) {
		// just for safely, make sure sprite doesn't exist yet
		if (!title_plane.findSprite({id: 'high_score_list'})) {
			
			var html = '';
			for (var idx = 0, len = session.scores.length; idx < len; idx++) {
				var score = session.scores[idx];
				html += '<div class="high_score_row" style="'+((idx % 2 == 1) ? 'background-image:url('+gImageLoader.getImageURL('title/high_score_row_bkgnd.png')+')' : '')+'">';
				html += '<div class="rank">' + Math.floor(idx+1) + '</div>';
				html += '<div class="username">' + score.username + '</div>';
				html += '<div class="score">' + score.score + '</div>';
				html += '<br clear="all"/>';
				html += '</div>';
			}
			
			var sprite = title_plane.createSprite({
				type: CustomSprite,
				id: 'high_score_list',
				x: 31,
				y: 200,
				width: 288,
				height: 1, // will tween to 280
				className: '',
				html: html
			});
			
			// magic trick: you can't really tween the height (yet) unless you also
			// tween something that sets the clip's dirty bit -- opacity does this, 
			// even though it doesn't really change (1.0 -----> 1.0).
			sprite.tween({
				duration: 15,
				mode: 'EaseInOut',
				algorithm: 'Quadratic',
				properties: { height: 280, opacity: 1.0 }
			});
		}
	}
}

function create_title_page_challenge() {
	// Challenge Friend Form
	
	title_plane.createSprite({
		type: SubHeader,
		color: 'green',
		text: 'CHALLENGE FRIEND',
		x: 14,
		y: 143
	});
	
	title_plane.createSprite({
		type: IconButton,
		icon: 'arrow-left',
		color: 'green',
		x: 4,
		y: 130,
		onMouseUp: function() {
			if (this.hover) title_goto_page('high_scores');
		}
	});
	
	title_plane.createSprite({
		type: CustomSprite,
		x: 68,
		y: 210,
		width: 220,
		height: 50,
		className: 'challenge_text',
		html: '<div class="subheader">Happy with your score?</div>' + 
			'Start a friendly rivalry by sending a challenge to a friend!'
	});
	
	var iback_url = gImageLoader.getImageURL('title/challenge_input.png');
	
	var to_name = '';
	var to_email = '';
	if (query.beat && (parseInt(session.high_score) > parseInt(query.beat))) {
		to_name = query.name || '';
		to_email = query.email || '';
	}
	var from_name = session.cookie.get('name') || '';
	var from_email = session.cookie.get('email') || '';
	
	title_plane.createSprite({
		type: CustomSprite,
		x: 68,
		y: 290,
		width: 240,
		height: 150,
		className: 'challenge_form_text',
		html: '<span class="emphasis">Your</span> Name and Email Address<br/>' + 
			'<input type=text id="fe_challenge_from_name" maxlength="32" class="challenge_input" style="background-image:url('+iback_url+');" value="'+from_name+'"/><br/>' + 
			'<input type=text id="fe_challenge_from_email" maxlength="64" class="challenge_input" style="background-image:url('+iback_url+');" value="'+from_email+'"/><br/><br/>' + 
			'Your <span class="emphasis">Friend</span>\'s Name and Email Address<br/>' + 
			'<input type=text id="fe_challenge_to_name" maxlength="32" class="challenge_input" style="background-image:url('+iback_url+');" value="'+to_name+'"/><br/>' + 
			'<input type=text id="fe_challenge_to_email" maxlength="64" class="challenge_input" style="background-image:url('+iback_url+');" value="'+to_email+'"/>'
	});
	
	title_plane.createSprite({
		type: CustomSprite,
		x: 31,
		y: 470,
		width: 288,
		height: 24,
		className: 'high_score_row',
		html: '<div class="rank">&nbsp;</div>' + 
			'<div class="username">' + (session.last_username || '&nbsp;') + '</div>' + 
			'<div class="score">' + session.high_score + '</div>' + 
			'<br clear="all"/>'
	}).setBackground('title/highscores_yourscore_bkgd.png');

	title_plane.createSprite({
		type: TextButton,
		id: 'challenge_send_button',
		color: 'green',
		text: 'SEND',
		x: 90,
		y: 500,
		onMouseUp: function() {
			if (this.hover) {
				do_send_challenge();
			}
			return true;
		}
	});
}

function do_challenge_message(type, title, msg) {
	// show message on challenge screen
	// type: success, error
	if (type == 'error') gAudio.playSound('bad_orb');
	
	var sprite = title_plane.createSprite({
		type: CustomSprite,
		x: 68,
		y: 210,
		width: 220,
		height: 50,
		zIndex: 50,
		html: '<div class="challenge_message '+type+'"><div class="subheader">' + title + '</div>' + msg + '</div>'
	});
	
	sprite.tween({
		duration: 30,
		delay: 90, // delay until start
		mode: 'EaseInOut',
		algorithm: 'Quadratic',
		properties: { opacity: 0.0 }
	});
	
	return true;
}

function rot13(str) {
	// encode/decode rot13
	str = str.toString().replace(/[a-zA-Z]/g, function(c){
		return String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26);
	});
	
	str = str.toString().replace(/[0-9]/g, function(c){
		return (parseInt(c) + 5) % 10;
	});
	
	return str;
}

function do_send_challenge() {
	// gather information from form and send it
	var from_name = document.getElementById('fe_challenge_from_name').value;
	if (!from_name) return do_challenge_message('error', 'Sorry, an error occurred:', 'Please enter your name.');
	
	var from_email = document.getElementById('fe_challenge_from_email').value;
	if (!from_email) return do_challenge_message('error', 'Sorry, an error occurred:', 'Please enter your email address.');
	if (!from_email.match(/^[\w\-\.]+\@[\w\-\.]+$/)) return do_challenge_message('error', 'Sorry, an error occurred:', 'Your email address is not valid.');
	
	var to_name = document.getElementById('fe_challenge_to_name').value;
	if (!to_name) return do_challenge_message('error', 'Sorry, an error occurred:', 'Please enter your friend\'s name.');
	
	var to_email = document.getElementById('fe_challenge_to_email').value;
	if (!to_email) return do_challenge_message('error', 'Sorry, an error occurred:', 'Please enter your friend\'s email address.');
	if (!to_email.match(/^[\w\-\.]+\@[\w\-\.]+$/)) return do_challenge_message('error', 'Sorry, an error occurred:', 'Your friend\'s email address is not valid.');
	
	session.cookie.set('name', from_name);
	session.cookie.set('email', from_email);
	session.cookie.save();
	
	var url = 'challenge.php' + composeQueryString({
		score: session.high_score,
		from_name: from_name,
		from_email: from_email,
		to_name: to_name,
		to_email: to_email,
		format: 'js',
		base_url: location.href,
		game_url: location.href.replace(/\?.*$/, '') + composeQueryString({ a: rot13(from_name), b: 'z'+rot13(''+session.high_score), c: rot13(from_email) }),
		callback: 'do_send_challenge_2',
		random: Math.random()
	});
	
	title_plane.findSprite({ id: 'challenge_send_button' }).hide();
	
	do_challenge_message('progress', 'Sending...', 'One moment please.');
	gAudio.playSound('good_orb');
	
	load_script(url);
}

function do_send_challenge_2(response) {
	// receive response from challenge send script
	if (response.Code != 0) return do_challenge_message('error', 'Sorry, an error occurred:', response.Description);
	do_challenge_message('success', 'Challenge Sent', 'Your e-mail was sent successfully.');
	gAudio.playSound('start_game');
	title_plane.findSprite({ id: 'challenge_send_button' }).show();
}

function composeQueryString(queryObj) {
	// compose key/value pairs into query string
	// supports duplicate keys (i.e. arrays)
	var qs = '';
	for (var key in queryObj) {
		qs += (qs.length ? '&' : '?') + escape(key) + '=' + escape(queryObj[key]);
	}
	return qs;
}

function load_script(url) {
	var scr = document.createElement('SCRIPT');
	scr.type = "text/javascript";
	scr.src = url;
	document.getElementsByTagName('head')[0].appendChild(scr);
}

function create_title_page_credits() {
	// Credits Page
	
	title_plane.createSprite({
		type: SubHeader,
		color: 'orange',
		text: 'CREDITS',
		x: 14,
		y: 143
	});
	
	title_plane.createSprite({
		type: IconButton,
		icon: 'arrow-left',
		color: 'orange',
		x: 4,
		y: 130,
		onMouseUp: function() {
			if (this.hover) title_goto_page('main');
		}
	});
	
	title_plane.createSprite({
		type: CustomSprite,
		x: 68,
		y: 200,
		width: 240,
		height: 130,
		className: 'title_text',
		html: '<table cellspacing="0" cellpadding="0" width="240" class="credits">' + 
			'<tr>' + 
				'<td align="right" valign="top"><b>Min Chang</b></td>' + 
				'<td width="15">&nbsp;</td>' + 
				'<td align="left" valign="top">Concept<br/>Art &amp; Design</td>' + 
			'</tr><tr>' + 
				'<td colspan="3" style="font-size:6pt;"><br/>&nbsp;</td>' + 
			'</tr><tr>' + 
				'<td align="right" valign="top"><b>Joseph<br/>Huckaby</b></td>' + 
				'<td width="15">&nbsp;</td>' + 
				'<td align="left" valign="top">Programming<br/>Design</td>' + 
			'</tr>' + 
		'</table>'
	});
	
	title_plane.createSprite({
		type: CustomSprite,
		x: 68,
		y: 330,
		width: 240,
		height: 130,
		className: 'title_text',
		html: '<b>absOrb</b> is built on the <b>Effect Engine</b>, a game engine that ' + 
			'runs right in your web browser and does not require download or ' + 
			'installation of 3rd party plugins.<br/><br/>' + 
			'To learn more about the Effect Engine, visit ' + 
			'<a href="http://www.effectgames.com" target="_blank"><b>www.effectgames.com</b></a>.'
	});
	
	title_plane.createSprite({
		type: CustomSprite,
		x: 68,
		y: 460,
		width: 240,
		height: 130,
		className: 'title_text',
		html: '<table cellspacing="0" cellpadding="0" width="240">' + 
			'<tr>' +
				'<td class="title_text" width="50%" align="center"><center><a href="http://www.goldcartridge.com" target="_blank">' + 
					gImageLoader.getImageTag('title/gold_cartridge_logo.png', {vspace: 5}) + '<br/>' + 
					'Gold Cartridge LLC</a></center>' + 
				'</td>' + 
				'<td class="title_text" width="50%" align="center"><center><a href="http://www.effectgames.com" target="_blank">' + 
					gImageLoader.getImageTag('title/effect_engine_logo.png', {vspace: 5}) + '<br/>' + 
					'Effect Engine</a></center>' + 
				'</td>' +
			'</tr>' + 
		'</table>'
	});
}

function load_level() {
	// copy level args into session
	var level_args = session.levels[ session.level ];
	if (level_args) {
		for (var key in level_args) session[key] = level_args[key];
		if (level_args.msg) post_game_message( level_args.msg );
	}
}

function start_new_game() {
	// start new game
	port.style.backgroundColor = '#000';
	
	// destroy all sprites from title screen
	ui_plane.deleteAll();
	sprite_plane.deleteAll();
	particle_plane.deleteAll();
	title_plane.deleteAll();
	
	// quiet audio and play start game fx
	gAudio.quiet();
	gAudio.playSound('start_game');
	
	// set level settings for new game
	// tweak these to taste
	session.level = 1;
	load_level();
	
	session.score = 0;
	session.lives = 5;
	session.score_per_orb = 10;
	session.next_level_at = 100;
	session.next_level_gap = 200;
	session.next_level_gap_inc = 100;
	
	// background
	ui_plane.createSprite({
		id: 'bkgnd',
		url: 'environments/canvas-bkgd_merged.png',
		x: 0,
		y: 0,
		zIndex: 1
	});
	
	// two panels
	ui_plane.createSprite({
		id: 'panel_top',
		url: 'panels/panel_top_long.png',
		x: 0,
		y: -350 + 80,
		zIndex: 30
	});
	ui_plane.createSprite({
		id: 'panel_bottom',
		url: 'panels/panel_bottom_short.png',
		x: 0,
		y: 600 - 80,
		width: 600,
		height: 80,
		zIndex: 30
	});
	
	// logo
	ui_plane.createSprite({
		id: 'logo',
		url: 'logos/absorb_large.png',
		x: 94,
		y: 11,
		zIndex: 31
	});
	
	// curly frame
	ui_plane.createSprite({
		url: 'frames/curlyframe_with-score_shadow.png',
		x: 0,
		y: 0,
		zIndex: 32
	});
	
	// buttons
	ui_plane.createSprite({
		id: 'pause_toggle',
		type: IconButton,
		icon: 'pause',
		color: 'blue',
		x: 0,
		y: 15,
		zIndex: 32,
		onMouseUp: function() {
			pause_toggle();
		}
	});
	ui_plane.createSprite({
		id: 'exit_game',
		type: IconButton,
		icon: 'close',
		color: 'blue',
		x: 300,
		y: 15,
		zIndex: 32,
		onMouseUp: function() {
			abort_game();
		}
	});
	
	// player
	sprite_plane.createSprite({
		id: 'player',
		type: Player,
		x: 125,
		y: 425,
		zIndex: 11
	});
	
	// score
	ui_plane.createSprite({
		id: 'score',
		type: ScoreDisplay,
		x: 0,
		y: 515,
		color: 'blue',
		zIndex: 32
	});
	
	// timer
	ui_plane.createSprite({
		type: TimerDisplay,
		x: 20,
		y: 560,
		zIndex: 32
	});
	
	// life dots
	ui_plane.createSprite({ id: 'lf1', type: LifeDot, x: 123, y: 570, life: 4, variant: '_30', zIndex: 32 });
	ui_plane.createSprite({ id: 'lf2', type: LifeDot, x: 143, y: 570, life: 2, variant: '_60', zIndex: 32 });
	ui_plane.createSprite({ id: 'lf3', type: LifeDot, x: 163, y: 570, life: 1, variant: '', zIndex: 32 });
	ui_plane.createSprite({ id: 'lf4', type: LifeDot, x: 183, y: 570, life: 3, variant: '_60', zIndex: 32 });
	ui_plane.createSprite({ id: 'lf5', type: LifeDot, x: 203, y: 570, life: 5, variant: '_30', zIndex: 32 });
	
	// level display
	ui_plane.createSprite({
		type: LevelDisplay,
		x: 280,
		y: 560,
		zIndex: 32
	});
	
	// create a few blue orbs just to start things off
	for (var idx = 0; idx < 5; idx++) {
		sprite_plane.createSprite({
			type: Orb,
			x: -8 + Math.floor( Math.random() * 317 ),
			y: -50 + Math.floor( Math.random() * 350 ),
			color: 'blue',
			zIndex: 10
		});
	}
		
	// set state for game
	gGame.setState('game');
	
	// game music
	if (session.music) {
		gAudio.getTrack('music_level').rewind();
		gAudio.playSound( 'music_level' );
	}
	
	// challenge system -- set flag so we know when its beat
	session.beat_challenge = false;
}

function post_game_message(msg) {
	// show temporary message onscreen (kills itself after a few secs)
	ui_plane.createSprite({
		type: GameMessage,
		text: msg,
		zIndex: 32
	});
}

function count_keys(hash) {
	// count the number of keys in a hash
	var count = 0;
	for (var a in hash) count++;
	return count;
}

function game_logic() {
	// continually spawn new orbs
	// this is called for every logic frame (30 per sec)
	if (probably( session.orb_probability )) {
		// locate random area free of other orbs
		// this way orbs don't appear on top of each other
		var ystart = -50;
		switch (session.behavior) {
			case 'wind':
				// vary gravity to simulate wind
				var sine = Math.sin( DECIMAL_TO_RADIANS( gGame.logicClock % 360 ) );
				var angle = 270 + (sine * session.wind_max_degrees);
				session.gravity.set(0,0).project( angle, session.gravity_distance );
				break;
			
			case 'reverse':
				// spawn from bottom
				ystart = 590;
				break;
		}
		
		var pt;
		do { pt = new Point( -8 + Math.floor( Math.random() * 317 ), ystart ); } 
		while (sprite_plane.movePointX(pt.x + 25, pt.y + 25, 1));
		
		var orb_type = Orb;
		if ((session.level >= 4) && probably(0.005)) orb_type = OrbWildcard; // yay!
		else if ((session.level >= 4) && probably(0.01)) orb_type = OrbDark; // oh no!
		
		// spawn an orb
		sprite_plane.createSprite({
			type: orb_type,
			x: pt.x,
			y: pt.y,
			color: rand_array(orb_colors, 0, session.orb_color_max),
			zIndex: 10
		});
	}
	
	// debugging information
	/* if (gGame.logicClock % 8 == 0) {
		var div = document.getElementById('d_info');
		var html = '';
		
		var total_sprites = count_keys(ui_plane.sprites) + count_keys(sprite_plane.sprites) + count_keys(particle_plane.sprites);
		html += 'Sprites: ' + total_sprites + ', ';
		html += 'FPS: ' + gGame.getCurrentFPS();
		
		div.innerHTML = html;
	} */
	
	if (query.beat && !session.beat_challenge && (session.score > parseInt(query.beat))) {
		// BEAT SCORE!  YAY!
		gAudio.playSound('beat_challenge');
		post_game_message("You beat " + unescape(query.name) + "'s score!");
		session.beat_challenge = true;
	}
}

function one_up() {
	// give extra life
	session.lives++;
	gAudio.playSound('beat_challenge');
}

function game_key_down(e) {
	// handle key press
	if (gGame.state == 'game') {
		switch (e.keyCode) {
			case 27: // ESC
			case 80: // P
				pause_toggle();
				break;
			case 49: // 1
				set_ring_color(0);
				break;
			case 50: // 2
				set_ring_color(1);
				break;
			case 51: // 3
				set_ring_color(2);
				break;
			case 52: // 4
				set_ring_color(3);
				break;
			case 39: // right arrow
				var player = sprite_plane.findSprite('player');
				if (player) set_ring_color( (player.colorIndex + 1) % session.orb_color_max );
				break;
			case 37: // left arrow
				var player = sprite_plane.findSprite('player');
				if (player) {
					var color = player.colorIndex - 1;
					if (color < 0) color += session.orb_color_max;
					set_ring_color( color );
				}
				break;
			
			case 88: // X
				abort_game();
				break;
			
			case 77: // M
				absorb_set_music( !session.music );
				break;
			
			case 83: // S
				absorb_set_sound( !gAudio.enabled );
				break;
			
			// CHEATS:
			
			case 219: // left bracket [ advance level
				next_level();
				break;
			
			case 221: // right bracket ] increase lives
				one_up();
				break;
			
			case 220: // backslash = wildcard orb
				sprite_plane.createSprite({
					type: OrbWildcard,
					x: 150,
					y: 0,
					zIndex: 10
				});
				break;
			
			case 8: // delete = dark orb
				sprite_plane.createSprite({
					type: OrbDark,
					x: 150,
					y: 0,
					zIndex: 10
				});
				break;
		}
	}
	else if (gGame.state == 'death') {
		var name_entry = title_plane.findSprite({ id: 'name_entry' });
		if (name_entry) {
			if (((e.keyCode > 32) && (e.keyCode < 128)) || (e.keyCode == 8)) {
				name_entry.typeKey( e.keyCode );
			}
			else if (e.keyCode == 13) {
				do_submit_high_score();
			}
		} // found name entry field
	} // death state
	
	// this tells the browser to ignore the event if we are in game
	return (!gGame.inGame || (gGame.state == 'title'));
}

function game_mouse_down(e, pt, buttonNum) {
	// game mouse down handler
	// this gets fired for every click not caught by the portal (see port_mouse_down())
	// this is just to stop the event from bubbling while in game
	return (!gGame.inGame || (gGame.state == 'title'));
}

function game_mouse_move(e, globalPt, buttonNum) {
	// called globally every time the mouse moves
	// need to use this to track mouse position in portal because
	// port onMouseMove is only fired in the mouse is down (i.e. for dragging)
	pt = port.getMouseCoords();
	
	if (pt && gGame.inGame && (gGame.state == 'game')) {
		if ((pt.x >= 0) && (pt.x < 350) && (pt.y >= 64) && (pt.y < 600 - 58)) {
			var player = sprite_plane.findSprite('player');
			if (player) player.target = pt;
		} // point in game area
	}
	
	return true; // pass event through
}

function port_mouse_down(e, pt, buttonNum) {
	// left mouse click, move player
	// right mouse click, cycle through ring states
	/*if (gGame.inGame && (gGame.state == 'game')) {
		switch (buttonNum) {
			case 1: // left mouse button, move player
				if ((pt.x >= 0) && (pt.x < 350) && (pt.y >= 64) && (pt.y < 600 - 58)) {
					var player = sprite_plane.findSprite('player');
					player.target = pt;
				} // point in game area
				break;
			
			case 2: // right mouse button, cycle color
				var player = sprite_plane.findSprite('player');
				set_ring_color( (player.colorIndex + 1) % session.orb_color_max );
				break;
		} // switch buttonNum
		return false; // stop event
	} // in game */
	
	return true; // pass event
}

function set_ring_color(idx) {
	// change player ring color
	if (idx < session.orb_color_max) {
		var player = sprite_plane.findSprite('player');
		if (player && (player.colorIndex != idx)) {
			player.colorIndex = idx;
			player.color = orb_colors[idx];
			player.setImage( 'ring/ring_highlight_'+player.color+'.png' );
			gAudio.playSound('switch_color');
			
			// change score background color too
			var score = ui_plane.findSprite('score');
			score.color = player.color;
			score.setBackground( 'score/score_bkgd_'+score.color+'.png' );
			
			// change life dot colors too
			for (var idx = 1; idx <= 5; idx++) {
				var dot = ui_plane.findSprite('lf' + idx);
				dot.setColor( player.color );
			}
		}
	}
}

function next_level() {
	// next level!  yay!
	session.level++;
	load_level();
	
	// play happy sound
	gAudio.playSound('level_up');
	
	// give joy
	var player = sprite_plane.findSprite('player');
	if (player) player.joy = 256;
}

function pause_toggle() {
	// toggle play / paused
	gAudio.quiet();
	gAudio.playSound('pause');
	
	var button = ui_plane.findSprite('pause_toggle');
	button.reset();
	
	if (gGame.inGame) {
		gGame.stop();
		button.icon = 'play';
	}
	else {
		setTimeout( function() { gGame.run(); }, 1 );
		button.icon = 'pause';
		if (session.music) gAudio.playSound( 'music_level' );
	}
	
	button.init();
}

function abort_game() {
	// user clicked "X" icon, same as game over
	if (!gGame.inGame) pause_toggle();
	
	if (gGame.state == 'game') game_over();
	else game_title_screen();
}

function game_over() {
	// no more lives
	if (session.score > session.high_score) {
		// a new personal high score!
		session.high_score = session.score;
		session.cookie.set('high_score', session.high_score);
		session.cookie.save();
	}
	
	gAudio.quiet();
	
	var player = sprite_plane.findSprite('player');
	if (player) {
		player.pop();
		gAudio.playSound('game_over');
	}
	
	// return to title screen and play death sound
	// game_title_screen();
	
	ui_plane.findSprite({id: 'pause_toggle'}).destroy();
	
	gGame.setState('death');
	
	session.pain = 256;
	gGame.scheduleEvent( gGame.logicClock + 90, 'onCheckHighScore' );
}

function death_logic() {
	// called every logic frame after player dies
	
	game_logic(); // keep orbs moving
	
	if (session.pain) {
		// flash background red
		// session.pain /= 2;
		session.pain -= (session.pain / 8);
		if (session.pain < 1.0) session.pain = 0;
		port.style.backgroundColor = 'rgb(' + Math.floor(session.pain) + ', 0, 0)';
	}
}

function check_high_score() {
	// see if user achieved a high score in the master list
	for (var idx = 0, len = session.scores.length; idx < len; idx++) {
		var score = session.scores[idx];
		if (session.score > score.score) {
			do_enter_high_score_form();
			return;
		}
	}
	
	game_title_screen();
}

function do_enter_high_score_form() {
	// show enter high score form
	gAudio.playSound('high_score' );
	
	// need to change around some z-indexes because of the curly frame
	ui_plane.findSprite({ id: 'panel_top' }).setZIndex(33); // above curly frame
	ui_plane.findSprite({ id: 'logo' }).setZIndex(33);
	ui_plane.findSprite({ id: 'exit_game' }).setZIndex(33);
	
	// tween animate the top panel so it makes room for submission form
	var common_tween_args = {
		duration: 30,
		mode: 'EaseInOut',
		algorithm: 'Quadratic'
	};
	
	ui_plane.findSprite({ id: 'logo' }).tween(merge_objects(common_tween_args, {
		properties: {
			y: 250
		},
		onTweenComplete: do_enter_high_score_form_2
	}));
	
	ui_plane.findSprite({ id: 'panel_top' }).tween(merge_objects(common_tween_args, {
		properties: {
			y: -350 + 320
		}
	}));
	
	ui_plane.findSprite({ id: 'exit_game' }).tween(merge_objects(common_tween_args, {
		properties: {
			y: 255
		}
	}));
}

function do_enter_high_score_form_2() {
	// part 2 of tween, create and fade in the form elements
	if (opacity_transitions) title_plane.setOpacity(0.0);
	
	title_plane.createSprite({
		type: CustomSprite,
		x: 58,
		y: 35,
		width: 240,
		height: 30,
		className: 'congratulations',
		html: 'Congratulations!'
	});
	
	title_plane.createSprite({
		type: CustomSprite,
		x: 58,
		y: 75,
		width: 240,
		height: 130,
		className: 'title_text',
		html: 'You\'ve achieved a top ten high score!  Enter your name below to post to the leaderboard. ' + 
			'You can also send a challenge to a friend to try to beat your score.'
	});
	
	title_plane.createSprite({
		type: NameEntry,
		id: 'name_entry',
		x: 95,
		y: 140
	});
	
	title_plane.createSprite({
		type: TextButton,
		id: 'submit_button',
		color: 'green',
		text: 'SUBMIT',
		x: 90,
		y: 190,
		onMouseUp: function() {
			if (this.hover) {
				do_submit_high_score();
			}
			return true;
		}
	});
	
	if (opacity_transitions) {
		title_plane.tween({
			duration: 15,
			mode: 'EaseInOut',
			algorithm: 'Quadratic',
			properties: { opacity: 1.0 }
		});
	}
}

function do_submit_high_score() {
	// submit high score to global database
	var name_entry = title_plane.findSprite({ id: 'name_entry' });
	if (name_entry) {
		var username = name_entry.text;
		if (!username.length) {
			// no name entered
			gAudio.playSound('bad_orb');
			return;
		}
		session.username = username;
		session.last_username = username;
		
		session.cookie.set('username', username);
		session.cookie.save();
		
		gGame.setState('submitting');
		title_plane.findSprite({ id: 'submit_button' }).destroy();
		
		load_script( '/effect/api/game_get_time?format=js&callback=do_submit_high_score_2' );
	}
}

function do_submit_high_score_2(response) {	
	var time_key = Math.floor( response.Time / 60 );
	var hash = hex_md5( '' + time_key );
	load_script( '/effect/api/game_post_high_score' + 
		'?game=absorb' + 
		'&auth=' + hash + 
		'&username=' + escape(session.username) + 
		'&score=' + escape(session.score) + 
		'&level=' + session.level + 
		'&elapsed=' + session.game_elapsed + 
		'&format=js&callback=do_submit_high_score_3' 
	);
}

function do_submit_high_score_3(response) {
	// check for error?
	if (response.Code) return alert("ERROR: " + response.Code + ": " + response.Description);
	
	load_high_scores(true, 'do_submit_high_score_4');
}

function do_submit_high_score_4(response) {
	// finally, we're done, go back to title, but straight to high scores page
	game_title_screen('high_scores');
	gAudio.playSound('start_game');
}

function contact_message(msg) {
	// show notification message in form
	el('s_contact_notify').innerHTML = msg;
	if (msg) setTimeout( 'contact_message("")', 5000 );
}

function contact_send_email() {
	// send e-mail to authors
	contact_message('Sending...');
	
	var name = el('fe_contact_name').value;
	if (!name) { el('fe_contact_name').focus(); gAudio.playSound('bad_orb'); return; }
	
	var email = el('fe_contact_email').value;
	if (!email.match(/^[\w\.\-]+\@[\w\.\-]+$/)) { el('fe_contact_email').focus(); gAudio.playSound('bad_orb'); return; }
	
	var body = el('fe_contact_body').value;
	if (!body) { el('fe_contact_body').focus(); gAudio.playSound('bad_orb'); return; }
	
	var url = 'email.php?name=' + escape(name) + '&email=' + escape(email) + '&body=' + escape(body) + '&callback=contact_send_email_2';
	load_script(url);
}

function contact_send_email_2(response) {
	// receive response from server
	contact_message( response.Description );
	if (response.Code == 0) {
		el('fe_contact_name').value = '';
		el('fe_contact_email').value = '';
		el('fe_contact_body').value = '';
	}
}// GameMessage Sprite

function GameMessage() {
	// class constructor
	this.text = '';
	
	// time to live
	this.timer = 50;
}

GameMessage.prototype = new CustomSprite();
GameMessage.prototype.type = 'GameMessage';
GameMessage.prototype.width = 322;
GameMessage.prototype.height = 60;

GameMessage.prototype.init = function() {
	// fixed position
	this.x = 14;
	this.y = 250;
	
	// call super's init
	CustomSprite.prototype.init.call(this);
	
	// style it up
	this.div.className = 'game_message';
	this.div.innerHTML = this.text;
}

GameMessage.prototype.logic = function() {
	// countdown to death
	if (this.timer > 0) {
		this.timer--;
		if (this.timer == 0) {
			this.tween({
				duration: 30,
				mode: 'EaseInOut',
				algorithm: 'Quadratic',
				properties: { opacity: 0.0 }
			});
		}
	}
}

GameMessage.prototype.onTweenComplete = function() {
	// fade out complete, kill
	this.destroy();
}
// IconButton Sprite

function IconButton() {
	// class constructor
	this.color = 'blue';
	this.icon = 'close';
	this.hover = false;
	this.hitRect = new Rect(13, 13, 37, 37);
}

IconButton.prototype = new CustomSprite();
IconButton.prototype.type = 'IconButton';
IconButton.prototype.width = 50;
IconButton.prototype.height = 50;

IconButton.prototype.init = function() {		
	// call super's init
	CustomSprite.prototype.init.call(this);
	
	// style it up
	this.setBackground( 'buttons/small-button_'+this.color+'.png' );
	this.div.innerHTML = '<div id="'+this.globalID+'_highlight">' + 
		gImageLoader.getImageTag('buttons/icon_'+this.icon+'.png') + '</div>';
	
	// setup mouse capture for clicking
	this.div.captureMouse = this;
	
	// locate highlight image for hover fx
	this.highlight_src = gImageLoader.getImageURL( 'buttons/small-button_highlight.png' );
}

IconButton.prototype.logic = function() {
	var mouseOver = this.isMouseOver();
	if (mouseOver && !this.hover) {
		// mouse is over but we are not highlighted, so highlight us
		// TODO: IE 6
		el(this.globalID+'_highlight').style.backgroundImage = 'url(' + this.highlight_src + ')';
		this.hover = true;
	}
	else if (!mouseOver && this.hover) {
		// mouse is out but we are still highlighted, so turn off highlight
		el(this.globalID+'_highlight').style.backgroundImage = '';
		this.hover = false;
	}
}
// LevelDisplay Sprite

function LevelDisplay() {
	// class constructor
	this.levelCache = 0;
	this.label = 'Level';
}

LevelDisplay.prototype = new CustomSprite();
LevelDisplay.prototype.type = 'LevelDisplay';
LevelDisplay.prototype.width = 50;
LevelDisplay.prototype.height = 30;

LevelDisplay.prototype.init = function() {
	// setup sprite
	CustomSprite.prototype.init.call(this);
}

LevelDisplay.prototype.logic = function() {
	// monitor level and update accordingly
	if (session.level != this.levelCache) {
		this.div.innerHTML = '<div class="hud_text" style="text-align:right">' + session.level + '</div>' + 
			'<div class="hud_label" style="text-align:right">' + this.label + '</div>';
		
		this.levelCache = session.level;
	} // time has changed, update
}
// LifeDot Sprite

function LifeDot() {
	// class constructor
	this.lifeCache = 0;
	this.color = 'blue';
	this.variant = '';
}

LifeDot.prototype = new Sprite();
LifeDot.prototype.type = 'LifeDot';
LifeDot.prototype.width = 24;
LifeDot.prototype.height = 24;

LifeDot.prototype.init = function() {
	// set image based on color
	this.url = 'score/life_' + this.color + this.variant + '.png';
	
	// call super's init
	Sprite.prototype.init.call(this);
	
	if (this.life == 1) {
		// center life dot has halo
		this.setBackground( 'score/life_glow_' + this.color + '.png' );
	}
}

LifeDot.prototype.logic = function() {
	// monitor lives and adjust accordingly
	if (session.lives != this.lifeCache) {
		if (session.lives >= this.life) this.show();
		else this.hide();
		this.lifeCache = session.lives;
	}
}

LifeDot.prototype.setColor = function(color) {
	// set new color for dot
	this.color = color;
	this.setImage( 'score/life_' + this.color + this.variant + '.png' );
	
	if (this.life == 1) {
		// center life dot has halo
		this.setBackground( 'score/life_glow_' + this.color + '.png' );
	}
}
// NameEntry Sprite

function NameEntry() {
	// class constructor
	this.text = '';
}

NameEntry.prototype = new CustomSprite();
NameEntry.prototype.type = 'NameEntry';
NameEntry.prototype.width = 160;
NameEntry.prototype.height = 50;

NameEntry.prototype.init = function() {
	// call super's init
	CustomSprite.prototype.init.call(this);
	
	// style it up
	this.setBackground( 'title/text_input.png' );
	this.div.className = 'name_entry';
	
	// text
	this.div.innerHTML = this.text + '<span class="ibeam">|</span>';
}

NameEntry.prototype.logic = function() {
	// blinking cursor?
	if (gGame.state == 'submitting') {
		// hey, a poor man's progress bar!
		var frame = Math.floor(gGame.logicClock / 3) % 5;
		var text = '';
		for (var idx = 0; idx < 5; idx++) {
			if (idx == frame) text += '-'; else text += '&nbsp;';
		}
		this.div.innerHTML = text;
	}
}

NameEntry.prototype.typeKey = function(code) {
	if (code == 8) {
		// backspace
		this.text = this.text.replace(/.$/, '');
		// gAudio.playSound('bad_orb');
	}
	else {
		var ch = String.fromCharCode(code);
		if (this.text.length < 3) {
			this.text += ch;
			gAudio.playSound('good_orb');
		}
	}
	
	this.div.innerHTML = this.text + '<span class="ibeam">|</span>';
}
// Orb Sprite

function Orb() {
	// class constructor
	this.collisions = true;
	this.dieOffscreen = true;
	this.hitRect = new Rect(12, 12, 36, 36);
	this.behavior = 'standard';
	this.color = 'blue';
	this.solid = false;
	
	this.xd = 0;
	this.yd = 0;
}

Orb.prototype = new Sprite();
Orb.prototype.type = 'Orb';
Orb.prototype.width = 50;
Orb.prototype.height = 50;

Orb.prototype.init = function() {
	// set image based on color
	this.url = 'orbs/orb_'+this.color+'.png';
	
	// call super's init
	Sprite.prototype.init.call(this);
}

Orb.prototype.logic = function() {
	// allow different behaviors, each with its own method
	this[ 'behavior_' + this.behavior ]();
}

Orb.prototype.behavior_standard = function() {
	// standard behavior, just fall straight down at set speed
	// this.y += session.orb_speed;
	this.xd += (session.gravity.x - this.xd) / 8;
	this.yd += (session.gravity.y - this.yd) / 8;
	
	this.x += this.xd;
	this.y += this.yd;
}

Orb.prototype.pop = function() {
	// pop orb into several particles, and destroy self
	var total_particles = count_keys( particle_plane.sprites );
	var num_particles = 30;
	if (total_particles > 50) num_particles = 15;
	if (total_particles > 100) num_particles = 10;
	
	for (var idx = 0; idx < num_particles; idx++) {
		var px = Math.floor( Math.random() * this.width );
		var py = Math.floor( Math.random() * this.height );
		
		particle_plane.createSprite({
			type: OrbParticle,
			x: this.x + px,
			y: this.y + py,
			color: this.color,
			xd: (px - (this.width / 2)) / 10,
			yd: (py - (this.height / 2)) / 10,
			timer: Math.floor( Math.random() * 30 ) + 30
		});
	}
	
	gAudio.playSound('good_orb');
	this.destroy();
}// OrbDark Sprite

function OrbDark() {
	// class constructor
	this.collisions = true;
	this.dieOffscreen = true;
	this.hitRect = new Rect(12, 12, 36, 36);
	this.solid = false;
	this.behavior = 'standard';
	this.frameX = 0;
	this.frameY = 0;
	this.xd = 0;
	this.yd = 0;
}

OrbDark.prototype = new Orb();
OrbDark.prototype.type = 'OrbDark';
OrbDark.prototype.width = 50;
OrbDark.prototype.height = 50;

OrbDark.prototype.init = function() {
	// set image 
	this.url = 'orbs/orb_dark.png';
	
	// call super's init
	Sprite.prototype.init.call(this);
}

OrbDark.prototype.logic = function() {
	// allow different behaviors, each with its own method
	this[ 'behavior_' + this.behavior ]();
}

OrbDark.prototype.pop = function() {
	// hurt player X2 and explode
	var player = this.plane.findSprite('player');
	if (player) player.pain = 256;
	
	// gAudio.playSound('bad_orb');
	gAudio.playSound('explosion');
	
	// repel all sprites nearby
	var centerPt = this.centerPoint();
	
	for (var id in this.plane.sprites) {
		var sprite = this.plane.sprites[id];
		if (sprite.type == 'Orb') {
			var sCenter = sprite.centerPoint();
			var distance = sCenter.getDistance( centerPt );
			if (distance < 300) {
				var angle = sCenter.getAngle( centerPt );
				var delta = (new Point()).project( angle, (300 - distance) / 10 );
				sprite.xd -= delta.x;
				sprite.yd -= delta.y;
			} // nearby
		} // is orb
	} // foreach sprite
	
	// decrement lives
	session.lives--;
	if (!session.lives) {
		// end game, but do it after current logic loop is complete
		setTimeout( function() { game_over(); }, 1 );
	}
	
	this.destroy();
}
// OrbWildcard Sprite

function OrbWildcard() {
	// class constructor
	this.collisions = true;
	this.dieOffscreen = true;
	this.hitRect = new Rect(12, 12, 36, 36);
	this.solid = false;
	this.behavior = 'standard';
	this.frameX = 0;
	this.frameY = 0;
	this.xd = 0;
	this.yd = 0;
}

OrbWildcard.prototype = new Orb();
OrbWildcard.prototype.type = 'OrbWildcard';
OrbWildcard.prototype.width = 50;
OrbWildcard.prototype.height = 50;

OrbWildcard.prototype.init = function() {
	// set image 
	this.url = 'orbs/wildcard.png';
	
	// call super's init
	Sprite.prototype.init.call(this);
}

OrbWildcard.prototype.logic = function() {
	// allow different behaviors, each with its own method
	this[ 'behavior_' + this.behavior ]();
}

OrbWildcard.prototype.draw = function() {
	// flash between all colors
	// this.setFrameX( gGame.drawClock % 4 );
	this.setFrameX( Math.floor( Math.random() * 4 ) );
	Sprite.prototype.draw.call(this);
}

OrbWildcard.prototype.pop = function() {
	// give player +100 points, +1 life, and flash blue
	var total_particles = count_keys( particle_plane.sprites );
	var num_particles = 30;
	if (total_particles > 50) num_particles = 15;
	if (total_particles > 100) num_particles = 10;
	
	for (var idx = 0; idx < num_particles; idx++) {
		var px = Math.floor( Math.random() * this.width );
		var py = Math.floor( Math.random() * this.height );
		
		particle_plane.createSprite({
			type: OrbParticle,
			x: this.x + px,
			y: this.y + py,
			color: 'white',
			xd: (px - (this.width / 2)) / 10,
			yd: (py - (this.height / 2)) / 10,
			timer: Math.floor( Math.random() * 30 ) + 30
		});
	}
	
	session.score += 100;
	one_up();
	
	var player = this.plane.findSprite('player');
	if (player) player.fun = 256;
	
	post_game_message('1UP');
	
	this.destroy();
}
// OrbParticle Sprite

function OrbParticle() {
	// class constructor
	this.color = 'blue';
	this.timer = 30;
	this.xd = 0;
	this.yd = -4;
	
	this.collisions = false;
	this.dieOffscreen = true;
}

OrbParticle.prototype = new CustomSprite();
OrbParticle.prototype.type = 'OrbParticle';
OrbParticle.prototype.width = 1;
OrbParticle.prototype.height = 1;

OrbParticle.prototype.init = function() {
	// setup background color plus text
	
	// call super's init
	CustomSprite.prototype.init.call(this);
	
	// style it up
	this.div.className = 'particle_' + this.color;
}

OrbParticle.prototype.logic = function() {
	// move particle
	this.x += this.xd;
	this.y += this.yd;
	
	// gravity
	this.yd += 0.2;
	
	// wind resistance
	this.xd -= (this.xd / 16);
	
	// time to live
	this.timer--;
	if (!this.timer) this.destroy();
}// Player Sprite (Ring)

function Player() {
	// class constructor
	this.color = 'blue';
	this.colorIndex = 0;
	this.collisions = true;
	this.dieOffscreen = false;
	this.target = new Point();
	this.hitRect = new Rect(30, 30, 70, 70);
	this.solid = false;
	
	// used to flash the background red
	this.pain = 0;
	
	// used to flash the background white
	this.joy = 0;
	
	// used to flash the background blue
	this.fun = 0;
}

Player.prototype = new Sprite();
Player.prototype.type = 'Player';
Player.prototype.width = 100;
Player.prototype.height = 100;

Player.prototype.init = function() {
	// set image based on color
	this.url = 'ring/ring_highlight_'+this.color+'.png';
	
	// call super's init
	Sprite.prototype.init.call(this);
	
	// set shadow background
	this.setBackground( 'ring/ring_shadow.png' );
	
	// set target to current loc
	this.target.x = this.x + 50;
	this.target.y = this.y + 49; // one pixel off to enable collision detection
}

Player.prototype.logic = function() {
	// moving towards target point, easing out
	this.xd = (((this.target.x - 50) - this.x) / 4);
	this.yd = (((this.target.y - 50) - this.y) / 4);
	
	var hit = this.move();
	if (hit) {
		switch (hit.target.type) {
			case 'Orb':
				// we hit an orb!  good or bad?
				var orb = hit.target;
				if (orb.color == this.color) {
					// good orb!  yay!
					orb.pop();
					
					// increment score
					session.score += session.score_per_orb;
					
					if (session.score >= session.next_level_at) {
						next_level();
						
						session.next_level_at += session.next_level_gap;
						session.next_level_gap += session.next_level_gap_inc;
					} // next level
				}
				else {
					// bad orb!  hurt us!
					gAudio.playSound('bad_orb');
					
					// give us a flash of red
					this.pain = 256;
					
					// orb be gone
					orb.destroy();
					
					// decrement lives
					session.lives--;
					if (!session.lives) {
						// end game, but do it after current logic loop is complete
						setTimeout( function() { game_over(); }, 1 );
					}
				}
				
				break;
			
			case 'OrbWildcard':
			case 'OrbDark':
				hit.target.pop();
				break;
		} // switch type
	} // collision
	
	if (this.pain) {
		// flash background red (hit bad orb)
		this.pain /= 2;
		if (this.pain < 1.0) this.pain = 0;
		port.style.backgroundColor = 'rgb(' + Math.floor(this.pain) + ', 0, 0)';
	}
	else if (this.joy) {
		// flash background white (next level)
		this.joy -= (this.joy / 4);
		if (this.joy < 1.0) this.joy = 0;
		port.style.backgroundColor = 'rgb(' + Math.floor(this.joy) + ', ' + Math.floor(this.joy) + ', ' + Math.floor(this.joy) + ')';
	}
	else if (this.fun) {
		// flash background blue (wildcard orb)
		this.fun -= (this.fun / 8);
		if (this.fun < 1.0) this.fun = 0;
		port.style.backgroundColor = 'rgb(0, 0, ' + Math.floor(this.fun) + ')';
	}
}

Player.prototype.pop = function() {
	// pop into several particles, and destroy self
	
	// to help the effect, kill all existing particles that may be around
	particle_plane.deleteAll();
	
	for (var idx = 0; idx < 90; idx++) {
		var px = Math.floor( Math.random() * this.width );
		var py = Math.floor( Math.random() * this.height );
		
		particle_plane.createSprite({
			type: OrbParticle,
			x: this.x + px,
			y: this.y + py,
			color: this.color,
			xd: (px - (this.width / 2)) / 5,
			yd: (py - (this.height / 2)) / 5,
			timer: Math.floor( Math.random() * 30 ) + 30
		});
	}
	
	// repel all sprites nearby
	var centerPt = this.centerPoint();
	
	for (var id in this.plane.sprites) {
		var sprite = this.plane.sprites[id];
		if (sprite.type == 'Orb') {
			var sCenter = sprite.centerPoint();
			var distance = sCenter.getDistance( centerPt );
			if (distance < 300) {
				var angle = sCenter.getAngle( centerPt );
				var delta = (new Point()).project( angle, (300 - distance) / 10 );
				sprite.xd -= delta.x;
				sprite.yd -= delta.y;
			} // nearby
		} // is orb
	} // foreach sprite
	
	gAudio.playSound('explosion');
	this.destroy();
}
// ScoreDisplay Sprite

function ScoreDisplay() {
	// class constructor
	this.color = 'blue';
	this.scoreCache = -1;
}

ScoreDisplay.prototype = new CustomSprite();
ScoreDisplay.prototype.type = 'ScoreDisplay';
ScoreDisplay.prototype.width = 350;
ScoreDisplay.prototype.height = 50;

ScoreDisplay.prototype.init = function() {
	// setup background color plus text
	
	// call super's init
	CustomSprite.prototype.init.call(this);
	
	// style it up
	this.div.className = 'score_display';
	this.setBackground( 'score/score_bkgd_'+this.color+'.png' );
	this.div.innerHTML = this.padScore();
}

ScoreDisplay.prototype.padScore = function() {
	// return score padded with zeros
	return pad_int(session.score, 6);
}

ScoreDisplay.prototype.logic = function() {
	// monitor score and update accordingly
	if (session.score != this.scoreCache) {
		// score has changed, update display
		this.div.innerHTML = this.padScore();
		this.scoreCache = session.score;
	}
}// Slideshow Sprite

function Slideshow() {
	// class constructor
	this.images = [];
	this.imageIdx = 0;
	this.delay = 90;
}

Slideshow.prototype = new Sprite();
Slideshow.prototype.type = 'Slideshow';

Slideshow.prototype.init = function() {
	// set image based on color
	this.url = this.images[ this.imageIdx ];
	this.startTime = gGame.logicClock;
	
	// call super's init
	Sprite.prototype.init.call(this);
}

Slideshow.prototype.logic = function() {
	// swap image at preset intervals
	if (gGame.logicClock - this.startTime >= this.delay) {
		// time to swap image
		this.imageIdx++;
		this.imageIdx = (this.imageIdx % this.images.length);
		this.setImage( this.images[ this.imageIdx ] );
		this.startTime = gGame.logicClock;
	}
}
// SubHeader Sprite

function SubHeader() {
	// class constructor
	this.color = 'blue';
	this.text = '';
}

SubHeader.prototype = new CustomSprite();
SubHeader.prototype.type = 'SubHeader';
SubHeader.prototype.width = 322;
SubHeader.prototype.height = 24;

SubHeader.prototype.init = function() {		
	// call super's init
	CustomSprite.prototype.init.call(this);
	
	// style it up
	this.setBackground( 'subheaders/subheader_'+this.color+'.png' );
	this.div.className = 'sub_header';
	this.div.innerHTML = this.text;
}
// TextButton Sprite

function TextButton() {
	// class constructor
	this.color = 'blue';
	this.text = '';
	this.hover = false;
	this.hitRect = new Rect(13, 13, 157, 37);
}

TextButton.prototype = new CustomSprite();
TextButton.prototype.type = 'TextButton';
TextButton.prototype.width = 170;
TextButton.prototype.height = 50;

TextButton.prototype.init = function() {		
	// call super's init
	CustomSprite.prototype.init.call(this);
	
	// style it up
	this.setBackground( 'buttons/button_'+this.color+'.png' );
	this.div.innerHTML = '<div id="'+this.globalID+'_highlight" class="text_button">' + this.text + '</div>';
	
	// setup mouse capture for clicking
	this.div.captureMouse = this;
	
	// locate highlight image for hover fx
	this.highlight_src = gImageLoader.getImageURL( 'buttons/button_highlight.png' );
}

TextButton.prototype.logic = function() {
	var mouseOver = this.isMouseOver();
	if (mouseOver && !this.hover) {
		// mouse is over but we are not highlighted, so highlight us
		// TODO: IE 6
		el(this.globalID+'_highlight').style.backgroundImage = 'url(' + this.highlight_src + ')';
		this.hover = true;
	}
	else if (!mouseOver && this.hover) {
		// mouse is out but we are still highlighted, so turn off highlight
		el(this.globalID+'_highlight').style.backgroundImage = '';
		this.hover = false;
	}
}// TimerDisplay Sprite

function TimerDisplay() {
	// class constructor
	this.timerCache = 0;
	this.startTime = Math.floor(now_epoch());
	this.label = 'Time';
}

TimerDisplay.prototype = new CustomSprite();
TimerDisplay.prototype.type = 'TimerDisplay';
TimerDisplay.prototype.width = 50;
TimerDisplay.prototype.height = 30;

TimerDisplay.prototype.init = function() {
	// setup sprite
	CustomSprite.prototype.init.call(this);
}

TimerDisplay.prototype.logic = function() {
	// monitor clock and update accordingly
	if (gGame.state == 'game') {
		var now = Math.floor(now_epoch());
		if (now != this.timerCache) {
			var elapsed = now - this.startTime;
			var minutes = Math.floor( elapsed / 60 );
			var seconds = Math.floor( elapsed % 60 );
			if (seconds < 10) seconds = '0' + seconds;
		
			this.div.innerHTML = '<div class="hud_text" style="text-align:left">' + minutes + ':' + seconds + '</div>' + 
				'<div class="hud_label" style="text-align:left">' + this.label + '</div>';
		
			this.timerCache = now;
			
			session.game_elapsed = elapsed;
		} // time has changed, update
	}
}
