Merge pull request #7 from javierlc2000/master

Christmas pull request v2
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..27b3fd4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,74 @@
+# Graf Alternatiu de la FME

+## Independent interface for FME's graph

+

+This project is an alternative read-only user-interface for FME's graph. It is compatible and user-friendly with mobile devices.

+

+## How to contibute

+For the sake of safe and understandable code, we divide our JS scripts and CSS styles into multiples files. But, to make the page load faster, before a pull request, you should concatenate these files in the following manner. <br><br>

+

+In the file script.js, concatenate: (order is important!)

+<ol>

+	<li>circle-mode.js</li>

+	<li>graf.js</li>

+	<li>limit-years.js</li>

+	<li>search-bar.js</li>

+	<li>dialog.js</li>

+	<li>camera.js</li>

+	<li>easter-eg.js</li>

+</ol>

+<br><br>

+

+In the file styles.css, concatenate: (order is important!)

+<ol>

+	<li>general.css</li>

+	<li>graf.css</li>

+	<li>dialog.css</li>

+	<li>option-buttons.css</li>

+	<li>year-list.css</li>

+	<li>search-bar.css</li>

+</ol>

+

+## Things to do

+<ul>

+	<li>

+		Make limit-years prettier and incluse/exclusive with double check-boxes

+	</li>

+	<li>

+		Make statistics about the graph: biggest K_n, diameter, etc...

+	</li>

+	<li>

+		Make circle-mode + year-limit = smaller-circle-mode

+	</li>

+	<li>

+		Modify the autocomplete so that eliminated nodes don't appear

+	</li>

+	<li>

+		Separate non-connex nodes in circle-mode

+	</li>

+</ul>

+

+

+## Things done

+<ul>

+	<li>

+		Search bar with autocomplete

+	</li>

+	<li>

+		Dialog for nodes

+	</li>

+	<li>

+		Easter egg 

+	</li>

+	<li>

+		Limit years to a specific range

+	</li>

+	<li>

+		Circle visualization

+	</li>

+	<li>

+		Hide nodes inside erasing rectangle and very far from origin

+	</li>

+	<li>

+		Hide zoom buttons for tactile devices

+	</li>

+</ul>

diff --git a/api.php b/api.php
index 176fdfe..e6957b6 100644
--- a/api.php
+++ b/api.php
@@ -1,33 +1,33 @@
-<?php
-require_once("config.php");
-
-class write {
-  public static function output($json) {
-    print_r(json_encode($json));
-    exit();
-  }
-
-  public static function error($n, $msg) {
-    self::output(["error" => $n, "msg" => $msg]);
-  }
-}
-
-function get_graph() {
-  global $conf;
-  return json_decode(file_get_contents($conf["apiurl"]), true);
-}
-
-if (!isset($_GET["action"])) {
-  write::error(1, "No action provided");
-}
-
-switch ($_GET["action"]) {
-  case "getgraf":
-  $graf = file_get_contents($conf["apiurl"]);
-  echo $graf;
-  break;
-
-  default:
-  write::error(2, "Unknown action");
-}
-?>
+<?php

+require_once("config.php");

+

+class write {

+  public static function output($json) {

+    print_r(json_encode($json));

+    exit();

+  }

+

+  public static function error($n, $msg) {

+    self::output(["error" => $n, "msg" => $msg]);

+  }

+}

+

+function get_graph() {

+  global $conf;

+  return json_decode(file_get_contents($conf["apiurl"]), true);

+}

+

+if (!isset($_GET["action"])) {

+  write::error(1, "No action provided");

+}

+

+switch ($_GET["action"]) {

+  case "getgraf":

+  $graf = file_get_contents($conf["apiurl"]);

+  echo $graf;

+  break;

+

+  default:

+  write::error(2, "Unknown action");

+}

+?>

diff --git a/assistant/en.json b/assistant/en.json
index cadd504..e7aa8f9 100644
--- a/assistant/en.json
+++ b/assistant/en.json
@@ -1,25 +1,25 @@
-{
-  "new_to_graph": "It seems like {person} is new to the graph, because they don't share any edges with another person.",
-  "edges_1": "Alright, {person} shares an edge with {count}: {edges}.",
-  "edges_2": "Ok, {person} shares an edge with {count}: {edges}.",
-  "edges_3": "{person} shares an edge with {count}: {edges}.",
-  "edges_display": "{person} shares an edge with {count}.",
-  "count_singular": "one person",
-  "count_plural": "{count} people",
-  "not_found": "I'm sorry, but I didn't find anyone called {person} in the graph.",
-  "random_fact": "Here's a random fact: {fact}",
-  "num_vertices": "The number of vertices in the graph is {count}.",
-  "num_edges": "The number of edges in the graph is {count}.",
-  "didyouknow_last_edge": "did you know that the last edge is the one between {person1} and {person2}?",
-  "didyouknow_num_vertices": "Did you know that the number of vertices in the graph is {count}?",
-  "didyouknow_num_edges": "Did you know that the number of edges in the graph is {count}?",
-  "didyouknow_k3": "did you know that the first three vertices of the graph form a K3?",
-  "didyouknow_creator": "Did you know that Dario currently hosts the graph and that he is thought to be its creator?",
-  "didyouknow_groph": "did you know that for a while, the graph wasn't connex and the smallest component was named 'the groph'? Fortunately, right now the Groph is connected to the rest of the graph.",
-  "followup_1": "Is there anything else I can do for you?",
-  "followup_2": "Now, is there anything else you want to know?",
-  "followup_3": "Is there anything else you want to know?",
-  "latest_news": "<speak>These are the latest edges in the graph: {edges}.</speak>",
-  "latest_news_count": "<speak>These are the last {count} edges in the graph: {edges}.</speak>",
-  "and": "and"
-}
+{

+  "new_to_graph": "It seems like {person} is new to the graph, because they don't share any edges with another person.",

+  "edges_1": "Alright, {person} shares an edge with {count}: {edges}.",

+  "edges_2": "Ok, {person} shares an edge with {count}: {edges}.",

+  "edges_3": "{person} shares an edge with {count}: {edges}.",

+  "edges_display": "{person} shares an edge with {count}.",

+  "count_singular": "one person",

+  "count_plural": "{count} people",

+  "not_found": "I'm sorry, but I didn't find anyone called {person} in the graph.",

+  "random_fact": "Here's a random fact: {fact}",

+  "num_vertices": "The number of vertices in the graph is {count}.",

+  "num_edges": "The number of edges in the graph is {count}.",

+  "didyouknow_last_edge": "did you know that the last edge is the one between {person1} and {person2}?",

+  "didyouknow_num_vertices": "Did you know that the number of vertices in the graph is {count}?",

+  "didyouknow_num_edges": "Did you know that the number of edges in the graph is {count}?",

+  "didyouknow_k3": "did you know that the first three vertices of the graph form a K3?",

+  "didyouknow_creator": "Did you know that Dario currently hosts the graph and that he is thought to be its creator?",

+  "didyouknow_groph": "did you know that for a while, the graph wasn't connex and the smallest component was named 'the groph'? Fortunately, right now the Groph is connected to the rest of the graph.",

+  "followup_1": "Is there anything else I can do for you?",

+  "followup_2": "Now, is there anything else you want to know?",

+  "followup_3": "Is there anything else you want to know?",

+  "latest_news": "<speak>These are the latest edges in the graph: {edges}.</speak>",

+  "latest_news_count": "<speak>These are the last {count} edges in the graph: {edges}.</speak>",

+  "and": "and"

+}

diff --git a/assistant/es.json b/assistant/es.json
index e9ba01b..307c2a1 100644
--- a/assistant/es.json
+++ b/assistant/es.json
@@ -1,25 +1,25 @@
-{
-  "new_to_graph": "Parece que {person} se añadió hace poco al grafo, porque no comparte ninguna arista con nadie.",
-  "edges_1": "Veamos, {person} comparte aristas con {count}: {edges}.",
-  "edges_2": "Vale, {person} comparte aristas con {count}: {edges}.",
-  "edges_3": "{person} comparte aristas con {count}: {edges}.",
-  "edges_display": "{person} comparte aristas con {count}.",
-  "count_singular": "una persona",
-  "count_plural": "{count} personas",
-  "not_found": "Lo siento, pero no he encontrado a nadie llamado {person} en el grafo.",
-  "random_fact": "Aquí tienes un dato curioso: {fact}",
-  "num_vertices": "El número de vértices en el grafo es de {count}",
-  "num_edges": "El número de aristas en el grafo es de {count}",
-  "didyouknow_last_edge": "¿sabías que la última arista es la que une {person1} y {person2}?",
-  "didyouknow_num_vertices": "¿Sabías que el número de vértices en el grafo es de {count}?",
-  "didyouknow_num_edges": "¿Sabías que el número de aristas en el grafo es de {count}?",
-  "didyouknow_k3": "¿sabías que los primeros tres vértices del grafo forman un K3?",
-  "didyouknow_creator": "¿Sabías que Dario hospeda actualmente el grafo y que se cree que él es su creador?",
-  "didyouknow_groph": "¿sabías que durante un tiempo, el grafo no era conexo y su componiente más pequeña se le llamó 'groph'? Afortunadamente, ahora el groph está conectado con la resta del grafo.",
-  "followup_1": "¿Hay algo más que pueda hacer por ti?",
-  "followup_2": "¿Tienes alguna otra pregunta?",
-  "followup_3": "¿Hay algo más que quieras saber?",
-  "latest_news": "<speak>Estos son los últimos vértices del grafo: {edges}.</speak>",
-  "latest_news_count": "<speak>Estos son los últimos {count} vértices del grafo: {edges}.</speak>",
-  "and": "y"
-}
+{

+  "new_to_graph": "Parece que {person} se añadió hace poco al grafo, porque no comparte ninguna arista con nadie.",

+  "edges_1": "Veamos, {person} comparte aristas con {count}: {edges}.",

+  "edges_2": "Vale, {person} comparte aristas con {count}: {edges}.",

+  "edges_3": "{person} comparte aristas con {count}: {edges}.",

+  "edges_display": "{person} comparte aristas con {count}.",

+  "count_singular": "una persona",

+  "count_plural": "{count} personas",

+  "not_found": "Lo siento, pero no he encontrado a nadie llamado {person} en el grafo.",

+  "random_fact": "Aquí tienes un dato curioso: {fact}",

+  "num_vertices": "El número de vértices en el grafo es de {count}",

+  "num_edges": "El número de aristas en el grafo es de {count}",

+  "didyouknow_last_edge": "¿sabías que la última arista es la que une {person1} y {person2}?",

+  "didyouknow_num_vertices": "¿Sabías que el número de vértices en el grafo es de {count}?",

+  "didyouknow_num_edges": "¿Sabías que el número de aristas en el grafo es de {count}?",

+  "didyouknow_k3": "¿sabías que los primeros tres vértices del grafo forman un K3?",

+  "didyouknow_creator": "¿Sabías que Dario hospeda actualmente el grafo y que se cree que él es su creador?",

+  "didyouknow_groph": "¿sabías que durante un tiempo, el grafo no era conexo y su componiente más pequeña se le llamó 'groph'? Afortunadamente, ahora el groph está conectado con la resta del grafo.",

+  "followup_1": "¿Hay algo más que pueda hacer por ti?",

+  "followup_2": "¿Tienes alguna otra pregunta?",

+  "followup_3": "¿Hay algo más que quieras saber?",

+  "latest_news": "<speak>Estos son los últimos vértices del grafo: {edges}.</speak>",

+  "latest_news_count": "<speak>Estos son los últimos {count} vértices del grafo: {edges}.</speak>",

+  "and": "y"

+}

diff --git a/assistant_callback.php b/assistant_callback.php
index 15b6f79..aca4d9a 100644
--- a/assistant_callback.php
+++ b/assistant_callback.php
@@ -1,248 +1,248 @@
-<?php
-require_once("config.php");
-
-class write {
-  public static function do($json) {
-    echo json_encode($json)."\n";
-    exit();
-  }
-}
-
-class conv {
-  public static function ask($msg_array) {
-    $items = array();
-
-    foreach ($msg_array as $msg) {
-      $items[] = array("simpleResponse" => array("textToSpeech" => $msg));
-    }
-
-    self::ask_custom($items);
-  }
-
-  public static function ask_followup($msg, &$i18n) {
-    self::ask([$msg, $i18n->msg("followup_".mt_rand(1, 3))]);
-  }
-
-  public static function ask_custom($items) {
-    $json = array();
-    $json["payload"] = array();
-    $json["payload"]["google"] = array();
-    $json["payload"]["google"]["expectUserResponse"] = true;
-    $json["payload"]["google"]["richResponse"] = array();
-    $json["payload"]["google"]["richResponse"]["items"] = $items;
-
-    write::do($json);
-  }
-
-  public static function has($item, &$json) {
-    foreach ($json["originalDetectIntentRequest"]["payload"]["surface"]["capabilities"] as $cap) {
-      if ($cap["name"] == $item) {
-        return true;
-      }
-    }
-    return false;
-  }
-}
-
-class i18n {
-  public static $hllist = array("en", "es");
-  public $i18n_strings = null;
-  public $language = null;
-
-  function __construct($lang) {
-    global $_GET;
-    global $conf;
-
-    if (empty($this->i18n_strings)) {
-      $this->i18n_strings = array();
-    }
-
-    $this->language = $lang;
-
-    $this->i18n_strings = json_decode(file_get_contents("assistant/".$this->language.".json"), true);
-
-    return true;
-  }
-
-  function msg($message, $strings = null) {
-    if (!isset($this->i18n_strings[$message])) {
-      return false;
-    }
-
-    $string = $this->i18n_strings[$message];
-
-    if ($strings != null && is_array($strings)) {
-      foreach ($strings as $i => $subst) {
-        $string = str_replace("{".$i."}", $subst, $string);
-      }
-    }
-
-    return $string;
-  }
-}
-
-function get_graph() {
-  global $conf;
-  return json_decode(file_get_contents($conf["apiurl"]), true);
-}
-
-function comma($array, $wait=false) {
-  global $i18n;
-  if (count($array) == 0) {
-    return "";
-  }
-  if (count($array) == 1) {
-    return $array[0];
-  }
-  $break = ($wait ? '<break time="0.5s"/>' : "");
-  return implode($break.", ", array_slice($array, 0, -1)).$break.", ".$i18n->msg("and")." ".$array[count($array)-1];
-}
-
-$json = json_decode(file_get_contents('php://input'), true);
-
-if ($json === NULL || !isset($json["originalDetectIntentRequest"]) || !isset($json["originalDetectIntentRequest"]["source"]) || $json["originalDetectIntentRequest"]["source"] != "google") {
-  exit();
-}
-
-$graph = get_graph();
-$lang = (isset($json["queryResult"]["languageCode"]) && in_array(substr($json["queryResult"]["languageCode"], 0, 2), i18n::$hllist) ? substr($json["queryResult"]["languageCode"], 0, 2) : "en");
-$i18n = new i18n($lang);
-
-switch ($json["queryResult"]["intent"]["displayName"]) {
-  case "showVertex":
-  if (!isset($json["queryResult"]["parameters"]["Vertex"])) {
-    exit();
-  }
-  
-  $shortest = -1;
-  $closest = -1;
-  $closest_id = -1;
-
-  foreach ($graph["nodes"] as $id => $node) {
-    $lev = levenshtein(strtolower($json["queryResult"]["parameters"]["Vertex"]), strtolower($node["name"]));
-
-    if ($lev == 0) {
-      $closest = $node["name"];
-      $shortest = $lev;
-      $closest_id = $id;
-    }
-
-    if (($lev <= $shortest || $shortest < 0) && $lev <= 3) {
-      $closest = $node["name"];
-      $shortest = $lev;
-      $closest_id = $id;
-    }
-  }
-
-  if ($shortest != -1) {
-    $neighbors = array();
-    // We're suposing each vertex has a different name. If not, this would get rid of some of the edges, but if two vertexs had the same name there would be no way to differentiate them anyway, so I think it's ok.
-    foreach ($graph["edges"] as $edge) {
-      if ($edge["a"] == $closest_id) {
-        $neighbors[$graph["nodes"][$edge["b"]]["name"]] = $edge["votes"];
-      } elseif ($edge["b"] == $closest_id) {
-        $neighbors[$graph["nodes"][$edge["a"]]["name"]] = $edge["votes"];
-      }
-    }
-    if (count($neighbors) == 0) {
-      conv::ask_followup($i18n->msg("new_to_graph", array("person" => $closest)), $i18n);
-    } else {
-      arsort($neighbors);
-      $params = array("person" => $closest, "count" => (count($neighbors) == 1 ? $i18n->msg("count_singular") : $i18n->msg("count_plural", array("count" => count($neighbors)))));
-      /*if (conv::has("actions.capability.SCREEN_OUTPUT", $json)) {
-        $items = [
-          array(
-            "simpleResponse" => array(
-              "textToSpeech" => $i18n->msg("edges_display", $params)
-            )
-          ),
-          array(
-            "tableCard" => array(
-              "columnProperties" => [
-                array(
-                  "header" => "Adjacent vertex"
-                ),
-                array("header" => "Votes")
-              ],
-              "rows" => []
-            )
-          ),
-          array(
-            "simpleResponse" => array(
-              "textToSpeech" => $i18n->msg("followup_".mt_rand(1, 3))
-            )
-          )
-        ];
-        foreach ($neighbors as $neighbor => $votes) {
-          $items[1]["tableCard"]["rows"][] = array("cells" => [array("text" => $neighbor), array("text" => (string)$votes)]);
-        }
-        conv::ask_custom($items);
-      } else {*/ // This code shows a table if the user has a screen, but
-                 // unfortunately tables are not public yet.
-                 // @TODO: Uncomment when tables are out of developer preview.
-        $people = array_keys($neighbors);
-        $people_string = comma($people);
-        $params["edges"] = $people_string;
-        $num = mt_rand(1, 3);
-        conv::ask_followup($i18n->msg("edges_".$num, $params), $i18n);
-      /*}*/
-    }
-  } else {
-    conv::ask_followup($i18n->msg("not_found", array("person" => $json["queryResult"]["parameters"]["Vertex"])), $i18n);
-  }
-  break;
-
-  case "randomFact":
-  $rand = mt_rand(0, 5);
-  switch ($rand) {
-    case 0: // Last edge in the graph
-    $last = array_values(array_slice($graph["edges"], -1))[0];
-    conv::ask_followup($i18n->msg("random_fact", array("fact" => $i18n->msg("didyouknow_last_edge", array("person1" => $graph["nodes"][$last["a"]]["name"], "person2" => $graph["nodes"][$last["b"]]["name"])))), $i18n);
-    break;
-
-    case 1: // Num vertices
-    conv::ask_followup($i18n->msg("didyouknow_num_vertices", array("count" => count($graph["nodes"]))), $i18n);
-    break;
-
-    case 2: // Num edges
-    conv::ask_followup($i18n->msg("didyouknow_num_edges", array("count" => count($graph["edges"]))), $i18n);
-    break;
-
-    case 3: // First 3 vertices K3
-    conv::ask_followup($i18n->msg("random_fact", array("fact" => $i18n->msg("didyouknow_k3"))), $i18n);
-    break;
-
-    case 4: // Creator
-    conv::ask_followup($i18n->msg("didyouknow_creator"), $i18n);
-    break;
-
-    case 5: // Groph
-    conv::ask_followup($i18n->msg("random_fact", array("fact" => $i18n->msg("didyouknow_groph"))), $i18n);
-    break;
-  }
-  
-  break;
-
-  case "numVertexs":
-  conv::ask_followup($i18n->msg("num_vertices", array("count" => count($graph["nodes"]))), $i18n);
-  break;
-
-  case "numEdges":
-  conv::ask_followup($i18n->msg("num_edges", array("count" => count($graph["edges"]))), $i18n);
-  break;
-
-  case "latestNews":
-  $param = (isset($json["queryResult"]["parameters"]["numEdges"]) ? $json["queryResult"]["parameters"]["numEdges"] : null);
-  $num = (isset($param) && !empty($param) ? (int)$param : 4);
-  $last = array_values(array_slice($graph["edges"], -$num));
-  $edges = [];
-  foreach ($last as $edge) {
-    $edges[] = $graph["nodes"][$edge["a"]]["name"]." - ".$graph["nodes"][$edge["b"]]["name"];
-  }
-  $edges_string = comma($edges, true);
-  conv::ask_followup($i18n->msg((isset($param) && !empty($param) ? "latest_news_count" : "latest_news"), array("edges" => $edges_string, "count" => $num)), $i18n);
-  break;
-
-  default:
-  exit();
-}
+<?php

+require_once("config.php");

+

+class write {

+  public static function do($json) {

+    echo json_encode($json)."\n";

+    exit();

+  }

+}

+

+class conv {

+  public static function ask($msg_array) {

+    $items = array();

+

+    foreach ($msg_array as $msg) {

+      $items[] = array("simpleResponse" => array("textToSpeech" => $msg));

+    }

+

+    self::ask_custom($items);

+  }

+

+  public static function ask_followup($msg, &$i18n) {

+    self::ask([$msg, $i18n->msg("followup_".mt_rand(1, 3))]);

+  }

+

+  public static function ask_custom($items) {

+    $json = array();

+    $json["payload"] = array();

+    $json["payload"]["google"] = array();

+    $json["payload"]["google"]["expectUserResponse"] = true;

+    $json["payload"]["google"]["richResponse"] = array();

+    $json["payload"]["google"]["richResponse"]["items"] = $items;

+

+    write::do($json);

+  }

+

+  public static function has($item, &$json) {

+    foreach ($json["originalDetectIntentRequest"]["payload"]["surface"]["capabilities"] as $cap) {

+      if ($cap["name"] == $item) {

+        return true;

+      }

+    }

+    return false;

+  }

+}

+

+class i18n {

+  public static $hllist = array("en", "es");

+  public $i18n_strings = null;

+  public $language = null;

+

+  function __construct($lang) {

+    global $_GET;

+    global $conf;

+

+    if (empty($this->i18n_strings)) {

+      $this->i18n_strings = array();

+    }

+

+    $this->language = $lang;

+

+    $this->i18n_strings = json_decode(file_get_contents("assistant/".$this->language.".json"), true);

+

+    return true;

+  }

+

+  function msg($message, $strings = null) {

+    if (!isset($this->i18n_strings[$message])) {

+      return false;

+    }

+

+    $string = $this->i18n_strings[$message];

+

+    if ($strings != null && is_array($strings)) {

+      foreach ($strings as $i => $subst) {

+        $string = str_replace("{".$i."}", $subst, $string);

+      }

+    }

+

+    return $string;

+  }

+}

+

+function get_graph() {

+  global $conf;

+  return json_decode(file_get_contents($conf["apiurl"]), true);

+}

+

+function comma($array, $wait=false) {

+  global $i18n;

+  if (count($array) == 0) {

+    return "";

+  }

+  if (count($array) == 1) {

+    return $array[0];

+  }

+  $break = ($wait ? '<break time="0.5s"/>' : "");

+  return implode($break.", ", array_slice($array, 0, -1)).$break.", ".$i18n->msg("and")." ".$array[count($array)-1];

+}

+

+$json = json_decode(file_get_contents('php://input'), true);

+

+if ($json === NULL || !isset($json["originalDetectIntentRequest"]) || !isset($json["originalDetectIntentRequest"]["source"]) || $json["originalDetectIntentRequest"]["source"] != "google") {

+  exit();

+}

+

+$graph = get_graph();

+$lang = (isset($json["queryResult"]["languageCode"]) && in_array(substr($json["queryResult"]["languageCode"], 0, 2), i18n::$hllist) ? substr($json["queryResult"]["languageCode"], 0, 2) : "en");

+$i18n = new i18n($lang);

+

+switch ($json["queryResult"]["intent"]["displayName"]) {

+  case "showVertex":

+  if (!isset($json["queryResult"]["parameters"]["Vertex"])) {

+    exit();

+  }

+  

+  $shortest = -1;

+  $closest = -1;

+  $closest_id = -1;

+

+  foreach ($graph["nodes"] as $id => $node) {

+    $lev = levenshtein(strtolower($json["queryResult"]["parameters"]["Vertex"]), strtolower($node["name"]));

+

+    if ($lev == 0) {

+      $closest = $node["name"];

+      $shortest = $lev;

+      $closest_id = $id;

+    }

+

+    if (($lev <= $shortest || $shortest < 0) && $lev <= 3) {

+      $closest = $node["name"];

+      $shortest = $lev;

+      $closest_id = $id;

+    }

+  }

+

+  if ($shortest != -1) {

+    $neighbors = array();

+    // We're suposing each vertex has a different name. If not, this would get rid of some of the edges, but if two vertexs had the same name there would be no way to differentiate them anyway, so I think it's ok.

+    foreach ($graph["edges"] as $edge) {

+      if ($edge["a"] == $closest_id) {

+        $neighbors[$graph["nodes"][$edge["b"]]["name"]] = $edge["votes"];

+      } elseif ($edge["b"] == $closest_id) {

+        $neighbors[$graph["nodes"][$edge["a"]]["name"]] = $edge["votes"];

+      }

+    }

+    if (count($neighbors) == 0) {

+      conv::ask_followup($i18n->msg("new_to_graph", array("person" => $closest)), $i18n);

+    } else {

+      arsort($neighbors);

+      $params = array("person" => $closest, "count" => (count($neighbors) == 1 ? $i18n->msg("count_singular") : $i18n->msg("count_plural", array("count" => count($neighbors)))));

+      /*if (conv::has("actions.capability.SCREEN_OUTPUT", $json)) {

+        $items = [

+          array(

+            "simpleResponse" => array(

+              "textToSpeech" => $i18n->msg("edges_display", $params)

+            )

+          ),

+          array(

+            "tableCard" => array(

+              "columnProperties" => [

+                array(

+                  "header" => "Adjacent vertex"

+                ),

+                array("header" => "Votes")

+              ],

+              "rows" => []

+            )

+          ),

+          array(

+            "simpleResponse" => array(

+              "textToSpeech" => $i18n->msg("followup_".mt_rand(1, 3))

+            )

+          )

+        ];

+        foreach ($neighbors as $neighbor => $votes) {

+          $items[1]["tableCard"]["rows"][] = array("cells" => [array("text" => $neighbor), array("text" => (string)$votes)]);

+        }

+        conv::ask_custom($items);

+      } else {*/ // This code shows a table if the user has a screen, but

+                 // unfortunately tables are not public yet.

+                 // @TODO: Uncomment when tables are out of developer preview.

+        $people = array_keys($neighbors);

+        $people_string = comma($people);

+        $params["edges"] = $people_string;

+        $num = mt_rand(1, 3);

+        conv::ask_followup($i18n->msg("edges_".$num, $params), $i18n);

+      /*}*/

+    }

+  } else {

+    conv::ask_followup($i18n->msg("not_found", array("person" => $json["queryResult"]["parameters"]["Vertex"])), $i18n);

+  }

+  break;

+

+  case "randomFact":

+  $rand = mt_rand(0, 5);

+  switch ($rand) {

+    case 0: // Last edge in the graph

+    $last = array_values(array_slice($graph["edges"], -1))[0];

+    conv::ask_followup($i18n->msg("random_fact", array("fact" => $i18n->msg("didyouknow_last_edge", array("person1" => $graph["nodes"][$last["a"]]["name"], "person2" => $graph["nodes"][$last["b"]]["name"])))), $i18n);

+    break;

+

+    case 1: // Num vertices

+    conv::ask_followup($i18n->msg("didyouknow_num_vertices", array("count" => count($graph["nodes"]))), $i18n);

+    break;

+

+    case 2: // Num edges

+    conv::ask_followup($i18n->msg("didyouknow_num_edges", array("count" => count($graph["edges"]))), $i18n);

+    break;

+

+    case 3: // First 3 vertices K3

+    conv::ask_followup($i18n->msg("random_fact", array("fact" => $i18n->msg("didyouknow_k3"))), $i18n);

+    break;

+

+    case 4: // Creator

+    conv::ask_followup($i18n->msg("didyouknow_creator"), $i18n);

+    break;

+

+    case 5: // Groph

+    conv::ask_followup($i18n->msg("random_fact", array("fact" => $i18n->msg("didyouknow_groph"))), $i18n);

+    break;

+  }

+  

+  break;

+

+  case "numVertexs":

+  conv::ask_followup($i18n->msg("num_vertices", array("count" => count($graph["nodes"]))), $i18n);

+  break;

+

+  case "numEdges":

+  conv::ask_followup($i18n->msg("num_edges", array("count" => count($graph["edges"]))), $i18n);

+  break;

+

+  case "latestNews":

+  $param = (isset($json["queryResult"]["parameters"]["numEdges"]) ? $json["queryResult"]["parameters"]["numEdges"] : null);

+  $num = (isset($param) && !empty($param) ? (int)$param : 4);

+  $last = array_values(array_slice($graph["edges"], -$num));

+  $edges = [];

+  foreach ($last as $edge) {

+    $edges[] = $graph["nodes"][$edge["a"]]["name"]." - ".$graph["nodes"][$edge["b"]]["name"];

+  }

+  $edges_string = comma($edges, true);

+  conv::ask_followup($i18n->msg((isset($param) && !empty($param) ? "latest_news_count" : "latest_news"), array("edges" => $edges_string, "count" => $num)), $i18n);

+  break;

+

+  default:

+  exit();

+}

diff --git a/config.default.php b/config.default.php
index 943d1d8..8d304dc 100644
--- a/config.default.php
+++ b/config.default.php
@@ -1,6 +1,6 @@
-<?php
-// Rename this file as config.php and fill in the details below.
-$conf = array();
-$conf["apiurl"] = ""; // L'adreça de l'API del graf original.
-$conf["password"] = ""; // La contrasenya perquè la gent pugui entrar al graf.
-
+<?php

+// Rename this file as config.php and fill in the details below.

+$conf = array();

+$conf["apiurl"] = ""; // L'adreça de l'API del graf original.

+$conf["password"] = ""; // La contrasenya perquè la gent pugui entrar al graf.

+

diff --git a/css/dialog.css b/css/dialog.css
new file mode 100644
index 0000000..313ec29
--- /dev/null
+++ b/css/dialog.css
@@ -0,0 +1,78 @@
+/* ********** HERE STARTS dialog.css ****** */

+

+#dialog {

+	position: absolute;

+	top: 0px;

+	left: 0px;

+	width: 300px;

+	height: 100%;

+	background-color: white;

+	color: black;

+	z-index: 120;

+	overflow-y: auto;

+}

+

+#backdrop {

+	display: none;

+	position: absolute;

+	top: 0;

+	left: 0;

+	width: 100%;

+	height: 100%;

+	background-color: rgba(0, 0, 0, .5);

+	z-index: 110;

+}

+

+#dialog-vertex, #dialog-edge {

+	padding: 8px;

+	user-select: auto;

+}

+

+#dialog h2 {

+	font-weight: bold;

+	font-size: 20px;

+}

+

+#dialog h3 {

+	font-weight: bold;

+	font-size: 16px;

+	margin-bottom: 0;

+}

+

+#quit-dialog, #quit2-dialog {

+	position: absolute;

+	top: 8px;

+	right: 8px;

+}

+

+#min-dialog, #max-dialog {

+	position: absolute;

+	top: 8px;

+	right: 48px;

+}

+

+#min-dialog {

+	display: none;

+}

+

+#summary-dialog {

+	position: absolute;

+	top: 0px;

+	left: 0px;

+	width: 100%;

+	height: 100px;

+	background-color: white;

+	color: black;

+	z-index: 120;

+}

+

+#summary-vertex {

+	padding: 8px;

+	user-select: auto;

+}

+

+#summary-dialog h2 {

+	font-weight: bold;

+	font-size: 20px;

+	margin: 0;

+}
\ No newline at end of file
diff --git a/css/general.css b/css/general.css
new file mode 100644
index 0000000..eda32ac
--- /dev/null
+++ b/css/general.css
@@ -0,0 +1,13 @@
+/* ********** HERE STARTS general.css ****** */

+

+html, body {

+	margin: 0;

+	width: 100%;

+	height: 100%;

+	background-color: #060606;

+	color: white;

+	-webkit-tap-highlight-color: rgba(0, 0, 0, 0);

+	user-select: none !important;

+	font-family: 'Roboto';

+}

+

diff --git a/css/graf.css b/css/graf.css
new file mode 100644
index 0000000..de6a5a6
--- /dev/null
+++ b/css/graf.css
@@ -0,0 +1,11 @@
+/* ********** HERE STARTS graf.css ****** */

+

+#graf {

+	width: 100%;

+	height: 100%;

+}

+

+span {

+	position: relative;

+	z-index: -1;

+}
\ No newline at end of file
diff --git a/css/option-buttons.css b/css/option-buttons.css
new file mode 100644
index 0000000..f23168b
--- /dev/null
+++ b/css/option-buttons.css
@@ -0,0 +1,43 @@
+/* ********** HERE STARTS search-bar.css ****** */

+#circle-mode {

+	position: absolute;

+	right: 10px;

+	bottom: 210px;

+	z-index: 100;

+}

+

+#settings {

+	position: absolute;

+	right: 10px;

+	bottom: 160px;

+	z-index: 100;

+}

+

+#search {

+	position: absolute;

+	right: 10px;

+	bottom: 110px;

+	z-index: 100;

+}

+

+#zoomin {

+	position: absolute;

+	right: 10px;

+	bottom: 60px;

+	z-index: 100;

+}

+

+#zoomout {

+	position: absolute;

+	right: 10px;

+	bottom: 10px;

+	z-index: 100;

+}

+

+#zoomin.touch {

+	display: none;

+}

+

+#zoomout.touch {

+	display: none;

+}
\ No newline at end of file
diff --git a/css/search-bar.css b/css/search-bar.css
new file mode 100644
index 0000000..46d121e
--- /dev/null
+++ b/css/search-bar.css
@@ -0,0 +1,136 @@
+/* ********** HERE STARTS search-bar.css ****** */

+.md-google-search__metacontainer {

+	position: absolute;

+	top: 10px;

+	height: 48px;

+	width: 100%;

+	z-index: 100;

+}

+

+.md-google-search__container {

+	display: block;

+	margin-left: auto;

+	margin-right: auto;

+	height: 48px;

+	width: Calc(100% - 66px);

+	max-width: 720px;

+	white-space: nowrap;

+}

+

+.md-google-search {

+	height: 48px;

+	background-color: rgba(245, 245, 245, 1);

+	border: 1px solid rgba(0, 0, 0, 0);

+	-webkit-border-radius: 4px;

+	border-radius: 4px;

+	max-width: 720px;

+	position: relative;

+	-webkit-transition: background-color 100ms ease-in, width 100ms ease-out;

+	transition: background-color 100ms ease-in, width 100ms ease-out;

+}

+

+.md-google-search:focus-within {

+	border: 1px solid rgba(0, 0, 0, 0.45);

+	background-color: rgba(255, 255, 255, 1);

+	-webkit-box-shadow: 0 1px 1px rgba(255, 255, 255, .5);

+	box-shadow: 0 1px 1px rgba(255, 255, 255, .5);

+}

+

+.md-google-search__search-btn {

+	float: left;

+	background: none;

+	border: none;

+	opacity: .54;

+	outline: none;

+	padding: 0 4px;

+	line-height: 0;

+	color: #212121;

+}

+

+.md-google-search__search-btn svg, .md-google-search__empty-btn svg {

+	padding: 7px;

+	margin: 4px;

+}

+

+.md-google-search__field-container {

+	height: 46px;

+	padding: 0 11px;

+	margin-right: 48px;

+}

+

+.md-google-search__field {

+	border: none;

+	font: normal 16px Roboto, sans-serif;

+	height: 24px;

+	outline: none;

+	padding: 11px 0 11px 16px;

+	width: 100%;

+	background: transparent;

+}

+

+.md-google-search__empty-btn {

+	position: absolute;

+	right: 0;

+	top: 0;

+	background: none;

+	border: none;

+	opacity: .54;

+	outline: none;

+	padding: 0 4px;

+	line-height: 0;

+	color: #212121;

+	cursor: pointer;

+}

+

+/**

+  * Search Box Autocomplete

+  */

+.autocomplete-container {

+	z-index: 110;

+	position: absolute;

+	top: 60px;

+	width: 100%;

+}

+

+.autocomplete-items {

+	display: block;

+	margin-left: auto;

+	margin-right: auto;

+	width: Calc(100% - 66px);

+	max-width: 720px;

+	background-color: white;

+	color: black;

+	box-shadow: 0 2px 5px 0 rgba(255, 255, 255, 0.258824), 0 2px 10px 0

+		rgba(255, 255, 255, 0.156863) !important;

+}

+

+.autocomplete-item {

+	font-size: 16px;

+	padding: 12px 14px;

+	cursor: pointer;

+}

+

+.autocomplete-item:hover, .autocomplete-active {

+	background: #eee;

+}

+

+.autocomplete-year {

+	position: relative;

+	z-index: 120;

+	

+	color: #222;

+}

+

+@media ( max-width : 700px) {

+	#dialog {

+		width: Calc(100% - 32px) !important;

+		height: Calc(100% - 32px) !important;

+		margin: 16px;

+	}

+	#backdrop {

+		display: block;

+	}

+	#min-dialog {

+		display: block !important;

+	}

+}
\ No newline at end of file
diff --git a/css/styles.css b/css/styles.css
index 2ca308f..29a342a 100644
--- a/css/styles.css
+++ b/css/styles.css
@@ -1,253 +1,304 @@
+/* ********** HERE STARTS general.css ****** */
+
 html, body {
-  margin: 0;
-  width: 100%;
-  height: 100%;
-  background-color: #060606;
-  color: white;
-  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-  user-select: none!important;
-  font-family: 'Roboto';
+	margin: 0;
+	width: 100%;
+	height: 100%;
+	background-color: #060606;
+	color: white;
+	-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+	user-select: none !important;
+	font-family: 'Roboto';
 }
 
+
+/* ********** HERE STARTS graf.css ****** */
+
 #graf {
-  width: 100%;
-  height: 100%;
+	width: 100%;
+	height: 100%;
 }
 
+span {
+	position: relative;
+	z-index: -1;
+}
+
+
+/* ********** HERE STARTS dialog.css ****** */
+
 #dialog {
-  position: absolute;
-  top: 0px;
-  left: 0px;
-  width: 300px;
-  height: 100%;
-  background-color: white;
-  color: black;
-  z-index: 120;
-  overflow-y: auto;
+	position: absolute;
+	top: 0px;
+	left: 0px;
+	width: 300px;
+	height: 100%;
+	background-color: white;
+	color: black;
+	z-index: 120;
+	overflow-y: auto;
 }
 
 #backdrop {
-  display: none;
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  background-color: rgba(0, 0, 0, .5);
-  z-index: 110;
+	display: none;
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	background-color: rgba(0, 0, 0, .5);
+	z-index: 110;
 }
 
-#dialog-vertex, #dialog-edge{
-  padding: 8px;
-  user-select: auto;
+#dialog-vertex, #dialog-edge {
+	padding: 8px;
+	user-select: auto;
 }
 
 #dialog h2 {
-  font-weight: bold;
-  font-size: 20px;
+	font-weight: bold;
+	font-size: 20px;
 }
 
 #dialog h3 {
-  font-weight: bold;
-  font-size: 16px;
-  margin-bottom: 0;
+	font-weight: bold;
+	font-size: 16px;
+	margin-bottom: 0;
 }
 
 #quit-dialog, #quit2-dialog {
-  position: absolute;
-  top: 8px;
-  right: 8px;
+	position: absolute;
+	top: 8px;
+	right: 8px;
 }
 
 #min-dialog, #max-dialog {
-  position: absolute;
-  top: 8px;
-  right: 48px;
+	position: absolute;
+	top: 8px;
+	right: 48px;
 }
 
 #min-dialog {
-  display: none;
+	display: none;
 }
 
 #summary-dialog {
-  position: absolute;
-  top: 0px;
-  left: 0px;
-  width: 100%;
-  height: 100px;
-  background-color: white;
-  color: black;
-  z-index: 120;
+	position: absolute;
+	top: 0px;
+	left: 0px;
+	width: 100%;
+	height: 100px;
+	background-color: white;
+	color: black;
+	z-index: 120;
 }
 
 #summary-vertex {
-  padding: 8px;
-  user-select: auto;
+	padding: 8px;
+	user-select: auto;
 }
 
 #summary-dialog h2 {
-  font-weight: bold;
-  font-size: 20px;
-  margin: 0;
+	font-weight: bold;
+	font-size: 20px;
+	margin: 0;
+}
+
+/* ********** HERE STARTS search-bar.css ****** */
+#circle-mode {
+	position: absolute;
+	right: 10px;
+	bottom: 210px;
+	z-index: 100;
+}
+
+#settings {
+	position: absolute;
+	right: 10px;
+	bottom: 160px;
+	z-index: 100;
 }
 
 #search {
-  position: absolute;
-  right: 10px;
-  bottom: 110px;
-  z-index: 100;
+	position: absolute;
+	right: 10px;
+	bottom: 110px;
+	z-index: 100;
 }
 
 #zoomin {
-  position: absolute;
-  right: 10px;
-  bottom: 60px;
-  z-index: 100;
+	position: absolute;
+	right: 10px;
+	bottom: 60px;
+	z-index: 100;
 }
 
 #zoomout {
-  position: absolute;
-  right: 10px;
-  bottom: 10px;
-  z-index: 100;
+	position: absolute;
+	right: 10px;
+	bottom: 10px;
+	z-index: 100;
 }
 
-/**
-  * MD search box
-  */
+#zoomin.touch {
+	display: none;
+}
+
+#zoomout.touch {
+	display: none;
+}
+
+/* ********** HERE STARTS year-list.css ****** */
+#year-list {
+	padding: 10px;
+	padding-top: 0px;
+	float: right;
+	position:relative;
+	z-index:10;
+	display:none
+}
+
+#year-list-span {
+	position:relative;
+	z-index:10;
+}
+
+
+/* ********** HERE STARTS search-bar.css ****** */
 .md-google-search__metacontainer {
-  position: absolute;
-  top: 10px;
-  height: 48px;
-  width: 100%;
-  z-index: 100;
+	position: absolute;
+	top: 10px;
+	height: 48px;
+	width: 100%;
+	z-index: 100;
 }
 
 .md-google-search__container {
-  display: block;
-  margin-left: auto;
-  margin-right: auto;
-  height: 48px;
-  width: Calc(100% - 66px);
-  max-width: 720px;
-  white-space: nowrap;
+	display: block;
+	margin-left: auto;
+	margin-right: auto;
+	height: 48px;
+	width: Calc(100% - 66px);
+	max-width: 720px;
+	white-space: nowrap;
 }
 
 .md-google-search {
-  height: 48px;
-  background-color: rgba(245,245,245,1);
-  border: 1px solid rgba(0,0,0,0);
-  -webkit-border-radius: 4px;
-  border-radius: 4px;
-  max-width: 720px;
-  position: relative;
-  -webkit-transition: background-color 100ms ease-in,width 100ms ease-out;
-  transition: background-color 100ms ease-in,width 100ms ease-out;
+	height: 48px;
+	background-color: rgba(245, 245, 245, 1);
+	border: 1px solid rgba(0, 0, 0, 0);
+	-webkit-border-radius: 4px;
+	border-radius: 4px;
+	max-width: 720px;
+	position: relative;
+	-webkit-transition: background-color 100ms ease-in, width 100ms ease-out;
+	transition: background-color 100ms ease-in, width 100ms ease-out;
 }
 
 .md-google-search:focus-within {
-  border: 1px solid rgba(0,0,0,0.45);
-  background-color: rgba(255,255,255,1);
-  -webkit-box-shadow: 0 1px 1px rgba(255,255,255,.5);
-  box-shadow: 0 1px 1px rgba(255,255,255,.5);
+	border: 1px solid rgba(0, 0, 0, 0.45);
+	background-color: rgba(255, 255, 255, 1);
+	-webkit-box-shadow: 0 1px 1px rgba(255, 255, 255, .5);
+	box-shadow: 0 1px 1px rgba(255, 255, 255, .5);
 }
 
 .md-google-search__search-btn {
-  float: left;
-  background: none;
-  border: none;
-  opacity: .54;
-  outline: none;
-  padding: 0 4px;
-  line-height: 0;
-  color: #212121;
+	float: left;
+	background: none;
+	border: none;
+	opacity: .54;
+	outline: none;
+	padding: 0 4px;
+	line-height: 0;
+	color: #212121;
 }
 
 .md-google-search__search-btn svg, .md-google-search__empty-btn svg {
-  padding: 7px;
-  margin: 4px;
+	padding: 7px;
+	margin: 4px;
 }
 
 .md-google-search__field-container {
-  height: 46px;
-  padding: 0 11px;
-  margin-right: 48px;
+	height: 46px;
+	padding: 0 11px;
+	margin-right: 48px;
 }
 
 .md-google-search__field {
-  border: none;
-  font: normal 16px Roboto,sans-serif;
-  height: 24px;
-  outline: none;
-  padding: 11px 0 11px 16px;
-  width: 100%;
-  background: transparent;
+	border: none;
+	font: normal 16px Roboto, sans-serif;
+	height: 24px;
+	outline: none;
+	padding: 11px 0 11px 16px;
+	width: 100%;
+	background: transparent;
 }
 
 .md-google-search__empty-btn {
-  position: absolute;
-  right: 0;
-  top: 0;
-  background: none;
-  border: none;
-  opacity: .54;
-  outline: none;
-  padding: 0 4px;
-  line-height: 0;
-  color: #212121;
-  cursor: pointer;
+	position: absolute;
+	right: 0;
+	top: 0;
+	background: none;
+	border: none;
+	opacity: .54;
+	outline: none;
+	padding: 0 4px;
+	line-height: 0;
+	color: #212121;
+	cursor: pointer;
 }
 
 /**
   * Search Box Autocomplete
   */
-
-.autocomplete-container{
-  z-index: 110;
-  position: absolute;
-  top: 60px;
-  width: 100%;
+.autocomplete-container {
+	z-index: 110;
+	position: absolute;
+	top: 60px;
+	width: 100%;
 }
 
 .autocomplete-items {
-  display: block;
-  margin-left: auto;
-  margin-right: auto;
-  width: Calc(100% - 66px);
-  max-width: 720px;
-  background-color: white;
-  color: black;
-  box-shadow: 0 2px 5px 0 rgba(255,255,255,0.258824),0 2px 10px 0 rgba(255,255,255,0.156863) !important;
+	display: block;
+	margin-left: auto;
+	margin-right: auto;
+	width: Calc(100% - 66px);
+	max-width: 720px;
+	background-color: white;
+	color: black;
+	box-shadow: 0 2px 5px 0 rgba(255, 255, 255, 0.258824), 0 2px 10px 0
+		rgba(255, 255, 255, 0.156863) !important;
   max-height: 510px;
   overflow-y: auto;
 }
 
 .autocomplete-item {
-  font-size: 16px;
-  padding: 12px 14px;
-  cursor: pointer;
+	font-size: 16px;
+	padding: 12px 14px;
+	cursor: pointer;
 }
 
 .autocomplete-item:hover, .autocomplete-active {
-  background: #eee;
+	background: #eee;
 }
 
 .autocomplete-year {
-  color: #222;
+	position: relative;
+	z-index: 120;
+	
+	color: #222;
 }
 
-@media (max-width: 700px) {
-  #dialog {
-    width: Calc(100% - 32px)!important;
-    height: Calc(100% - 32px)!important;
-    margin: 16px;
-  }
-
-  #backdrop {
-    display: block;
-  }
-
-  #min-dialog {
-    display: block!important;
-  }
+@media ( max-width : 700px) {
+	#dialog {
+		width: Calc(100% - 32px) !important;
+		height: Calc(100% - 32px) !important;
+		margin: 16px;
+	}
+	#backdrop {
+		display: block;
+	}
+	#min-dialog {
+		display: block !important;
+	}
 }
diff --git a/css/year-list.css b/css/year-list.css
new file mode 100644
index 0000000..45993b2
--- /dev/null
+++ b/css/year-list.css
@@ -0,0 +1,14 @@
+/* ********** HERE STARTS year-list.css ****** */

+#year-list {

+	padding: 10px;

+	padding-top: 0px;

+	float: right;

+	position:relative;

+	z-index:10;

+	display:none

+}

+

+#year-list-span {

+	position:relative;

+	z-index:10;

+}
\ No newline at end of file
diff --git a/graf.php b/graf.php
index e965a00..6bbcd70 100644
--- a/graf.php
+++ b/graf.php
@@ -1,102 +1,193 @@
-<?php
-require_once("config.php");
-
-session_start();
-
-if (!isset($_POST["password"])) {
-    header("Location: login.php");
-    exit();
-}
-
-if ($_POST["password"] != $conf["password"]) {
-    header("Location: login.php?msg=wrong");
-    exit();
-}
-?>
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Graf alternatiu FME</title>
-
-    <meta name=viewport content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
-    <link rel="manifest" href="manifest.json">
-
-    <link rel="stylesheet" href="css/styles.css">
-
-    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
-    <link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.blue_grey-blue.min.css" />
-
-    <!-- Apple web app -->
-    <link rel="apple-touch-icon" href="img/graf.png">
-    <meta name="apple-mobile-web-app-title" content="Graf FME">
-    <meta name="apple-mobile-web-app-capable" content="yes">
-    <meta name="apple-mobile-web-app-status-bar-style" content="black">
-  </head>
-  <body>
-    <button id="search" class="mdl-button mdl-js-button mdl-button--fab mdl-button--mini-fab mdl-js-ripple-effect mdl-button--colored"><i class="material-icons">search</i></button>
-    <button id="zoomin" class="mdl-button mdl-js-button mdl-button--fab mdl-button--mini-fab mdl-js-ripple-effect mdl-button--colored"><i class="material-icons">zoom_in</i></button>
-    <button id="zoomout" class="mdl-button mdl-js-button mdl-button--fab mdl-button--mini-fab mdl-js-ripple-effect mdl-button--colored"><i class="material-icons">zoom_out</i></button>
-
-    <div id="backdrop-container" style="display: none;">
-      <div id="backdrop"></div>
-    </div>
-    <div id="dialog" class="mdl-shadow--2dp" style="display: none;">
-      <button id="quit-dialog" class="mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect"><i class="material-icons">close</i></button>
-      <button id="min-dialog" class="mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect"><i class="material-icons">remove</i></button>
-      <div id="dialog-vertex">
-        <h2 data-fill="name"></h2>
-        <ul>
-          <li><b>Any:</b> <span data-fill="year"></span></li>
-          <li><b>Sexe:</b> <span data-fill="sex"></span></li>
-          <li><b>ID:</b> <span data-fill="id"></span></li>
-        </ul>
-        <h3>Arestes (<span data-fill="n-edges"></span>):</h3>
-        <ul data-fill="edges">
-        </ul>
-      </div>
-      <div id="dialog-edge" style="display: none;">
-      </div>
-    </div>
-    <div id="summary-dialog" class="mdl-shadow--2dp" style="display: none;">
-      <button id="quit2-dialog" class="mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect"><i class="material-icons">close</i></button>
-      <button id="max-dialog" class="mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect"><i class="material-icons">add</i></button>
-      <div id="summary-vertex">
-        <h2 data-fill="name"></h2>
-        <p><span data-fill="year"></span>, <span data-fill="sex"></span>, <span data-fill="id"></span></p>
-      </div>
-    </div>
-    <!-- MD Search Box -->
-    <div class="md-google-search__metacontainer" style="display: none;">
-      <div class="md-google-search__container">
-        <div class="md-google-search">
-          <button class="md-google-search__search-btn">
-            <svg height="24px" viewBox="0 0 24 24" width="24px" xmlns="http://www.w3.org/2000/svg"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path><path d="M0 0h24v24H0z" fill="none"></path></svg>
-          </button>
-          <div class="md-google-search__field-container">
-            <input id="search-input" class="md-google-search__field" autocomplete="off" placeholder="Cerca" value="" name="search" type="text" spellcheck="false" style="outline: none;">
-          </div>
-          <button class="md-google-search__empty-btn" style="display: none;">
-            <svg focusable="false" height="24px" viewBox="0 0 24 24" width="24px" xmlns="http://www.w3.org/2000/svg"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path><path d="M0 0h24v24H0z" fill="none"></path></svg>
-          </button>
-        </div>
-      </div>
-    </div>
-
-    <div class="autocomplete-container" style="display: none;">
-      <div id="autocomplete-list" class="autocomplete-items"></div>
-    </div>
-
-    <div id="graf"></div>
-
-    <script src="https://cdnjs.cloudflare.com/ajax/libs/sigma.js/1.2.0/sigma.min.js"></script>
-
-    <!-- Search Bar JS files -->
-    <script src="./js/autocomplete.js" ></script>
-
-	   <script src="js/script.js"></script>
-
-    <script defer src="https://code.getmdl.io/1.3.0/material.min.js"></script>
-    <!--<script src="js/service-worker.js"></script>-->
-  </body>
-</html>
+<?php

+require_once ("config.php");

+

+session_start();

+

+if (! isset($_POST["password"])) {

+    header("Location: login.php");

+    exit();

+}

+

+if ($_POST["password"] != $conf["password"]) {

+    header("Location: login.php?msg=wrong");

+    exit();

+}

+?>

+

+<!DOCTYPE html>

+<html>

+<head>

+

+<meta charset="utf-8">

+<title>Graf alternatiu FME</title>

+

+<meta name=viewport

+	content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

+<link rel="manifest" href="manifest.json">

+

+<!-- own css stylesheets -->

+<!-- FOR DEPLOYING -->

+<link rel="stylesheet" href="css/styles.css">

+

+<!-- FOR CODING

+<link rel="stylesheet" href="css/general.css">

+<link rel="stylesheet" href="css/graf.css">

+<link rel="stylesheet" href="css/dialog.css">

+<link rel="stylesheet" href="css/option-buttons.css">

+<link rel="stylesheet" href="css/year-list.css">

+<link rel="stylesheet" href="css/search-bar.css">

+-->

+

+

+<!-- imported css stylesheets -->

+<link rel="stylesheet"

+	href="https://fonts.googleapis.com/icon?family=Material+Icons">

+<link rel="stylesheet"

+	href="https://code.getmdl.io/1.3.0/material.blue_grey-blue.min.css" />

+

+<!-- apple web app -->

+<link rel="apple-touch-icon" href="img/graf.png">

+<meta name="apple-mobile-web-app-title" content="Graf FME">

+<meta name="apple-mobile-web-app-capable" content="yes">

+<meta name="apple-mobile-web-app-status-bar-style" content="black">

+

+</head>

+

+<body>

+	<!-- side buttons -->

+	<div id="option-buttons">

+		<button id="circle-mode"

+			class="mdl-button mdl-js-button mdl-button--fab mdl-button--mini-fab mdl-js-ripple-effect mdl-button--colored">

+			<i class="material-icons">trip_origin</i>

+		</button>

+		<button id="settings"

+			class="mdl-button mdl-js-button mdl-button--fab mdl-button--mini-fab mdl-js-ripple-effect mdl-button--colored">

+			<i class="material-icons">settings</i>

+		</button>

+		<button id="search"

+			class="mdl-button mdl-js-button mdl-button--fab mdl-button--mini-fab mdl-js-ripple-effect mdl-button--colored">

+			<i class="material-icons">search</i>

+		</button>

+		<button id="zoomin"

+			class="mdl-button mdl-js-button mdl-button--fab mdl-button--mini-fab mdl-js-ripple-effect mdl-button--colored">

+			<i class="material-icons">zoom_in</i>

+		</button>

+		<button id="zoomout"

+			class="mdl-button mdl-js-button mdl-button--fab mdl-button--mini-fab mdl-js-ripple-effect mdl-button--colored">

+			<i class="material-icons">zoom_out</i>

+		</button>

+	</div>

+

+	<!-- limit year list -->

+	<div id="year-list" style="display:none">

+		<span id="year-list-span"></span>

+	</div>

+

+	<!-- search container -->

+	<div id="backdrop-container" style="display: none;">

+		<div id="backdrop"></div>

+	</div>

+

+	<!-- dialog container -->

+	<div id="dialog" class="mdl-shadow--2dp" style="display: none;">

+		<button id="quit-dialog"

+			class="mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect">

+			<i class="material-icons">close</i>

+		</button>

+		<button id="min-dialog"

+			class="mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect">

+			<i class="material-icons">remove</i>

+		</button>

+

+		<div id="dialog-vertex">

+			<h2 data-fill="name"></h2>

+			<ul>

+				<li><b>Any:</b> <span data-fill="year"></span></li>

+				<li><b>Sexe:</b> <span data-fill="sex"></span></li>

+				<li><b>ID:</b> <span data-fill="id"></span></li>

+			</ul>

+			<h3>

+				Arestes (<span data-fill="n-edges"></span>):

+			</h3>

+			<ul data-fill="edges">

+			</ul>

+		</div>

+		<div id="dialog-edge" style="display: none;"></div>

+	</div>

+	<div id="summary-dialog" class="mdl-shadow--2dp" style="display: none;">

+		<button id="quit2-dialog"

+			class="mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect">

+			<i class="material-icons">close</i>

+		</button>

+		<button id="max-dialog"

+			class="mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect">

+			<i class="material-icons">add</i>

+		</button>

+		<div id="summary-vertex">

+			<h2 data-fill="name"></h2>

+			<p>

+				<span data-fill="year"></span>, <span data-fill="sex"></span>, <span

+					data-fill="id"></span>

+			</p>

+		</div>

+	</div>

+	

+	<!-- MD Search Box -->

+	<div class="md-google-search__metacontainer" style="display: none;">

+		<div class="md-google-search__container">

+			<div class="md-google-search">

+				<button class="md-google-search__search-btn">

+					<svg height="24px" viewBox="0 0 24 24" width="24px"

+						xmlns="http://www.w3.org/2000/svg">

+						<path

+							d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path>

+						<path d="M0 0h24v24H0z" fill="none"></path></svg>

+				</button>

+				<div class="md-google-search__field-container">

+					<input id="search-input" class="md-google-search__field"

+						autocomplete="off" placeholder="Cerca" value="" name="search"

+						type="text" spellcheck="false" style="outline: none;">

+				</div>

+				<button class="md-google-search__empty-btn" style="display: none;">

+					<svg focusable="false" height="24px" viewBox="0 0 24 24"

+						width="24px" xmlns="http://www.w3.org/2000/svg">

+						<path

+							d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path>

+						<path d="M0 0h24v24H0z" fill="none"></path></svg>

+				</button>

+			</div>

+		</div>

+	</div>

+

+	<div class="autocomplete-container" style="display: none;">

+		<div id="autocomplete-list" class="autocomplete-items"></div>

+	</div>

+

+	<div id="graf"></div>

+

+	<script

+		src="https://cdnjs.cloudflare.com/ajax/libs/sigma.js/1.2.0/sigma.min.js"></script>

+

+

+	<!-- our scripts -->

+	<script src="./js/autocomplete.js"></script>

+

+	<!-- FOR DEPLOYING -->

+	<script src="./js/script.js"></script>

+

+	<!-- FOR CODING

+	<script src="./js/circle-mode.js"></script>

+	<script src="./js/graf.js"></script>

+	<script src="./js/limit-years.js"></script>

+	<script src="./js/search-bar.js"></script>

+	<script src="./js/dialog.js"></script>

+	<script src="./js/camera.js"></script>

+	<script src="./js/easter-egg.js"></script>

+	-->

+	

+	<!--<script src="js/service-worker.js"></script>-->

+

+	<!-- imported scripts -->

+	<script defer src="https://code.getmdl.io/1.3.0/material.min.js"></script>

+</body>

+</html>

diff --git a/img/graf.svg b/img/graf.svg
index 271d998..eee8b0a 100644
--- a/img/graf.svg
+++ b/img/graf.svg
@@ -1,28 +1,28 @@
-<svg width="194" height="194" xmlns="http://www.w3.org/2000/svg">
- <g>
-  <title>Layer 1</title>
-  <ellipse fill="#060606" stroke-width="0" fill-opacity="null" cx="97" cy="97" id="svg_10" rx="88" ry="88"/>
- </g>
- <g>
-  <title>Layer 2</title>
-  <g id="svg_9">
-   <line fill-opacity="null" x1="48" y1="138" x2="98.5" y2="99" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>
-   <line fill-opacity="null" x1="98.5" y1="99" x2="105" y2="150" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>
-   <line fill-opacity="null" x1="53" y1="76" x2="149" y2="84" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>
-   <line fill-opacity="null" x1="53" y1="76" x2="105" y2="150" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>
-   <line fill-opacity="null" x1="98.5" y1="99" x2="149" y2="84" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>
-   <line fill-opacity="null" x1="53" y1="76" x2="98.5" y2="99" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>
-   <line fill-opacity="null" x1="53" y1="76" x2="121" y2="35" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>
-  </g>
- </g>
- <g>
-  <title>Layer 3</title>
-  <ellipse fill="#d61c08" cx="48" cy="138" id="svg_2" rx="8" ry="8"/>
-  <ellipse fill="#0159aa" cx="98.5" cy="99" id="svg_3" rx="8" ry="8"/>
-  <ellipse fill="#0159aa" cx="105" cy="150" id="svg_4" rx="8" ry="8"/>
-  <ellipse fill="#d61c08" cx="53" cy="76" id="svg_5" rx="8" ry="8"/>
-  <ellipse fill="#d61c08" cx="149" cy="84" id="svg_6" rx="8" ry="8"/>
-  <ellipse fill="#0159aa" cx="121" cy="35" id="svg_7" rx="8" ry="8"/>
-  <ellipse fill="#0ca80a" cx="147" cy="123" id="svg_8" rx="8" ry="8"/>
-  </g>
-</svg>
+<svg width="194" height="194" xmlns="http://www.w3.org/2000/svg">

+ <g>

+  <title>Layer 1</title>

+  <ellipse fill="#060606" stroke-width="0" fill-opacity="null" cx="97" cy="97" id="svg_10" rx="88" ry="88"/>

+ </g>

+ <g>

+  <title>Layer 2</title>

+  <g id="svg_9">

+   <line fill-opacity="null" x1="48" y1="138" x2="98.5" y2="99" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>

+   <line fill-opacity="null" x1="98.5" y1="99" x2="105" y2="150" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>

+   <line fill-opacity="null" x1="53" y1="76" x2="149" y2="84" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>

+   <line fill-opacity="null" x1="53" y1="76" x2="105" y2="150" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>

+   <line fill-opacity="null" x1="98.5" y1="99" x2="149" y2="84" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>

+   <line fill-opacity="null" x1="53" y1="76" x2="98.5" y2="99" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>

+   <line fill-opacity="null" x1="53" y1="76" x2="121" y2="35" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>

+  </g>

+ </g>

+ <g>

+  <title>Layer 3</title>

+  <ellipse fill="#d61c08" cx="48" cy="138" id="svg_2" rx="8" ry="8"/>

+  <ellipse fill="#0159aa" cx="98.5" cy="99" id="svg_3" rx="8" ry="8"/>

+  <ellipse fill="#0159aa" cx="105" cy="150" id="svg_4" rx="8" ry="8"/>

+  <ellipse fill="#d61c08" cx="53" cy="76" id="svg_5" rx="8" ry="8"/>

+  <ellipse fill="#d61c08" cx="149" cy="84" id="svg_6" rx="8" ry="8"/>

+  <ellipse fill="#0159aa" cx="121" cy="35" id="svg_7" rx="8" ry="8"/>

+  <ellipse fill="#0ca80a" cx="147" cy="123" id="svg_8" rx="8" ry="8"/>

+  </g>

+</svg>

diff --git a/img/graf_square.svg b/img/graf_square.svg
index 14f51c7..e7af204 100644
--- a/img/graf_square.svg
+++ b/img/graf_square.svg
@@ -1,28 +1,28 @@
-<svg width="194" height="194" xmlns="http://www.w3.org/2000/svg">
- <g>
-  <title>Layer 1</title>
-  <rect fill="#060606" stroke-width="0" id="svg_10" width="100%" height="100%"/>
- </g>
- <g>
-  <title>Layer 2</title>
-  <g id="svg_9">
-   <line fill-opacity="null" x1="48" y1="138" x2="98.5" y2="99" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />
-   <line fill-opacity="null" x1="98.5" y1="99" x2="105" y2="150" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />
-   <line fill-opacity="null" x1="53" y1="76" x2="149" y2="84" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />
-   <line fill-opacity="null" x1="53" y1="76" x2="105" y2="150" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />
-   <line fill-opacity="null" x1="98.5" y1="99" x2="149" y2="84" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />
-   <line fill-opacity="null" x1="53" y1="76" x2="98.5" y2="99" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />
-   <line fill-opacity="null" x1="53" y1="76" x2="121" y2="35" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />
-  </g>
- </g>
- <g>
-  <title>Layer 3</title>
-  <ellipse fill="#d61c08" cx="48" cy="138" id="svg_2" rx="8" ry="8"/>
-  <ellipse fill="#0159aa" cx="98.5" cy="99" id="svg_3" rx="8" ry="8"/>
-  <ellipse fill="#0159aa" cx="105" cy="150" id="svg_4" rx="8" ry="8"/>
-  <ellipse fill="#d61c08" cx="53" cy="76" id="svg_5" rx="8" ry="8"/>
-  <ellipse fill="#d61c08" cx="149" cy="84" id="svg_6" rx="8" ry="8"/>
-  <ellipse fill="#0159aa" cx="121" cy="35" id="svg_7" rx="8" ry="8"/>
-  <ellipse fill="#0ca80a" cx="147" cy="123" id="svg_8" rx="8" ry="8"/>
-  </g>
-</svg>
+<svg width="194" height="194" xmlns="http://www.w3.org/2000/svg">

+ <g>

+  <title>Layer 1</title>

+  <rect fill="#060606" stroke-width="0" id="svg_10" width="100%" height="100%"/>

+ </g>

+ <g>

+  <title>Layer 2</title>

+  <g id="svg_9">

+   <line fill-opacity="null" x1="48" y1="138" x2="98.5" y2="99" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />

+   <line fill-opacity="null" x1="98.5" y1="99" x2="105" y2="150" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />

+   <line fill-opacity="null" x1="53" y1="76" x2="149" y2="84" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />

+   <line fill-opacity="null" x1="53" y1="76" x2="105" y2="150" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />

+   <line fill-opacity="null" x1="98.5" y1="99" x2="149" y2="84" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />

+   <line fill-opacity="null" x1="53" y1="76" x2="98.5" y2="99" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />

+   <line fill-opacity="null" x1="53" y1="76" x2="121" y2="35" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />

+  </g>

+ </g>

+ <g>

+  <title>Layer 3</title>

+  <ellipse fill="#d61c08" cx="48" cy="138" id="svg_2" rx="8" ry="8"/>

+  <ellipse fill="#0159aa" cx="98.5" cy="99" id="svg_3" rx="8" ry="8"/>

+  <ellipse fill="#0159aa" cx="105" cy="150" id="svg_4" rx="8" ry="8"/>

+  <ellipse fill="#d61c08" cx="53" cy="76" id="svg_5" rx="8" ry="8"/>

+  <ellipse fill="#d61c08" cx="149" cy="84" id="svg_6" rx="8" ry="8"/>

+  <ellipse fill="#0159aa" cx="121" cy="35" id="svg_7" rx="8" ry="8"/>

+  <ellipse fill="#0ca80a" cx="147" cy="123" id="svg_8" rx="8" ry="8"/>

+  </g>

+</svg>

diff --git a/index.php b/index.php
index 33dc3be..8b8138c 100644
--- a/index.php
+++ b/index.php
@@ -1,69 +1,69 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Graf alternatiu FME</title>
-
-    <meta name=viewport content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
-
-    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
-    <link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.blue_grey-blue.min.css" />
-
-    <style>
-    .mdl-card {
-      height: 320px;
-    }
-    .mdl-card > .mdl-card__title.graf {
-      color: #fff;
-      background: url("img/graf_screenshot.png") center top 15% no-repeat #46B6AC;
-    }
-    .misc {
-      color: #fff;
-      background: #00695C;
-    }
-    </style>
-  </head>
-  <body>
-    <div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
-      <header class="mdl-layout__header">
-        <div class="mdl-layout__header-row">
-          <!-- Title -->
-          <span class="mdl-layout-title">Graf alternatiu FME</span>
-        </div>
-      </header>
-      <main class="mdl-layout__content">
-        <div class="page-content">
-          <div class="mdl-grid">
-            <div class="mdl-shadow--2dp mdl-cell mdl-cell--4-col mdl-card">
-              <div class="mdl-card__title mdl-card--expand graf">
-                <h2 class="mdl-card__title-text">El graf</h2>
-              </div>
-              <div class="mdl-card__supporting-text">
-                Una versió de només lectura del graf, optimitzada per navegadors móvils i amb una interfície millorada.
-              </div>
-              <div class="mdl-card__actions mdl-card--border">
-                <a href="graf.php" class="mdl-button mdl-button--colored mdl-js-button mdl-js-ripple-effect">
-                  Vés-hi
-                </a>
-              </div>
-            </div>
-            <div class="mdl-shadow--2dp mdl-cell mdl-cell--4-col mdl-card">
-              <div class="mdl-card__title mdl-card--expand misc">
-                <h2 class="mdl-card__title-text">App de l'Assistent de Google</h2>
-              </div>
-              <div class="mdl-card__supporting-text">
-                Com a novetat, ara pots navegar el graf amb l'Assistent de Google! Disponible en castellà i en anglés.
-              </div>
-              <div class="mdl-card__actions mdl-card--border">
-                <a href="https://assistant.google.com/services/a/uid/000000249b9f19cb" class="mdl-button mdl-button--colored mdl-js-button mdl-js-ripple-effect">
-                  Vés-hi
-                </a>
-              </div>
-            </div>
-          </div>
-        </div>
-      </main>
-    </div>
-    <script defer src="https://code.getmdl.io/1.3.0/material.min.js"></script>
-  </body>
-</html>
+<!DOCTYPE html>

+<html>

+  <head>

+    <meta charset="utf-8">

+    <title>Graf alternatiu FME</title>

+

+    <meta name=viewport content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

+

+    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">

+    <link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.blue_grey-blue.min.css" />

+

+    <style>

+    .mdl-card {

+      height: 320px;

+    }

+    .mdl-card > .mdl-card__title.graf {

+      color: #fff;

+      background: url("img/graf_screenshot.png") center top 15% no-repeat #46B6AC;

+    }

+    .misc {

+      color: #fff;

+      background: #00695C;

+    }

+    </style>

+  </head>

+  <body>

+    <div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">

+      <header class="mdl-layout__header">

+        <div class="mdl-layout__header-row">

+          <!-- Title -->

+          <span class="mdl-layout-title">Graf alternatiu FME</span>

+        </div>

+      </header>

+      <main class="mdl-layout__content">

+        <div class="page-content">

+          <div class="mdl-grid">

+            <div class="mdl-shadow--2dp mdl-cell mdl-cell--4-col mdl-card">

+              <div class="mdl-card__title mdl-card--expand graf">

+                <h2 class="mdl-card__title-text">El graf</h2>

+              </div>

+              <div class="mdl-card__supporting-text">

+                Una versió de només lectura del graf, optimitzada per navegadors móvils i amb una interfície millorada.

+              </div>

+              <div class="mdl-card__actions mdl-card--border">

+                <a href="graf.php" class="mdl-button mdl-button--colored mdl-js-button mdl-js-ripple-effect">

+                  Vés-hi

+                </a>

+              </div>

+            </div>

+            <div class="mdl-shadow--2dp mdl-cell mdl-cell--4-col mdl-card">

+              <div class="mdl-card__title mdl-card--expand misc">

+                <h2 class="mdl-card__title-text">App de l'Assistent de Google</h2>

+              </div>

+              <div class="mdl-card__supporting-text">

+                Com a novetat, ara pots navegar el graf amb l'Assistent de Google! Disponible en castellà i en anglés.

+              </div>

+              <div class="mdl-card__actions mdl-card--border">

+                <a href="https://assistant.google.com/services/a/uid/000000249b9f19cb" class="mdl-button mdl-button--colored mdl-js-button mdl-js-ripple-effect">

+                  Vés-hi

+                </a>

+              </div>

+            </div>

+          </div>

+        </div>

+      </main>

+    </div>

+    <script defer src="https://code.getmdl.io/1.3.0/material.min.js"></script>

+  </body>

+</html>

diff --git a/js/autocomplete.js b/js/autocomplete.js
index d7648da..107b03a 100644
--- a/js/autocomplete.js
+++ b/js/autocomplete.js
@@ -1,142 +1,152 @@
+// *********** 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:*/

-  var currentFocus;

-  /*execute a function when someone writes in the text field:*/

-  inp.addEventListener("input", function(e) {

-    var a, b, i, val = this.value;

-    /*close any already open lists of autocompleted values*/

-    clearLists();

-    document.querySelector(".md-google-search__empty-btn").style.display = (val ? "block" : "none");

-    if (!val || val.length < 3) return false;

-    currentFocus = -1;

-    var is_empty = true;

+	/*the autocomplete function takes two arguments,

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

+	var currentFocus;

+	/*execute a function when someone writes in the text field:*/

+	inp.addEventListener("input", function(e) {

+		var a, b, i, val = this.value;

+		/*close any already open lists of autocompleted values*/

+		clearLists();

+		document.querySelector(".md-google-search__empty-btn").style.display = (val ? "block" : "none");

+		if (!val || val.length < 3) return false;

+		currentFocus = -1;

+		var is_empty = true;

 

-    /*for each item in the object...*/

-    for (node in obj) {

-  		var nomNode = obj[node].name;

+		/*for each item in the object...*/

+		for (node in obj) {

+			var nomNode = obj[node].name;

 

-  		if (nomNode.toUpperCase().includes(val.toUpperCase())) {

-        is_empty = false;

-  			var parts = nomNode.toUpperCase().split(val.toUpperCase());

+			if (nomNode.toUpperCase().includes(val.toUpperCase())) {

+				is_empty = false;

+				var parts = nomNode.toUpperCase().split(val.toUpperCase());

+				

+				/*create a DIV element for each matching element:*/

+				b = document.createElement("div");

+				b.setAttribute("class", "autocomplete-item");

 

-			  /*create a DIV element for each matching element:*/

-			  b = document.createElement("div");

-        b.setAttribute("class", "autocomplete-item");

+				/*make the matching letters bold:*/

+				if (parts[0].length == 0) b.innerHTML = "";

+				else b.innerHTML = "<span style='font-weight: bold; position:relative; z-index:120;'>" + nomNode.substr(0, parts[0].length) + "</span>";

+				

+				b.innerHTML += nomNode.substr(parts[0].length, val.length);

+				b.innerHTML += "<span style='font-weight: bold; position:relative; z-index:120;'>" + nomNode.substr(parts[0].length + val.length) + "</span>";

+				b.innerHTML += " <span class='autocomplete-year'>(" + obj[node].year + ")</span>";

 

-			  /*make the matching letters bold:*/

-			  if (parts[0].length == 0) b.innerHTML = "";

-			  else b.innerHTML = "<span style='font-weight: bold;'>" + nomNode.substr(0, parts[0].length) + "</span>";

-			  b.innerHTML += nomNode.substr(parts[0].length, val.length);

-			  b.innerHTML += "<span style='font-weight: bold;'>" + nomNode.substr(parts[0].length + val.length) + "</span>";

-			  b.innerHTML += " <span class='autocomplete-year'>(" + obj[node].year + ")</span>";

+				/*include node id to keep track of which is it*/

+				b.dataset.id = node;

 

-        /*include node id to keep track of which is it*/

-        b.dataset.id = node;

+				/*execute a function when someone clicks on the item value (DIV element):*/

+				b.addEventListener("click", function(e) {

+					/*insert the value for the autocomplete text field:*/

+					var n = this.dataset.id;

+					inp.value = obj[n].name;

 

-			  /*execute a function when someone clicks on the item value (DIV element):*/

-			  b.addEventListener("click", function(e) {

-				  /*insert the value for the autocomplete text field:*/

-				  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);

-  						break;

-  					case "addEdge":

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

-  					  alert(obj[n].name);

-  					  break;

-				  }

+					switch (act) {

+						case "search":

+							// Move camera to desired node

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

+							break;

+						case "addEdge":

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

+							alert(obj[n].name);

+							break;

+					}

 

-				  /*close the list of autocompleted values,

-				  (or any other open lists of autocompleted values:*/

-				  clearLists();

-			  });

+					/*close the list of autocompleted values,

+					(or any other open lists of autocompleted values:*/

+					clearLists();

+				});

 

-			  // Set "data-edges" attribute and compare with others

-			  var nEdges = Object.keys(s.graph.neighbors(node)).length;

-			  b.dataset.edges = nEdges;

-			  var inserted = false;

+				// Set "data-edges" attribute and compare with others

+				var nEdges = Object.keys(s.graph.neighbors(node)).length;

+				b.dataset.edges = nEdges;

+				var inserted = false;

 

-			  // Sort nodes by degree

-			  for (i in document.querySelector("#autocomplete-list").childNodes) {

-				  var child = document.querySelector("#autocomplete-list").childNodes[i];

-				  if (!child.dataset) break;

-				  if (nEdges > child.dataset.edges) {

-					  document.querySelector("#autocomplete-list").insertBefore(b, child);

-					  inserted = true;

-					  break;

-				  }

-			  }

+				// Sort nodes by degree

+				for (i in document.querySelector("#autocomplete-list").childNodes) {

+					var child = document.querySelector("#autocomplete-list").childNodes[i];

+					if (!child.dataset) break;

+					if (nEdges > child.dataset.edges) {

+						document.querySelector("#autocomplete-list").insertBefore(b, child);

+						inserted = true;

+						break;

+					}

+				}

 

-			  if (!inserted) {

-				  document.querySelector("#autocomplete-list").appendChild(b);

-			  }

-		  }

-    }

+				if (!inserted) {

+					document.querySelector("#autocomplete-list").appendChild(b);

+				}

+			}

+		}

 

-    document.querySelector(".autocomplete-container").style.display = (is_empty ? "none" : "block");

-  });

+		document.querySelector(".autocomplete-container").style.display = (is_empty ? "none" : "block");

+	});

 

-  document.querySelector(".md-google-search__empty-btn").addEventListener("click", function() {

-    document.querySelector("#search-input").value = "";

-    this.style.display = "none";

-  });

+	document.querySelector(".md-google-search__empty-btn").addEventListener("click", function() {

+		document.querySelector("#search-input").value = "";

+		this.style.display = "none";

+	});

 

-  /*execute a function presses a key on the keyboard:*/

-  inp.addEventListener("keydown", function(e) {

-      var x = document.getElementById("autocomplete-list");

-      if (x) x = x.getElementsByTagName("div");

-      if (x.length == 0) return;

-      if (e.keyCode == 40) {

-        /*If the objow DOWN key is pressed,

-        increase the currentFocus variable:*/

-        currentFocus++;

-        /*and and make the current item more visible:*/

-        addActive(x);

-      } else if (e.keyCode == 38) { //up

-        /*If the objow UP key is pressed,

-        decrease the currentFocus variable:*/

-        currentFocus--;

-        /*and and make the current item more visible:*/

-        addActive(x);

-      } else if (e.keyCode == 13) {

-        /*If the ENTER key is pressed, prevent the form from being submitted,*/

-        e.preventDefault();

-        if (currentFocus > -1) {

-          /*and simulate a click on the "active" item:*/

-          if (x) x[currentFocus].click();

-        }

-      }

-  });

-  function addActive(x) {

-    /*a function to classify an item as "active":*/

-    if (!x) return false;

-    /*start by removing the "active" class on all items:*/

-    removeActive(x);

-    if (currentFocus >= x.length) currentFocus = 0;

-    if (currentFocus < 0) currentFocus = (x.length - 1);

-    /*add class "autocomplete-active":*/

-    x[currentFocus].classList.add("autocomplete-active");

-  }

-  function removeActive(x) {

-    /*a function to remove the "active" class from all autocomplete items:*/

-    for (var i = 0; i < x.length; i++) {

-      x[i].classList.remove("autocomplete-active");

-    }

-  }

-  function clearLists() {

-    /*close all autocomplete lists in the document,

-    except the one passed as an argument:*/

-    var x = document.querySelector("#autocomplete-list");

-    x.innerHTML = "";

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

-  }

-  /*execute a function when someone clicks in the document:*/

-  document.addEventListener("click", function (e) {

-      clearLists();

-  });

+	/*execute a function presses a key on the keyboard:*/

+	inp.addEventListener("keydown", function(e) {

+		var x = document.getElementById("autocomplete-list");

+		if (x) x = x.getElementsByTagName("div");

+		if (x.length == 0) return;

+		if (e.keyCode == 40) {

+			/*If the objow DOWN key is pressed,

+			increase the currentFocus variable:*/

+			currentFocus++;

+			/*and and make the current item more visible:*/

+			addActive(x);

+		} else if (e.keyCode == 38) { //up

+			/*If the objow UP key is pressed,

+			decrease the currentFocus variable:*/

+			currentFocus--;

+			/*and and make the current item more visible:*/

+			addActive(x);

+		} else if (e.keyCode == 13) {

+			/*If the ENTER key is pressed, prevent the form from being submitted,*/

+			e.preventDefault();

+			if (currentFocus > -1) {

+				/*and simulate a click on the "active" item:*/

+				if (x) x[currentFocus].click();

+			}

+		}

+	});

+	function addActive(x) {

+		/*a function to classify an item as "active":*/

+		if (!x) return false;

+		/*start by removing the "active" class on all items:*/

+		removeActive(x);

+		if (currentFocus >= x.length) currentFocus = 0;

+		if (currentFocus < 0) currentFocus = (x.length - 1);

+		/*add class "autocomplete-active":*/

+		x[currentFocus].classList.add("autocomplete-active");

+	}

+	function removeActive(x) {

+		/*a function to remove the "active" class from all autocomplete items:*/

+		for (var i = 0; i < x.length; i++) {

+			x[i].classList.remove("autocomplete-active");

+		}

+	}

+	function clearLists() {

+		/*close all autocomplete lists in the document,

+		except the one passed as an argument:*/

+		var x = document.querySelector("#autocomplete-list");

+		x.innerHTML = "";

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

+	}

+	/*execute a function when someone clicks in the document:*/

+	document.addEventListener("click", function (e) {

+		clearLists();

+	});

 }

diff --git a/js/camera.js b/js/camera.js
new file mode 100644
index 0000000..44e9f5d
--- /dev/null
+++ b/js/camera.js
@@ -0,0 +1,53 @@
+// *********** 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";

+	}

+}

diff --git a/js/circle-mode.js b/js/circle-mode.js
new file mode 100644
index 0000000..d5ecff3
--- /dev/null
+++ b/js/circle-mode.js
@@ -0,0 +1,52 @@
+// *********** 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]; 

+}
\ No newline at end of file
diff --git a/js/dialog.js b/js/dialog.js
new file mode 100644
index 0000000..ff3268f
--- /dev/null
+++ b/js/dialog.js
@@ -0,0 +1,84 @@
+// *********** 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);

+}

diff --git a/js/easter-egg.js b/js/easter-egg.js
new file mode 100644
index 0000000..35dce24
--- /dev/null
+++ b/js/easter-egg.js
@@ -0,0 +1,42 @@
+// *********** 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/graf.js b/js/graf.js
new file mode 100644
index 0000000..155ce2d
--- /dev/null
+++ b/js/graf.js
@@ -0,0 +1,203 @@
+// *********** 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;

+	});

+}
\ No newline at end of file
diff --git a/js/limit-years.js b/js/limit-years.js
new file mode 100644
index 0000000..cbbef95
--- /dev/null
+++ b/js/limit-years.js
@@ -0,0 +1,93 @@
+// *********** 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);

+}

+

diff --git a/js/script.js b/js/script.js
index 1b4a36f..1493ae1 100644
--- a/js/script.js
+++ b/js/script.js
@@ -1,249 +1,551 @@
-var s, graf;
-
-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 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";
-  }
-}
-
-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);
-}
-
-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 = "#fff";
-    });
-
-    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 init() {
-  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;
-  });
-
-  s = new sigma({
-    renderers: [{
-      container: "graf",
-      type: "webgl"
-    }],
-    settings: {
-      defaultEdgeColor: "#fff",
-      edgeColor: "default",
-      defaultLabelColor: "#fff",
-      autoRescale: false,
-      zoomMax: 10,
-      //enableEdgeHovering: true,
-      font: "Roboto",
-      labelThreshold: 5
-    }
-  });
-
-  xhr("GET", "api.php", "action=getgraf", function(responseText, status) {
-    graf = JSON.parse(responseText);
-
-    console.log(graf);
-
-    for (var i in graf.nodes) {
-      var ncolor = (graf.nodes[i].sex == "F" ? "#d61c08" : (graf.nodes[i].sex == "M" ? "#0159aa" : "#0ca80a"));
-
-      s.graph.addNode({
-        id: graf.nodes[i].id,
-        label: graf.nodes[i].name,
-        x: graf.nodes[i].x,
-        y: graf.nodes[i].y,
-        size: 10,
-        color: ncolor,
-        originalColor: ncolor
-      });
-    }
-
-    for (var i in graf.edges) {
-      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';
-        }
-      });
-
-      s.refresh();
-
-      dialog.show(nodeId, toKeep);
-    });
-
-    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);
-
-    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))
-      });
-    });
-
-    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;
-    })
-    document.querySelector("#search").addEventListener("click", altSearchBar);
-
-    if (window.innerWidth > 700) altSearchBar();
-
-    s.refresh();
-    autocomplete(document.querySelector("#search-input"), graf.nodes, "search");
-  });
-}
-
-function cameraGoto(nodeX, nodeY) {
-	sigma.misc.animation.camera( s.camera,
-	  { x: nodeX, y: nodeY, ratio: 1 },
-	  { duration: s.settings('animationsTime') || 300 }
-	);
-}
-
-window.addEventListener("load", init);
+// *********** 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
new file mode 100644
index 0000000..e5c8846
--- /dev/null
+++ b/js/search-bar.js
@@ -0,0 +1,18 @@
+// *********** 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.

+});

diff --git a/login.php b/login.php
index 4c3ccec..eed968f 100644
--- a/login.php
+++ b/login.php
@@ -1,50 +1,50 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Graf alternatiu FME</title>
-
-    <meta name=viewport content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
-
-    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
-    <link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.blue_grey-blue.min.css" />
-
-    <style>
-    .page-content {
-      display: block;
-      max-width: 500px;
-      margin-top: 16px;
-      margin-left: auto;
-      margin-right: auto;
-      padding: 16px;
-    }
-    </style>
-  </head>
-  <body>
-    <div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
-      <header class="mdl-layout__header">
-        <div class="mdl-layout__header-row">
-          <!-- Title -->
-          <span class="mdl-layout-title">Graf alternatiu FME</span>
-        </div>
-      </header>
-      <main class="mdl-layout__content">
-        <div class="page-content mdl-shadow--4dp">
-          <form action="graf.php" method="POST">
-            <div class="mdl-textfield mdl-textfield--floating-label mdl-js-textfield"><input class="mdl-textfield__input" type="password" name="password" id="password"><label class="mdl-textfield__label" for="password">Contrasenya</label></div>
-            <p><button class="mdl-button mdl-js-button mdl-button--raised mdl-button--accent mdl-js-ripple-effect" type="submit">Entrar</button></p>
-          </form>
-        </div>
-      </main>
-    </div>
-    <div class="mdl-snackbar mdl-js-snackbar"><div class="mdl-snackbar__text"></div><button type="button" class="mdl-snackbar__action"></button></div>
-    
-    <script src="https://code.getmdl.io/1.3.0/material.min.js"></script> 
-
-    <?php
-    if (isset($_GET["msg"]) && $_GET["msg"] == "wrong") {
-      echo "<script>window.addEventListener('load', function() { var notification = document.querySelector('.mdl-js-snackbar'); notification.MaterialSnackbar.showSnackbar({ message: 'Contrasenya incorrecta' }); });</script>";
-    }
-    ?>  
-   </body>
-</html>
+<!DOCTYPE html>

+<html>

+  <head>

+    <meta charset="utf-8">

+    <title>Graf alternatiu FME</title>

+

+    <meta name=viewport content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

+

+    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">

+    <link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.blue_grey-blue.min.css" />

+

+    <style>

+    .page-content {

+      display: block;

+      max-width: 500px;

+      margin-top: 16px;

+      margin-left: auto;

+      margin-right: auto;

+      padding: 16px;

+    }

+    </style>

+  </head>

+  <body>

+    <div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">

+      <header class="mdl-layout__header">

+        <div class="mdl-layout__header-row">

+          <!-- Title -->

+          <span class="mdl-layout-title">Graf alternatiu FME</span>

+        </div>

+      </header>

+      <main class="mdl-layout__content">

+        <div class="page-content mdl-shadow--4dp">

+          <form action="graf.php" method="POST">

+            <div class="mdl-textfield mdl-textfield--floating-label mdl-js-textfield"><input class="mdl-textfield__input" type="password" name="password" id="password"><label class="mdl-textfield__label" for="password">Contrasenya</label></div>

+            <p><button class="mdl-button mdl-js-button mdl-button--raised mdl-button--accent mdl-js-ripple-effect" type="submit">Entrar</button></p>

+          </form>

+        </div>

+      </main>

+    </div>

+    <div class="mdl-snackbar mdl-js-snackbar"><div class="mdl-snackbar__text"></div><button type="button" class="mdl-snackbar__action"></button></div>

+    

+    <script src="https://code.getmdl.io/1.3.0/material.min.js"></script> 

+

+    <?php

+    if (isset($_GET["msg"]) && $_GET["msg"] == "wrong") {

+      echo "<script>window.addEventListener('load', function() { var notification = document.querySelector('.mdl-js-snackbar'); notification.MaterialSnackbar.showSnackbar({ message: 'Contrasenya incorrecta' }); });</script>";

+    }

+    ?>  

+   </body>

+</html>

diff --git a/manifest.json b/manifest.json
index 7560119..85ff23f 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,15 +1,15 @@
-{
-  "short_name": "Graf FME",
-  "name": "El graf alternatiu de la FME",
-  "icons": [
-    {
-      "src": "img/graf.png",
-      "type": "image/png",
-      "sizes": "192x192"
-    }
-  ],
-  "background_color": "#060606",
-  "theme_color": "#060606",
-  "display": "fullscreen",
-  "start_url": "graf.php?utm_source=homescreen"
-}
+{

+  "short_name": "Graf FME",

+  "name": "El graf alternatiu de la FME",

+  "icons": [

+    {

+      "src": "img/graf.png",

+      "type": "image/png",

+      "sizes": "192x192"

+    }

+  ],

+  "background_color": "#060606",

+  "theme_color": "#060606",

+  "display": "fullscreen",

+  "start_url": "graf.php?utm_source=homescreen"

+}