Add files via upload
diff --git a/js/autocomplete.js b/js/autocomplete.js
index 7751bc1..107b03a 100644
--- a/js/autocomplete.js
+++ b/js/autocomplete.js
@@ -1,3 +1,5 @@
+// *********** HERE STARTS autocomplete.js *************

+

 function autocomplete(inp, obj, act) {

 	/*the autocomplete function takes two arguments,

 	the text field element and an objay of possible autocompleted values:*/

@@ -41,10 +43,17 @@
 					var n = this.dataset.id;

 					inp.value = obj[n].name;

 

+					var node = null;

+					

+					s.graph.nodes().forEach( function(nnode) {

+						if(nnode.id == n) node = nnode;

+					});

+					

+

 					switch (act) {

 						case "search":

 							// Move camera to desired node

-							cameraGoto(obj[n].x, obj[n].y);

+							cameraGoto(node.x, node.y);

 							break;

 						case "addEdge":

 							// @TODO: Add an edge between A and B

diff --git a/js/camera.js b/js/camera.js
index ac4894f..44e9f5d 100644
--- a/js/camera.js
+++ b/js/camera.js
@@ -1,3 +1,6 @@
+// *********** HERE STARTS camera.js *************

+

+

 window.addEventListener('load', initCamera);

 

 function cameraGoto(nodeX, nodeY) {

@@ -7,16 +10,44 @@
 	);

 }

 

-function initCamera() {

-	document.querySelector("#zoomin").addEventListener("click", function() {

-		s.camera.goTo({

-			ratio: Math.max(s.camera.settings("zoomMin"), s.camera.ratio / Math.sqrt(2))

-		});

-	});

+function is_touch_device() {

+	  var prefixes = ' -webkit- -moz- -o- -ms- '.split(' ');

+	  var mq = function(query) {

+	    return window.matchMedia(query).matches;

+	  }

 

-	document.querySelector("#zoomout").addEventListener("click", function() {

-		s.camera.goTo({

-			ratio: Math.min(s.camera.settings("zoomMax"), s.camera.ratio * Math.sqrt(2))

+	  if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {

+	    return true;

+	  }

+

+	  // include the 'heartz' as a way to have a non matching MQ to help terminate the join

+	  // https://git.io/vznFH

+	  var query = ['(', prefixes.join('touch-enabled),('), 'heartz', ')'].join('');

+	  return mq(query);

+}

+

+function initCamera() {

+	

+	if(!is_touch_device()) {

+		

+		document.querySelector("#zoomin").addEventListener("click", function() {

+			s.camera.goTo({

+				ratio: Math.max(s.camera.settings("zoomMin"), s.camera.ratio / Math.sqrt(2))

+			});

 		});

-	});

+	

+		document.querySelector("#zoomout").addEventListener("click", function() {

+			s.camera.goTo({

+				ratio: Math.min(s.camera.settings("zoomMax"), s.camera.ratio * Math.sqrt(2))

+			});

+		});

+	}

+	else{

+		document.querySelector("#zoomin").style.display = "none";

+		document.querySelector("#zoomout").style.display = "none";

+		

+		document.querySelector("#circle-mode").style.bottom = "110px";

+		document.querySelector("#settings").style.bottom = "60px";

+		document.querySelector("#search").style.bottom = "10px";

+	}

 }

diff --git a/js/circle-mode.js b/js/circle-mode.js
index ea5f423..d5ecff3 100644
--- a/js/circle-mode.js
+++ b/js/circle-mode.js
@@ -1,9 +1,11 @@
+// *********** HERE STARTS circle-mode.js *************

+

 window.addEventListener("load", initCircleMode);

 

 circleMode = false;

 

 function initCircleMode() {

-	document.querySelector("#circle-mode").addEventListener('click', function() {

+	document.querySelector("#circle-mode").addEventListener('click', function() {		

 		if(circleMode) {

 			circleMode = false;

 			document.querySelector("#circle-mode i").innerText = "trip_origin";

@@ -29,4 +31,22 @@
 			s.refresh();

 		}

 	});

+}

+

+function isInRect (x, y, rect) {

+	if (x < -10000 || x > 10000) return true;

+	if (y < -10000 || y > 10000) return true;

+	

+	var ans = true;

+	var c = crossProd (rect[0], rect[1], x, y);

+	

+	for(var i=1; i<4; i++) {	

+		var temp = crossProd (rect[i], rect[(i+1)%4], x, y);

+		if (c*temp < 0) ans = false;

+	}

+	return ans;

+}

+

+function crossProd(r1, r2, x, y) {

+	return r1[0]*r2[1] + r2[0]*y + x*r1[1] - r1[0]*y - r2[0]*r1[1] - x*r2[1]; 

 }
\ No newline at end of file
diff --git a/js/dialog.js b/js/dialog.js
index 275fb0c..ff3268f 100644
--- a/js/dialog.js
+++ b/js/dialog.js
@@ -1,3 +1,5 @@
+// *********** HERE STARTS dialog.js *************

+

 window.addEventListener("load", initDialog);

 

 var dialog = {

diff --git a/js/easter-egg.js b/js/easter-egg.js
index ac73b2a..35dce24 100644
--- a/js/easter-egg.js
+++ b/js/easter-egg.js
@@ -1,3 +1,5 @@
+// *********** HERE STARTS easter-egg.js *************

+

 window.addEventListener("load", initEasterEgg);

 

 var seq = [38, 38, 40, 40, 37, 39, 37, 39, 65, 66, 13];

diff --git a/js/graf.js b/js/graf.js
index feab604..155ce2d 100644
--- a/js/graf.js
+++ b/js/graf.js
@@ -1,3 +1,5 @@
+// *********** HERE STARTS graf.js *************

+

 window.addEventListener("load", initGraf);

 

 // s is the sigma graph

@@ -46,7 +48,7 @@
 			edgeColor: "default",

 			defaultLabelColor: "#fff",

 			autoRescale: false,

-			zoomMax: 10,

+			zoomMax: 30,

 			// enableEdgeHovering: true,

 			font: "Roboto",

 			labelThreshold: 5

@@ -60,11 +62,19 @@
 		graf = JSON.parse(responseText);

 

 		// does graf.nodes have a size attribute?

-		var sizegraf = 0;

+		var rectBorrar = [[0,0], [0,0], [0,0], [0,0]];

 		for (var i in graf.nodes) {

-			sizegraf++;

+			if (graf.nodes[i].name == "Erase")    rectBorrar[0] = [ graf.nodes[i].x , graf.nodes[i].y ];

+			if (graf.nodes[i].name == "Borrar")   rectBorrar[1] = [ graf.nodes[i].x , graf.nodes[i].y ];

+			if (graf.nodes[i].name == "Esborrar") rectBorrar[2] = [ graf.nodes[i].x , graf.nodes[i].y ];

+			if (graf.nodes[i].name == "Delete")   rectBorrar[3] = [ graf.nodes[i].x , graf.nodes[i].y ];

 		}

 		

+		var sizegraf = 0;

+		for (var i in graf.nodes) {

+			if ( isInRect(graf.nodes[i].x, graf.nodes[i].y, rectBorrar) ) continue;	

+			sizegraf++;

+		}		

 		var nnode = 0;

 		for (var i in graf.nodes) {

 			var ncolor = null;

@@ -79,6 +89,8 @@
 			var newX = 5000*Math.cos( 2*Math.PI*nnode/sizegraf );

 			var newY = 5000*Math.sin( 2*Math.PI*nnode/sizegraf );	

 			

+			if (isInRect(graf.nodes[i].x, graf.nodes[i].y, rectBorrar) ) continue;	

+			

 			s.graph.addNode({

 				// we add color, originalColor, size, originalX..Y, circleX..Y atributes

 				id: graf.nodes[i].id,

@@ -96,9 +108,12 @@
 				originalColor: ncolor

 			});

 			nnode++;

+		

 		}

 

 		for (var i in graf.edges) {

+			if (isInRect(graf.nodes[graf.edges[i].a].x, graf.nodes[graf.edges[i].a].y, rectBorrar)) continue;	

+			if (isInRect(graf.nodes[graf.edges[i].b].x, graf.nodes[graf.edges[i].b].y, rectBorrar)) continue;	

 			

 			s.graph.addEdge({

 				id: i,

@@ -113,7 +128,8 @@
 			var nodeId = e.data.node.id,

 				toKeep = s.graph.neighbors(nodeId);

 				// toKeep[nodeId] = e.data.node;

-	

+			

+			

 			s.graph.nodes().forEach(function(n) {

 				if (toKeep[n.id] || n.id == nodeId) {

 					n.color = n.originalColor;

@@ -131,6 +147,12 @@
 			});

 			

 			if (circleMode) {

+				s.graph.nodes().forEach(function (n) {

+					n.x = n.circleX;

+					n.y = n.circleY;

+					n.size = 10;

+				});

+				

 				e.data.node.x = 0;

 				e.data.node.y = 0;

 				e.data.node.size = 30;

diff --git a/js/limit-years.js b/js/limit-years.js
index 6d99c37..cbbef95 100644
--- a/js/limit-years.js
+++ b/js/limit-years.js
@@ -1,3 +1,5 @@
+// *********** HERE STARTS limit-years.js *************

+

 window.addEventListener("load", addYearList);

 

 var limitYears = false;

@@ -45,10 +47,12 @@
 	

 	if(yearlist.style.display == "none"){

 		yearlist.style.display = "block";

+		document.querySelector("#settings i").innerText = "close";

 		yearLimits = true;

 	}

 	else{

 		yearlist.style.display = "none";

+		document.querySelector("#settings i").innerText = "settings";

 		yearLimits = true;

 	}

 }

diff --git a/js/script.js b/js/script.js
new file mode 100644
index 0000000..1493ae1
--- /dev/null
+++ b/js/script.js
@@ -0,0 +1,551 @@
+// *********** HERE STARTS circle-mode.js *************

+

+window.addEventListener("load", initCircleMode);

+

+circleMode = false;

+

+function initCircleMode() {

+	document.querySelector("#circle-mode").addEventListener('click', function() {		

+		if(circleMode) {

+			circleMode = false;

+			document.querySelector("#circle-mode i").innerText = "trip_origin";

+

+			s.graph.nodes().forEach(function(n) {

+				n.x = n.originalX;

+				n.y = n.originalY;

+				n.size = 10;

+			});

+			

+			s.refresh();

+			

+		}

+		else {	

+			circleMode = true;

+			document.querySelector("#circle-mode i").innerText = "shuffle";

+			

+			s.graph.nodes().forEach(function(n) {

+				n.x = n.circleX;

+				n.y = n.circleY;		

+			});

+			

+			s.refresh();

+		}

+	});

+}

+

+function isInRect (x, y, rect) {

+	if (x < -10000 || x > 10000) return true;

+	if (y < -10000 || y > 10000) return true;

+	

+	var ans = true;

+	var c = crossProd (rect[0], rect[1], x, y);

+	

+	for(var i=1; i<4; i++) {	

+		var temp = crossProd (rect[i], rect[(i+1)%4], x, y);

+		if (c*temp < 0) ans = false;

+	}

+	return ans;

+}

+

+function crossProd(r1, r2, x, y) {

+	return r1[0]*r2[1] + r2[0]*y + x*r1[1] - r1[0]*y - r2[0]*r1[1] - x*r2[1]; 

+}

+

+

+// *********** HERE STARTS graf.js *************

+

+window.addEventListener("load", initGraf);

+

+// s is the sigma graph

+// graf is the JSON graph

+var s, graf;

+

+// query dario JSON for the graph information

+function xhr(method, url, params, callback) {

+	var http = new XMLHttpRequest();

+	if (method == "POST") {

+		http.open(method, url, true);

+	} else {

+		if (params != "") {

+			http.open(method, url+"?"+params, true);

+		} else {

+			http.open(method, url, true);

+		}

+	}

+	http.onload = function() {

+		if(this.status != 200) {

+			console.warn("Attention, status code "+this.status+" when loading via xhr url "+url);

+		}

+		callback(this.responseText, this.status);

+	};

+	if (method == "POST") {

+		http.setRequestHeader("Content-type","application/x-www-form-urlencoded");

+		http.send(params);

+	} else {

+		http.send();

+	}

+}

+

+

+function initGraf() {

+	// create new methods for sigma library

+	updateSigma();

+	

+	// create graf, s is the sigma graf

+	s = new sigma({

+		renderers: [{

+			container: "graf",

+			type: "webgl"

+		}],

+		settings: {

+			defaultEdgeColor: "#fff",

+			edgeColor: "default",

+			defaultLabelColor: "#fff",

+			autoRescale: false,

+			zoomMax: 30,

+			// enableEdgeHovering: true,

+			font: "Roboto",

+			labelThreshold: 5

+		}

+	});

+

+	

+	// query for JSON for graph data

+	xhr("GET", "api.php", "action=getgraf", function(responseText, status) {

+		// graf is the JSON data

+		graf = JSON.parse(responseText);

+

+		// does graf.nodes have a size attribute?

+		var rectBorrar = [[0,0], [0,0], [0,0], [0,0]];

+		for (var i in graf.nodes) {

+			if (graf.nodes[i].name == "Erase")    rectBorrar[0] = [ graf.nodes[i].x , graf.nodes[i].y ];

+			if (graf.nodes[i].name == "Borrar")   rectBorrar[1] = [ graf.nodes[i].x , graf.nodes[i].y ];

+			if (graf.nodes[i].name == "Esborrar") rectBorrar[2] = [ graf.nodes[i].x , graf.nodes[i].y ];

+			if (graf.nodes[i].name == "Delete")   rectBorrar[3] = [ graf.nodes[i].x , graf.nodes[i].y ];

+		}

+		

+		var sizegraf = 0;

+		for (var i in graf.nodes) {

+			if ( isInRect(graf.nodes[i].x, graf.nodes[i].y, rectBorrar) ) continue;	

+			sizegraf++;

+		}		

+		var nnode = 0;

+		for (var i in graf.nodes) {

+			var ncolor = null;

+			

+			if(graf.nodes[i].sex =="F") ncolor = "#d61c08";

+			else if(graf.nodes[i].sex == "M") ncolor = "#0159aa";

+			else ncolor = "#0ca80a";

+			

+			// post-processing for year corrections

+			if(1970 < graf.nodes[i].year && graf.nodes[i].year < 2004) graf.nodes[i].year += 18;

+			

+			var newX = 5000*Math.cos( 2*Math.PI*nnode/sizegraf );

+			var newY = 5000*Math.sin( 2*Math.PI*nnode/sizegraf );	

+			

+			if (isInRect(graf.nodes[i].x, graf.nodes[i].y, rectBorrar) ) continue;	

+			

+			s.graph.addNode({

+				// we add color, originalColor, size, originalX..Y, circleX..Y atributes

+				id: graf.nodes[i].id,

+				year: graf.nodes[i].year,

+				sex: graf.nodes[i].sex,

+				label: graf.nodes[i].name,

+				x: graf.nodes[i].x,

+				y: graf.nodes[i].y, 

+				circleX: newX,

+				circleY: newY,

+				originalX: graf.nodes[i].x,

+				originalY: graf.nodes[i].y, 

+				size: 10,

+				color: ncolor,

+				originalColor: ncolor

+			});

+			nnode++;

+		

+		}

+

+		for (var i in graf.edges) {

+			if (isInRect(graf.nodes[graf.edges[i].a].x, graf.nodes[graf.edges[i].a].y, rectBorrar)) continue;	

+			if (isInRect(graf.nodes[graf.edges[i].b].x, graf.nodes[graf.edges[i].b].y, rectBorrar)) continue;	

+			

+			s.graph.addEdge({

+				id: i,

+				source: graf.edges[i].a,

+				target: graf.edges[i].b,

+				size: Math.min(4, Math.max((7/(2*Math.pow(20, 2)))*Math.pow(graf.edges[i].votes, 2) + 1/2, 0.5))

+			});

+		

+		}

+

+		s.bind('clickNode', function(e) {			

+			var nodeId = e.data.node.id,

+				toKeep = s.graph.neighbors(nodeId);

+				// toKeep[nodeId] = e.data.node;

+			

+			

+			s.graph.nodes().forEach(function(n) {

+				if (toKeep[n.id] || n.id == nodeId) {

+					n.color = n.originalColor;

+				} else {

+					n.color = '#333';

+				}

+			});

+

+			s.graph.edges().forEach(function(e) {

+				if ((e.source == nodeId || e.target == nodeId) && (toKeep[e.source] || toKeep[e.target])) {

+					e.color = '#fff';

+				} else {

+					e.color = '#333';

+				}

+			});

+			

+			if (circleMode) {

+				s.graph.nodes().forEach(function (n) {

+					n.x = n.circleX;

+					n.y = n.circleY;

+					n.size = 10;

+				});

+				

+				e.data.node.x = 0;

+				e.data.node.y = 0;

+				e.data.node.size = 30;

+			}

+			

+			s.refresh();

+

+			dialog.show(nodeId, toKeep);

+		});

+

+

+		s.refresh();

+		initSearchBar();

+

+		autocomplete(document.querySelector("#search-input"), graf.nodes, "search");

+	});

+}

+

+function updateSigma() {

+	// returns set of neighouts

+	sigma.classes.graph.addMethod("neighbors", function(nodeId) {

+		var k,

+		neighbors = {},

+		index = this.allNeighborsIndex[nodeId] || [];

+

+		for (k in index) {

+			neighbors[k] = this.nodesIndex[k];

+		}

+

+		return neighbors;

+	});

+	

+	// returns number of neighbours from a set of years

+	sigma.classes.graph.addMethod("numNeighborsFromYears", function(nodeId, showYearsCopy) {

+		var k,

+		neighbors = 0,

+		index = this.allNeighborsIndex[nodeId] || [];

+

+		for (k in index) {

+			if(this.nodesIndex){

+				if (showYearsCopy.has("" + this.nodesIndex[k].year)) neighbors++;

+				else if (this.nodesIndex[k].year == 0) neighbors++;

+			}

+		}

+		

+		return neighbors;

+	});

+}

+

+

+// *********** HERE STARTS limit-years.js *************

+

+window.addEventListener("load", addYearList);

+

+var limitYears = false;

+var showYears = new Set();

+

+function repaint() {

+	//targetYear: graf.nodes[e.source].year,

+	if(limitYears) {

+		var added = new Set();

+		

+		s.graph.nodes().forEach(function(n) {

+			var numNeig = s.graph.numNeighborsFromYears(n.id, showYears);

+			

+			if ((n.year == 0 && (n.sex == 'F' || n.sex == 'M') )

+					|| numNeig == 0

+					|| (!showYears.has("" + n.year) && (n.year != 0) )) {

+				n.hidden = true;

+			}

+			else {

+				n.hidden = false;

+				added.add(n.id);

+			}

+		});

+		

+		s.graph.edges().forEach(function(e) {

+			if(!added.has(e.source) && !added.has(e.target)){

+				e.hidden = true;

+			}

+			else e.hidden = false;

+		}); 

+	}

+	else {

+		s.graph.nodes().forEach(function(n) {

+			n.hidden = false;

+		});

+		

+		s.graph.edges().forEach(function(e) {

+			e.hidden = false;

+		});

+	}

+}

+

+function altYearList() {

+	var yearlist = document.querySelector("#year-list");

+	

+	if(yearlist.style.display == "none"){

+		yearlist.style.display = "block";

+		document.querySelector("#settings i").innerText = "close";

+		yearLimits = true;

+	}

+	else{

+		yearlist.style.display = "none";

+		document.querySelector("#settings i").innerText = "settings";

+		yearLimits = true;

+	}

+}

+

+function addYearList() {	

+	var ylistspan = document.querySelector("#year-list-span")

+	for(var year=2006; year<2019; year++) {

+		var yin = document.createElement("input");

+		yin.type = "checkbox";

+		yin.class = "mdl-button mdl-js-button mdl-button--fab mdl-button--mini-fab mdl-js-ripple-effect mdl-button--colored";

+		yin.name = "" + year;

+		yin.addEventListener("change", function(){ 

+			limitYears = true;

+			

+			if(this.checked) {

+				showYears.add(this.name);

+			}

+			else {

+				showYears.delete(this.name);

+			}

+			

+			if(showYears.size == 0) limitYears = false;

+			

+			repaint();

+			

+			s.refresh();

+		});

+		

+		var lab = document.createElement("label");

+		lab.innerHTML = "" + year + "<br>";

+		

+		ylistspan.appendChild(yin);

+		ylistspan.appendChild(lab);

+	}

+	

+	document.querySelector("#settings").addEventListener("click", altYearList);

+}

+

+// *********** HERE STARTS search-bar.js *************

+

+

+function altSearchBar() {

+	if (document.querySelector(".md-google-search__metacontainer").style.display == "none") {

+		document.querySelector(".md-google-search__metacontainer").style.display = "block";

+		document.querySelector("#search i").innerText = "fullscreen";

+	} else {

+		document.querySelector(".md-google-search__metacontainer").style.display = "none";

+		document.querySelector(".autocomplete-container").style.display = "none";

+		document.querySelector("#search i").innerText = "search";

+	}

+}

+

+function initSearchBar() {

+	document.querySelector("#search").addEventListener("click", altSearchBar);

+	if (window.innerWidth > 700) altSearchBar();

+}

+// *********** HERE STARTS dialog.js *************

+

+window.addEventListener("load", initDialog);

+

+var dialog = {

+	fill: function(data, text, html=false) {

+		var el = document.querySelectorAll("*[data-fill=\""+data+"\"]");

+		for (var i in el) {

+			if (html === true) {

+				el[i].innerHTML = text;

+			} else {

+				el[i].innerText = text;

+			}

+		}

+	},

+	show: function(id, neighbors) {

+		var neighbors = Object.values(neighbors);

+

+		this.fill("name", graf.nodes[id].name);

+		this.fill("year", graf.nodes[id].year);

+		this.fill("sex", graf.nodes[id].sex);

+		this.fill("id", "#"+id);

+		this.fill("n-edges", neighbors.length);

+

+		var list = "";

+		neighbors.forEach(function (a) {

+			list += "<li><b>"+graf.nodes[id].name+" - "+a.label+":</b> "+(graf.edges[id+"_"+a.id] ? graf.edges[id+"_"+a.id].votes : graf.edges[a.id+"_"+id].votes)+" vots</li>";

+		});

+		this.fill("edges", list, true);

+

+		if (window.innerWidth > 700) {

+			document.querySelector("#dialog").style.display = "block";

+			document.querySelector("#backdrop-container").style.display = "block";

+		} else {

+			document.querySelector("#summary-dialog").style.display = "block";

+		}

+	},

+	close: function() {

+		document.querySelector("#dialog").style.display = "none";

+		document.querySelector("#summary-dialog").style.display = "none";

+		document.querySelector("#backdrop-container").style.display = "none";

+

+		s.graph.nodes().forEach(function(n) {

+			n.color = n.originalColor;

+		});

+

+		s.graph.edges().forEach(function(e) {

+			e.color = e.originalColor;

+		});

+

+		if(circleMode) {

+			s.graph.nodes().forEach(function (n) {

+				n.x = n.circleX;

+				n.y = n.circleY;

+				n.size = 10;

+			});

+		}

+		else {

+			s.graph.nodes().forEach(function (n) {

+				n.x = n.originalX;

+				n.y = n.originalY;

+				n.size = 10;

+			});

+		}

+		s.refresh();

+

+	},

+	max: function() {

+		document.querySelector("#summary-dialog").style.display = "none";

+		document.querySelector("#dialog").style.display = "block";

+	},

+	min: function() {

+		document.querySelector("#dialog").style.display = "none";

+		document.querySelector("#summary-dialog").style.display = "block";

+	}

+};

+

+

+function initDialog() {

+	document.querySelector("#quit-dialog").addEventListener("click", dialog.close);

+	document.querySelector("#quit2-dialog").addEventListener("click", dialog.close);

+	document.querySelector("#max-dialog").addEventListener("click", dialog.max);

+	document.querySelector("#min-dialog").addEventListener("click", dialog.min);

+}

+

+// *********** HERE STARTS camera.js *************

+

+

+window.addEventListener('load', initCamera);

+

+function cameraGoto(nodeX, nodeY) {

+	sigma.misc.animation.camera( s.camera,

+		{ x: nodeX, y: nodeY, ratio: 1 },

+		{ duration: s.settings('animationsTime') || 300 }

+	);

+}

+

+function is_touch_device() {

+	  var prefixes = ' -webkit- -moz- -o- -ms- '.split(' ');

+	  var mq = function(query) {

+	    return window.matchMedia(query).matches;

+	  }

+

+	  if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {

+	    return true;

+	  }

+

+	  // include the 'heartz' as a way to have a non matching MQ to help terminate the join

+	  // https://git.io/vznFH

+	  var query = ['(', prefixes.join('touch-enabled),('), 'heartz', ')'].join('');

+	  return mq(query);

+}

+

+function initCamera() {

+	

+	if(!is_touch_device()) {

+		

+		document.querySelector("#zoomin").addEventListener("click", function() {

+			s.camera.goTo({

+				ratio: Math.max(s.camera.settings("zoomMin"), s.camera.ratio / Math.sqrt(2))

+			});

+		});

+	

+		document.querySelector("#zoomout").addEventListener("click", function() {

+			s.camera.goTo({

+				ratio: Math.min(s.camera.settings("zoomMax"), s.camera.ratio * Math.sqrt(2))

+			});

+		});

+	}

+	else{

+		document.querySelector("#zoomin").style.display = "none";

+		document.querySelector("#zoomout").style.display = "none";

+		

+		document.querySelector("#circle-mode").style.bottom = "110px";

+		document.querySelector("#settings").style.bottom = "60px";

+		document.querySelector("#search").style.bottom = "10px";

+	}

+}

+

+// *********** HERE STARTS easter-egg.js *************

+

+window.addEventListener("load", initEasterEgg);

+

+var seq = [38, 38, 40, 40, 37, 39, 37, 39, 65, 66, 13];

+var cur = 0;

+

+function justdoit() {

+	s.graph.nodes().forEach(function(n) {

+		switch(n.color) {

+			case "#d61c08":

+			n.color = "#0159aa";

+			break;

+

+			case "#0159aa":

+			n.color = "#0ca80a";

+			break;

+

+			case "#0ca80a":

+			n.color = "#d61c08";

+			break;

+		}

+	});

+

+	s.refresh();

+	setTimeout(justdoit, 333);

+}

+

+

+function initEasterEgg() {

+	document.addEventListener("keydown", function() {

+		if (event.key == "f" && event.target.getAttribute("id") != "search-input") altSearchBar();

+		if (event.which == seq[cur]) {

+			if (cur < seq.length) {

+				++cur;

+				if (cur == seq.length) {

+					justdoit();

+				}

+			}

+		} else cur = 0;

+	});

+}

diff --git a/js/search-bar.js b/js/search-bar.js
index a52ee65..e5c8846 100644
--- a/js/search-bar.js
+++ b/js/search-bar.js
@@ -1,15 +1,18 @@
-function altSearchBar() {
-	if (document.querySelector(".md-google-search__metacontainer").style.display == "none") {
-		document.querySelector(".md-google-search__metacontainer").style.display = "block";
-		document.querySelector("#search i").innerText = "fullscreen";
-	} else {
-		document.querySelector(".md-google-search__metacontainer").style.display = "none";
-		document.querySelector(".autocomplete-container").style.display = "none";
-		document.querySelector("#search i").innerText = "search";
-	}
-}
-
-function initSearchBar() {
-	document.querySelector("#search").addEventListener("click", altSearchBar);
-	if (window.innerWidth > 700) altSearchBar();
-}
+// *********** HERE STARTS search-bar.js *************

+

+

+function altSearchBar() {

+	if (document.querySelector(".md-google-search__metacontainer").style.display == "none") {

+		document.querySelector(".md-google-search__metacontainer").style.display = "block";

+		document.querySelector("#search i").innerText = "fullscreen";

+	} else {

+		document.querySelector(".md-google-search__metacontainer").style.display = "none";

+		document.querySelector(".autocomplete-container").style.display = "none";

+		document.querySelector("#search i").innerText = "search";

+	}

+}

+

+function initSearchBar() {

+	document.querySelector("#search").addEventListener("click", altSearchBar);

+	if (window.innerWidth > 700) altSearchBar();

+}

diff --git a/js/service-worker.js b/js/service-worker.js
index f5ed189..a70346f 100644
--- a/js/service-worker.js
+++ b/js/service-worker.js
@@ -1,102 +1,102 @@
-/*
- Copyright 2015 Google Inc. All Rights Reserved.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-
-'use strict';
-
-// Incrementing CACHE_VERSION will kick off the install event and force previously cached
-// resources to be cached again.
-const CACHE_VERSION = 1;
-let CURRENT_CACHES = {
-  offline: 'offline-v' + CACHE_VERSION
-};
-const OFFLINE_URL = 'offline.html';
-
-function createCacheBustedRequest(url) {
-  let request = new Request(url, {cache: 'reload'});
-  // See https://fetch.spec.whatwg.org/#concept-request-mode
-  // This is not yet supported in Chrome as of M48, so we need to explicitly check to see
-  // if the cache: 'reload' option had any effect.
-  if ('cache' in request) {
-    return request;
-  }
-
-  // If {cache: 'reload'} didn't have any effect, append a cache-busting URL parameter instead.
-  let bustedUrl = new URL(url, self.location.href);
-  bustedUrl.search += (bustedUrl.search ? '&' : '') + 'cachebust=' + Date.now();
-  return new Request(bustedUrl);
-}
-
-self.addEventListener('install', event => {
-  event.waitUntil(
-    // We can't use cache.add() here, since we want OFFLINE_URL to be the cache key, but
-    // the actual URL we end up requesting might include a cache-busting parameter.
-    fetch(createCacheBustedRequest(OFFLINE_URL)).then(function(response) {
-      return caches.open(CURRENT_CACHES.offline).then(function(cache) {
-        return cache.put(OFFLINE_URL, response);
-      });
-    })
-  );
-});
-
-self.addEventListener('activate', event => {
-  // Delete all caches that aren't named in CURRENT_CACHES.
-  // While there is only one cache in this example, the same logic will handle the case where
-  // there are multiple versioned caches.
-  let expectedCacheNames = Object.keys(CURRENT_CACHES).map(function(key) {
-    return CURRENT_CACHES[key];
-  });
-
-  event.waitUntil(
-    caches.keys().then(cacheNames => {
-      return Promise.all(
-        cacheNames.map(cacheName => {
-          if (expectedCacheNames.indexOf(cacheName) === -1) {
-            // If this cache name isn't present in the array of "expected" cache names,
-            // then delete it.
-            console.log('Deleting out of date cache:', cacheName);
-            return caches.delete(cacheName);
-          }
-        })
-      );
-    })
-  );
-});
-
-self.addEventListener('fetch', event => {
-  // We only want to call event.respondWith() if this is a navigation request
-  // for an HTML page.
-  // request.mode of 'navigate' is unfortunately not supported in Chrome
-  // versions older than 49, so we need to include a less precise fallback,
-  // which checks for a GET request with an Accept: text/html header.
-  if (event.request.mode === 'navigate' ||
-      (event.request.method === 'GET' &&
-       event.request.headers.get('accept').includes('text/html'))) {
-    console.log('Handling fetch event for', event.request.url);
-    event.respondWith(
-      fetch(event.request).catch(error => {
-        // The catch is only triggered if fetch() throws an exception, which will most likely
-        // happen due to the server being unreachable.
-        // If fetch() returns a valid HTTP response with an response code in the 4xx or 5xx
-        // range, the catch() will NOT be called. If you need custom handling for 4xx or 5xx
-        // errors, see https://github.com/GoogleChrome/samples/tree/gh-pages/service-worker/fallback-response
-        console.log('Fetch failed; returning offline page instead.', error);
-        return caches.match(OFFLINE_URL);
-      })
-    );
-  }
-
-  // If our if() condition is false, then this fetch handler won't intercept the request.
-  // If there are any other fetch handlers registered, they will get a chance to call
-  // event.respondWith(). If no fetch handlers call event.respondWith(), the request will be
-  // handled by the browser as if there were no service worker involvement.
-});
+/*

+ Copyright 2015 Google Inc. All Rights Reserved.

+ Licensed under the Apache License, Version 2.0 (the "License");

+ you may not use this file except in compliance with the License.

+ You may obtain a copy of the License at

+ http://www.apache.org/licenses/LICENSE-2.0

+ Unless required by applicable law or agreed to in writing, software

+ distributed under the License is distributed on an "AS IS" BASIS,

+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ See the License for the specific language governing permissions and

+ limitations under the License.

+*/

+

+'use strict';

+

+// Incrementing CACHE_VERSION will kick off the install event and force previously cached

+// resources to be cached again.

+const CACHE_VERSION = 1;

+let CURRENT_CACHES = {

+  offline: 'offline-v' + CACHE_VERSION

+};

+const OFFLINE_URL = 'offline.html';

+

+function createCacheBustedRequest(url) {

+  let request = new Request(url, {cache: 'reload'});

+  // See https://fetch.spec.whatwg.org/#concept-request-mode

+  // This is not yet supported in Chrome as of M48, so we need to explicitly check to see

+  // if the cache: 'reload' option had any effect.

+  if ('cache' in request) {

+    return request;

+  }

+

+  // If {cache: 'reload'} didn't have any effect, append a cache-busting URL parameter instead.

+  let bustedUrl = new URL(url, self.location.href);

+  bustedUrl.search += (bustedUrl.search ? '&' : '') + 'cachebust=' + Date.now();

+  return new Request(bustedUrl);

+}

+

+self.addEventListener('install', event => {

+  event.waitUntil(

+    // We can't use cache.add() here, since we want OFFLINE_URL to be the cache key, but

+    // the actual URL we end up requesting might include a cache-busting parameter.

+    fetch(createCacheBustedRequest(OFFLINE_URL)).then(function(response) {

+      return caches.open(CURRENT_CACHES.offline).then(function(cache) {

+        return cache.put(OFFLINE_URL, response);

+      });

+    })

+  );

+});

+

+self.addEventListener('activate', event => {

+  // Delete all caches that aren't named in CURRENT_CACHES.

+  // While there is only one cache in this example, the same logic will handle the case where

+  // there are multiple versioned caches.

+  let expectedCacheNames = Object.keys(CURRENT_CACHES).map(function(key) {

+    return CURRENT_CACHES[key];

+  });

+

+  event.waitUntil(

+    caches.keys().then(cacheNames => {

+      return Promise.all(

+        cacheNames.map(cacheName => {

+          if (expectedCacheNames.indexOf(cacheName) === -1) {

+            // If this cache name isn't present in the array of "expected" cache names,

+            // then delete it.

+            console.log('Deleting out of date cache:', cacheName);

+            return caches.delete(cacheName);

+          }

+        })

+      );

+    })

+  );

+});

+

+self.addEventListener('fetch', event => {

+  // We only want to call event.respondWith() if this is a navigation request

+  // for an HTML page.

+  // request.mode of 'navigate' is unfortunately not supported in Chrome

+  // versions older than 49, so we need to include a less precise fallback,

+  // which checks for a GET request with an Accept: text/html header.

+  if (event.request.mode === 'navigate' ||

+      (event.request.method === 'GET' &&

+       event.request.headers.get('accept').includes('text/html'))) {

+    console.log('Handling fetch event for', event.request.url);

+    event.respondWith(

+      fetch(event.request).catch(error => {

+        // The catch is only triggered if fetch() throws an exception, which will most likely

+        // happen due to the server being unreachable.

+        // If fetch() returns a valid HTTP response with an response code in the 4xx or 5xx

+        // range, the catch() will NOT be called. If you need custom handling for 4xx or 5xx

+        // errors, see https://github.com/GoogleChrome/samples/tree/gh-pages/service-worker/fallback-response

+        console.log('Fetch failed; returning offline page instead.', error);

+        return caches.match(OFFLINE_URL);

+      })

+    );

+  }

+

+  // If our if() condition is false, then this fetch handler won't intercept the request.

+  // If there are any other fetch handlers registered, they will get a chance to call

+  // event.respondWith(). If no fetch handlers call event.respondWith(), the request will be

+  // handled by the browser as if there were no service worker involvement.

+});