diff --git a/build.sh b/build.sh
index 304e42a64be5c418ea98d2945c729c010dfa3f43..8b43f60e87ada57a1c0035820f11c37f5c34ab14 100755
--- a/build.sh
+++ b/build.sh
@@ -1,20 +1,5 @@
 #!/bin/sh
 
-# =========================================================
-# CONFIGURATION
-# =========================================================
-
-# configuration for remote deploy, requirements:
-# * curl
-# * a recent tomcat server is required (8+)
-# * a user that is allowed to deploy, with following roles: tomcat, manager-gui, manager-script, manager-jmx, manager-status
-# * installed and running tomcat manager application
-DEPLOY="true"
-TOMCAT_HOST="localhost"
-TOMCAT_PORT="8000"
-TOMCAT_USER="tomcat"
-TOMCAT_PW="tomcat"
-
 # =========================================================
 # DO NOT EDIT AFTER THIS POINT
 # =========================================================
@@ -75,13 +60,5 @@ if [ $? -ne 0 ]; then
         exit 1
 fi
 
-if [ $DEPLOY = "true" ]; then
-	echo "" >> $LOG
-	echo "-------------------------------" >> $LOG
-	echo "deploying vipra-rest" | tee -a $LOG
-	echo "-------------------------------" >> $LOG
-	curl "http://$TOMCAT_USER:TOMCAT_PW@$TOMCAT_HOST:$TOMCAT_PORT/manager/text/deploy?path=/vipra-rest&update=true" --upload-file ./vipra-rest/target/vipra-rest.war >> $LOG 2>&1
-fi
-
 echo "complete"
 exit $?
diff --git a/ma-impl.sublime-workspace b/ma-impl.sublime-workspace
index 7c5b9bb10d9edbca297a7daa966258ae733304dd..0b75fa13f7a32cb8e9bfc3bd2566f078e749bdb0 100644
--- a/ma-impl.sublime-workspace
+++ b/ma-impl.sublime-workspace
@@ -3,6 +3,14 @@
 	{
 		"selected_items":
 		[
+			[
+				"angular",
+				"angular-ui-router"
+			],
+			[
+				"state",
+				"stateProvider"
+			],
 			[
 				"border-top",
 				"border-top-style"
@@ -279,43 +287,11 @@
 	},
 	"buffers":
 	[
-		{
-			"file": "vipra-ui/index.html",
-			"settings":
-			{
-				"buffer_size": 1013,
-				"line_ending": "Unix"
-			}
-		},
-		{
-			"file": "vipra-ui/js/app.js",
-			"settings":
-			{
-				"buffer_size": 326,
-				"line_ending": "Unix"
-			}
-		},
 		{
 			"file": "vipra-ui/js/controllers.js",
 			"settings":
 			{
-				"buffer_size": 102,
-				"line_ending": "Unix"
-			}
-		},
-		{
-			"file": "vipra-ui/js/directives.js",
-			"settings":
-			{
-				"buffer_size": 49,
-				"line_ending": "Unix"
-			}
-		},
-		{
-			"file": "vipra-ui/js/services.js",
-			"settings":
-			{
-				"buffer_size": 47,
+				"buffer_size": 1591,
 				"line_ending": "Unix"
 			}
 		}
@@ -499,21 +475,39 @@
 	[
 		"/home/eike/repos/master/ma-impl",
 		"/home/eike/repos/master/ma-impl/vipra-ui",
-		"/home/eike/repos/master/ma-impl/vipra-ui/bower_components/angular",
-		"/home/eike/repos/master/ma-impl/vipra-ui/bower_components/angular-resource",
-		"/home/eike/repos/master/ma-impl/vipra-ui/bower_components/angular-ui-router",
 		"/home/eike/repos/master/ma-impl/vipra-ui/bower_components/angular-ui-router/release",
-		"/home/eike/repos/master/ma-impl/vipra-ui/bower_components/bootstrap/dist/js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/bower_components/bootstrap/dist",
 		"/home/eike/repos/master/ma-impl/vipra-ui/bower_components/jquery/dist",
+		"/home/eike/repos/master/ma-impl/vipra-ui/css",
 		"/home/eike/repos/master/ma-impl/vipra-ui/html",
 		"/home/eike/repos/master/ma-impl/vipra-ui/js"
 	],
 	"file_history":
 	[
+		"/home/eike/repos/master/ma-impl/vipra-ui/js/app.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/html/index.html",
-		"/home/eike/repos/master/ma-impl/vipra-ui/css/main.less",
-		"/home/eike/repos/master/ma-impl/vipra-ui/gulpfile.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/css/app.less",
 		"/home/eike/repos/master/ma-impl/vipra-ui/index.html",
+		"/run/user/1000/gvfs/smb-share:server=eike-ain,share=share/interceptors.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/js/factories.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/js/controllers.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/html/topics/index.html",
+		"/home/eike/repos/master/ma-impl/vipra-ui/html/topics/show.html",
+		"/home/eike/repos/master/ma-impl/vipra-ui/html/words/index.html",
+		"/home/eike/repos/master/ma-impl/vipra-ui/html/words/show.html",
+		"/home/eike/repos/master/ma-impl/vipra-ui/gulpfile.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/css/footer.less",
+		"/home/eike/repos/master/ma-impl/vipra-ui/less/vendor.less",
+		"/home/eike/repos/master/ma-impl/vipra-ui/less/vendor.less.css",
+		"/home/eike/repos/master/ma-impl/vipra-ui/less/app.less",
+		"/home/eike/repos/master/ma-impl/vipra-ui/public/index.html",
+		"/home/eike/repos/master/ma-impl/vipra-ui/js/vendor.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/less/main.less",
+		"/home/eike/repos/master/ma-impl/vipra-ui/js/directives.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/js/services.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/html/articles/show.html",
+		"/home/eike/repos/master/ma-impl/vipra-ui/html/articles/index.html",
+		"/home/eike/repos/master/ma-impl/vipra-ui/css/main.less",
 		"/home/eike/.config/sublime-text-3/Packages/User/Preferences.sublime-settings",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/adapters/application.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/styles/app.scss",
@@ -617,27 +611,7 @@
 		"/home/eike/repos/master/ma-impl/vm/config/environment",
 		"/home/eike/repos/master/ma-impl/vm/config/initd-tomcat",
 		"/home/eike/Repositories/fu/ss15/ma/impl/TODO",
-		"/home/eike/.local/share/vipra/jgibb/vocab",
-		"/home/eike/.local/share/vipra/jgibb/index",
-		"/home/eike/.local/share/vipra/jgibb/jgibb.phi",
-		"/home/eike/Downloads/JGibbLDA-v.1.0/src/jgibblda/Constants.java",
-		"/home/eike/Downloads/JGibbLDA-v.1.0/models/casestudy-en/model-final.others",
-		"/home/eike/Downloads/JGibbLDA-v.1.0/models/casestudy-en/model-final.twords",
-		"/home/eike/Downloads/JGibbLDA-v.1.0/models/casestudy-en/newdocs.dat",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vm/data/data.json",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vm/data/test-2.json",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vm/data/test-1.json",
-		"/home/eike/.cache/.fr-rPaUI0/LICENSE.txt",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vipra-rest/src/main/resources/log4j2.xml",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vipra-config/log4j2.xml",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vipra-cmd/test/test-1.json",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vipra-cmd/vipra-cmd.sh",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/components/article-list.js",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/components/text-marker.js",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/components/debounced-input.js",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/components/dynamic-high-charts.js",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/templates/components/text-marker.hbs",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/templates/components/article-list.hbs"
+		"/home/eike/.local/share/vipra/jgibb/vocab"
 	],
 	"find":
 	{
@@ -961,29 +935,29 @@
 	"groups":
 	[
 		{
-			"selected": 2,
+			"selected": 0,
 			"sheets":
 			[
 				{
 					"buffer": 0,
-					"file": "vipra-ui/index.html",
+					"file": "vipra-ui/js/controllers.js",
 					"semi_transient": false,
 					"settings":
 					{
-						"buffer_size": 1013,
+						"buffer_size": 1591,
 						"regions":
 						{
 						},
 						"selection":
 						[
 							[
-								239,
-								239
+								1057,
+								1057
 							]
 						],
 						"settings":
 						{
-							"syntax": "Packages/HTML/HTML.tmLanguage",
+							"syntax": "Packages/JavaScriptNext - ES6 Syntax/JavaScriptNext.tmLanguage",
 							"tab_size": 2,
 							"translate_tabs_to_spaces": true
 						},
@@ -991,124 +965,8 @@
 						"translation.y": 0.0,
 						"zoom_level": 1.0
 					},
-					"stack_index": 2,
-					"type": "text"
-				},
-				{
-					"buffer": 1,
-					"file": "vipra-ui/js/app.js",
-					"semi_transient": false,
-					"settings":
-					{
-						"buffer_size": 326,
-						"regions":
-						{
-						},
-						"selection":
-						[
-							[
-								321,
-								321
-							]
-						],
-						"settings":
-						{
-							"open_with_edit": true,
-							"syntax": "Packages/JavaScriptNext - ES6 Syntax/JavaScriptNext.tmLanguage"
-						},
-						"translation.x": 0.0,
-						"translation.y": 0.0,
-						"zoom_level": 1.0
-					},
-					"stack_index": 1,
-					"type": "text"
-				},
-				{
-					"buffer": 2,
-					"file": "vipra-ui/js/controllers.js",
-					"semi_transient": false,
-					"settings":
-					{
-						"buffer_size": 102,
-						"regions":
-						{
-						},
-						"selection":
-						[
-							[
-								102,
-								102
-							]
-						],
-						"settings":
-						{
-							"open_with_edit": true,
-							"syntax": "Packages/JavaScriptNext - ES6 Syntax/JavaScriptNext.tmLanguage"
-						},
-						"translation.x": 0.0,
-						"translation.y": 0.0,
-						"zoom_level": 1.0
-					},
 					"stack_index": 0,
 					"type": "text"
-				},
-				{
-					"buffer": 3,
-					"file": "vipra-ui/js/directives.js",
-					"semi_transient": false,
-					"settings":
-					{
-						"buffer_size": 49,
-						"regions":
-						{
-						},
-						"selection":
-						[
-							[
-								42,
-								42
-							]
-						],
-						"settings":
-						{
-							"open_with_edit": true,
-							"syntax": "Packages/JavaScriptNext - ES6 Syntax/JavaScriptNext.tmLanguage"
-						},
-						"translation.x": 0.0,
-						"translation.y": 0.0,
-						"zoom_level": 1.0
-					},
-					"stack_index": 3,
-					"type": "text"
-				},
-				{
-					"buffer": 4,
-					"file": "vipra-ui/js/services.js",
-					"semi_transient": false,
-					"settings":
-					{
-						"buffer_size": 47,
-						"regions":
-						{
-						},
-						"selection":
-						[
-							[
-								40,
-								40
-							]
-						],
-						"settings":
-						{
-							"open_with_edit": true,
-							"syntax": "Packages/JavaScriptNext - ES6 Syntax/JavaScriptNext.tmLanguage"
-						},
-						"translation.x": 0.0,
-						"translation.y": 0.0,
-						"zoom_level": 1.0
-					},
-					"stack_index": 4,
-					"type": "text"
 				}
 			]
 		}
@@ -1276,7 +1134,7 @@
 	"show_open_files": true,
 	"show_tabs": true,
 	"side_bar_visible": true,
-	"side_bar_width": 295.0,
+	"side_bar_width": 274.0,
 	"status_bar_visible": true,
 	"template_settings":
 	{
diff --git a/vipra-rest/src/main/java/de/vipra/rest/resource/ArticleResource.java b/vipra-rest/src/main/java/de/vipra/rest/resource/ArticleResource.java
index 6b7791a5a7e25627d759b1037c390bb28f5cb0bc..a919f92ab5273cf3cad2d0c12783f89bef1724d1 100644
--- a/vipra-rest/src/main/java/de/vipra/rest/resource/ArticleResource.java
+++ b/vipra-rest/src/main/java/de/vipra/rest/resource/ArticleResource.java
@@ -5,9 +5,7 @@ import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
-import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 
 import javax.servlet.ServletContext;
 import javax.ws.rs.Consumes;
@@ -29,17 +27,11 @@ import org.bson.types.ObjectId;
 import org.ehcache.Cache;
 import org.ehcache.CacheManager;
 import org.ehcache.config.CacheConfigurationBuilder;
-import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.client.transport.TransportClient;
-import org.elasticsearch.index.query.QueryBuilders;
-import org.elasticsearch.search.SearchHit;
-import org.elasticsearch.search.SearchHits;
 
 import de.vipra.rest.Messages;
 import de.vipra.rest.model.APIError;
 import de.vipra.rest.model.Wrapper;
 import de.vipra.util.Config;
-import de.vipra.util.ESClient;
 import de.vipra.util.MongoUtils;
 import de.vipra.util.StringUtils;
 import de.vipra.util.ex.ConfigException;
@@ -55,7 +47,6 @@ public class ArticleResource {
 
 	final Cache<String, ArticleFull> cache;
 	final DatabaseService<ArticleFull, ObjectId> service;
-	final TransportClient client;
 
 	public ArticleResource(@Context ServletContext servletContext) throws ConfigException, IOException {
 		Config config = Config.getConfig();
@@ -67,8 +58,6 @@ public class ArticleResource {
 			articleCache = manager.createCache("articlecache", CacheConfigurationBuilder.newCacheConfigurationBuilder()
 					.buildConfig(String.class, ArticleFull.class));
 		this.cache = articleCache;
-
-		client = ESClient.getClient(config);
 	}
 
 	@GET
@@ -76,13 +65,6 @@ public class ArticleResource {
 	public Response getArticles(@QueryParam("skip") Integer skip, @QueryParam("limit") Integer limit,
 			@QueryParam("sort") @DefaultValue("date") String sortBy, @QueryParam("fields") String fields,
 			@QueryParam("query") String query) {
-		if (query == null)
-			return getArticlesFromDb(skip, limit, sortBy, fields);
-		else
-			return getArticlesFromIndex(skip, limit, query);
-	}
-
-	private Response getArticlesFromDb(Integer skip, Integer limit, String sortBy, String fields) {
 		Wrapper<List<ArticleFull>> res = new Wrapper<>();
 
 		if (skip != null && limit != null)
@@ -106,40 +88,6 @@ public class ArticleResource {
 		}
 	}
 
-	private Response getArticlesFromIndex(Integer skip, Integer limit, String query) {
-		Wrapper<List<ArticleFull>> res = new Wrapper<>();
-
-		if (skip == null || skip < 0)
-			skip = 0;
-
-		if (limit == null || limit < 0)
-			limit = 20;
-
-		SearchResponse response = null;
-		try {
-			response = client.prepareSearch("articles").setQuery(QueryBuilders.multiMatchQuery(query, "_all"))
-					.setFrom(skip).setSize(limit).execute().actionGet();
-		} catch (Exception e) {
-			res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage()));
-			return res.badRequest();
-		}
-
-		SearchHits hits = response.getHits();
-		List<ArticleFull> articles = new ArrayList<>(10);
-		for (SearchHit hit : hits) {
-			Map<String, Object> source = hit.getSource();
-			ArticleFull article = new ArticleFull();
-			article.setId(MongoUtils.objectId(hit.getId()));
-			article.setTitle(source.get("title").toString());
-			article.setText(StringUtils.ellipsize(source.get("text").toString(), 150));
-			articles.add(article);
-		}
-
-		res.addMeta("total", articles.size());
-
-		return res.ok(articles);
-	}
-
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
 	@Consumes(MediaType.APPLICATION_JSON)
diff --git a/vipra-rest/src/main/java/de/vipra/rest/resource/SearchResource.java b/vipra-rest/src/main/java/de/vipra/rest/resource/SearchResource.java
new file mode 100644
index 0000000000000000000000000000000000000000..530ec3610d78b560292547c59aed293e315b1c03
--- /dev/null
+++ b/vipra-rest/src/main/java/de/vipra/rest/resource/SearchResource.java
@@ -0,0 +1,87 @@
+package de.vipra.rest.resource;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.client.transport.TransportClient;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.search.SearchHit;
+import org.elasticsearch.search.SearchHits;
+
+import de.vipra.rest.model.APIError;
+import de.vipra.rest.model.Wrapper;
+import de.vipra.util.Config;
+import de.vipra.util.ESClient;
+import de.vipra.util.MongoUtils;
+import de.vipra.util.StringUtils;
+import de.vipra.util.ex.ConfigException;
+import de.vipra.util.model.ArticleFull;
+
+@Path("/search")
+public class SearchResource {
+
+	@Context
+	UriInfo uri;
+
+	final TransportClient client;
+
+	public SearchResource(@Context ServletContext servletContext) throws ConfigException, IOException {
+		Config config = Config.getConfig();
+
+		client = ESClient.getClient(config);
+	}
+
+	@GET
+	@Produces(MediaType.APPLICATION_JSON)
+	public Response doSearch(@QueryParam("skip") Integer skip, @QueryParam("limit") Integer limit,
+			@QueryParam("query") String query) {
+		Wrapper<List<ArticleFull>> res = new Wrapper<>();
+
+		if (skip == null || skip < 0)
+			skip = 0;
+
+		if (limit == null || limit < 0)
+			limit = 20;
+
+		if (query == null || query.isEmpty() || limit == 0)
+			return res.noContent();
+
+		SearchResponse response = null;
+		try {
+			response = client.prepareSearch("articles").setQuery(QueryBuilders.multiMatchQuery(query, "_all"))
+					.setFrom(skip).setSize(limit).execute().actionGet();
+		} catch (Exception e) {
+			res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage()));
+			return res.badRequest();
+		}
+
+		SearchHits hits = response.getHits();
+		List<ArticleFull> articles = new ArrayList<>(10);
+		for (SearchHit hit : hits) {
+			Map<String, Object> source = hit.getSource();
+			ArticleFull article = new ArticleFull();
+			article.setId(MongoUtils.objectId(hit.getId()));
+			article.setTitle(source.get("title").toString());
+			article.setText(StringUtils.ellipsize(source.get("text").toString(), 150));
+			articles.add(article);
+		}
+
+		res.addMeta("total", articles.size());
+
+		return res.ok(articles);
+	}
+
+}
diff --git a/vipra-ui/css/app.css b/vipra-ui/css/app.css
new file mode 100644
index 0000000000000000000000000000000000000000..dd2e3404b6c36783e3eb7f46be657a3b84e4e14b
--- /dev/null
+++ b/vipra-ui/css/app.css
@@ -0,0 +1,2 @@
+html{position:relative;min-height:100%}body{padding-bottom:20px;margin-bottom:60px}.word{cursor:pointer;padding-right:5px}.center{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.loading{background:url(/assets/images/squares.gif) no-repeat center center;width:120px;height:120px}.heading{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default;font-size:82px;margin:30px 0;background:transparent url(/img/logo.png) no-repeat 50% 50%;min-width:272px;min-height:216px;padding-top:90px}.ellipsize{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.search-result{margin-bottom:5px}.navbar-default .collapse:not(.in) .navbar-nav>.active>a,.navbar-default .collapse:not(.in) .navbar-nav>.active>a:focus,.navbar-default .collapse:not(.in) .navbar-nav>.active>a:hover{border-bottom:3px solid;padding-bottom:12px}.footer{position:absolute;bottom:0;width:100%;height:50px;border-top-width:1px;border-top-style:solid}.noselect{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default}
+/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFwcC5sZXNzIiwiYXBwLmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxLQUNFLGtCQUFBLEFBQ0EsZUFBQSxDQ0NELEFERUQsS0FDRSxvQkFBQSxBQUVBLGtCQUFBLENDQUQsQURHRCxNQUNFLGVBQUEsQUFDQSxpQkFBQSxDQ0RELEFESUQsUUFDRSxrQkFBQSxBQUNBLFFBQUEsQUFDQSxTQUFBLEFBQ0EsOEJBQUEsQ0NGRCxBREtELFNBQ0UsbUVBQUEsQUFDQSxZQUFBLEFBQ0EsWUFBQSxDQ0hELEFETUQsU0E0Q0UsMkJBQUEsQUFDQSx5QkFBQSxBQUNBLHNCQUFBLEFBQ0EscUJBQUEsQUFDQSxpQkFBQSxBQUNBLGVBQUEsQUEvQ0EsZUFBQSxBQUNBLGNBQUEsQUFDQSw0REFBQSxBQUNBLGdCQUFBLEFBQ0EsaUJBQUEsQUFDQSxnQkFBQSxDQ0NELEFERUQsV0FDRSxtQkFBQSxBQUNBLGdCQUFBLEFBQ0Esc0JBQUEsQ0NBRCxBREdELGVBQ0UsaUJBQUEsQ0NERCxBRE9LLHVMQUdFLHdCQUFBLEFBQ0EsbUJBQUEsQ0NMUCxBRFdELFFBQ0Usa0JBQUEsQUFDQSxTQUFBLEFBQ0EsV0FBQSxBQUVBLFlBQUEsQUFDQSxxQkFBQSxBQUNBLHNCQUFBLENDVEQsQURZRCxVQUNFLDJCQUFBLEFBQ0EseUJBQUEsQUFDQSxzQkFBQSxBQUNBLHFCQUFBLEFBQ0EsaUJBQUEsQUFDQSxjQUFBLENDVkQiLCJmaWxlIjoiYXBwLmNzcyIsInNvdXJjZXNDb250ZW50IjpbImh0bWwge1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gIG1pbi1oZWlnaHQ6IDEwMCU7XG59XG5cbmJvZHkge1xuICBwYWRkaW5nLWJvdHRvbTogMjBweDtcbiAgLyogTWFyZ2luIGJvdHRvbSBieSBmb290ZXIgaGVpZ2h0ICovXG4gIG1hcmdpbi1ib3R0b206IDYwcHg7XG59XG5cbi53b3JkIHtcbiAgY3Vyc29yOiBwb2ludGVyO1xuICBwYWRkaW5nLXJpZ2h0OiA1cHg7XG59XG5cbi5jZW50ZXIge1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHRvcDogNTAlO1xuICBsZWZ0OiA1MCU7XG4gIHRyYW5zZm9ybTogdHJhbnNsYXRlKC01MCUsIC01MCUpO1xufVxuXG4ubG9hZGluZyB7XG4gIGJhY2tncm91bmQ6IHVybCgvYXNzZXRzL2ltYWdlcy9zcXVhcmVzLmdpZikgbm8tcmVwZWF0IGNlbnRlciBjZW50ZXI7XG4gIHdpZHRoOiAxMjBweDtcbiAgaGVpZ2h0OiAxMjBweDtcbn1cblxuLmhlYWRpbmcge1xuICAubm9zZWxlY3Q7XG4gIGZvbnQtc2l6ZTogODJweDtcbiAgbWFyZ2luOiAzMHB4IDA7XG4gIGJhY2tncm91bmQ6IHRyYW5zcGFyZW50IHVybCgvaW1nL2xvZ28ucG5nKSBuby1yZXBlYXQgNTAlIDUwJTtcbiAgbWluLXdpZHRoOiAyNzJweDtcbiAgbWluLWhlaWdodDogMjE2cHg7XG4gIHBhZGRpbmctdG9wOiA5MHB4O1xufVxuXG4uZWxsaXBzaXplIHtcbiAgd2hpdGUtc3BhY2U6IG5vd3JhcDtcbiAgb3ZlcmZsb3c6IGhpZGRlbjtcbiAgdGV4dC1vdmVyZmxvdzogZWxsaXBzaXM7XG59XG5cbi5zZWFyY2gtcmVzdWx0IHtcbiAgbWFyZ2luLWJvdHRvbTogNXB4O1xufVxuXG4ubmF2YmFyLWRlZmF1bHQge1xuICAuY29sbGFwc2U6bm90KC5pbikge1xuICAgIC5uYXZiYXItbmF2ID4gLmFjdGl2ZSB7XG4gICAgICAmPiBhLFxuICAgICAgJj4gYTpob3ZlcixcbiAgICAgICY+IGE6Zm9jdXMge1xuICAgICAgICBib3JkZXItYm90dG9tOiAzcHggc29saWQ7XG4gICAgICAgIHBhZGRpbmctYm90dG9tOiAxMnB4O1xuICAgICAgfVxuICAgIH1cbiAgfVxufVxuXG4uZm9vdGVyIHtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICBib3R0b206IDA7XG4gIHdpZHRoOiAxMDAlO1xuICAvKiBTZXQgdGhlIGZpeGVkIGhlaWdodCBvZiB0aGUgZm9vdGVyIGhlcmUgKi9cbiAgaGVpZ2h0OiA1MHB4O1xuICBib3JkZXItdG9wLXdpZHRoOiAxcHg7XG4gIGJvcmRlci10b3Atc3R5bGU6IHNvbGlkO1xufVxuXG4ubm9zZWxlY3Qge1xuICAtd2Via2l0LXRvdWNoLWNhbGxvdXQ6IG5vbmU7XG4gIC13ZWJraXQtdXNlci1zZWxlY3Q6IG5vbmU7XG4gIC1tb3otdXNlci1zZWxlY3Q6IG5vbmU7XG4gIC1tcy11c2VyLXNlbGVjdDogbm9uZTtcbiAgdXNlci1zZWxlY3Q6IG5vbmU7XG4gIGN1cnNvcjogZGVmYXVsdDtcbn0iLCJodG1sIHtcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xuICBtaW4taGVpZ2h0OiAxMDAlO1xufVxuYm9keSB7XG4gIHBhZGRpbmctYm90dG9tOiAyMHB4O1xuICAvKiBNYXJnaW4gYm90dG9tIGJ5IGZvb3RlciBoZWlnaHQgKi9cbiAgbWFyZ2luLWJvdHRvbTogNjBweDtcbn1cbi53b3JkIHtcbiAgY3Vyc29yOiBwb2ludGVyO1xuICBwYWRkaW5nLXJpZ2h0OiA1cHg7XG59XG4uY2VudGVyIHtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICB0b3A6IDUwJTtcbiAgbGVmdDogNTAlO1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtNTAlKTtcbn1cbi5sb2FkaW5nIHtcbiAgYmFja2dyb3VuZDogdXJsKC9hc3NldHMvaW1hZ2VzL3NxdWFyZXMuZ2lmKSBuby1yZXBlYXQgY2VudGVyIGNlbnRlcjtcbiAgd2lkdGg6IDEyMHB4O1xuICBoZWlnaHQ6IDEyMHB4O1xufVxuLmhlYWRpbmcge1xuICAtd2Via2l0LXRvdWNoLWNhbGxvdXQ6IG5vbmU7XG4gIC13ZWJraXQtdXNlci1zZWxlY3Q6IG5vbmU7XG4gIC1tb3otdXNlci1zZWxlY3Q6IG5vbmU7XG4gIC1tcy11c2VyLXNlbGVjdDogbm9uZTtcbiAgdXNlci1zZWxlY3Q6IG5vbmU7XG4gIGN1cnNvcjogZGVmYXVsdDtcbiAgZm9udC1zaXplOiA4MnB4O1xuICBtYXJnaW46IDMwcHggMDtcbiAgYmFja2dyb3VuZDogdHJhbnNwYXJlbnQgdXJsKC9pbWcvbG9nby5wbmcpIG5vLXJlcGVhdCA1MCUgNTAlO1xuICBtaW4td2lkdGg6IDI3MnB4O1xuICBtaW4taGVpZ2h0OiAyMTZweDtcbiAgcGFkZGluZy10b3A6IDkwcHg7XG59XG4uZWxsaXBzaXplIHtcbiAgd2hpdGUtc3BhY2U6IG5vd3JhcDtcbiAgb3ZlcmZsb3c6IGhpZGRlbjtcbiAgdGV4dC1vdmVyZmxvdzogZWxsaXBzaXM7XG59XG4uc2VhcmNoLXJlc3VsdCB7XG4gIG1hcmdpbi1ib3R0b206IDVweDtcbn1cbi5uYXZiYXItZGVmYXVsdCAuY29sbGFwc2U6bm90KC5pbikgLm5hdmJhci1uYXYgPiAuYWN0aXZlID4gYSxcbi5uYXZiYXItZGVmYXVsdCAuY29sbGFwc2U6bm90KC5pbikgLm5hdmJhci1uYXYgPiAuYWN0aXZlID4gYTpob3Zlcixcbi5uYXZiYXItZGVmYXVsdCAuY29sbGFwc2U6bm90KC5pbikgLm5hdmJhci1uYXYgPiAuYWN0aXZlID4gYTpmb2N1cyB7XG4gIGJvcmRlci1ib3R0b206IDNweCBzb2xpZDtcbiAgcGFkZGluZy1ib3R0b206IDEycHg7XG59XG4uZm9vdGVyIHtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICBib3R0b206IDA7XG4gIHdpZHRoOiAxMDAlO1xuICAvKiBTZXQgdGhlIGZpeGVkIGhlaWdodCBvZiB0aGUgZm9vdGVyIGhlcmUgKi9cbiAgaGVpZ2h0OiA1MHB4O1xuICBib3JkZXItdG9wLXdpZHRoOiAxcHg7XG4gIGJvcmRlci10b3Atc3R5bGU6IHNvbGlkO1xufVxuLm5vc2VsZWN0IHtcbiAgLXdlYmtpdC10b3VjaC1jYWxsb3V0OiBub25lO1xuICAtd2Via2l0LXVzZXItc2VsZWN0OiBub25lO1xuICAtbW96LXVzZXItc2VsZWN0OiBub25lO1xuICAtbXMtdXNlci1zZWxlY3Q6IG5vbmU7XG4gIHVzZXItc2VsZWN0OiBub25lO1xuICBjdXJzb3I6IGRlZmF1bHQ7XG59XG4iXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0= */
diff --git a/vipra-ui/css/app.less b/vipra-ui/css/app.less
new file mode 100644
index 0000000000000000000000000000000000000000..f08068ef03252e4220fffabfe9fea63ad1914870
--- /dev/null
+++ b/vipra-ui/css/app.less
@@ -0,0 +1,80 @@
+html {
+  position: relative;
+  min-height: 100%;
+}
+
+body {
+  padding-bottom: 20px;
+  /* Margin bottom by footer height */
+  margin-bottom: 60px;
+}
+
+.word {
+  cursor: pointer;
+  padding-right: 5px;
+}
+
+.center {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+}
+
+.loading {
+  background: url(/assets/images/squares.gif) no-repeat center center;
+  width: 120px;
+  height: 120px;
+}
+
+.heading {
+  .noselect;
+  font-size: 82px;
+  margin: 30px 0;
+  background: transparent url(/img/logo.png) no-repeat 50% 50%;
+  min-width: 272px;
+  min-height: 216px;
+  padding-top: 90px;
+}
+
+.ellipsize {
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.search-result {
+  margin-bottom: 5px;
+}
+
+.navbar-default {
+  .collapse:not(.in) {
+    .navbar-nav > .active {
+      &> a,
+      &> a:hover,
+      &> a:focus {
+        border-bottom: 3px solid;
+        padding-bottom: 12px;
+      }
+    }
+  }
+}
+
+.footer {
+  position: absolute;
+  bottom: 0;
+  width: 100%;
+  /* Set the fixed height of the footer here */
+  height: 50px;
+  border-top-width: 1px;
+  border-top-style: solid;
+}
+
+.noselect {
+  -webkit-touch-callout: none;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  cursor: default;
+}
\ No newline at end of file
diff --git a/vipra-ui/css/main.less b/vipra-ui/css/main.less
deleted file mode 100644
index 27e5a0030710240b902e7c51598704f57ba2bdff..0000000000000000000000000000000000000000
--- a/vipra-ui/css/main.less
+++ /dev/null
@@ -1,3 +0,0 @@
-html, body {
-  background: red;
-}
\ No newline at end of file
diff --git a/vipra-ui/favicon/android-chrome-144x144.png b/vipra-ui/favicon/android-chrome-144x144.png
new file mode 100644
index 0000000000000000000000000000000000000000..23b3628a1c53b9d711e044621691e0230d960282
Binary files /dev/null and b/vipra-ui/favicon/android-chrome-144x144.png differ
diff --git a/vipra-ui/favicon/android-chrome-192x192.png b/vipra-ui/favicon/android-chrome-192x192.png
new file mode 100644
index 0000000000000000000000000000000000000000..ca42e62464c1d62bc966768bbad7a9719fd6cc47
Binary files /dev/null and b/vipra-ui/favicon/android-chrome-192x192.png differ
diff --git a/vipra-ui/favicon/android-chrome-36x36.png b/vipra-ui/favicon/android-chrome-36x36.png
new file mode 100644
index 0000000000000000000000000000000000000000..cf4620b3bb98a754e90415e7ad468b0e72dfa960
Binary files /dev/null and b/vipra-ui/favicon/android-chrome-36x36.png differ
diff --git a/vipra-ui/favicon/android-chrome-48x48.png b/vipra-ui/favicon/android-chrome-48x48.png
new file mode 100644
index 0000000000000000000000000000000000000000..e9ee2f0428f4a37b2faf5c06770d6c77b656f19a
Binary files /dev/null and b/vipra-ui/favicon/android-chrome-48x48.png differ
diff --git a/vipra-ui/favicon/android-chrome-72x72.png b/vipra-ui/favicon/android-chrome-72x72.png
new file mode 100644
index 0000000000000000000000000000000000000000..4f29e40d75691511cafb15f3bb6735f9c456ed67
Binary files /dev/null and b/vipra-ui/favicon/android-chrome-72x72.png differ
diff --git a/vipra-ui/favicon/android-chrome-96x96.png b/vipra-ui/favicon/android-chrome-96x96.png
new file mode 100644
index 0000000000000000000000000000000000000000..e42b114b5412839588e78595707b410fdcb7868e
Binary files /dev/null and b/vipra-ui/favicon/android-chrome-96x96.png differ
diff --git a/vipra-ui/favicon/apple-touch-icon-114x114.png b/vipra-ui/favicon/apple-touch-icon-114x114.png
new file mode 100644
index 0000000000000000000000000000000000000000..3ce8caf70ec12fcadd16e355b9d43971eb7d8de9
Binary files /dev/null and b/vipra-ui/favicon/apple-touch-icon-114x114.png differ
diff --git a/vipra-ui/favicon/apple-touch-icon-120x120.png b/vipra-ui/favicon/apple-touch-icon-120x120.png
new file mode 100644
index 0000000000000000000000000000000000000000..3ed93796b41ae1522cc0467f1078fb97da48d46f
Binary files /dev/null and b/vipra-ui/favicon/apple-touch-icon-120x120.png differ
diff --git a/vipra-ui/favicon/apple-touch-icon-144x144.png b/vipra-ui/favicon/apple-touch-icon-144x144.png
new file mode 100644
index 0000000000000000000000000000000000000000..57956a0277ff1eb295a7a44ee33965831de36d93
Binary files /dev/null and b/vipra-ui/favicon/apple-touch-icon-144x144.png differ
diff --git a/vipra-ui/favicon/apple-touch-icon-152x152.png b/vipra-ui/favicon/apple-touch-icon-152x152.png
new file mode 100644
index 0000000000000000000000000000000000000000..d5004ac03673398fe6f7d418b00d2eea02bd0c42
Binary files /dev/null and b/vipra-ui/favicon/apple-touch-icon-152x152.png differ
diff --git a/vipra-ui/favicon/apple-touch-icon-180x180.png b/vipra-ui/favicon/apple-touch-icon-180x180.png
new file mode 100644
index 0000000000000000000000000000000000000000..c3de5f5a7023e83ec308380a38f385afa05de5f2
Binary files /dev/null and b/vipra-ui/favicon/apple-touch-icon-180x180.png differ
diff --git a/vipra-ui/favicon/apple-touch-icon-57x57.png b/vipra-ui/favicon/apple-touch-icon-57x57.png
new file mode 100644
index 0000000000000000000000000000000000000000..a44ba21162954e0bcc774d6745522175bb55e898
Binary files /dev/null and b/vipra-ui/favicon/apple-touch-icon-57x57.png differ
diff --git a/vipra-ui/favicon/apple-touch-icon-60x60.png b/vipra-ui/favicon/apple-touch-icon-60x60.png
new file mode 100644
index 0000000000000000000000000000000000000000..aad2e11b045c59e009a994ed274c0b3f5a682a80
Binary files /dev/null and b/vipra-ui/favicon/apple-touch-icon-60x60.png differ
diff --git a/vipra-ui/favicon/apple-touch-icon-72x72.png b/vipra-ui/favicon/apple-touch-icon-72x72.png
new file mode 100644
index 0000000000000000000000000000000000000000..8b4b2fcdaa5f0cd53c169b699db48c63e28b40cf
Binary files /dev/null and b/vipra-ui/favicon/apple-touch-icon-72x72.png differ
diff --git a/vipra-ui/favicon/apple-touch-icon-76x76.png b/vipra-ui/favicon/apple-touch-icon-76x76.png
new file mode 100644
index 0000000000000000000000000000000000000000..e015e2a1bc495c1a39e8c5c7ab39d3187d9dd67b
Binary files /dev/null and b/vipra-ui/favicon/apple-touch-icon-76x76.png differ
diff --git a/vipra-ui/favicon/apple-touch-icon-precomposed.png b/vipra-ui/favicon/apple-touch-icon-precomposed.png
new file mode 100644
index 0000000000000000000000000000000000000000..cf4d130553bb1d08464f0e2576bb7cc1203d94d3
Binary files /dev/null and b/vipra-ui/favicon/apple-touch-icon-precomposed.png differ
diff --git a/vipra-ui/favicon/apple-touch-icon.png b/vipra-ui/favicon/apple-touch-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..c3de5f5a7023e83ec308380a38f385afa05de5f2
Binary files /dev/null and b/vipra-ui/favicon/apple-touch-icon.png differ
diff --git a/vipra-ui/favicon/browserconfig.xml b/vipra-ui/favicon/browserconfig.xml
new file mode 100644
index 0000000000000000000000000000000000000000..9ecd35003349383dc5417fe45d5b97a8842f3541
--- /dev/null
+++ b/vipra-ui/favicon/browserconfig.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<browserconfig>
+  <msapplication>
+    <tile>
+      <square70x70logo src="/favicon/mstile-70x70.png"/>
+      <square150x150logo src="/favicon/mstile-150x150.png"/>
+      <square310x310logo src="/favicon/mstile-310x310.png"/>
+      <wide310x150logo src="/favicon/mstile-310x150.png"/>
+      <TileColor>#da532c</TileColor>
+    </tile>
+  </msapplication>
+</browserconfig>
diff --git a/vipra-ui/favicon/favicon-16x16.png b/vipra-ui/favicon/favicon-16x16.png
new file mode 100644
index 0000000000000000000000000000000000000000..df7bc123350e7b54707f457eb2c623591914e1ed
Binary files /dev/null and b/vipra-ui/favicon/favicon-16x16.png differ
diff --git a/vipra-ui/favicon/favicon-194x194.png b/vipra-ui/favicon/favicon-194x194.png
new file mode 100644
index 0000000000000000000000000000000000000000..e956726d2909bb3095e52cecc1311d0102706f5a
Binary files /dev/null and b/vipra-ui/favicon/favicon-194x194.png differ
diff --git a/vipra-ui/favicon/favicon-32x32.png b/vipra-ui/favicon/favicon-32x32.png
new file mode 100644
index 0000000000000000000000000000000000000000..54a622eb15653b7821540cf0f4c3a0d9ac602c40
Binary files /dev/null and b/vipra-ui/favicon/favicon-32x32.png differ
diff --git a/vipra-ui/favicon/favicon-96x96.png b/vipra-ui/favicon/favicon-96x96.png
new file mode 100644
index 0000000000000000000000000000000000000000..984784f51e26962ecfa7eed0390f6d71f8bc2d86
Binary files /dev/null and b/vipra-ui/favicon/favicon-96x96.png differ
diff --git a/vipra-ui/favicon/favicon.ico b/vipra-ui/favicon/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..82069d823ecf0ce9130f015ba9c2f54df0764982
Binary files /dev/null and b/vipra-ui/favicon/favicon.ico differ
diff --git a/vipra-ui/favicon/manifest.json b/vipra-ui/favicon/manifest.json
new file mode 100644
index 0000000000000000000000000000000000000000..cab1e0a2b8a78dee0d05770b9cbb6a3eec6830a9
--- /dev/null
+++ b/vipra-ui/favicon/manifest.json
@@ -0,0 +1,41 @@
+{
+	"name": "Vipra",
+	"icons": [
+		{
+			"src": "\/favicon\/android-chrome-36x36.png",
+			"sizes": "36x36",
+			"type": "image\/png",
+			"density": 0.75
+		},
+		{
+			"src": "\/favicon\/android-chrome-48x48.png",
+			"sizes": "48x48",
+			"type": "image\/png",
+			"density": 1
+		},
+		{
+			"src": "\/favicon\/android-chrome-72x72.png",
+			"sizes": "72x72",
+			"type": "image\/png",
+			"density": 1.5
+		},
+		{
+			"src": "\/favicon\/android-chrome-96x96.png",
+			"sizes": "96x96",
+			"type": "image\/png",
+			"density": 2
+		},
+		{
+			"src": "\/favicon\/android-chrome-144x144.png",
+			"sizes": "144x144",
+			"type": "image\/png",
+			"density": 3
+		},
+		{
+			"src": "\/favicon\/android-chrome-192x192.png",
+			"sizes": "192x192",
+			"type": "image\/png",
+			"density": 4
+		}
+	]
+}
diff --git a/vipra-ui/favicon/mstile-144x144.png b/vipra-ui/favicon/mstile-144x144.png
new file mode 100644
index 0000000000000000000000000000000000000000..fe6ee38a065d4a60c239b34159cabe2657ca786f
Binary files /dev/null and b/vipra-ui/favicon/mstile-144x144.png differ
diff --git a/vipra-ui/favicon/mstile-150x150.png b/vipra-ui/favicon/mstile-150x150.png
new file mode 100644
index 0000000000000000000000000000000000000000..a7f5e77e38a09a5d124327aea41e3851a36776e7
Binary files /dev/null and b/vipra-ui/favicon/mstile-150x150.png differ
diff --git a/vipra-ui/favicon/mstile-310x150.png b/vipra-ui/favicon/mstile-310x150.png
new file mode 100644
index 0000000000000000000000000000000000000000..313f68de8d63dfb214085770fa47082d9310d3d9
Binary files /dev/null and b/vipra-ui/favicon/mstile-310x150.png differ
diff --git a/vipra-ui/favicon/mstile-310x310.png b/vipra-ui/favicon/mstile-310x310.png
new file mode 100644
index 0000000000000000000000000000000000000000..59bd91e2e6f1bf335f24980050838a670c37df2b
Binary files /dev/null and b/vipra-ui/favicon/mstile-310x310.png differ
diff --git a/vipra-ui/favicon/mstile-70x70.png b/vipra-ui/favicon/mstile-70x70.png
new file mode 100644
index 0000000000000000000000000000000000000000..bab8744d95ae3da4e74d41dd65c312975443d5e0
Binary files /dev/null and b/vipra-ui/favicon/mstile-70x70.png differ
diff --git a/vipra-ui/favicon/safari-pinned-tab.svg b/vipra-ui/favicon/safari-pinned-tab.svg
new file mode 100644
index 0000000000000000000000000000000000000000..a4fbb7ddd7e9e63ad948cb3545cc57cdb7b8b66f
--- /dev/null
+++ b/vipra-ui/favicon/safari-pinned-tab.svg
@@ -0,0 +1,43 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+ "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
+ width="272.000000pt" height="272.000000pt" viewBox="0 0 272.000000 272.000000"
+ preserveAspectRatio="xMidYMid meet">
+<metadata>
+Created by potrace 1.11, written by Peter Selinger 2001-2013
+</metadata>
+<g transform="translate(0.000000,272.000000) scale(0.100000,-0.100000)"
+fill="#000000" stroke="none">
+<path d="M78 2334 c18 -21 32 -44 32 -51 0 -7 5 -13 10 -13 6 0 10 -5 10 -11
+0 -6 13 -30 30 -54 17 -24 30 -48 30 -53 0 -6 3 -12 8 -14 9 -4 192 -297 192
+-308 0 -5 3 -10 8 -12 10 -4 62 -86 62 -98 0 -5 4 -10 10 -10 5 0 12 -11 16
+-25 3 -14 10 -25 15 -25 5 0 9 -5 9 -10 0 -6 5 -16 10 -23 32 -37 101 -158 95
+-167 -4 -6 -2 -9 5 -8 6 2 18 -10 28 -27 9 -16 20 -32 23 -35 5 -5 70 -109
+116 -188 12 -20 25 -41 30 -47 5 -5 18 -26 29 -45 10 -19 24 -38 29 -42 7 -5
+7 -8 0 -8 -6 0 -6 -3 0 -8 12 -8 72 -98 101 -152 10 -19 22 -37 25 -40 7 -7
+230 -361 249 -397 8 -15 18 -30 21 -33 3 -3 22 -33 43 -68 21 -34 41 -62 46
+-62 6 0 144 213 200 309 8 14 18 28 22 29 5 2 8 8 8 12 0 9 110 184 130 207 5
+7 10 15 10 19 0 3 18 33 40 66 22 33 40 62 40 65 0 2 23 38 50 80 28 42 50 79
+50 82 0 4 8 16 18 27 9 10 23 30 30 44 7 14 26 45 42 70 16 25 39 61 50 80 11
+19 29 49 40 65 11 17 27 43 36 59 9 16 20 34 25 40 19 23 58 86 53 86 -2 0 4
+9 14 20 10 11 25 33 33 48 7 15 31 54 53 87 21 33 39 66 39 73 0 6 3 12 7 12
+7 0 106 156 111 175 2 6 11 19 21 29 10 11 18 26 18 33 0 7 5 13 10 13 6 0 10
+4 10 10 0 5 20 38 45 73 25 36 45 72 45 81 0 10 5 14 10 11 5 -3 10 -1 10 6 0
+7 10 24 23 38 22 24 22 24 -28 -4 -27 -15 -53 -33 -56 -38 -4 -5 -15 -7 -25
+-3 -10 4 -15 4 -12 0 9 -8 -9 -25 -47 -44 -16 -8 -33 -19 -37 -25 -4 -5 -8 -6
+-8 -1 0 5 -8 2 -18 -6 -9 -9 -35 -26 -57 -38 -81 -45 -260 -155 -263 -162 -2
+-5 -12 -8 -21 -8 -10 0 -26 -9 -36 -20 -10 -11 -24 -20 -32 -20 -7 0 -13 -4
+-13 -8 0 -4 -12 -13 -27 -19 -31 -13 -105 -55 -113 -64 -11 -12 -279 -169
+-288 -169 -6 0 -16 -8 -21 -17 -6 -10 -11 -14 -11 -9 0 6 -7 1 -16 -10 -8 -10
+-13 -13 -9 -6 3 7 -14 -1 -38 -18 -25 -16 -50 -30 -56 -30 -6 0 -11 -4 -11 -8
+0 -5 -11 -14 -25 -20 -23 -11 -41 -3 -198 90 -94 56 -174 105 -177 108 -3 3
+-16 11 -30 18 -14 7 -38 21 -55 32 -54 34 -80 49 -97 56 -10 3 -18 10 -18 15
+0 5 -6 9 -13 9 -7 0 -23 9 -35 20 -12 12 -22 19 -22 16 0 -6 -32 11 -87 47
+-24 15 -47 27 -53 27 -5 0 -10 5 -10 10 0 6 -6 10 -13 10 -7 0 -22 7 -32 15
+-52 41 -134 82 -143 73 -7 -7 -10 -4 -9 8 1 11 -4 18 -10 17 -7 -2 -13 1 -13
+6 0 5 -22 20 -50 32 -27 12 -50 26 -50 30 0 4 -19 16 -42 28 -48 24 -81 43
+-88 52 -3 4 -25 16 -49 29 l-44 22 31 -38z m1816 -532 c3 -5 -1 -9 -9 -9 -8 0
+-12 4 -9 9 3 4 7 8 9 8 2 0 6 -4 9 -8z"/>
+</g>
+</svg>
diff --git a/vipra-ui/gulpfile.js b/vipra-ui/gulpfile.js
index 79093a23e0e9d315a8ff3fe6867487dfeb70574a..84c266cf4eedf06a70c04decd3931e34d613701f 100644
--- a/vipra-ui/gulpfile.js
+++ b/vipra-ui/gulpfile.js
@@ -9,17 +9,17 @@ gulp.task('less', function() {
       .pipe(sourcemaps.init())
       .pipe(less())
       .pipe(cssnano())
-      .pipe(gulp.dest('css/main.css'));
+      .pipe(sourcemaps.write())
+      .pipe(gulp.dest('css'));
 });
 
 gulp.task('watch', function() {
-  gulp.watch('css/**/*.less', ['less']);
+  gulp.watch('css/app.less', ['less']);
 });
 
 gulp.task('server', function() {
   gulp.src('.')
       .pipe(webserver({
-        livereload: true,
         open: true,
         port: 4200,
         fallback: 'index.html'
diff --git a/vipra-ui/html/articles/index.html b/vipra-ui/html/articles/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..f3476ca851c6080072a12c494aa8af4ff852d82b
--- /dev/null
+++ b/vipra-ui/html/articles/index.html
@@ -0,0 +1,5 @@
+<ul>
+  <li ng-repeat="article in articles">
+    <a ui-sref="articles.show({id: article.id})">{{article.title}}</a>
+  </li>
+</ul>
\ No newline at end of file
diff --git a/vipra-ui/html/articles/show.html b/vipra-ui/html/articles/show.html
new file mode 100644
index 0000000000000000000000000000000000000000..d5b6176cab5ea25d863f6b34364f8ef7cb53e25d
--- /dev/null
+++ b/vipra-ui/html/articles/show.html
@@ -0,0 +1,5 @@
+<h1 ng-bind="article.title"></h1>
+
+<p ng-bind="article.date"></p>
+
+<p ng-bind="article.text"></p>
\ No newline at end of file
diff --git a/vipra-ui/html/index.html b/vipra-ui/html/index.html
index 357dd48952bc1b93508f0b0bebb790f09e0730be..45a05c583cade5eda1d5837acabf9310c718ba0c 100644
--- a/vipra-ui/html/index.html
+++ b/vipra-ui/html/index.html
@@ -1 +1,53 @@
-Home
\ No newline at end of file
+<div class="container">
+
+  <div class="row">
+    <div class="col-md-12">
+      <div class="text-center">
+        <h1 class="heading">'v&#618;pr&#601;</h1>
+      </div>
+
+      <br>
+      <input type="text" class="form-control input-lg" placeholder="Search..." ng-model="search" ng-model-options="{debounce:500}">
+    </div>
+  </div>
+
+  <br><br>
+  <div class="row">
+    <div class="col-md-6 text-center">
+      <h4>Latest articles</h4>
+      <ul class="list-unstyled">
+        <li class="ellipsize" ng-repeat="article in latestArticles">
+          <a ui-sref="articles.show({id:article.id})" ng-bind="article.title"></a>
+        </li>
+      </ul>
+    </div>
+    <div class="col-md-3 text-center">
+      <h4>Latest topics</h4>
+      <ul class="list-unstyled">
+        <li class="ellipsize" ng-repeat="topic in latestTopics">
+          <a ui-sref="topics.show({id:topic.id})" ng-bind="topic.name"></a>
+        </li>
+      </ul>
+    </div>
+    <div class="col-md-3 text-center">
+      <h4>Latest words</h4>
+      <ul class="list-unstyled">
+        <li class="ellipsize" ng-repeat="word in latestWords">
+          <a ui-sref="words.show({id:word.id})" ng-bind="word.id"></a>
+        </li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="row" ng-show="searchResults.length > 0">
+    <hr>
+    <h4>Results</h4>
+    <ul class="list-unstyled">
+      <li class="search-result" ng-repeat="article in searchResults">
+        <a ui-sref="articles.show({id:article.id})" ng-bind="article.title"></a><br>
+        <p ng-bind="article.text"></p>
+      </li>
+    </ul>
+  </div>
+
+</div>
\ No newline at end of file
diff --git a/vipra-ui/html/topics/index.html b/vipra-ui/html/topics/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/vipra-ui/html/topics/show.html b/vipra-ui/html/topics/show.html
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/vipra-ui/html/words/index.html b/vipra-ui/html/words/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/vipra-ui/html/words/show.html b/vipra-ui/html/words/show.html
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/vipra-ui/img/logo.png b/vipra-ui/img/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..76f867cc2335f6b8c77feb78c80504be1cb9387d
Binary files /dev/null and b/vipra-ui/img/logo.png differ
diff --git a/vipra-ui/index.html b/vipra-ui/index.html
index ecf5817a249b9a428015eed532b7958b5eda1612..f1a83eec3051398daeab4b94d094f0f92ddf78f8 100644
--- a/vipra-ui/index.html
+++ b/vipra-ui/index.html
@@ -1,26 +1,80 @@
 <!DOCTYPE html>
-<html lang="en" ng-app="vipra">
+<html lang="en" ng-app="vipra.app">
   <head>
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width, initial-scale=1">
     <title>Vipra</title>
 
+    <link rel="apple-touch-icon" sizes="57x57" href="/favicon/apple-touch-icon-57x57.png">
+    <link rel="apple-touch-icon" sizes="60x60" href="/favicon/apple-touch-icon-60x60.png">
+    <link rel="apple-touch-icon" sizes="72x72" href="/favicon/apple-touch-icon-72x72.png">
+    <link rel="apple-touch-icon" sizes="76x76" href="/favicon/apple-touch-icon-76x76.png">
+    <link rel="apple-touch-icon" sizes="114x114" href="/favicon/apple-touch-icon-114x114.png">
+    <link rel="apple-touch-icon" sizes="120x120" href="/favicon/apple-touch-icon-120x120.png">
+    <link rel="apple-touch-icon" sizes="144x144" href="/favicon/apple-touch-icon-144x144.png">
+    <link rel="apple-touch-icon" sizes="152x152" href="/favicon/apple-touch-icon-152x152.png">
+    <link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon-180x180.png">
+    <link rel="icon" type="image/png" href="/favicon/favicon-32x32.png" sizes="32x32">
+    <link rel="icon" type="image/png" href="/favicon/favicon-194x194.png" sizes="194x194">
+    <link rel="icon" type="image/png" href="/favicon/favicon-96x96.png" sizes="96x96">
+    <link rel="icon" type="image/png" href="/favicon/android-chrome-192x192.png" sizes="192x192">
+    <link rel="icon" type="image/png" href="/favicon/favicon-16x16.png" sizes="16x16">
+    <link rel="manifest" href="/favicon/manifest.json">
+    <link rel="mask-icon" href="/favicon/safari-pinned-tab.svg" color="#5bbad5">
+    <link rel="shortcut icon" href="/favicon/favicon.ico">
+    <meta name="msapplication-TileColor" content="#da532c">
+    <meta name="msapplication-TileImage" content="/favicon/mstile-144x144.png">
+    <meta name="msapplication-config" content="/favicon/browserconfig.xml">
+    <meta name="theme-color" content="#ffffff">
+
     <!-- stylesheets -->
     <link href="bower_components/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
+    <link href="css/app.css" rel="stylesheet">
 
     <!-- javascript -->
     <script src="bower_components/jquery/dist/jquery.min.js"></script>
-    <script src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
     <script src="bower_components/angular/angular.min.js"></script>
     <script src="bower_components/angular-resource/angular-resource.min.js"></script>
     <script src="bower_components/angular-ui-router/release/angular-ui-router.min.js"></script>
+    <script src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
     <script src="js/app.js"></script>
     <script src="js/controllers.js"></script>
     <script src="js/directives.js"></script>
+    <script src="js/factories.js"></script>
     <script src="js/services.js"></script>
   </head>
   <body>
-    <div ui-view></div>
+    <nav class="navbar navbar-default navbar-static-top">
+      <div class="container-fluid">
+        <!-- Brand and toggle get grouped for better mobile display -->
+        <div class="navbar-header">
+          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#vipra-navbar-collapse-1" aria-expanded="false">
+            <span class="sr-only">Toggle navigation</span>
+            <span class="icon-bar"></span>
+            <span class="icon-bar"></span>
+            <span class="icon-bar"></span>
+          </button>
+          <a ui-sref="index" class="navbar-brand">vipra</a>
+        </div>
+
+        <!-- Collect the nav links, forms, and other content for toggling -->
+        <div class="collapse navbar-collapse" id="vipra-navbar-collapse-1">
+          <ul class="nav navbar-nav">
+            <li><a ui-sref="articles.index">Articles</a></li>
+            <li><a ui-sref="topics.index">Topics</a></li>
+            <li><a ui-sref="words.index">Words</a></li>
+            <div id="testi"></div>
+          </ul>
+        </div><!-- /.navbar-collapse -->
+      </div><!-- /.container-fluid -->
+    </nav>
+
+    <div class="container" ui-view></div>
+
+    <footer class="footer navbar-default">
+      <div class="container-fluid">
+      </div>
+    </footer>
   </body>
 </html>
\ No newline at end of file
diff --git a/vipra-ui/js/app.js b/vipra-ui/js/app.js
index 60d74aac2bde976726600ee6fab20284db6942ec..66b7d3b4936ca75ed3fe66a83055b57f5a9e0063 100644
--- a/vipra-ui/js/app.js
+++ b/vipra-ui/js/app.js
@@ -1,18 +1,137 @@
-var app = angular.module('vipra', [
-  'ngResource',
-  'ui.router',
-  'vipra.controllers',
-  'vipra.directives',
-  'vipra.services'
-]);
+(function() {
 
-app.config(function($stateProvider, $urlRouterProvider) {
+  var app = angular.module('vipra.app', [
+    'ngResource',
+    'ui.router',
+    'vipra.controllers',
+    'vipra.directives',
+    'vipra.factories',
+    'vipra.services'
+  ]);
 
-  $urlRouterProvider.otherwise('/');
+  app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
 
-  $stateProvider.state('index', {
-    url: '/',
-    templateUrl: 'html/index.html'
-  });
+    var tplBase = 'html';
 
-});
\ No newline at end of file
+    $urlRouterProvider.otherwise('/');
+
+    // states
+
+    $stateProvider.state('index', {
+      url: '/',
+      templateUrl: tplBase + '/index.html',
+      controller: 'IndexController',
+      reloadOnSearch: false
+    });
+
+    // states: articles
+
+    $stateProvider.state('articles', {
+      url: '/articles',
+      abstract: true,
+      template: '<ui-view/>'
+    });
+
+    $stateProvider.state('articles.index', {
+      url: '',
+      templateUrl: tplBase + '/articles/index.html',
+      controller: 'ArticlesIndexController'
+    });
+
+    $stateProvider.state('articles.show', {
+      url: '/:id',
+      templateUrl: tplBase + '/articles/show.html',
+      controller: 'ArticlesShowController'
+    });
+
+    // states: topics
+
+    $stateProvider.state('topics', {
+      url: '/topics',
+      abstract: true,
+      template: '<ui-view/>'
+    });
+
+    $stateProvider.state('topics.index', {
+      url: '',
+      templateUrl: tplBase + '/topics/index.html',
+      controller: 'ArticlesIndexController'
+    });
+
+    $stateProvider.state('topics.show', {
+      url: '/:id',
+      templateUrl: tplBase + '/topics/show.html',
+      controller: 'ArticlesShowController'
+    });
+
+    // states: words
+
+    $stateProvider.state('words', {
+      url: '/words',
+      abstract: true,
+      template: '<ui-view/>'
+    });
+
+    $stateProvider.state('words.index', {
+      url: '',
+      templateUrl: tplBase + '/words/index.html',
+      controller: 'ArticlesIndexController'
+    });
+
+    $stateProvider.state('words.show', {
+      url: '/:id',
+      templateUrl: tplBase + '/words/show.html',
+      controller: 'ArticlesShowController'
+    });
+
+  }]);
+
+  app.config(['$httpProvider', function($httpProvider) {
+
+    $httpProvider.interceptors.push(function ($q, $injector, $rootScope) { 
+      var requestIncrement = function(config) {
+        $rootScope.loading.requests = ++$rootScope.loading.requests || 1;
+        $rootScope.loading[config.method] = ++$rootScope.loading[config.method] || 1;
+      };
+
+      var requestDecrement = function(config) {
+        $rootScope.loading.requests = Math.max(--$rootScope.loading.requests, 0);
+        $rootScope.loading[config.method] = Math.max(--$rootScope.loading[config.method], 0);
+      };
+
+      return {
+        request: function(config) {
+          requestIncrement(config);
+          return config;
+        },
+
+        requestError: function(rejection) {
+          requestDecrement(rejection.config);
+          return $q.reject(rejection);
+        },
+
+        response: function(response) {
+          requestDecrement(response.config);
+          return response;
+        },
+
+        responseError: function(rejection) {
+          requestDecrement(rejection.config);
+          return $q.reject(rejection);
+        }
+      };
+    });
+
+  }]);
+
+  app.run(['$rootScope', function($rootScope) {
+
+    $rootScope.loading = {};
+
+    $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
+      console.log('changing state: ' + toState.name);
+    });
+
+  }]);
+
+})();
\ No newline at end of file
diff --git a/vipra-ui/js/controllers.js b/vipra-ui/js/controllers.js
index 63b99a10c99c34ed7b8889295aacd0c68f2a0612..42300697efa7050097dd57b92a41d8007ab424cd 100644
--- a/vipra-ui/js/controllers.js
+++ b/vipra-ui/js/controllers.js
@@ -1,5 +1,48 @@
-var app = angular.module('vipra.controllers', []);
+(function() {
 
-app.controller('MainCtrl', function($scope) {
+  var app = angular.module('vipra.controllers', [
+    'ui.router',
+    'vipra.factories'
+  ]);
 
-});
\ No newline at end of file
+  app.controller('IndexController', ['$scope', '$location', 'ArticleFactory', 'TopicFactory', 'WordFactory', 'SearchFactory',function($scope, $location, ArticleFactory, TopicFactory, WordFactory, SearchFactory) {
+    $scope.search = $location.search().query;
+
+    ArticleFactory.query({limit:3, sort:'-created'}, function(response) {
+      $scope.latestArticles = response.data;
+    });
+
+    TopicFactory.query({limit:3, sort:'-created'}, function(response) {
+      $scope.latestTopics = response.data;
+    });
+
+    WordFactory.query({limit:3, sort:'-created'}, function(response) {
+      $scope.latestWords = response.data;
+    });
+
+    $scope.$watch('search', function() {
+      if($scope.search) {
+        $location.search('query', $scope.search);
+        SearchFactory.query({limit:10, query:$scope.search}, function(response) {
+          $scope.searchResults = response.data;
+        });
+      } else {
+        $location.search('query', null);
+        $scope.searchResults = [];
+      }
+    });
+  }]);
+
+  app.controller('ArticlesIndexController', ['$scope', 'ArticleFactory', function($scope, ArticleFactory) {
+    ArticleFactory.query(function(response) {
+      $scope.articles = response.data;
+    });
+  }]);
+
+  app.controller('ArticlesShowController', ['$scope', '$stateParams', 'ArticleFactory', function($scope, $stateParams, ArticleFactory) {
+    ArticleFactory.get({id: $stateParams.id}, function(response) {
+      $scope.article = response.data;
+    });
+  }]);
+
+})();
\ No newline at end of file
diff --git a/vipra-ui/js/directives.js b/vipra-ui/js/directives.js
index 07291e39bee63c79e85193bef7729ef3083687b9..05b64aefd11b6771f2b6e9194caec1710896db86 100644
--- a/vipra-ui/js/directives.js
+++ b/vipra-ui/js/directives.js
@@ -1 +1,5 @@
-var app = angular.module('vipra.directives', []);
\ No newline at end of file
+(function() {
+
+  var app = angular.module('vipra.directives', []);
+
+})();
\ No newline at end of file
diff --git a/vipra-ui/js/factories.js b/vipra-ui/js/factories.js
new file mode 100644
index 0000000000000000000000000000000000000000..62564fd739a65b6d70cf4898c80ad5fbd2674c7f
--- /dev/null
+++ b/vipra-ui/js/factories.js
@@ -0,0 +1,31 @@
+(function() {
+
+  var app = angular.module('vipra.factories', []);
+
+  var endpoint = '//' + location.hostname + ':8000';
+
+  app.factory('ArticleFactory', ['$resource', function($resource) {
+    return $resource(endpoint + '/vipra-rest/articles/:id', {}, {
+      query: { isArray: false }
+    });
+  }]);
+
+  app.factory('TopicFactory', ['$resource', function($resource) {
+    return $resource(endpoint + '/vipra-rest/topics/:id', {}, {
+      query: { isArray: false }
+    });
+  }]);
+
+  app.factory('WordFactory', ['$resource', function($resource) {
+    return $resource(endpoint + '/vipra-rest/words/:id', {}, {
+      query: { isArray: false }
+    });
+  }]);
+
+  app.factory('SearchFactory', ['$resource', function($resource) {
+    return $resource(endpoint + '/vipra-rest/search', {}, {
+      query: { isArray: false }
+    });
+  }]);
+
+})();
\ No newline at end of file
diff --git a/vipra-ui/js/services.js b/vipra-ui/js/services.js
index 998e2cefe83939d8e5e3de9f5383fcac6cdf23cc..cc05f9e1c0d8c72892ecbc564480f283451a7a58 100644
--- a/vipra-ui/js/services.js
+++ b/vipra-ui/js/services.js
@@ -1 +1,5 @@
-var app = angular.module('vipra.services', []);
\ No newline at end of file
+(function() {
+
+  var app = angular.module('vipra.services', []);
+
+})();
\ No newline at end of file
diff --git a/vipra-ui/package.json b/vipra-ui/package.json
index 1b966c29474c3e17500c4b36b4cf0312585b6153..330c6ea849b29a919ba5e850c676cf886092d808 100644
--- a/vipra-ui/package.json
+++ b/vipra-ui/package.json
@@ -7,6 +7,7 @@
   "devDependencies": {
     "gulp": "^3.9.0",
     "gulp-cssnano": "^2.1.0",
+    "gulp-include": "^2.1.0",
     "gulp-less": "^3.0.5",
     "gulp-sourcemaps": "^1.6.0",
     "gulp-webserver": "^0.9.1"