text: /*==============================================================================
  Init
  ==============================================================================*/
  $.init = function() {
  	$.setupStorage();
  	$.wrap = document.getElementById( 'wrap' );
  	$.wrapInner = document.getElementById( 'wrap-inner' );
  	$.cbg1 = document.getElementById( 'cbg1' );
  	$.cbg2 = document.getElementById( 'cbg2' );
  	$.cbg3 = document.getElementById( 'cbg3' );
  	$.cbg4 = document.getElementById( 'cbg4' );
  	$.cmg = document.getElementById( 'cmg' );
  	$.cfg = document.getElementById( 'cfg' );	
  	$.ctxbg1 = $.cbg1.getContext( '2d' );
  	$.ctxbg2 = $.cbg2.getContext( '2d' );
  	$.ctxbg3 = $.cbg3.getContext( '2d' );
  	$.ctxbg4 = $.cbg4.getContext( '2d' );
  	$.ctxmg = $.cmg.getContext( '2d' );
  	$.ctxfg = $.cfg.getContext( '2d' );
  	$.cw = $.cmg.width = $.cfg.width = window.innerWidth;
  	$.ch = $.cmg.height = $.cfg.height = window.innerHeight;
  	$.wrap.style.width = $.wrapInner.style.width = $.cw + 'px';
  	$.wrap.style.height = $.wrapInner.style.height = $.ch + 'px';
  	$.wrap.style.marginLeft = ( -$.cw / 2 ) - 10 + 'px';
  	$.wrap.style.marginTop = ( -$.ch / 2 ) - 10 + 'px';
  	$.ww = Math.floor( $.cw * 2 );
  	$.wh = Math.floor( $.ch * 2 );
  	$.cbg1.width = Math.floor( $.cw * 1.1 );
  	$.cbg1.height = Math.floor( $.ch * 1.1 );
  	$.cbg2.width = Math.floor( $.cw * 1.15 );
  	$.cbg2.height = Math.floor( $.ch * 1.15 );
  	$.cbg3.width = Math.floor( $.cw * 1.2 );
  	$.cbg3.height = Math.floor( $.ch * 1.2 );
  	$.cbg4.width = Math.floor( $.cw * 1.25 );
  	$.cbg4.height = Math.floor( $.ch * 1.25 );
  
  	$.screen = {
  		x: ( $.ww - $.cw ) / -2,
  		y: ( $.wh - $.ch ) / -2
  	};
  
  	$.mute = $.storage['mute'];
  	$.autofire = $.storage['autofire'];
  	$.slowEnemyDivider = 3;	
  
  	$.keys = {
  		state: {
  			up: 0,
  			down: 0,
  			left: 0,
  			right: 0,
  			f: 0,
  			m: 0,
  			p: 0
  		},
  		pressed: {
  			up: 0,
  			down: 0,
  			left: 0,
  			right: 0,
  			f: 0,
  			m: 0,
  			p: 0
  		}
  	};
  	$.okeys = {};
  	$.mouse = {
  		x: $.ww / 2,
  		y: $.wh / 2,
  		sx: 0,
  		sy: 0,
  		ax: window.innerWidth / 2,
  		ay: 0,
  		down: 0
  	};
  	$.buttons = [];
  
  	$.minimap = {		
  		x: 20,
  		y: $.ch - Math.floor( $.ch * 0.1 ) - 20,
  		width: Math.floor( $.cw * 0.1 ),
  		height: Math.floor( $.ch * 0.1 ),
  		scale: Math.floor( $.cw * 0.1 ) / $.ww,
  		color: 'hsla(0, 0%, 0%, 0.85)',
  		strokeColor: '#3a3a3a'
  	},	
  	$.cOffset = { 
  		left: 0, 
  		top: 0 
  	};
  	
  	$.levelCount = $.definitions.levels.length;
  	$.states = {};
  	$.state = '';
  	$.enemies = [];
  	$.bullets = [];
  	$.explosions = [];
  	$.powerups = [];	
  	$.particleEmitters = [];
  	$.textPops = [];
  	$.levelPops = [];
  	$.powerupTimers = [];
  
  	$.resizecb();
  	$.bindEvents();
  	$.setupStates();	
  	$.renderBackground1();
  	$.renderBackground2();
  	$.renderBackground3();
  	$.renderBackground4();
  	$.renderForeground();
  	$.renderFavicon();
  	$.setState( 'menu' );
  	$.loop();
  };
  
  /*==============================================================================
  Reset
  ==============================================================================*/
  $.reset = function() {
  	$.indexGlobal = 0;
  	$.dt = 1;
  	$.lt = 0;
  	$.elapsed = 0;
  	$.tick = 0;
  
  	$.gameoverTick = 0;
  	$.gameoverTickMax = 200;
  	$.gameoverExplosion = 0;
  
  	$.instructionTick = 0;
  	$.instructionTickMax = 400;
  
  	$.levelDiffOffset = 0;
  	$.enemyOffsetMod = 0;
  	$.slow = 0;
  
  	$.screen = {
  		x: ( $.ww - $.cw ) / -2,
  		y: ( $.wh - $.ch ) / -2
  	};
  	$.rumble = {
  		x: 0,
  		y: 0,
  		level: 0,
  		decay: 0.4
  	};	
  
  	$.mouse.down = 0;
  	$.mouse.up =1;
  	$.level = {
  		current: 0,
  		kills: 0,
  		killsToLevel: $.definitions.levels[ 0 ].killsToLevel,
  		distribution: $.definitions.levels[ 0 ].distribution,
  		distributionCount: $.definitions.levels[ 0 ].distribution.length
  	};
  
  	$.enemies.length = 0;
  	$.bullets.length = 0;
  	$.explosions.length = 0;
  	$.powerups.length = 0;
  	$.particleEmitters.length = 0;
  	$.textPops.length = 0;
  	$.levelPops.length = 0;
  	$.powerupTimers.length = 0;
  
  	for( var i = 0; i < $.definitions.powerups.length; i++ ) {
  		$.powerupTimers.push( 0 );
  	}
  
  	$.kills = 0;
  	$.bulletsFired = 0;
  	$.powerupsCollected = 0;
  	$.score = 0;
  
  	$.hero = new $.Hero();
  
  	$.levelPops.push( new $.LevelPop( {
  		level: 1
  	} ) );
  };
  
  /*==============================================================================
  Create Favicon
  ==============================================================================*/
  $.renderFavicon = function() {
  	var favicon = document.getElementById( 'favicon' ),
  		favc = document.createElement( 'canvas' ),
  		favctx = favc.getContext( '2d' ),
  		faviconGrid = [
  			[ 1, 1, 1, 1, 1,  ,  , 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
  			[ 1,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  , 1 ],
  			[ 1,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  , 1 ],
  			[ 1,  ,  ,  ,  , 1, 1,  ,  , 1, 1, 1, 1, 1,  , 0 ],
  			[ 1,  ,  ,  ,  , 1, 1,  ,  , 1, 1, 1, 1, 1,  , 0 ],
  			[ 1,  ,  ,  ,  , 1, 1,  ,  , 1, 1,  ,  ,  ,  , 1 ],
  			[ 1,  ,  ,  ,  , 1, 1,  ,  , 1, 1,  ,  ,  ,  , 1 ],
  			[ 1,  ,  ,  ,  , 1, 1,  ,  , 1, 1,  ,  ,  ,  , 1 ],
  			[ 1,  ,  ,  ,  , 1, 1,  ,  , 1, 1,  ,  ,  ,  , 1 ],
  			[ 1,  ,  ,  ,  , 1, 1,  ,  , 1, 1,  ,  ,  ,  , 1 ],
  			[ 1,  ,  ,  ,  , 1, 1,  ,  , 1, 1,  ,  ,  ,  , 1 ],
  			[  ,  , 1, 1, 1, 1, 1,  ,  , 1, 1,  ,  ,  ,  , 1 ],
  			[  ,  , 1, 1, 1, 1, 1,  ,  , 1, 1,  ,  ,  ,  , 1 ],
  			[ 1,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  , 1 ],
  			[ 1,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  , 1 ],
  			[ 1, 1, 1, 1, 1, 1, 1, 1, 1,  ,  , 1, 1, 1, 1, 1 ]
  		];
  	favc.width = favc.height = 16;
  	favctx.beginPath();
  	for( var y = 0; y < 16; y++ ) {
  		for( var x = 0; x < 16; x++ ) {
  			if( faviconGrid[ y ][ x ] === 1 ) {
  				favctx.rect( x, y, 1, 1 );
  			}
  		}
  	}
  	favctx.fill();
  	//favicon.href = favc.toDataURL();
  };
  
  /*==============================================================================
  Render Backgrounds
  ==============================================================================*/
  $.renderBackground1 = function() {
  	var gradient = $.ctxbg1.createRadialGradient( $.cbg1.width / 2, $.cbg1.height / 2, 0, $.cbg1.width / 2, $.cbg1.height / 2, $.cbg1.height );
  	gradient.addColorStop( 0, 'hsla(0, 0%, 100%, 0.1)' );
  	gradient.addColorStop( 0.65, 'hsla(0, 0%, 100%, 0)' );
  	$.ctxbg1.fillStyle = gradient;
  	$.ctxbg1.fillRect( 0, 0, $.cbg1.width, $.cbg1.height );
  
  	var i = 2000;
  	while( i-- ) {
  		$.util.fillCircle( $.ctxbg1, $.util.rand( 0, $.cbg1.width ), $.util.rand( 0, $.cbg1.height ), $.util.rand( 0.2, 0.5 ), 'hsla(0, 0%, 100%, ' + $.util.rand( 0.05, 0.2 ) + ')' );
  	}
  
  	var i = 800;
  	while( i-- ) {
  		$.util.fillCircle( $.ctxbg1, $.util.rand( 0, $.cbg1.width ), $.util.rand( 0, $.cbg1.height ), $.util.rand( 0.1, 0.8 ), 'hsla(0, 0%, 100%, ' + $.util.rand( 0.05, 0.5 ) + ')' );
  	}
  }
  
  $.renderBackground2 = function() {
  	var i = 80;
  	while( i-- ) {
  		$.util.fillCircle( $.ctxbg2, $.util.rand( 0, $.cbg2.width ), $.util.rand( 0, $.cbg2.height ), $.util.rand( 1, 2 ), 'hsla(0, 0%, 100%, ' + $.util.rand( 0.05, 0.15 ) + ')' );
  	}
  }
  
  $.renderBackground3 = function() {
  	var i = 40;
  	while( i-- ) {
  		$.util.fillCircle( $.ctxbg3, $.util.rand( 0, $.cbg3.width ), $.util.rand( 0, $.cbg3.height ), $.util.rand( 1, 2.5 ), 'hsla(0, 0%, 100%, ' + $.util.rand( 0.05, 0.1 ) + ')' );
  	}
  }
  
  $.renderBackground4 = function() {
  	var size = 50;
  	$.ctxbg4.fillStyle = 'hsla(0, 0%, 50%, 0.05)';
  	var i = Math.round( $.cbg4.height / size );
  	while( i-- ) {
  		$.ctxbg4.fillRect( 0, i * size + 25, $.cbg4.width, 1 );
  	}
  	i = Math.round( $.cbg4.width / size );
  	while( i-- ) {
  		$.ctxbg4.fillRect( i * size, 0, 1, $.cbg4.height );
  	}
  }
  
  /*==============================================================================
  Render Foreground
  ==============================================================================*/
  $.renderForeground = function() {
  	var gradient = $.ctxfg.createRadialGradient( $.cw / 2, $.ch / 2, $.ch / 3, $.cw / 2, $.ch / 2, $.ch );
  	gradient.addColorStop( 0, 'hsla(0, 0%, 0%, 0)' );
  	gradient.addColorStop( 1, 'hsla(0, 0%, 0%, 0.5)' );
  	$.ctxfg.fillStyle = gradient;
  	$.ctxfg.fillRect( 0, 0, $.cw, $.ch );
  
  	$.ctxfg.fillStyle = 'hsla(0, 0%, 50%, 0.1)';
  	var i = Math.round( $.ch / 2 );
  	while( i-- ) {
  		$.ctxfg.fillRect( 0, i * 2, $.cw, 1 );
  	}
  
  	var gradient2 = $.ctxfg.createLinearGradient( $.cw, 0, 0, $.ch );
  	gradient2.addColorStop( 0, 'hsla(0, 0%, 100%, 0.04)' );
  	gradient2.addColorStop( 0.75, 'hsla(0, 0%, 100%, 0)' );
  	$.ctxfg.beginPath();
  	$.ctxfg.moveTo( 0, 0 );
  	$.ctxfg.lineTo( $.cw, 0 );
  	$.ctxfg.lineTo( 0, $.ch );
  	$.ctxfg.closePath();
  	$.ctxfg.fillStyle = gradient2;
  	$.ctxfg.fill();
  }
  
  /*==============================================================================
  User Interface / UI / GUI / Minimap
  ==============================================================================*/
  
  $.renderInterface = function() {
  	/*==============================================================================
  	Powerup Timers
  	==============================================================================*/
  		for( var i = 0; i < $.definitions.powerups.length; i++ ) {
  			var powerup = $.definitions.powerups[ i ],
  				powerupOn = ( $.powerupTimers[ i ] > 0 );
  			$.ctxmg.beginPath();
  			var powerupText = $.text( {
  				ctx: $.ctxmg,
  				x: $.minimap.x + $.minimap.width + 90,
  				y: $.minimap.y + ( i * 12 ),
  				text: powerup.title,
  				hspacing: 1,
  				vspacing: 1,
  				halign: 'right',
  				valign: 'top',
  				scale: 1,
  				snap: 1,
  				render: 1
  			} );
  			if( powerupOn ) {
  				$.ctxmg.fillStyle = 'hsla(0, 0%, 100%, ' + ( 0.25 + ( ( $.powerupTimers[ i ] / 300 ) * 0.75 ) ) + ')';
  			} else {
  				$.ctxmg.fillStyle = 'hsla(0, 0%, 100%, 0.25)';
  			}
  			$.ctxmg.fill();
  			if( powerupOn ) {
  				var powerupBar = {
  					x: powerupText.ex + 5,
  					y: powerupText.sy,
  					width: 110,
  					height: 5
  				};
  				$.ctxmg.fillStyle = 'hsl(' + powerup.hue + ', ' + powerup.saturation + '%, ' + powerup.lightness + '%)';
  				$.ctxmg.fillRect( powerupBar.x, powerupBar.y, ( $.powerupTimers[ i ] / 300 ) * powerupBar.width, powerupBar.height );
  			}
  		}
  
  		/*==============================================================================
  		Instructions
  		
  		/*==============================================================================
  		Slow Enemies Screen Cover
  		==============================================================================*/
  		if( $.powerupTimers[ 1 ] > 0 ) {
  			$.ctxmg.fillStyle = 'hsla(200, 100%, 20%, 0.05)';
  			$.ctxmg.fillRect( 0, 0, $.cw, $.ch );
  		}
  
  	/*==============================================================================
  	Health
  	==============================================================================*/
  	$.ctxmg.beginPath();
  	var healthText = $.text( {
  		ctx: $.ctxmg,
  		x: 20,
  		y: 20,
  		text: 'HEALTH',
  		hspacing: 1,
  		vspacing: 1,
  		halign: 'top',
  		valign: 'left',
  		scale: 2,
  		snap: 1,
  		render: 1
  	} );
  	$.ctxmg.fillStyle = 'hsla(0, 0%, 100%, 0.5)';
  	$.ctxmg.fill();
  	var healthBar = {
  		x: healthText.ex + 10,
  		y: healthText.sy,
  		width: ($.cw*0.065),
  		height: 10
  	};
  	$.ctxmg.fillStyle = 'hsla(0, 0%, 20%, 1)';
  	$.ctxmg.fillRect( healthBar.x, healthBar.y, healthBar.width, healthBar.height );
  	$.ctxmg.fillStyle = 'hsla(0, 0%, 100%, 0.25)';
  	$.ctxmg.fillRect( healthBar.x, healthBar.y, healthBar.width, healthBar.height / 2 );
  	$.ctxmg.fillStyle = 'hsla(' + $.hero.life * 120 + ', 100%, 40%, 1)';
  	$.ctxmg.fillRect( healthBar.x, healthBar.y, $.hero.life * healthBar.width, healthBar.height );
  	$.ctxmg.fillStyle = 'hsla(' + $.hero.life * 120 + ', 100%, 75%, 1)';
  	$.ctxmg.fillRect( healthBar.x, healthBar.y, $.hero.life * healthBar.width, healthBar.height / 2 );
  	
  	if( $.hero.takingDamage && $.hero.life > 0.01 ) {
  		$.particleEmitters.push( new $.ParticleEmitter( {
  			x: -$.screen.x + healthBar.x + $.hero.life * healthBar.width,
  			y: -$.screen.y + healthBar.y + healthBar.height / 2,
  			count: 1,
  			spawnRange: 2,
  			friction: 0.85,
  			minSpeed: 2,
  			maxSpeed: 20,
  			minDirection: $.pi / 2 - 0.2,
  			maxDirection: $.pi / 2 + 0.2,
  			hue: $.hero.life * 120,
  			saturation: 100
  		} ) );
  	}
  
  	/*==============================================================================
  	Progress
  	==============================================================================*/
  	$.ctxmg.beginPath();
  	var progressText = $.text( {
  		ctx: $.ctxmg,
  		x: healthBar.x + healthBar.width + 40,
  		y: 20,
  		text: 'PROGRESS',
  		hspacing: 1,
  		vspacing: 1,
  		halign: 'top',
  		valign: 'left',
  		scale: 2,
  		snap: 1,
  		render: 1
  	} );
  	$.ctxmg.fillStyle = 'hsla(0, 0%, 100%, 0.5)';
  	$.ctxmg.fill();
  	var progressBar = {
  		x: progressText.ex + 10,
  		y: progressText.sy,
  		width: healthBar.width,
  		height: healthBar.height
  	};
  	$.ctxmg.fillStyle = 'hsla(0, 0%, 20%, 1)';
  	$.ctxmg.fillRect( progressBar.x, progressBar.y, progressBar.width, progressBar.height );
  	$.ctxmg.fillStyle = 'hsla(0, 0%, 100%, 0.25)';
  	$.ctxmg.fillRect( progressBar.x, progressBar.y, progressBar.width, progressBar.height / 2 );
  	$.ctxmg.fillStyle = 'hsla(0, 0%, 50%, 1)';
  	$.ctxmg.fillRect( progressBar.x, progressBar.y, ( $.level.kills / $.level.killsToLevel ) * progressBar.width, progressBar.height );
  	$.ctxmg.fillStyle = 'hsla(0, 0%, 100%, 1)';
  	$.ctxmg.fillRect( progressBar.x, progressBar.y, ( $.level.kills / $.level.killsToLevel ) * progressBar.width, progressBar.height / 2 );
  	
  	if( $.level.kills == $.level.killsToLevel ) {
  		$.particleEmitters.push( new $.ParticleEmitter( {
  			x: -$.screen.x + progressBar.x + progressBar.width,
  			y: -$.screen.y + progressBar.y + progressBar.height / 2,
  			count: 30,
  			spawnRange: 5,
  			friction: 0.95,
  			minSpeed: 2,
  			maxSpeed: 25,
  			minDirection: 0,
  			minDirection: $.pi / 2 - $.pi / 4,
  			maxDirection: $.pi / 2 + $.pi / 4,
  			hue: 0,
  			saturation: 0
  		} ) );
  	}
  
  	/*==============================================================================
  	Score
  	==============================================================================*/
  	$.ctxmg.beginPath();
  	var scoreLabel = $.text( {
  		ctx: $.ctxmg,
  		x: progressBar.x + progressBar.width + 40,
  		y: 20,
  		text: 'SCORE',
  		hspacing: 1,
  		vspacing: 1,
  		halign: 'top',
  		valign: 'left',
  		scale: 2,
  		snap: 1,
  		render: 1
  	} );
  	$.ctxmg.fillStyle = 'hsla(0, 0%, 100%, 0.5)';
  	$.ctxmg.fill();
  
  	$.ctxmg.beginPath();
  	var scoreText = $.text( {
  		ctx: $.ctxmg,
  		x: scoreLabel.ex + 10,
  		y: 20,
  		text: $.util.pad( $.score, 6 ),
  		hspacing: 1,
  		vspacing: 1,
  		halign: 'top',
  		valign: 'left',
  		scale: 2,
  		snap: 1,
  		render: 1
  	} );
  	$.ctxmg.fillStyle = 'hsla(0, 0%, 100%, 1)';
  	$.ctxmg.fill();
  
  	$.ctxmg.beginPath();
  	var bestLabel = $.text( {
  		ctx: $.ctxmg,
  		x: scoreText.ex + 40,
  		y: 20,
  		text: 'BEST',
  		hspacing: 1,
  		vspacing: 1,
  		halign: 'top',
  		valign: 'left',
  		scale: 2,
  		snap: 1,
  		render: 1
  	} );
  	$.ctxmg.fillStyle = 'hsla(0, 0%, 100%, 0.5)';
  	$.ctxmg.fill();
  
  	$.ctxmg.beginPath();
  	var bestText = $.text( {
  		ctx: $.ctxmg,
  		x: bestLabel.ex + 10,
  		y: 20,
  		text: $.util.pad( Math.max( $.storage['score'], $.score ), 6 ),
  		hspacing: 1,
  		vspacing: 1,
  		halign: 'top',
  		valign: 'left',
  		scale: 2,
  		snap: 1,
  		render: 1
  	} );
  	$.ctxmg.fillStyle = 'hsla(0, 0%, 100%, 1)';
  	$.ctxmg.fill();
  };
  
  $.renderMinimap = function() {
  	$.ctxmg.fillStyle = $.minimap.color;
  	$.ctxmg.fillRect( $.minimap.x, $.minimap.y, $.minimap.width, $.minimap.height );
  
  	$.ctxmg.fillStyle = 'hsla(0, 0%, 100%, 0.1)';
  	$.ctxmg.fillRect( 
  		Math.floor( $.minimap.x + -$.screen.x * $.minimap.scale ), 
  		Math.floor( $.minimap.y + -$.screen.y * $.minimap.scale ), 
  		Math.floor( $.cw * $.minimap.scale ), 
  		Math.floor( $.ch * $.minimap.scale )
  	);
  
  	//$.ctxmg.beginPath();
  	for( var i = 0; i < $.enemies.length; i++ ){
  		var enemy = $.enemies[ i ],
  			x = $.minimap.x + Math.floor( enemy.x * $.minimap.scale ),
  			y = $.minimap.y + Math.floor( enemy.y * $.minimap.scale );
  		if( $.util.pointInRect( x + 1, y + 1, $.minimap.x, $.minimap.y, $.minimap.width, $.minimap.height ) ) {
  			//$.ctxmg.rect( x, y, 2, 2 );
  			$.ctxmg.fillStyle = 'hsl(' + enemy.hue + ', ' + enemy.saturation + '%, 50%)';
  			$.ctxmg.fillRect( x, y, 2, 2 );
  		}
  	}
  	//$.ctxmg.fillStyle = '#f00';
  	//$.ctxmg.fill();
  
  	$.ctxmg.beginPath();
  	for( var i = 0; i < $.bullets.length; i++ ){
  		var bullet = $.bullets[ i ],
  			x = $.minimap.x + Math.floor( bullet.x * $.minimap.scale ),
  			y = $.minimap.y + Math.floor( bullet.y * $.minimap.scale );
  		if( $.util.pointInRect( x, y, $.minimap.x, $.minimap.y, $.minimap.width, $.minimap.height ) ) {
  			$.ctxmg.rect( x, y, 1, 1 );
  		}
  	}
  	$.ctxmg.fillStyle = '#fff';
  	$.ctxmg.fill();
  
  	$.ctxmg.fillStyle = $.hero.fillStyle;
  	$.ctxmg.fillRect( $.minimap.x + Math.floor( $.hero.x * $.minimap.scale ), $.minimap.y + Math.floor( $.hero.y * $.minimap.scale ), 2, 2 );
  
  	$.ctxmg.strokeStyle = $.minimap.strokeColor;
  	$.ctxmg.strokeRect( $.minimap.x - 0.5, $.minimap.y - 0.5, $.minimap.width + 1, $.minimap.height + 1 );
  };
  
  /*==============================================================================
  Enemy Spawning
  ==============================================================================*/
  $.getSpawnCoordinates = function( radius ) {
  	var quadrant = Math.floor( $.util.rand( 0, 4 ) ),
  		x,
  		y,
  		start;
  	
  	if( quadrant === 0){
  		x = $.util.rand( 0, $.ww );
  		y = -radius;
  		start = 'top';
  	} else if( quadrant === 1 ){
  		x = $.ww + radius;
  		y = $.util.rand( 0, $.wh );
  		start = 'right';
  	} else if( quadrant === 2 ) {
  		x = $.util.rand( 0, $.ww );
  		y = $.wh + radius;
  		start = 'bottom';
  	} else {
  		x = -radius;
  		y = $.util.rand( 0, $.wh );
  		start = 'left';
  	}
  
  	return { x: x, y: y, start: start };
  };
  
  $.spawnEnemy = function( type ) {
  	var params = $.definitions.enemies[ type ],
  		coordinates = $.getSpawnCoordinates( params.radius );
  	params.x = coordinates.x;
  	params.y = coordinates.y;
  	params.start = coordinates.start;
  	params.type = type;
  	return new $.Enemy( params );
  };
  
  $.spawnEnemies = function() {
  	var floorTick = Math.floor( $.tick );
  	for( var i = 0; i < $.level.distributionCount; i++ ) {
  		var timeCheck = $.level.distribution[ i ];		
  		if( $.levelDiffOffset > 0 ){
  			timeCheck = Math.max( 1, timeCheck - ( $.levelDiffOffset * 2) );
  		}
  		if( floorTick % timeCheck === 0 ) {
  			$.enemies.push( $.spawnEnemy( i ) );
  		}
  	}
  };
  
  /*==============================================================================
  Events
  ==============================================================================*/
  $.mousemovecb = function( e ) {
  	e.preventDefault();
  	$.mouse.ax = e.pageX;
  	$.mouse.ay = e.pageY;
  	$.mousescreen();
  };
  
  $.touchmovecb = function(e){
  	if($.state !='play'){
  	$.mouse.ax = e.targetTouches[0].pageX;
  	$.mouse.ay = e.targetTouches[0].pageY;
  	$.mousescreen();
  	}
  	else{
  		$.mouse.ax=window.innerWidth/2;
  		$.mouse.ay=window.innerHeight/2;
  		Joystick_rightAction();
  	}
  }
  
  
  $.touchstartcb = function(e){
  	//e.preventDefault();
  	if($.state!='play'){
  	$.mouse.down = 1;
  	$.mouse.up=0;
  	$.mouse.ax = e.targetTouches[0].pageX;
  	$.mouse.ay = e.targetTouches[0].pageY;
  	$.mousescreen();
  	}
  }
  
  $.touchendcb = function( e ) {
  	//e.preventDefault();
  	if(!joystick_right._pressed){
  	$.mouse.up = 1;
  	$.mouse.down = 0;
  	}
  	$.keys.state.up = 0;
  	$.keys.state.down = 0;
  	$.keys.state.right = 0;
  	$.keys.state.left = 0;
  };
  
  $.mousescreen = function() {
  	$.mouse.sx = $.mouse.ax - $.cOffset.left;
  	$.mouse.sy = $.mouse.ay - $.cOffset.top;
  	$.mouse.x = $.mouse.sx - $.screen.x;
  	$.mouse.y = $.mouse.sy - $.screen.y;
  };
  
  $.mousedowncb = function( e ) {
  	e.preventDefault();
  	$.mouse.down = 1;
  	$.mouse.up=0;
  };
  
  $.mouseupcb = function( e ) {
  	e.preventDefault();
  	$.mouse.up = 1;
  };
  
  /*
  window.on('swipeleft',  function(){ $.keys.state.left = 1;  })
                .on('swiperight', function(){  $.keys.state.right = 1; })
                .on('swipeup',    function(){ $.keys.state.up = 1; })
  			  .on('swipedown',  function(){  $.keys.state.down = 1;  });
  		  
  
  var joystick = new VirtualJoystick({
  	mouseSupport	: true,
  	limitStickTravel: true,
  	stickRadius	: 50
  });
  */
  
  // one on the right of the screen
  var joystick_left	= new VirtualJoystick({
  	container	: document.body,
  	strokeStyle	: 'orange',
  	limitStickTravel: true,
  	stickRadius	: 50
  });
  joystick_left.addEventListener('touchStartValidation', function(event){
  	var touch	= event.changedTouches[0];
  	if( touch.pageX > window.innerWidth/2 ){
  		return false;
  	}	
  	return true;
  });
  
  var joystick_right	= new VirtualJoystick({
  	container	: document.body,
  	strokeStyle	: 'cyan',
  	limitStickTravel: true,
  	stickRadius	: 50		
  });
  joystick_right.addEventListener('touchStartValidation', function(event){
  	var touch	= event.changedTouches[0];
  	if( touch.pageX <= window.innerWidth/2 )
  	{
  		return false;
  	}
  	return true;
  });
  
  
  
  
  
  $.keydowncb = function( e ) {
  	var e = ( e.keyCode ? e.keyCode : e.which );
  	if( e === 38 || e === 87 ){ $.keys.state.up = 1; }
  	if( e === 39 || e === 68 ){ $.keys.state.right = 1; }
  	if( e === 40 || e === 83 ){ $.keys.state.down = 1; }
  	if( e === 37 || e === 65 ){ $.keys.state.left = 1; }
  	if( e === 70 ){ $.keys.state.f = 1; }
  	if( e === 77 ){ $.keys.state.m = 1; }
  	if( e === 80 ){ $.keys.state.p = 1; }
  }
  
  $.keyupcb = function( e ) {
  	var e = ( e.keyCode ? e.keyCode : e.which );
  	if( e === 38 || e === 87 ){ $.keys.state.up = 0; }
  	if( e === 39 || e === 68 ){ $.keys.state.right = 0; }
  	if( e === 40 || e === 83 ){ $.keys.state.down = 0; }
  	if( e === 37 || e === 65 ){ $.keys.state.left = 0; }
  	if( e === 70 ){ $.keys.state.f = 0; }
  	if( e === 77 ){ $.keys.state.m = 0; }
  	if( e === 80 ){ $.keys.state.p = 0; }
  }
  
  $.resizecb = function( e ) {
  	var rect = $.cmg.getBoundingClientRect();
  	$.cOffset = {
  		left: rect.left,
  		top: rect.top
  	}
  }
  
  $.blurcb = function(e) {
  	e.preventDefault();
  	if( $.state == 'play' ){
  		$.setState( 'pause' );
  	}
  	else if($.state=='menu'){
  		if(confirm("Quit??")){
  			navigator.app.exitApp();
  		}
  	}
  }
  
  $.bindEvents = function() {
  	window.addEventListener( 'touchmove', $.touchmovecb,false );
  	window.addEventListener( 'touchstart', $.touchstartcb, false );
  	window.addEventListener( 'touchend', $.touchendcb ,false);
  	window.addEventListener( 'keydown', $.keydowncb );
  	window.addEventListener( 'keyup', $.keyupcb );
  	window.addEventListener( 'resize', $.resizecb );
  	//window.addEventListener( 'backbutton', $.blurcb,false );
  };
  
  /*==============================================================================
  Miscellaneous
  ==============================================================================*/
  $.clearScreen = function() {
  	$.ctxmg.clearRect( 0, 0, $.cw, $.ch );
  };
  
  $.updateDelta = function() { 
  	var now = Date.now();
  	$.dt = ( now - $.lt ) / ( 1000 / 60 );
  	$.dt = ( $.dt < 0 ) ? 0.001 : $.dt;
  	$.dt = ( $.dt > 10 ) ? 10 : $.dt;
  	$.lt = now;
  	$.elapsed += $.dt;
  };
  
  $.updateScreen = function() {
  	var xSnap,
  		xModify, 
  		ySnap,
  		yModify;
  
  	if( $.hero.x < $.cw / 2 ) {
  		xModify = $.hero.x / $.cw;
  	} else if( $.hero.x > $.ww - $.cw / 2 ) {
  		xModify = 1 - ( $.ww - $.hero.x ) / $.cw;
  	} else {
  		xModify = 0.5;		
  	}
  
  	if( $.hero.y < $.ch / 2 ) {
  		yModify = $.hero.y / $.ch;
  	} else if( $.hero.y > $.wh - $.ch / 2 ) {
  		yModify = 1 - ( $.wh - $.hero.y ) / $.ch;
  	} else {
  		yModify = 0.5;		
  	}
  
  	xSnap = ( ( $.cw * xModify - $.hero.x ) - $.screen.x ) / 30;
  	ySnap = ( ( $.ch * yModify - $.hero.y ) - $.screen.y ) / 30;	
  
  	// ease to new coordinates
  	$.screen.x += xSnap * $.dt;
  	$.screen.y += ySnap * $.dt;
  
  	// update rumble levels, keep X and Y changes consistent, apply rumble
  	if( $.rumble.level > 0 ) {
  		$.rumble.level -= $.rumble.decay;
  		$.rumble.level = ( $.rumble.level < 0 ) ? 0 : $.rumble.level;			
  		$.rumble.x = $.util.rand( -$.rumble.level, $.rumble.level );
  		$.rumble.y = $.util.rand( -$.rumble.level, $.rumble.level );
  	} else {
  		$.rumble.x = 0;
  		$.rumble.y = 0;
  	}
  
  	//$.screen.x -= $.rumble.x;
  	//$.screen.y -= $.rumble.y;
  
  	// animate background canvas
  	$.cbg1.style.marginLeft = 
  		-( ( $.cbg1.width - $.cw ) / 2 ) // half the difference from bg to viewport
  		- ( ( $.cbg1.width - $.cw ) / 2 ) // half the diff again, modified by a percentage below
  		* ( ( -$.screen.x - ( $.ww - $.cw ) / 2 ) / ( ( $.ww - $.cw ) / 2) ) // viewport offset applied to bg
  		- $.rumble.x + 'px';
  	$.cbg1.style.marginTop = 
  		-( ( $.cbg1.height - $.ch ) / 2 ) 
  		- ( ( $.cbg1.height - $.ch ) / 2 )
  		* ( ( -$.screen.y - ( $.wh - $.ch ) / 2 ) / ( ( $.wh - $.ch ) / 2) ) 
  		- $.rumble.y + 'px';
  	$.cbg2.style.marginLeft = 
  		-( ( $.cbg2.width - $.cw ) / 2 ) // half the difference from bg to viewport
  		- ( ( $.cbg2.width - $.cw ) / 2 ) // half the diff again, modified by a percentage below
  		* ( ( -$.screen.x - ( $.ww - $.cw ) / 2 ) / ( ( $.ww - $.cw ) / 2) ) // viewport offset applied to bg
  		- $.rumble.x + 'px';
  	$.cbg2.style.marginTop = 
  		-( ( $.cbg2.height - $.ch ) / 2 ) 
  		- ( ( $.cbg2.height - $.ch ) / 2 )
  		* ( ( -$.screen.y - ( $.wh - $.ch ) / 2 ) / ( ( $.wh - $.ch ) / 2) ) 
  		- $.rumble.y + 'px';
  	$.cbg3.style.marginLeft = 
  		-( ( $.cbg3.width - $.cw ) / 2 ) // half the difference from bg to viewport
  		- ( ( $.cbg3.width - $.cw ) / 2 ) // half the diff again, modified by a percentage below
  		* ( ( -$.screen.x - ( $.ww - $.cw ) / 2 ) / ( ( $.ww - $.cw ) / 2) ) // viewport offset applied to bg
  		- $.rumble.x + 'px';
  	$.cbg3.style.marginTop = 
  		-( ( $.cbg3.height - $.ch ) / 2 ) 
  		- ( ( $.cbg3.height - $.ch ) / 2 )
  		* ( ( -$.screen.y - ( $.wh - $.ch ) / 2 ) / ( ( $.wh - $.ch ) / 2) ) 
  		- $.rumble.y + 'px';
  	$.cbg4.style.marginLeft = 
  		-( ( $.cbg4.width - $.cw ) / 2 ) // half the difference from bg to viewport
  		- ( ( $.cbg4.width - $.cw ) / 2 ) // half the diff again, modified by a percentage below
  		* ( ( -$.screen.x - ( $.ww - $.cw ) / 2 ) / ( ( $.ww - $.cw ) / 2) ) // viewport offset applied to bg
  		- $.rumble.x + 'px';
  	$.cbg4.style.marginTop = 
  		-( ( $.cbg4.height - $.ch ) / 2 ) 
  		- ( ( $.cbg4.height - $.ch ) / 2 )
  		* ( ( -$.screen.y - ( $.wh - $.ch ) / 2 ) / ( ( $.wh - $.ch ) / 2) ) 
  		- $.rumble.y + 'px';
  
  	$.mousescreen();
  	Joystick_leftAction();
  };
  
  function Joystick_rightAction(){
  	if(joystick_right._pressed){
  	$.mouse.ax += joystick_right.deltaX() * 10;
  	$.mouse.ay += joystick_right.deltaY()*10;
  	
  	$.mousescreen();
  	$.mouse.down=1;
  	}
  
  }
  
  function Joystick_leftAction(){
  	if( joystick_left.right() ){
  		$.keys.state.right =true;
  		$.keys.state.left =false;
  		}
  		if( joystick_left.left() ){
  			$.keys.state.left =true;
  			$.keys.state.right =false;
  		}
  		if( joystick_left.up() ){
  			$.keys.state.up =true;
  			$.keys.state.down =false;
  		}
  		if( joystick_left.down() ){
  			$.keys.state.down =true;
  			$.keys.state.up =false;
  		}
  
  }
  
  
  $.updateLevel = function() {
  	if( $.level.kills >= $.level.killsToLevel ) {
  		if( $.level.current + 1 < $.levelCount ){
  			$.level.current++;
  			$.level.kills = 0;
  			$.level.killsToLevel = $.definitions.levels[ $.level.current ].killsToLevel;
  			$.level.distribution = $.definitions.levels[ $.level.current ].distribution;
  			$.level.distributionCount = $.level.distribution.length;
  		} else {
  			$.level.current++;
  			$.level.kills = 0;
  			// no more level definitions, so take the last level and increase the spawn rate slightly
  			//for( var i = 0; i < $.level.distributionCount; i++ ) {
  				//$.level.distribution[ i ] = Math.max( 1, $.level.distribution[ i ] - 5 );
  			//}
  		}
  		$.levelDiffOffset = $.level.current + 1 - $.levelCount;
  		$.levelPops.push( new $.LevelPop( {
  			level: $.level.current + 1
  		} ) );
  	}
  };
  
  $.updatePowerupTimers = function() {
  	// HEALTH
  	if( $.powerupTimers[ 0 ] > 0 ){
  		if( $.hero.life < 1 ) {
  			$.hero.life += 0.005;
  		}
  		if( $.hero.life > 1 ) {
  			$.hero.life = 1;
  		}
  		$.powerupTimers[ 0 ] -= $.dt;
  	}
  
  	// SLOW ENEMIES
  	if( $.powerupTimers[ 1 ] > 0 ){
  		$.slow = 1;
  		$.powerupTimers[ 1 ] -= $.dt/2;
  	} else {
  		$.slow = 0;
  	}
  
  	// FAST SHOT
  	if( $.powerupTimers[ 2 ] > 0 ){
  		$.hero.weapon.fireRate = 2;
  		$.hero.weapon.bullet.speed = 14;
  		$.powerupTimers[ 2 ] -= $.dt/($.level.current);
  	} else {
  		$.hero.weapon.fireRate = 5;
  		$.hero.weapon.bullet.speed = 10;
  	}
  
  	// TRIPLE SHOT
  	if( $.powerupTimers[ 3 ] > 0 ){
  		$.hero.weapon.count = 3;
  		$.powerupTimers[ 3 ] -= $.dt/($.level.current * 2);
  	} else {
  		$.hero.weapon.count = 1;
  	}
  
  	// PIERCE SHOT
  	if( $.powerupTimers[ 4 ] > 0 ){
  		$.hero.weapon.bullet.piercing = 1;
  		$.powerupTimers[ 4 ] -= $.dt/($.level.current * 1.5);
  	} else {
  		$.hero.weapon.bullet.piercing = 0;
  	}
  };	
  
  $.spawnPowerup = function( x, y ) {
  	if( Math.random() < 0.1 ) {
  		var min = ( $.hero.life < 0.9 ) ? 0 : 1,
  			type = Math.floor( $.util.rand( min, $.definitions.powerups.length ) ),
  			params = $.definitions.powerups[ type ];
  		params.type = type;
  		params.x = x;
  		params.y = y;
  		$.powerups.push( new $.Powerup( params ) );
  	}
  };
  
  /*==============================================================================
  States
  ==============================================================================*/
  $.setState = function( state ) {
  	// handle clean up between states
  	$.buttons.length = 0;
  
  	if( state == 'menu' ) {
  		$.mouse.down = 0;		
  		$.mouse.ax = 0;
  		$.mouse.ay = 0;
  
  		$.reset();
  
  		var playButton = new $.Button( {
  			x: $.cw / 2 + 1,
  			y: $.ch / 2 - 24,
  			lockedWidth: 299,
  			lockedHeight: 49,
  			scale: 3,
  			title: 'PLAY',
  			action: function() {
  				$.reset();
  				$.audio.play( 'levelup' );
  				$.setState( 'play' );
  			}
  		} );
  		$.buttons.push( playButton );
  
  		var statsButton = new $.Button( {
  			x: $.cw / 2 + 1,
  			y: playButton.ey + 25,
  			lockedWidth: 299,
  			lockedHeight: 49,
  			scale: 3,
  			title: 'STATS',
  			action: function() {
  				$.setState( 'stats' );
  			}
  		} );
  		$.buttons.push( statsButton );
  	}
  
  	if( state == 'stats' ) {
  		$.mouse.down = 0;
  	
  		var clearButton = new $.Button( {
  			x: 150,
  			y: $.ch-50,
  			lockedWidth: 299,
  			lockedHeight: 49,
  			scale: 3,
  			title: 'CLEAR DATA',
  			action: function() {
  				$.mouse.down = 0;				
  				if( window.confirm( 'Are you sure you want to clear all locally stored game data? This cannot be undone.') ) {
  					$.clearStorage();
  					$.mouse.down = 0;
  				}
  			}
  		} );
  		$.buttons.push( clearButton );
  
  		var menuButton = new $.Button( {
  			x: $.cw -150,
  			y: $.ch-50,
  			lockedWidth: 299,
  			lockedHeight: 49,
  			scale: 3,
  			title: 'MENU',
  			action: function() {
  				$.setState( 'menu' );
  			}
  		} );
  		$.buttons.push( menuButton );	
  	}
  
  	
  	if( state == 'pause' ) {
  		$.mouse.down = 0;
  		$.screenshot = $.ctxmg.getImageData( 0, 0, $.cw, $.ch );
  		var resumeButton = new $.Button( {
  			x: $.cw / 2 + 1,
  			y: $.ch / 2 + 26,
  			lockedWidth: 299,
  			lockedHeight: 49,
  			scale: 3,
  			title: 'RESUME',
  			action: function() {
  				$.lt = Date.now() + 1000;
  				$.setState( 'play' );
  			}
  		} );
  		$.buttons.push( resumeButton );
  
  		var menuButton = new $.Button( {
  			x: $.cw / 2 + 1,
  			y: resumeButton.ey + 25,
  			lockedWidth: 299,
  			lockedHeight: 49,
  			scale: 3,
  			title: 'MENU',
  			action: function() {
  				$.mouse.down = 0;
  				if( window.confirm( 'Are you sure you want to end this game and return to the menu?') ) {
  					$.mousescreen();
  					$.setState( 'menu' );
  				}			
  			}
  		} );
  		$.buttons.push( menuButton );
  	}
  
  	if( state == 'gameover' ) {
  		$.mouse.down = 0;
  	
  		$.screenshot = $.ctxmg.getImageData( 0, 0, $.cw, $.ch );
  		var resumeButton = new $.Button( {
  			x: 150,
  			y: $.ch-50,
  			lockedWidth: 299,
  			lockedHeight: 49,
  			scale: 3,
  			title: 'PLAY AGAIN',
  			action: function() {
  				$.reset();
  				$.audio.play( 'levelup' );
  				$.setState( 'play' );
  			}
  		} );
  		$.buttons.push( resumeButton );
  
  		var menuButton = new $.Button( {
  			x: $.cw - 150,
  			y: $.ch-50,
  			lockedWidth: 299,
  			lockedHeight: 49,
  			scale: 3,
  			title: 'MENU',
  			action: function() {
  				$.setState( 'menu' );
  			}
  		} );
  		$.buttons.push( menuButton );
  
  		$.storage['score'] = Math.max( $.storage['score'], $.score );
  		$.storage['level'] = Math.max( $.storage['level'], $.level.current );		
  		$.storage['rounds'] += 1;
  		$.storage['kills'] += $.kills;
  		$.storage['bullets'] += $.bulletsFired;
  		$.storage['powerups'] += $.powerupsCollected;		
  		$.storage['time'] += Math.floor( $.elapsed );
  		$.updateStorage();
  	}
  
  	// set state
  	$.state = state;
  };
  
  $.setupStates = function() {
  	$.states['menu'] = function() {
  		$.clearScreen();
  		$.updateScreen();
  
  		var i = $.buttons.length; while( i-- ){ $.buttons[ i ].update( i ) }
  			i = $.buttons.length; while( i-- ){ $.buttons[ i ].render( i ) }
  
  		$.ctxmg.beginPath();
  		var title = $.text( {
  			ctx: $.ctxmg,
  			x: $.cw / 2,
  			y: $.ch / 2 - 100,
  			text: 'VIRUS ATTACK',
  			hspacing: 2,
  			vspacing: 1,
  			halign: 'center',
  			valign: 'bottom',
  			scale: 10,
  			snap: 1,
  			render: 1
  		} );
  		gradient = $.ctxmg.createLinearGradient( title.sx, title.sy, title.sx, title.ey );
  		gradient.addColorStop( 0, '#fff' );
  		gradient.addColorStop( 1, '#999' );
  		$.ctxmg.fillStyle = gradient;
  		$.ctxmg.fill();
  
  		$.ctxmg.beginPath();
  		var bottomInfo = $.text( {
  			ctx: $.ctxmg,
  			x: $.cw / 2,
  			y: $.ch - 60,
  			text: 'LETS FIGHT THE VIRUS TOGETHER',
  			hspacing: 1,
  			vspacing: 1,
  			halign: 'center',
  			valign: 'bottom',
  			scale: 1,
  			snap: 1,
  			render: 1
  		} );
  		$.ctxmg.fillStyle = '#666';
  		$.ctxmg.fill();
  
  		$.ctxmg.beginPath();
  		var bottomInfo = $.text( {
  			ctx: $.ctxmg,
  			x: $.cw / 2,
  			y: $.ch - 40,
  			text: 'WELCOME FIGHTER    GREETINGS FRM KSHV',
  			hspacing: 1,
  			vspacing: 1,
  			halign: 'center',
  			valign: 'bottom',
  			scale: 1,
  			snap: 1,
  			render: 1
  		} );
  		$.ctxmg.fillStyle = '#666';
  		$.ctxmg.fill();
  
  	};
  
  	$.states['stats'] = function() {
  		$.clearScreen();
  
  		$.ctxmg.beginPath();
  		var statsTitle = $.text( {
  			ctx: $.ctxmg,
  			x: $.cw / 2,
  			y: 50,
  			text: 'STATS',
  			hspacing: 3,
  			vspacing: 1,
  			halign: 'center',
  			valign: 'bottom',
  			scale: 10,
  			snap: 1,
  			render: 1
  		} );
  		var gradient = $.ctxmg.createLinearGradient( statsTitle.sx, statsTitle.sy, statsTitle.sx, statsTitle.ey );
  		gradient.addColorStop( 0, '#fff' );
  		gradient.addColorStop( 1, '#999' );
  		$.ctxmg.fillStyle = gradient;
  		$.ctxmg.fill();
  
  		$.ctxmg.beginPath();
  		var statKeys = $.text( {
  			ctx: $.ctxmg,
  			x: $.cw / 2 - 10,
  			y: statsTitle.ey + 20,
  			text: 'BEST SCORE\nBEST LEVEL\nROUNDS PLAYED\nENEMIES KILLED\nBULLETS FIRED\nPOWERUPS COLLECTED\nTIME ELAPSED',
  			hspacing: 1,
  			vspacing: 10,
  			halign: 'right',
  			valign: 'top',
  			scale: 2,
  			snap: 1,
  			render: 1
  		} );		
  		$.ctxmg.fillStyle = 'hsla(0, 0%, 100%, 0.5)';
  		$.ctxmg.fill();
  
  		$.ctxmg.beginPath();
  		var statsValues = $.text( {
  			ctx: $.ctxmg,
  			x: $.cw / 2 + 10,
  			y: statsTitle.ey + 20,
  			text: 
  				$.util.commas( $.storage['score'] ) + '\n' + 
  				( $.storage['level'] + 1 ) + '\n' + 
  				$.util.commas( $.storage['rounds'] ) + '\n' + 
  				$.util.commas( $.storage['kills'] ) + '\n' + 
  				$.util.commas( $.storage['bullets'] ) + '\n' + 
  				$.util.commas( $.storage['powerups'] ) + '\n' + 
  				$.util.convertTime( ( $.storage['time'] * ( 1000 / 60 ) ) / 1000 )
  			,
  			hspacing: 1,
  			vspacing: 10,
  			halign: 'left',
  			valign: 'top',
  			scale: 2,
  			snap: 1,
  			render: 1
  		} );		
  		$.ctxmg.fillStyle = '#fff';
  		$.ctxmg.fill();
  
  		var i = $.buttons.length; while( i-- ){ $.buttons[ i ].render( i ) }
  			i = $.buttons.length; while( i-- ){ $.buttons[ i ].update( i ) }
  	};
  	
  	$.states['play'] = function() {
  		$.updateDelta();
  		$.updateScreen();
  		$.updateLevel();
  		$.updatePowerupTimers();
  		$.spawnEnemies();
  		$.enemyOffsetMod += ( $.slow ) ? $.dt / 3 : $.dt;
  		
  		// update entities	
  		var i = $.enemies.length; while( i-- ){ $.enemies[ i ].update( i ) }
  			i = $.explosions.length; while( i-- ){ $.explosions[ i ].update( i ) }
  			i = $.powerups.length; while( i-- ){ $.powerups[ i ].update( i ) }
  			i = $.particleEmitters.length; while( i-- ){ $.particleEmitters[ i ].update( i ) }
  			i = $.textPops.length; while( i-- ){ $.textPops[ i ].update( i ) }
  			i = $.levelPops.length; while( i-- ){ $.levelPops[ i ].update( i ) }
  			i = $.bullets.length; while( i-- ){ $.bullets[ i ].update( i ) }
  		$.hero.update();
  
  		// render entities
  		$.clearScreen();
  		$.ctxmg.save();
  		$.ctxmg.translate( $.screen.x - $.rumble.x, $.screen.y - $.rumble.y );
  		i = $.enemies.length; while( i-- ){ $.enemies[ i ].render( i ) }
  		i = $.explosions.length; while( i-- ){ $.explosions[ i ].render( i ) }
  		i = $.powerups.length; while( i-- ){ $.powerups[ i ].render( i ) }
  		i = $.particleEmitters.length; while( i-- ){ $.particleEmitters[ i ].render( i ) }
  		i = $.textPops.length; while( i-- ){ $.textPops[ i ].render( i ) }		
  		i = $.bullets.length; while( i-- ){ $.bullets[ i ].render( i ) }
  		$.hero.render();		
  		$.ctxmg.restore();		
  		i = $.levelPops.length; while( i-- ){ $.levelPops[ i ].render( i ) }
  		$.renderInterface();
  		$.renderMinimap();
  
  		// handle gameover
  		if( $.hero.life <= 0 ) {
  			var alpha = ( ( $.gameoverTick / $.gameoverTickMax ) * 0.8 );
  				alpha = Math.min( 1, Math.max( 0, alpha ) );
  			$.ctxmg.fillStyle = 'hsla(0, 100%, 0%, ' + alpha + ')';
  			$.ctxmg.fillRect( 0, 0, $.cw, $.ch );
  			if( $.gameoverTick < $.gameoverTickMax ){				
  				$.gameoverTick += $.dt;				
  			} else {
  				$.setState( 'gameover' );
  			}
  
  			if( !$.gameoverExplosion ) {
  				$.audio.play( 'death' );
  				$.rumble.level = 25;
  				$.explosions.push( new $.Explosion( {
  					x: $.hero.x + $.util.rand( -10, 10 ),
  					y: $.hero.y + $.util.rand( -10, 10 ),
  					radius: 50,
  					hue: 0,
  					saturation: 0
  				} ) );
  				$.particleEmitters.push( new $.ParticleEmitter( {
  					x: $.hero.x,
  					y: $.hero.y,
  					count: 45,
  					spawnRange: 10,
  					friction: 0.95,
  					minSpeed: 2,
  					maxSpeed: 20,
  					minDirection: 0,
  					maxDirection: $.twopi,
  					hue: 0,
  					saturation: 0
  				} ) );
  				for( var i = 0; i < $.powerupTimers.length; i++ ){
  					$.powerupTimers[ i ] = 0;
  				}
  				$.gameoverExplosion = 1;
  			}		
  		}
  
  		// update tick	
  		$.tick += $.dt;	
  
  		// listen for pause
  		if( $.keys.pressed.p ){
  			$.setState( 'pause' );
  		}
  
  		// always listen for autofire toggle
  		if( $.keys.pressed.f ){
  			$.autofire = ~~!$.autofire;			
  			$.storage['autofire'] = $.autofire;
  			$.updateStorage();
  		}
  	};
  
  	$.states['pause'] = function() {
  		$.clearScreen();
  		$.ctxmg.putImageData( $.screenshot, 0, 0 );
  
  		$.ctxmg.fillStyle = 'hsla(0, 0%, 0%, 0.4)';
  		$.ctxmg.fillRect( 0, 0, $.cw, $.ch );
  
  		$.ctxmg.beginPath();
  		var pauseText = $.text( {
  			ctx: $.ctxmg,
  			x: $.cw / 2,
  			y: $.ch / 2 - 50,
  			text: 'PAUSED',
  			hspacing: 3,
  			vspacing: 1,
  			halign: 'center',
  			valign: 'bottom',
  			scale: 10,
  			snap: 1,
  			render: 1
  		} );
  		var gradient = $.ctxmg.createLinearGradient( pauseText.sx, pauseText.sy, pauseText.sx, pauseText.ey );
  		gradient.addColorStop( 0, '#fff' );
  		gradient.addColorStop( 1, '#999' );
  		$.ctxmg.fillStyle = gradient;
  		$.ctxmg.fill();
  
  		var i = $.buttons.length; while( i-- ){ $.buttons[ i ].render( i ) }
  			i = $.buttons.length; while( i-- ){ $.buttons[ i ].update( i ) }
  
  		if( $.keys.pressed.p ){
  			$.setState( 'play' );
  		}
  	};
  
  	$.states['gameover'] = function() {
  		$.clearScreen();
  		$.ctxmg.putImageData( $.screenshot, 0, 0 );
  
  		var i = $.buttons.length; while( i-- ){ $.buttons[ i ].update( i ) }
  			i = $.buttons.length; while( i-- ){ $.buttons[ i ].render( i ) }
  
  		$.ctxmg.beginPath();
  		var gameoverTitle = $.text( {
  			ctx: $.ctxmg,
  			x: $.cw / 2,
  			y: 50,
  			text: 'INFECTED',
  			hspacing: 3,
  			vspacing: 1,
  			halign: 'center',
  			valign: 'bottom',
  			scale: 10,
  			snap: 1,
  			render: 1
  		} );
  		var gradient = $.ctxmg.createLinearGradient( gameoverTitle.sx, gameoverTitle.sy, gameoverTitle.sx, gameoverTitle.ey );
  		gradient.addColorStop( 0, '#f22' );
  		gradient.addColorStop( 1, '#b00' );
  		$.ctxmg.fillStyle = gradient;
  		$.ctxmg.fill();
  
  		$.ctxmg.beginPath();
  		var gameoverStatsKeys = $.text( {
  			ctx: $.ctxmg,
  			x: $.cw / 2 - 10,
  			y: gameoverTitle.ey + 25,
  			text: 'SCORE\nLEVEL\nKILLS\nBULLETS\nPOWERUPS\nTIME',
  			hspacing: 1,
  			vspacing: 17,
  			halign: 'right',
  			valign: 'top',
  			scale: 2,
  			snap: 1,
  			render: 1
  		} );		
  		$.ctxmg.fillStyle = 'hsla(0, 0%, 100%, 0.5)';
  		$.ctxmg.fill();
  
  		$.ctxmg.beginPath();
  		var gameoverStatsValues = $.text( {
  			ctx: $.ctxmg,
  			x: $.cw / 2 + 10,
  			y: gameoverTitle.ey + 25,
  			text: 
  				$.util.commas( $.score ) + '\n' + 
  				( $.level.current + 1 ) + '\n' + 
  				$.util.commas( $.kills ) + '\n' + 
  				$.util.commas( $.bulletsFired ) + '\n' + 
  				$.util.commas( $.powerupsCollected ) + '\n' + 
  				$.util.convertTime( ( $.elapsed * ( 1000 / 60 ) ) / 1000 )
  			,
  			hspacing: 1,
  			vspacing: 17,
  			halign: 'left',
  			valign: 'top',
  			scale: 2,
  			snap: 1,
  			render: 1
  		} );		
  		$.ctxmg.fillStyle = '#fff';
  		$.ctxmg.fill();
  	};
  }
  
  /*==============================================================================
  Loop
  ==============================================================================*/
  $.loop = function() {
  	requestAnimFrame( $.loop );
  
  	// setup the pressed state for all keys
  	for( var k in $.keys.state ) {
  		if( $.keys.state[ k ] && !$.okeys[ k ] ) {
  			$.keys.pressed[ k ] = 1;
  		} else {
  			$.keys.pressed[ k ] = 0;
  		}
  	}
  
  	// run the current state
  	$.states[ $.state ]();
  
  	// always listen for mute toggle
  	if( $.keys.pressed.m ){
  		$.mute = ~~!$.mute;
  		var i = $.audio.references.length;
  		while( i-- ) {
  			$.audio.references[ i ].volume = ~~!$.mute;
  		}
  		$.storage['mute'] = $.mute;
  		$.updateStorage();
  	}
  
  	// move current keys into old keys
  	$.okeys = {};
  	for( var k in $.keys.state ) {
  		$.okeys[ k ] = $.keys.state[ k ];
  	}
  };
  
  /*==============================================================================
  Start Game on Load
  ==============================================================================*/
  window.addEventListener( 'load', function() {
  	document.documentElement.className += ' loaded';
  	$.init();
  });