<|start_filename|>js/sharing.js<|end_filename|>
window.addEventListener("load",function(){

	// Find the "sharing" dom
	var sharingDOM = document.body.querySelector("sharing");

	// URL encodeable
	var text = sharingDOM.getAttribute("text");
	var link = sharingDOM.getAttribute("link");
	text = encodeURIComponent(text);
	link = encodeURIComponent(link);

	// Create full html
	var sharing = document.createElement("span");
	sharing.className = "sharing";
	sharing.innerHTML = '<a href="https://www.facebook.com/sharer/sharer.php?u='+link+'&t='+text+'" title="Share on Facebook" target="_blank"><img alt="Share on Facebook" src="social/facebook.png"></a>'+
						'<a href="https://twitter.com/intent/tweet?source='+link+'&text='+text+'%20'+link+'&via=ncasenmare" target="_blank" title="Tweet"><img alt="Tweet" src="social/twitter.png"></a>'+
						'<a href="mailto:?subject='+text+'&body='+link+'" target="_blank" title="Send email"><img alt="Send email" src="social/email.png"></a>';

	// Replace it in the dom
	sharingDOM.parentNode.replaceChild(sharing, sharingDOM);

});

<|start_filename|>js/index.js<|end_filename|>
/******************************

PRE-LOAD THESE PUPPIES 

*******************************/
_addToManifest(manifest,{
	firefly: "sprites/firefly.json"
});

/******************************

CONSTANTS-ISH
(they're not really constants, w/e)

*******************************/

var NUM_FIREFLIES,
	FLY_LOOP,
	FLY_SWERVE,
	SHOW_CLOCKS,
	FLY_CLOCK_SPEED,
	FLY_RADIUS,
	FLY_PULL,
	FLY_SYNC,
	MOUSE_RADIUS;

var _resetConstants = function(){
	var area = window.innerWidth * window.innerHeight;
	NUM_FIREFLIES = Math.round(area * (150)/(1280*600)); // 150 fireflies per 1280x600
	if(NUM_FIREFLIES<100) NUM_FIREFLIES=100; // actually, MINUMUM 100
	
	FLY_LOOP = 50;
	FLY_SWERVE = 0.1;
	SHOW_CLOCKS = false;
	FLY_CLOCK_SPEED = 0.3;
	FLY_RADIUS = 200;
	FLY_PULL = 0.035;
	FLY_SYNC = false;
	MOUSE_RADIUS = 200;
};

_resetConstants();

/******************************

THE MAIN GAME CODE

*******************************/

var app;
var fireflies = [];
window.onload = function(){

	// Create app!
	app = new PIXI.Application(document.body.clientWidth, document.body.clientHeight, {backgroundColor:0x000000});
	$("#game").appendChild(app.view);

	// Mouse
	Mouse.init($("#game"));

	// When loaded all assets...
	_loadAssets(manifest, function(){

		// Add fireflies!
		_addFireflies(NUM_FIREFLIES);

		// Animation loop
		app.ticker.add(function(delta){
			for(var i=0; i<fireflies.length; i++){
				fireflies[i].update(delta);
			}
		});

		// Synchronize 'em!
		_syncConstants();

	});

	// Play bg music whenever
	var bg_loop = new Howl({
		src: "sounds/forest.mp3",
		volume: 0.8,
		loop: true
	});
	bg_loop.play();

	// Set up widgets!
	Widgets.convert($("#words"));

};

var _addFireflies = function(num){
	for(var i=0; i<num; i++){
		var ff = new Firefly();
		fireflies.push(ff);
		app.stage.addChild(ff.graphics);
	}
};

var _removeFireflies = function(num){
	for(var i=0; i<num; i++){
		var ff = fireflies.pop();
		app.stage.removeChild(ff.graphics);
	}
};

var _resetFireflies = function(){
	for(var i=0; i<fireflies.length; i++){
		var ff = fireflies[i];
		ff.clock = Math.random();
	}	
};

/******************************

THE FIREFLY CODE

*******************************/

function Firefly(){

	var self = this;

	// Graphics
	self.graphics = new PIXI.Container();
	var g = self.graphics;
	g.scale.set(0.15);

	// Random spot
	self.x = Math.random()*app.renderer.width;
	self.y = Math.random()*app.renderer.height;
	self.angle = Math.random()*Math.TAU;
	self.speed = 0.5 + Math.random()*1;
	self.swerve = (Math.random()-0.5)*FLY_SWERVE;

	// Clock! From 0 to 1
	self.clock = Math.random();

	// Flash
	var flash = _makeMovieClip("firefly", {anchorX:0.5, anchorY:0.5});
	flash.gotoAndStop(2);
	flash.alpha = 0;
	g.addChild(flash);

	// Body
	var body = _makeMovieClip("firefly", {anchorX:0.5, anchorY:0.5});
	body.gotoAndStop(0);
	g.addChild(body);

	// Body2
	var body2 = _makeMovieClip("firefly", {anchorX:0.5, anchorY:0.5});
	body2.gotoAndStop(1);
	body2.alpha = 0;
	g.addChild(body2);

	// Wings
	var wings = _makeMovieClip("firefly", {anchorX:0.5, anchorY:0.5});
	wings.gotoAndStop((Math.random()<0.5) ? 3 : 4);
	g.addChild(wings);

	// Clock
	var clock = new PIXI.Container();
	clock.visible = false;
	g.addChild(clock);

	// Dark Clock
	var darkClock = new PIXI.Container();
	clock.addChild(darkClock);
	var darkClockBody = _makeMovieClip("firefly", {anchorX:0.5, anchorY:0.5});
	darkClockBody.gotoAndStop(7);
	darkClock.addChild(darkClockBody);
	var darkClockHand = _makeMovieClip("firefly", {anchorX:0.5, anchorY:0.5});
	darkClockHand.gotoAndStop(8);
	darkClock.addChild(darkClockHand);

	// Light Clock
	var lightClock = new PIXI.Container();
	lightClock.alpha = 0;
	clock.addChild(lightClock);
	var lightClockBody = _makeMovieClip("firefly", {anchorX:0.5, anchorY:0.5});
	lightClockBody.gotoAndStop(5);
	lightClock.addChild(lightClockBody);
	var lightClockHand = _makeMovieClip("firefly", {anchorX:0.5, anchorY:0.5});
	lightClockHand.gotoAndStop(6);
	lightClock.addChild(lightClockHand);

	// Mouse LAST pressed... a little decay...
	var _chaos = 0;

	// Update
	self.update = function(delta){

		//////////////////////
		// Position & Angle //
		//////////////////////

		// Update position
		self.x += self.speed * delta * Math.cos(self.angle);
		self.y += self.speed * delta * Math.sin(self.angle);

		// Loop around
		if(self.x<-FLY_LOOP) self.x=app.renderer.width+FLY_LOOP;
		if(self.x>app.renderer.width+FLY_LOOP) self.x=-FLY_LOOP;
		if(self.y<-FLY_LOOP) self.y=app.renderer.height+FLY_LOOP;
		if(self.y>app.renderer.height+FLY_LOOP) self.y=-FLY_LOOP;

		// Swerve
		self.angle += self.swerve;
		if(Math.random()<0.05) self.swerve = (Math.random()-0.5)*FLY_SWERVE;

		////////////////////////
		// Cycling & Flashing //
		////////////////////////

		// Increment cycle
		flash.alpha *= 0.9;
		self.clock += (delta/60)*FLY_CLOCK_SPEED;

		// If near mouse, get chaotic, and fast!
		if(Mouse.pressed) _chaos=1;
		if(_chaos>0.01 && closeEnough(self,Mouse,MOUSE_RADIUS)){
			self.clock += Math.random()*0.15;
		}
		_chaos *= 0.8;

		// Flashed?
		if(self.clock>1){

			// Flash!
			flash.alpha = 1;
			self.clock = 0;

			// Bring nearby fireflies up.
			if(FLY_SYNC){
				for(var i=0;i<fireflies.length;i++){
					var ff = fireflies[i];
					if(ff==self) continue; // is self? forget it
					if(closeEnough(self,ff,FLY_RADIUS)){ // is close enough?
						var pull = (ff.clock/1); // to prevent double-pulling
						ff.clock += pull*FLY_PULL;
						if(ff.clock>1) ff.clock=1;
					}
				}
			}

		}
		body2.alpha = flash.alpha;
		lightClock.alpha = flash.alpha;

		//////////////
		// Graphics //
		//////////////

		// Position
		g.x = self.x;
		g.y = self.y;
		g.rotation = self.angle+Math.TAU/4;

		// Flap wings
		wings.gotoAndStop( (wings.currentFrame==3) ? 4 : 3 );

		// Clocks!
		clock.rotation = -g.rotation;
		clock.visible = SHOW_CLOCKS;
		darkClockHand.rotation = lightClockHand.rotation = self.clock*Math.TAU;

	};
	self.update(0);

}

/******************************

UI CODE: Resize, make widgets, etc...

*******************************/

subscribe("mousedown",function(){
	$("#words").className = "no-select";
});
subscribe("mouseup",function(){
	$("#words").className = "";
});

window.onresize = function(){
	if(app) app.renderer.resize(document.body.clientWidth, document.body.clientHeight);
};

/******************************

WIDGET CODE: Modifying "Constants"

*******************************/

// Synchronize with the UI
var _syncConstants = function(){

	publish("slider/numFireflies", [NUM_FIREFLIES]);

	publish("toggle/showClocks", [SHOW_CLOCKS]);
	publish("slider/clockSpeed", [FLY_CLOCK_SPEED]);

	publish("toggle/neighborNudgeRule", [FLY_SYNC]);
	publish("slider/nudgeAmount", [FLY_PULL]);
	publish("slider/neighborRadius", [FLY_RADIUS]);

};

// Num of Fireflies

subscribe("slider/numFireflies", function(value){

	// Settle the difference...
	if(value > fireflies.length){
		_addFireflies(value-fireflies.length);
	}
	if(value < fireflies.length){
		_removeFireflies(fireflies.length-value);
	}

	// Then make that the new constant.
	NUM_FIREFLIES = value;

});

// Internal Clock

subscribe("toggle/showClocks", function(value){
	SHOW_CLOCKS = value;
});
subscribe("slider/clockSpeed", function(value){
	FLY_CLOCK_SPEED = value
});

// Neighbor Nudge Rule

subscribe("toggle/neighborNudgeRule", function(value){
	FLY_SYNC = value;
	if(FLY_SYNC){
		$("#nudgeAmount").removeAttribute("inactive");
		$("#neighborRadius").removeAttribute("inactive");
	}else{
		$("#nudgeAmount").setAttribute("inactive","yes");
		$("#neighborRadius").setAttribute("inactive","yes");
	}
});
subscribe("slider/nudgeAmount", function(value){
	FLY_PULL = value;
});
subscribe("slider/neighborRadius", function(value){
	FLY_RADIUS = value;
});

// Reset Everything

subscribe("button/resetFireflies", function(){
	_resetFireflies();
});

subscribe("button/resetEverything", function(){
	_resetConstants();
	_syncConstants();
	_resetFireflies();
});


<|start_filename|>js/widgets.js<|end_filename|>
(function(exports){

// SINGLETON
var Widgets = {};
exports.Widgets = Widgets;

// CONVERT 'EM ALL
Widgets.convert = function(dom){

	// All elements
	var widgetDOMs = dom.querySelectorAll("widget");

	// For each one...
	for(var i=0; i<widgetDOMs.length; i++){
		var widgetDOM = widgetDOMs[i];

		// Convert attributes into an object
		var config = {};
		for(var j=0; j<widgetDOM.attributes.length; j++){
			var key = widgetDOM.attributes[j].name;
			var value = widgetDOM.attributes[j].value;
			config[key] = value;
		}

		// Create widget of said type
		var type = config.type;
		var widget = new Widgets[type](config);

		// Replace it in the dom
		widgetDOM.parentNode.replaceChild(widget.dom, widgetDOM);

		// Gimme an ID!
		widget.dom.id = config.id;

	}

};

// TOGGLE WIDGET
// id, value, label, background, color, background-on, color-on, label-on, label-off
Widgets.toggle = function(config){

	var self = this;
	self.config = config;

	// Start value
	self.value = !!(parseInt(config.value));

	// Graphics
	var dom = document.createElement("div");
	dom.classList.add("widget");
	dom.classList.add("widget-toggle");
	self.dom = dom;

	// Label
	var label = document.createElement("div");
	label.id = "label";
	label.innerHTML = config.label;
	dom.appendChild(label);

	// Toggle
	var toggle = document.createElement("div");
	toggle.id = "toggle";
	toggle.style.borderColor = config.color;
	dom.appendChild(toggle);

	var toggle_switch = document.createElement("div");
	toggle_switch.id = "toggle_switch";
	toggle.appendChild(toggle_switch);
	
	var toggle_on = document.createElement("div");
	toggle_on.id = "toggle_on";
	toggle_on.innerHTML = config["label-on"];
	toggle_on.style.color = config["color-on"];
	toggle_switch.appendChild(toggle_on);

	var toggle_off = document.createElement("div");
	toggle_off.id = "toggle_off";
	toggle_off.innerHTML = config["label-off"];
	toggle_off.style.color = config["color"];
	toggle_switch.appendChild(toggle_off);
	
	var toggle_knob = document.createElement("div");
	toggle_knob.id = "toggle_knob";
	toggle_knob.style.background = config.color;
	toggle_switch.appendChild(toggle_knob);

	// Colors
	dom.style.color = config.color;

	// On change value
	self.onChangeValue = function(newValue){
		self.value = newValue;
		if(self.value){ // on
			toggle_switch.style.left = "0px";
			toggle.style.background = config["background-on"];
		}else{ // off
			toggle_switch.style.left = "-80px";
			toggle.style.background = config["background"];
		}
	}
	subscribe("toggle/"+config.id, self.onChangeValue);
	self.onChangeValue(self.value);

	// On Click
	self.dom.onclick = function(){
		self.value = !self.value;
		publish("toggle/"+config.id, [self.value]);
	};

};

// SLIDER WIDGET
// id, label, background, color, min, max, step, label-min, label-max
Widgets.slider = function(config){

	var self = this;
	self.config = config;

	// Graphics
	var dom = document.createElement("div");
	dom.classList.add("widget");
	dom.classList.add("widget-slider");
	dom.style.color = config.color;
	self.dom = dom;

	// Top Label
	var top_label = document.createElement("div");
	top_label.id = "top_label";
	top_label.innerHTML = config.label;
	dom.appendChild(top_label);

	// Slider
	var slider = document.createElement("div");
	slider.id = "slider";
	dom.appendChild(slider);

	var slider_bar = document.createElement("div");
	slider_bar.id = "slider_bar";
	slider_bar.style.borderColor = config.color;
	slider.appendChild(slider_bar);

	var slider_knob = document.createElement("div");
	slider_knob.id = "slider_knob";
	slider_knob.style.background = config.color;
	slider.appendChild(slider_knob);

	// Bottom labels
	var bottom_label = document.createElement("div");
	bottom_label.id = "bottom_label";
	dom.appendChild(bottom_label);
	
	var label_min = document.createElement("div");
	label_min.id = "label_min";
	label_min.innerHTML = config["label-min"];
	bottom_label.appendChild(label_min);
	
	var label_max = document.createElement("div");
	label_max.id = "label_max";
	label_max.innerHTML = config["label-max"];
	bottom_label.appendChild(label_max);

	// MOUSE EVENTS

	self.grabbing = false;
	self.onGrabbing = function(grabbing){
		self.grabbing = grabbing;
		if(grabbing){
			slider_knob.style.background = config.background;
			slider_knob.setAttribute("grabbing",true);
		}else{
			slider_knob.style.background = config.color;
			slider_knob.removeAttribute("grabbing");
		}
	};

	// Notches
	var min = parseFloat(config.min);
	var max = parseFloat(config.max);
	var step = parseFloat(config.step);

	// Convert Touch to Cursor
	var _getMouse = function(event){
		var pos = {};
		if(event.changedTouches){
			// Touch
			//var bounds = target ? target.getBoundingClientRect() : {left:0,top:0};
			pos.x = event.changedTouches[0].clientX;// - bounds.left;
			pos.y = event.changedTouches[0].clientY;// - bounds.top;
		}else{
			// Not Touch
			pos.x = event.pageX;
			pos.y = event.pageY;
		}
		return pos;
	};

	// Mouse Events
	var offsetX = 0;
	slider_knob.onmousedown = function(event){
		var pos = _getMouse(event);
		self.onGrabbing(true);
		offsetX = pos.x - slider_knob.getBoundingClientRect().left;
		event.stopPropagation();
	};
	slider_knob.addEventListener("touchstart", function(event){
		slider_knob.onmousedown(event);
		event.preventDefault();
	});
	self.dom.onmousedown = function(event){
		var pos = _getMouse(event);
		self.onGrabbing(true);
		offsetX = slider_knob.getBoundingClientRect().width/2;
		self.mouseToValue(pos.x);
	};
	self.dom.addEventListener("touchstart", function(event){
		self.dom.onmousedown(event);
	});
	var _onMouseUp = function(){
		self.onGrabbing(false);
	};
	window.addEventListener("mouseup", _onMouseUp);
	document.body.addEventListener("touchend",_onMouseUp,false);
	var _onMouseMove = function(event){
		if(self.grabbing){
			var pos = _getMouse(event);
			self.mouseToValue(pos.x);
		}
	};
	window.addEventListener("mousemove", _onMouseMove);
	window.addEventListener("touchmove", _onMouseMove);
	self.mouseToValue = function(pageX){

		// Convert to raw position
		var mouseX = pageX - offsetX;
		var bounds = self.dom.getBoundingClientRect();
		var sliderX = bounds.left;
		var knobWidth = slider_knob.getBoundingClientRect().width;
		var sliderWidth = bounds.width - knobWidth;
		var x = mouseX-sliderX;
		if(x<0) x=0;
		if(x>sliderWidth) x=sliderWidth;

		// Convert to value
		x = x/sliderWidth; // 0 to 1
		x = min + x*(max-min); // min to max
		x = Math.round(x/step)*step; // round to nearest step

		// Publish value
		publish("slider/"+config.id, [x]);

	};
	self.value = 0;
	self.onChangeValue = function(value){
		
		self.value = value;

		// Convert value to position
		var knobWidth = slider_knob.getBoundingClientRect().width;
		var sliderWidth = self.dom.getBoundingClientRect().width - knobWidth;
		var x = (value-min)/(max-min); // 0 to 1
		var left = x*sliderWidth;
		slider_knob.style.left = left;

	};
	subscribe("slider/"+config.id, self.onChangeValue);

};

// BUTTON WIDGET: 
// id, label, background, color, background-hover, color-hover, onclick
Widgets.button = function(config){

	var self = this;
	self.config = config;

	// Graphics
	var dom = document.createElement("div");
	dom.classList.add("widget");
	dom.classList.add("widget-button");
	dom.innerHTML = config.label;
	self.dom = dom;

	// Colors
	self.setColors = function(hover){
		dom.style.background = hover ? config["background-hover"] : config.background;
		dom.style.color = hover ? config["color-hover"] : config.color;
		dom.style.borderColor = dom.style.color;
	};
	self.setColors(false);

	// Mouse Events
	self.dom.onmouseover = function(){
		self.setColors(true);
	};
	self.dom.onmouseout = function(){
		self.setColors(false);
	};
	self.dom.onclick = function(){
		publish("button/"+config.id);
	};

};
	
})(window);

<|start_filename|>js/Mouse.js<|end_filename|>
window.Mouse = {};
Mouse.init = function(target){

	// Events!
	var _onmousedown = function(event){
		Mouse.pressed = true;
		publish("mousedown");
	};
	var _onmousemove = function(event){
		Mouse.x = event.x;
		Mouse.y = event.y;
		publish("mousemove");
	};
	var _onmouseup = function(){
		Mouse.pressed = false;
		publish("mouseup");
	};

	// Add mouse & touch events!
	_addMouseEvents(target, _onmousedown, _onmousemove, _onmouseup);

};