morphological view for testbed
diff --git a/Changes b/Changes
index 22af086..4278db6 100755
--- a/Changes
+++ b/Changes
@@ -1,3 +1,6 @@
+0.02 2014-06-17
+        - Added morphological tables
+
 0.01 2014-05-31
         - Simplifications:
           Notifications plugin is at https://github.com/Akron/Mojolicious-Plugin-Notifications
diff --git a/korap.conf b/korap.conf
index 7f6b63b..807b772 100644
--- a/korap.conf
+++ b/korap.conf
@@ -16,5 +16,9 @@
       root_dir => app->home . '/cache',
       cache_size => '12m'
     }
+  },
+  hypnotoad => {
+    listen => ['http://*:6666'],
+    workers => 3
   }
 }
diff --git a/lib/Korap.pm b/lib/Korap.pm
index 9209706..9c7ddda 100644
--- a/lib/Korap.pm
+++ b/lib/Korap.pm
@@ -1,7 +1,7 @@
 package Korap;
 use Mojo::Base 'Mojolicious';
 
-our $VERSION = '0.01';
+our $VERSION = '0.02';
 
 # This method will run once at server start
 sub startup {
@@ -43,28 +43,25 @@
     }
   );
 
-  $r->get('/')->to(
-    cb => sub {
-      my $c = shift;
-      $c->render(
-	text =>
-	  'Go to '.
-	    $c->link_to('search', '/collection/search'));
-    }
-  );
-
-
-
+  $r->get('/')->to('search#remote');
   $r->get('/util/query')->to('search#query');
 
   # Tutorial
   $r->get('/tutorial')->to('tutorial#page')->name('tutorial');
   $r->get('/tutorial/(*tutorial)')->to('tutorial#page');
 
-  my $res = $r->route('/:resource', resource => [qw/collection corpus/]);
-  $res->to('search#info');
-  $res->search;
-  $res->route('/:corpus_id')->search;
+  my $collection = $r->route('/collection');
+  $collection->to('search#info');
+  $collection->search;
+
+  my $corpus = $r->route('/corpus');
+  $corpus->search;
+  $corpus->route('/:corpus_id')->search;
+  $corpus->route('/:corpus_id/:doc_id')->search;
+  $corpus->route('/:corpus_id/#doc_id/:match_id')->to('info#about_match');
+
+
+
 #  $r->get(
 #    '/:resource/:corpus_id/#doc_id/#match_id',
 #    resource => [qw/collection corpus/])->to('search#match')->name('match');
diff --git a/lib/Korap/Info.pm b/lib/Korap/Info.pm
new file mode 100644
index 0000000..d090a88
--- /dev/null
+++ b/lib/Korap/Info.pm
@@ -0,0 +1,15 @@
+package Korap::Info;
+use Mojo::Base 'Mojolicious::Controller';
+
+sub about_match {
+  my $c = shift;
+  my $corpus_id = $c->stash('corpus_id');
+  my $doc_id = $c->stash('doc_id');
+  my $match_id = $c->stash('match_id');
+
+  return $c->render(json => $c->notifications(json => $c->match_info($corpus_id, $doc_id, $match_id)));
+};
+
+1;
+
+__END__
diff --git a/lib/Korap/Plugin/KorapInfo.pm b/lib/Korap/Plugin/KorapInfo.pm
index 77a3061..a1f3af8 100644
--- a/lib/Korap/Plugin/KorapInfo.pm
+++ b/lib/Korap/Plugin/KorapInfo.pm
@@ -1,5 +1,7 @@
 package Korap::Plugin::KorapInfo;
 use Mojo::Base 'Mojolicious::Plugin';
+use Mojo::JSON qw/decode_json/;
+use Mojo::ByteStream 'b';
 
 sub register {
   my ($plugin, $mojo, $param) = @_;
@@ -10,13 +12,23 @@
     $param = { %$param, %$config_param };
   };
 
+  state $json = decode_json(b(join('', <DATA>))->encode);
+
   my $api = $param->{api};
 
   # Todo: Make this recognize the user!
   $mojo->helper(
-    info_on => sub {
+    resource_info => sub {
       my $c = shift;
+
       my $src = shift;
+
+      if ($c->app->mode eq 'test') {
+	if ($src eq 'collection') {
+	  return $json->{collections};
+	};
+      };
+
       $src = 'VirtualCollection' if $src eq 'collection';
       $src = 'Corpus' if $src eq 'corpus';
 
@@ -35,7 +47,45 @@
       return [];
     }
   );
+
+  $mojo->helper(
+    match_info => sub {
+      my $c = shift;
+      my ($corpus, $doc, $match) = @_;
+
+      return $json->{matchInfo} if $c->app->mode eq 'test';
+
+      my $url = Mojo::URL->new($api)->path('resource/matchInfo');
+      $match = 'match-' . $corpus . '!' . $corpus . '_' . $doc . '-' . $match;
+      $url->query({
+	id => $match,
+	f => 'mate',
+	l => ''
+      });
+
+      if (my $response = $c->ua->get($url)->success) {
+	return $response->json;
+      };
+      $c->notify(error => 'Unable to retrieve resource');
+      return {};
+    }
+  );
 };
 
 
 1;
+
+__DATA__
+
+{
+"matchInfo":
+
+{"author":"Filzstift,Alexander Sommer,TheK","textClass":"freizeit-unterhaltung reisen","corpusID":"WPD","title":"Neuseeland","foundries":"xip xip/morpho xip/constituency xip/dependency corenlp corenlp/namedentities corenlp/namedentities/ne_dewac_175m_600 corenlp/namedentities corenlp/namedentities/ne_hgc_175m_600 mate mate/morpho mate/dependency connexor connexor/morpho connexor/syntax connexor/phrase treetagger treetagger/morpho base base/sentences base/paragraphs opennlp opennlp/morpho","tokenization":"opennlp#tokens","field":"tokens","startMore":false,"endMore":false,"docID":"WPD_NNN.02848","snippet":"<span class=\"context-left\"></span><span class=\"match\"><span title=\"mate/l:besonders\"><span title=\"mate/p:ADV\">Besonders</span></span> <span title=\"mate/l:auffällig\"><span title=\"mate/m:degree:pos\"><span title=\"mate/p:ADJD\">auffällig</span></span></span> <span title=\"mate/l:sein\"><span title=\"mate/m:mood:ind\"><span title=\"mate/m:number:pl\"><span title=\"mate/m:person:3\"><span title=\"mate/m:tense:pres\"><span title=\"mate/p:VAFIN\">sind</span></span></span></span></span></span> <span title=\"mate/l:schließlich\"><span title=\"mate/p:ADV\">schließlich</span></span> <span title=\"mate/l:noch\"><span title=\"mate/p:ADV\">noch</span></span> <span title=\"mate/l:der\"><span title=\"mate/m:case:nom\"><span title=\"mate/m:gender:masc\"><span title=\"mate/m:number:sg\"><span title=\"mate/p:ART\">der</span></span></span></span></span> <span title=\"mate/l:pohutukawa\"><span title=\"mate/m:case:nom\"><span title=\"mate/m:gender:masc\"><span title=\"mate/m:number:sg\"><span title=\"mate/p:NE\">Pohutukawa</span></span></span></span></span> <span title=\"mate/l:und\"><span title=\"mate/p:KON\">und</span></span> <span title=\"mate/l:der\"><span title=\"mate/m:case:nom\"><span title=\"mate/m:gender:masc\"><span title=\"mate/m:number:sg\"><span title=\"mate/p:ART\">der</span></span></span></span></span> <span title=\"mate/l:cabbage\"><span title=\"mate/m:case:nom\"><span title=\"mate/m:gender:masc\"><span title=\"mate/m:number:sg\"><span title=\"mate/p:NE\">Cabbage</span></span></span></span></span> <span title=\"mate/l:tree\"><span title=\"mate/m:gender:masc\"><span title=\"mate/m:number:sg\"><span title=\"mate/p:NE\"><span title=\"mate/m:case:nom\">Tree</span></span></span></span></span></span><span class=\"context-right\"></span>","ID":"match-WPD!WPD_NNN.02848-p1213-1224","pubDate":"2005-03-28","context":{"left":["token",0],"right":["token",0]}},
+
+
+"collections" :
+[{"shared":false,"id":1,"managed":true,"created":1401193381119,"stats":{"documents":196510,"tokens":51545081,"sentences":4116282,"paragraphs":2034752},"query":[{"@type":"korap:meta-filter","@value":{"@type":"korap:term","@field":"korap:field#corpusID","@value":"WPD"}}],"description":"Die freie Enzyklopädie","name":"Wikipedia","foundries":"base;corenlp;mate;mpt;opennlp;tt;xip"}]
+
+
+
+}
diff --git a/lib/Korap/Plugin/KorapMatchinfo.pm b/lib/Korap/Plugin/KorapMatchinfo.pm
deleted file mode 100644
index fc9ba97..0000000
--- a/lib/Korap/Plugin/KorapMatchinfo.pm
+++ /dev/null
@@ -1,44 +0,0 @@
-package Korap::Plugin::KorapMatchInfo;
-use Mojo::Base 'Mojolicious::Plugin';
-
-sub register {
-  my ($plugin, $mojo, $param) = @_;
-  $param ||= {};
-
-  # Load parameter from Config file
-  if (my $config_param = $mojo->config('KorAP')) {
-    $param = { %$param, %$config_param };
-  };
-
-  my $api = $param->{api};
-
-  # Todo: Make this recognize the user!
-  $mojo->helper(
-    info_on => sub {
-      my $c = shift;
-      my $src = shift;
-
-#/resource/matchInfo?id=match-WPD!WPD_TTT.07206-p151-152&f=treetagger&l=morpho&spans=true
-
-      $src = 'VirtualCollection' if $src eq 'collection';
-      $src = 'Corpus' if $src eq 'corpus';
-
-      my $url = Mojo::URL->new($api)->path('resource/' . $src);
-
-      my $chi = $c->chi;
-      if (my $json = $chi->get($url->to_string)) {
-	return $json;
-      }
-      elsif (my $response = $c->ua->get($url)->success) {
-	my $json = $response->json;
-	$c->chi->set($url->to_string => $json);
-	return $json;
-      };
-      $c->notify(error => 'Unable to retrieve resource');
-      return [];
-    }
-  );
-};
-
-
-1;
diff --git a/lib/Korap/Plugin/KorapSearch.pm b/lib/Korap/Plugin/KorapSearch.pm
index efe1e8b..39b5c15 100644
--- a/lib/Korap/Plugin/KorapSearch.pm
+++ b/lib/Korap/Plugin/KorapSearch.pm
@@ -32,7 +32,7 @@
       my $c = shift;
       my $match = shift;
       my $did = $match->{docID};
-      $did =~ s/^[^_]_//;
+      $did =~ s/^[^_]+_//;
       return $did // '';
     }
   );
@@ -47,21 +47,18 @@
       my %param = @_;
 
       # Test envronment
-#      if ($c->app->mode eq 'test') {
-#	my $json = b(join(' ', <DATA>))->encode;
-#	$json = decode_json($json);
-#
-#	$c->stash('search.count' => 10);
-#	$c->stash('search.startPage' => 1);
-#	$c->stash('search.totalResults' => 666);
-#	$c->stash('search.itemsPerPage' => 10);
-#	$_->{'search.bm.hit'} = 20;
-#	$_->{'search.bm.result'} = 10;
-#	$_->{'search.query'} = $json->{request}->{query};
-#	$_->{'search.hits'} = $json->{matches};
-#	return $cb->();
-#      };
-
+      if ($c->app->mode eq 'test') {
+	state $json = decode_json(join(' ', <DATA>));
+	$c->stash('search.count' => 10);
+	$c->stash('search.startPage' => 1);
+	$c->stash('search.totalResults' => 666);
+	$c->stash('search.itemsPerPage' => 10);
+	$c->stash('search.bm.hit' => 20);
+	$c->stash('search.bm.result' => 10);
+	$c->stash('search.query' => $json->{request}->{query});
+	$c->stash('search.hits' => $json->{matches});
+	return $cb->();
+      };
 
       $c->stash(
 	'search.count' =>
diff --git a/lib/Korap/Search.pm b/lib/Korap/Search.pm
index 466266b..b8972a0 100644
--- a/lib/Korap/Search.pm
+++ b/lib/Korap/Search.pm
@@ -34,18 +34,6 @@
   $c->render(template => 'search');
 };
 
-
-#sub match {
-#  my $c = shift;
-#  my $corpus_id = $c->stash('corpus_id');
-#  my $doc_id = $c->stash('doc_id');
-#  my $match_id = $c->stash('match_id');
-#  return $c->render(json => {
-#    match => 'match-' . $corpus_id . '!' . $corpus_id . '_' . $doc_id . '-' . $match_id
-#  });
-#};
-
-
 sub info {
   my $c = shift;
   my $api = $c->config('KorAP')->{api};
diff --git a/public/kwic-4.0.css b/public/kwic-4.0.css
index 64f1232..84a32aa 100644
--- a/public/kwic-4.0.css
+++ b/public/kwic-4.0.css
@@ -1,5 +1,4 @@
 @charset "utf-8";
-
 /*
   light orange: #f4eebb;
   dark orange: #ffa500;
@@ -43,18 +42,18 @@
   background-color: #ddd;
 }
 
-ol > li > div.snippet > span,
-ol > li > div.snippet > span > span {
+ol > li > div > div.snippet > span,
+ol > li > div > div.snippet > span > span {
   white-space: no-wrap !important;
 }
 
-ol.free-aligned > li:not(.active) > div.snippet > span.left {
+ol.free-aligned > li:not(.active) > div > div.snippet > span.left {
   display: inline-block;
   text-align: right;
   width: 50.046%;
 }
 
-ol.free-aligned > li:not(.active) > div.snippet > span.separator {
+ol.free-aligned > li:not(.active) > div > div.snippet > span.separator {
   width: 0px;
   height: 1em;
   margin-bottom: -2px;
@@ -65,11 +64,11 @@
   margin-right: 2px;
 }
 
-ol.free-aligned > li > div.snippet > span.right {
+ol.free-aligned > li > div > div.snippet > span.right {
   text-align: left;
 }
 
-ol.left-aligned > li > div.snippet > span.context-left {
+ol.left-aligned > li > div > div.snippet > span.context-left {
   display: inline-block;
   text-align: right;
   width: 50.01%;
@@ -79,13 +78,13 @@
   text-align: right;
 }
 
-ol.right-aligned > li:not(.active) > div.snippet > span.context-right {
+ol.right-aligned > li:not(.active) > div > div.snippet > span.context-right {
   display: inline-block;
   text-align: left;
   width: 49.915%;
 }
 
-li > div.snippet > span {
+li > div > div.snippet > span {
   color: #666;
 }
 
@@ -132,21 +131,23 @@
   border-width: 2px;
   background-color: #f4eebb;
   position: relative;
-  min-height: 68pt;
 }
 
-ol > li.active > div.snippet {
+ol > li.active > div > div.snippet {
   margin: 5pt 10pt;
   margin-right: 3em;
-  margin-bottom: 24pt;
 }
 
-ol > li.active > div.snippet > span {
+ol > li.active > div {
+  min-height: 42pt;
+}
+
+ol > li.active > div > div.snippet > span {
   line-height: 1.4em;
   width: auto;
 }
 
-ol > li.active > div.snippet > span.context-left {
+ol > li.active > div > div.snippet > span.context-left {
   margin-left: 0;
   display: inline;
   overflow: visible;
@@ -213,8 +214,8 @@
   color: white;
   text-shadow: none;
   padding: 3pt 10pt;
-  margin: 5pt 0pt 0pt 0pt;
-  position: absolute;
+  margin: 0pt;
   width: 100%;
   bottom: 0;
+  z-index: 300;
 }
\ No newline at end of file
diff --git a/public/style.css b/public/style.css
index 0ad372e..537c72f 100644
--- a/public/style.css
+++ b/public/style.css
@@ -1,4 +1,5 @@
 @charset "utf-8";
+
 body, html {
   color: #666;
   font-family: verdana, tahoma, arial;
@@ -8,8 +9,11 @@
 
 #top {
   background-color: #7ba400;
+  -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
+  -moz-box-sizing: border-box;    /* Firefox, other Gecko */
+  box-sizing: border-box;         /* Opera/IE 8+ */
   padding: 0pt;
-  padding-top: 10px;
+  padding-top: 5px;
   position: relative;
   height: 70px;
   padding-left: 30px;
@@ -29,7 +33,7 @@
 
 form {
   margin-left: 235px;
-  margin-top:10px
+  margin-top:0px
 }
 
 form input[type=search] {
@@ -259,7 +263,7 @@
     background-image: url('/img/korap-logo-solo.svg');
     position: absolute;
     width: 260px; /* like sidebars*/
-    height: 80px; /* like #top */
+    height: 68px; /* like #top */
     z-index: 999;
     background-repeat: no-repeat;
     background-position: center center;
@@ -268,4 +272,4 @@
 
 h1 span {
     margin-left: -3000px;
-}
\ No newline at end of file
+}
diff --git a/public/table.css b/public/table.css
new file mode 100644
index 0000000..4a4ac27
--- /dev/null
+++ b/public/table.css
@@ -0,0 +1,77 @@
+@charset "utf-8";
+
+ol > li:not(.active) .tokenInfo {
+  display: none;
+}
+
+.tokenInfo {
+  display: none;
+}
+
+.tokenInfo.active {
+  display: block;
+}
+
+.tokenInfo {
+  background-color: #ffa500;
+  margin-right: 26px;
+  overflow: auto;
+}
+
+.tokenInfo table {
+  font-size: 10pt;
+  padding: 3pt 10pt; /* wie in ol > li.active p */
+}
+
+.tokenInfo table tr {
+}
+
+/* All cells */
+.tokenInfo table tr > * {
+  padding: 1pt 6pt;
+  vertical-align: top;
+}
+
+.tokenInfo table tr td {
+  text-align: center;
+  background-color: #f4eebb;
+}
+
+/* header */
+.tokenInfo table tr th:not([rowspan]) {
+  color: white;
+  text-shadow: none;
+}
+
+.tokenInfo table tr:first-child th {
+  text-align: center;
+  background-color: #ff8000;
+}
+
+/* first column header */
+.tokenInfo table tr:first-child th:first-child {
+  position: relative;
+  padding-right: 20pt;
+  padding-left: 5pt;
+}
+
+/* second column header */
+.tokenInfo table tr:first-child th:nth-child(2) {
+  text-align: left;
+  padding-left: 20pt;
+  padding-right: 5pt;
+}
+
+.tokenInfo th[rowspan]:first-child {
+  background-color: #f4eebb;
+}
+
+.tokenInfo table th span.switchSort {
+  cursor: pointer;
+  position: absolute;
+  display: inline-block;
+  right: 0;
+  margin-right: -11pt;
+  width: 20pt;
+};
+
diff --git a/public/translateTable.js b/public/translateTable.js
new file mode 100644
index 0000000..ec8b00a
--- /dev/null
+++ b/public/translateTable.js
@@ -0,0 +1,179 @@
+// Store Table object in global object
+
+var splitRegex = /^([^\/]+?)(?:\/(.+?))?:([^:]+?)$/;
+
+var textFoundry = "Foundry";
+var textLayer = "Layer";
+
+
+// SnippetTable constructor
+function SnippetTable (snippet) {
+  this.info = [];
+  this.foundry = {};
+  this.layer = {};
+  this.pos = 0;
+
+  this.load = function (children) {
+    for (var i in children) {
+      var c = children[i];
+
+      // element with title
+      if (c.nodeType === 1) {
+	if (c.getAttribute("title")) {
+	  if (splitRegex.exec(c.getAttribute("title"))) {
+
+	    // Fill position with info
+	    var foundry, layer;
+	    if (RegExp.$2) {
+	      foundry = RegExp.$1;
+	      layer = RegExp.$2;
+	    }
+	    else {
+	      foundry = "base";
+	      layer = RegExp.$1
+	    };
+
+	    // Create object on position unless it exists
+	    if (!this.info[this.pos])
+	      this.info[this.pos] = {};
+
+	    this.info[this.pos][foundry + "/" + layer] = RegExp.$3;
+
+	    // Set foundry
+	    if (!this.foundry[foundry])
+	      this.foundry[foundry] = {};
+	    this.foundry[foundry][layer] = 1;
+
+	    // Set layer
+	    if (!this.layer[layer])
+	      this.layer[layer] = {};
+	    this.layer[layer][foundry] = 1;
+	  };
+	};
+
+	// depth search
+	if (c.hasChildNodes())
+	  this.load(c.childNodes);
+      }
+
+      // Leaf node - store string on position and go to next string
+      else if (c.nodeType === 3)
+	if (c.nodeValue.match(/[-a-z0-9]/i))
+	  this.info[this.pos++]["-s"] = c.nodeValue;
+    };
+    return this;
+  };
+
+  this.toTable = function (base) {
+    var i, f, l;
+
+    // Create HTML based on info
+    var d = document;
+    var table = d.createElement('table');
+    var tr = d.createElement('tr');
+    table.appendChild(tr);
+    var th = d.createElement('th');
+    th.appendChild(document.createTextNode(base === "layer" ? textLayer : textFoundry));
+
+    // Add icon to switch sorting
+    var span = document.createElement("span");
+
+    // Add switch event
+    var that = this;
+    span.addEventListener("click", function (obj) {
+      var x = that.toTable(base === "layer" ? "foundry" : "layer");
+      table.parentNode.replaceChild(x, table);
+    }, false);
+
+    span.setAttribute("class", "switchSort");
+    var icon = document.createElement("i");
+    icon.setAttribute("class", "fa fa-arrows-h");
+    span.appendChild(icon);
+    th.appendChild(span);
+
+    tr.appendChild(th);
+    th = d.createElement('th');
+    th.appendChild(document.createTextNode(base === "layer" ? textFoundry : textLayer));
+    tr.appendChild(th);
+
+    // Header line with surface strings
+    for (i in this.info) {
+      th = d.createElement('th');
+      tr.appendChild(th);
+      th.appendChild(d.createTextNode(this.info[i]["-s"]));
+    };
+
+    // Sort keys
+    var baseArray = [];
+    if (base === "layer") {
+      for (i in this.layer) {
+	baseArray.push(i);
+      };
+    }
+    else {
+      for (i in this.foundry) {
+	baseArray.push(i);
+      };
+    };
+    baseArray.sort();
+
+    // Annotations
+    for (f in baseArray) {
+      f = baseArray[f];
+      var thBase = d.createElement('th');
+      thBase.appendChild(d.createTextNode(f));
+
+      var rowSpan = 0;
+
+      // Sort keys
+      var subArray = [];
+      if (base === "layer") {
+	for (i in this.layer[f]) {
+	  subArray.push(i);
+	};
+      }
+      else {
+	for (i in this.foundry[f]) {
+	  subArray.push(i);
+	};
+      };
+      subArray.sort();
+
+      for (l in subArray) {
+	l = subArray[l];
+	tr = d.createElement('tr');
+	table.appendChild(tr);
+
+	if (rowSpan === 0)
+	  tr.appendChild(thBase);
+
+	th = d.createElement('th');
+	tr.appendChild(th);
+	th.appendChild(d.createTextNode(l));
+
+	var infoString = base === "layer" ? l + '/' + f : f + '/' + l;
+
+	for (t in this.info) {
+	  var td = d.createElement('td');
+	  tr.appendChild(td);
+
+	  if (this.info[t][infoString] !== undefined)
+	    td.appendChild(d.createTextNode(this.info[t][infoString]));
+	};
+	rowSpan++;
+      };
+      thBase.setAttribute("rowspan", rowSpan);
+    };
+    // return HTML object
+    return table;
+  };
+
+  // Create wrapper element
+  var html = document.createElement("table");
+  html.innerHTML = snippet;
+
+  // Create table object and load data from HTML
+  this.load(html.childNodes);
+};
+
+
diff --git a/public/translateTree.js b/public/translateTree.js
new file mode 100644
index 0000000..728877b
--- /dev/null
+++ b/public/translateTree.js
@@ -0,0 +1,53 @@
+var cleanRegex = /^([^\/]+?\/)?[^\:]+?\:/;
+
+// SnippetTree constructor
+function SnippetTree (obj) {
+  this.children = [];
+  this.data = obj;
+
+  // Replace title
+  this.cleanTitle = function (title) {
+    return title.replace(cleanRegex, "");
+  };
+
+  // Add new child to tree
+  this.addChild = function (childData) {
+    var c = new SnippetTree (childData);
+    this.children.push(c);
+    return c;
+  };
+
+  // Recursively parse children
+  this.parseChildren = function (children) {
+    for (var i in children) {
+      var c = children[i];
+      if (c.nodeType === 1) {
+	if (c.getAttribute("title")) {
+	  var title = this.cleanTitle(c.getAttribute("title"));
+	  var childTree = this.addChild({ type : title });
+	  if (c.hasChildNodes())
+	    childTree.parseChildren(c.childNodes);
+	}
+	else if (c.hasChildNodes())
+	  this.parseChildren(c.childNodes);
+      }
+      else if (c.nodeType === 3)
+	if (c.nodeValue.match(/[-a-z0-9]/i)) {
+	  this.addChild({
+	    type : "leaf",
+	    word : c.nodeValue
+	  });
+	};
+    };
+    return this;
+  };
+};
+
+// Make tree from snippet
+function translateTree (snippet) {
+  var html = document.createElement("tree");
+  html.innerHTML = snippet;
+  return new SnippetTree({ type : "ROOT" }).parseChildren(html.childNodes);
+};
+
+
diff --git a/public/translatehtml.js b/public/translatehtml.js
deleted file mode 100644
index 4a1f4c6..0000000
--- a/public/translatehtml.js
+++ /dev/null
@@ -1,147 +0,0 @@
-var cleanRegex = /^([^\/]+?\/)?[^\:]+?\:/;
-var splitRegex = /^(.+?):([^:]+?)$/;
-
-// SnippetTree constructor
-function SnippetTree (obj) {
-  this.children = [];
-  this.data = obj;
-
-  // Replace title
-  this.cleanTitle = function (title) {
-    return title.replace(cleanRegex, "");
-  };
-
-  // Add new child to tree
-  this.addChild = function (childData) {
-    var c = new SnippetTree (childData);
-    this.children.push(c);
-    return c;
-  };
-
-  // Recursively parse children
-  this.parseChildren = function (children) {
-    for (var i in children) {
-      var c = children[i];
-      if (c.nodeType === 1) {
-	if (c.getAttribute("title")) {
-	  var title = this.cleanTitle(c.getAttribute("title"));
-	  var childTree = this.addChild({ type : title });
-	  if (c.hasChildNodes())
-	    childTree.parseChildren(c.childNodes);
-	}
-	else if (c.hasChildNodes())
-	  this.parseChildren(c.childNodes);
-      }
-      else if (c.nodeType === 3)
-	if (c.nodeValue.match(/[-a-z0-9]/i)) {
-	  this.addChild({
-	    type : "leaf",
-	    word : c.nodeValue
-	  });
-	};
-    };
-    return this;
-  };
-};
-
-
-// SnippetTable constructor
-function SnippetTable (obj) {
-  this.info = [];
-  this.overall = {};
-  this.pos = 0;
-  this.load = function (children) {
-    for (var i in children) {
-      var c = children[i];
-
-      // element with title
-      if (c.nodeType === 1) {
-	if (c.getAttribute("title")) {
-	  if (splitRegex.exec(c.getAttribute("title"))) {
-
-	    // Create object on position unless it exists
-	    if (!this.info[this.pos]) {
-	      this.info[this.pos] = {};
-	    };
-
-	    // Fill position with info
-	    this.info[this.pos][RegExp.$1] = RegExp.$2;
-	    this.overall[RegExp.$1] = 1;
-	  };
-	};
-
-	// depth search
-	if (c.hasChildNodes())
-	  this.load(c.childNodes);
-      }
-
-      // Leaf node - store string on position and go to next string
-      else if (c.nodeType === 3)
-	if (c.nodeValue.match(/[-a-z0-9]/i))
-	  this.info[this.pos++]["-s"] = c.nodeValue;
-    };
-    return this;
-  };
-};
-
-
-// Make tree from snippet
-function translateTree (snippet) {
-  var html = document.createElement("tree");
-  html.innerHTML = snippet;
-  return new SnippetTree({ type : "ROOT" }).parseChildren(html.childNodes);
-};
-
-
-// Make table from snippet
-function translateTable (snippet) {
-  // Create wrapper element
-  var html = document.createElement("table");
-  html.innerHTML = snippet;
-
-  // Create table object and load data from HTML
-  var info = new SnippetTable();
-  info.load(html.childNodes);
-
-  // Sort keys
-  var overallArray = [];
-  for (i in info.overall) {
-    overallArray.push(i);
-  };
-  overallArray.sort();
-
-  // Create HTML based on info
-  var d = document;
-  var table = d.createElement('table');
-  var tr = d.createElement('tr');
-  table.appendChild(tr);
-  var th = d.createElement('th');
-  tr.appendChild(th);
-
-  // Header line with surface strings
-  for (i in info.info) {
-    th = d.createElement('th');
-    tr.appendChild(th);
-    th.appendChild(d.createTextNode(info.info[i]["-s"]));
-  };
-
-  // Annotations
-  for (i in overallArray) {
-    i = overallArray[i];
-
-    tr = d.createElement('tr');
-    table.appendChild(tr);
-    th = d.createElement('th');
-    tr.appendChild(th);
-    th.appendChild(d.createTextNode(i));
-    for (t in info.info) {
-      var td = d.createElement('td');
-      tr.appendChild(td);
-      if (info.info[t][i] !== undefined)
-	td.appendChild(d.createTextNode(info.info[t][i]));
-    };
-  };
-
-  // return HTML object
-  return table;
-};
diff --git a/templates/collections.html.ep b/templates/collections.html.ep
index cab2ea2..3dee20f 100644
--- a/templates/collections.html.ep
+++ b/templates/collections.html.ep
@@ -1,6 +1,6 @@
 <h2>Virtual Collections</h2>
 <ul>
-% foreach my $vc (@{info_on 'collection'}) {
+% foreach my $vc (@{resource_info('collection')}) {
 <li class="active" title="<%= $vc->{description} // '' %>"><h3><%= $vc->{name} %></h3>
 % my $stats = $vc->{stats};
   <dl class="info">
diff --git a/templates/layouts/default.html.ep b/templates/layouts/default.html.ep
index c34f35f..f7abfb7 100644
--- a/templates/layouts/default.html.ep
+++ b/templates/layouts/default.html.ep
@@ -3,10 +3,11 @@
   <head>
     <title><%= title %></title>
 %= stylesheet '/style.css'
+%= stylesheet '/table.css'
 %= stylesheet '/kwic-4.0.css'
 %= stylesheet '/fontawesome/font-awesome.min.css'
 %= javascript '/jquery-2.0.0.min.js'
-%= javascript '/translatehtml.js'
+%= javascript '/translateTable.js'
 <meta charset="utf-8" />
   </head>
   <body>
@@ -40,13 +41,6 @@
 %= include 'collections'
 </div>
 
-
-%# <div>
-%#= javascript begin
-%# translateTable();
-%# end
-%# </div>
-
 <div id="search">
 %= content
 </div>
@@ -61,13 +55,30 @@
   $(o.parentNode.parentNode).removeClass('active');
 };
 
-
 function showTable (o) {
   var match = o.parentNode.parentNode;
+  var table = $(match).children("div").children("div.tokenInfo").first();
+
+  if (table.hasClass("active")) {
+    table.removeClass("active");
+    return;
+  }
+  else if (table.children("table").length > 0) {
+    table.addClass("active");
+    return;
+  };
+
   var corpusID = match.getAttribute('data-corpus-id');
   var docID = match.getAttribute('data-doc-id');
   var matchID = match.getAttribute('data-match-id');
-  jQuery.getJSON('/corpus/' + corpusID + '/' + docID + '/' + matchID);
+  var url = '/corpus/' + corpusID + '/' + docID + '/' + matchID;
+  var snippet;
+
+  jQuery.getJSON(url, function (res) {
+    var snippet = new SnippetTable(res['snippet']);
+    table.addClass("active");
+    table.append(snippet.toTable());
+  });
 };
 
 function openTutorial (o) {
diff --git a/templates/search.html.ep b/templates/search.html.ep
index 7cfa791..a409a40 100644
--- a/templates/search.html.ep
+++ b/templates/search.html.ep
@@ -26,12 +26,15 @@
 
 <ol class="left-aligned">
 %=  search_hits begin
+
   <li data-corpus-id="<%= $_->{corpusID} %>"
       data-doc-id="<%= korap_doc_id($_) %>"
       data-match-id="<%= korap_match_id($_) %>">
 %# ID, title, corpusID, author, pubDate, textClass
-    <div class="snippet"><%== $_->{snippet} %></div>
-    <p><strong><%= $_->{title} %></strong><%= $_->{author} ? ' by ' . $_->{author}  : '' %>; published on <%= date_format $_->{pubDate} %> as <%= $_->{docID} %> (<%= $_->{corpusID} %>)</p>
+    <div>
+      <div class="snippet">
+        <%== $_->{snippet} %>
+      </div>
 
 %#    as <%= $_->{ID} %>
 %#  textClass docID
@@ -39,14 +42,28 @@
 %#  <%= $_ %>
 %# };
 
+      <div class="tokenInfo"></div>
+    </div>
+    <p><strong><%= $_->{title} %></strong><%= $_->{author} ? ' by ' . $_->{author}  : '' %>; published on <%= date_format $_->{pubDate} %> as <%= $_->{docID} %> (<%= $_->{corpusID} %>)</p>
+
     <ul class="action right">
       <li onmouseup="closeSnippet(this)" title="close"><i class="fa fa-toggle-up"></i></li>
       <li onclick="showTable(this)" title="Annotations"><i class="fa fa-info-circle"></i></li>
+<!--
       <li title="Tree Visualizations"><i class="fa fa-sitemap"></i></li>
       <li title="Remember"><i class="fa fa-star-o"></i></li>
+-->
     </ul>
+
+
   </li>
 %   end
 </ol>
 % end
-% }
+% } else {
+<div id="intro">
+  <p>This is the alternative KorAP Frontend.</p>
+  <p>The primary goal is to serve as a testbed for the query serialization and for different flavours of user interfaces.</p>
+  <p>Search capabilities are limited to the demo user.</p>
+</div>
+% };
diff --git a/templates/tutorial.html.ep b/templates/tutorial.html.ep
index 0c938d1..8179135 100644
--- a/templates/tutorial.html.ep
+++ b/templates/tutorial.html.ep
@@ -2,7 +2,7 @@
 
 <p>Links to Blog, FAQ, About, Contact ...</p>
 
-<h1>KorAP-Tutorial</h1>
+<h2>KorAP-Tutorial</h2>
 
 <ul>
   <li>Introduction to KorAP</li>