diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java b/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java
index 1e04b318e3a8010b883e00b2b3f6a7809d5e8303..be212bc682da4ad02d1c47b0077f06da0cbb35c7 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java
@@ -1,5 +1,6 @@
 package de.vipra.cmd.lda;
 
+import java.awt.Color;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
@@ -12,6 +13,7 @@ import java.util.Comparator;
 import java.util.Date;
 import java.util.List;
 import java.util.ListIterator;
+import java.util.Random;
 import java.util.function.Consumer;
 
 import org.bson.types.ObjectId;
@@ -21,6 +23,7 @@ import de.vipra.cmd.file.FilebaseIDDateIndexEntry;
 import de.vipra.cmd.file.FilebaseWindowIndex;
 import de.vipra.cmd.file.FilebaseWordIndex;
 import de.vipra.util.ArrayUtils;
+import de.vipra.util.ColorUtils;
 import de.vipra.util.CompareMap;
 import de.vipra.util.Config;
 import de.vipra.util.ConsoleUtils;
@@ -207,6 +210,9 @@ public class Analyzer {
 
 		final List<SequenceFull> newSequences = new ArrayList<>(topicCount * windowCount);
 		final List<TopicFull> newTopics = new ArrayList<>(topicCount);
+		if (topicModel.getColorSeed() == null)
+			topicModel.setColorSeed(new Random().nextLong());
+		final List<Color> newColors = ColorUtils.generateRandomColors(topicCount, topicModel.getColorSeed());
 
 		topicModel.setWordCount((long) wordCount);
 		topicModel.setWindowCount((long) windowCount);
@@ -226,6 +232,7 @@ public class Analyzer {
 			// create new topic
 			final TopicFull newTopic = new TopicFull();
 			newTopic.setTopicModel(new TopicModel(topicModel.getId()));
+			newTopic.setColor(ColorUtils.toHexString(newColors.get(idxTopic)));
 			newTopics.add(newTopic);
 
 			in = new BufferedReader(new InputStreamReader(new FileInputStream(seqFile)));
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/CreateModelCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/CreateModelCommand.java
index 0c44072fd2f38605b73eb51c15a5d0a837a6c13e..a4235dfdeec330aa536e67558e04825be711544c 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/option/CreateModelCommand.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/CreateModelCommand.java
@@ -2,6 +2,7 @@ package de.vipra.cmd.option;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Random;
 
 import org.bson.types.ObjectId;
 
@@ -20,6 +21,7 @@ public class CreateModelCommand implements Command {
 	private final String[] names;
 	private Config config;
 	private MongoService<TopicModelFull, ObjectId> dbTopicModels;
+	private Random random;
 
 	public CreateModelCommand(final String[] names) {
 		this.names = names;
@@ -30,6 +32,7 @@ public class CreateModelCommand implements Command {
 		modelConfig.setName(name);
 		modelConfig.saveToFile(modelDir);
 		final TopicModelFull topicModel = new TopicModelFull(name, modelConfig);
+		topicModel.setColorSeed(random.nextLong());
 		dbTopicModels.createSingle(topicModel);
 		config.getTopicModelConfigs().put(name, modelConfig);
 	}
@@ -41,6 +44,7 @@ public class CreateModelCommand implements Command {
 
 		config = Config.getConfig();
 		dbTopicModels = MongoService.getDatabaseService(config, TopicModelFull.class);
+		random = new Random();
 
 		final TopicModelConfig modelConfig;
 
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java
index c3ca6cbe58493901ebc5dd61742b84d592200f54..4f7a5b3fe1e457d9465bac192a6828cf7daac66b 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java
@@ -296,16 +296,6 @@ public class ImportCommand implements Command {
 		 */
 		filebase.sync();
 
-		/*
-		 * update topic model
-		 */
-		topicModelFull.setWindows(filebase.getWindowIndex().getWindows());
-		topicModelFull.setArticleCount((long) filebase.getIdDateIndex().size());
-		topicModelFull.setWordCount((long) filebase.getWordIndex().size());
-		final long entityCount = dbEntities.count(QueryBuilder.builder().eq("topicModel", topicModel));
-		topicModelFull.setEntityCount(entityCount);
-		dbTopicModels.replaceSingle(topicModelFull);
-
 		/*
 		 * add new words
 		 */
@@ -333,6 +323,15 @@ public class ImportCommand implements Command {
 		}
 		dbWindows.createMultiple(newWindows);
 
+		/*
+		 * update topic model
+		 */
+		topicModelFull.setWindows(filebase.getWindowIndex().getWindows());
+		topicModelFull.setArticleCount((long) filebase.getIdDateIndex().size());
+		topicModelFull.setWordCount((long) filebase.getWordIndex().size());
+		topicModelFull.setEntityCount(dbEntities.count(QueryBuilder.builder().eq("topicModel", topicModel)));
+		dbTopicModels.replaceSingle(topicModelFull);
+
 		/*
 		 * run information
 		 */
diff --git a/vipra-ui/app/html/articles/index.html b/vipra-ui/app/html/articles/index.html
index 61662e45884561e9e63dd3eafc154c5d2e1b41ac..6479cf27dd8575314c1e14c93a66a095e84aa670 100644
--- a/vipra-ui/app/html/articles/index.html
+++ b/vipra-ui/app/html/articles/index.html
@@ -54,11 +54,6 @@
         </div>
       </div>
     </div>
-    <div class="row">
-      <div class="col-md-12 text-center">
-        <pagination total="articlesTotal" page="articlesIndexModels.page" limit="articlesIndexModels.limit" />
-      </div>
-    </div>
   </div>
 </div>
 <div ng-cloak ui-view></div>
diff --git a/vipra-ui/app/html/directives/article-link.html b/vipra-ui/app/html/directives/article-link.html
index 1e7d04ab33a358c2f2b348966165bc4ba860294a..52198c121d9d281efddf6a49b4269143640d15c8 100644
--- a/vipra-ui/app/html/directives/article-link.html
+++ b/vipra-ui/app/html/directives/article-link.html
@@ -13,7 +13,7 @@
   <div ng-if="detailsShown" class="details">
     <span ng-bind="::articleDetails.text"></span>
     <div>
-      <a ui-sref="topics.show({id:topic.topic.id})" class="badge" ng-bind="::topic.topic.name" ng-attr-title="::topic.topic.name" ng-repeat="topic in articleDetails.topics"></a>
+      <a ui-sref="topics.show({id:topic.topic.id})" class="badge topic-badge text-outline" ng-bind="::topic.topic.name" ng-style="{'background':topic.topic.color}" ng-attr-title="{{::topic.topic.name}}" ng-repeat="topic in articleDetails.topics"></a>
     </div>
   </div>
 </div>
\ No newline at end of file
diff --git a/vipra-ui/app/html/entities/index.html b/vipra-ui/app/html/entities/index.html
index 5cf739fd136182f6d6336abd3a69838616cd1716..5bc4a38b80475c233ee4ab7794139c877993c6fe 100644
--- a/vipra-ui/app/html/entities/index.html
+++ b/vipra-ui/app/html/entities/index.html
@@ -51,11 +51,6 @@
         </div>
       </div>
     </div>
-    <div class="row">
-      <div class="col-md-12 text-center">
-        <pagination total="entitiesTotal" page="entitiesIndexModels.page" limit="entitiesIndexModels.limit" />
-      </div>
-    </div>
   </div>
 </div>
 <div ng-cloak ui-view></div>
diff --git a/vipra-ui/app/html/index.html b/vipra-ui/app/html/index.html
index fac08c019dd57c2012badca338c9859630d43580..4d959af5956b19dc2d9988f434645beea39f382d 100644
--- a/vipra-ui/app/html/index.html
+++ b/vipra-ui/app/html/index.html
@@ -75,7 +75,7 @@
           </div>
         </div>
         <div class="col-xs-5 text-center">
-          <h3>Introduction slides</h3>
+          <h3>Slides</h3>
           <div class="embed-responsive embed-responsive-16by9 media-box">
             <a ui-sref="slides">
               <img src="img/vipra-slides.png" class="slides-thumb">
diff --git a/vipra-ui/app/html/partials/topicmodel-popover.html b/vipra-ui/app/html/partials/topicmodel-popover.html
index ab7142f10a066bdc9f262d1871cbb1a61af42927..585b8ee94eb9e5f10cbb96691054b4806ccf3133 100644
--- a/vipra-ui/app/html/partials/topicmodel-popover.html
+++ b/vipra-ui/app/html/partials/topicmodel-popover.html
@@ -1,30 +1,30 @@
 <table class="td-padded">
   <tr>
     <th>Articles</th>
-    <td ng-bind="::topicModel.articleCount"></td>
+    <td ng-bind="::topicModel.articleCount || 0"></td>
   </tr>
   <tr>
     <th>Topics</th>
-    <td ng-bind="::topicModel.topicCount"></td>
+    <td ng-bind="::topicModel.topicCount || 0"></td>
   </tr>
   <tr>
     <th>Words</th>
-    <td ng-bind="::topicModel.wordCount"></td>
+    <td ng-bind="::topicModel.wordCount || 0"></td>
   </tr>
   <tr>
     <th>Entities</th>
-    <td ng-bind="::topicModel.entityCount"></td>
+    <td ng-bind="::topicModel.entityCount || 0"></td>
   </tr>
   <tr>
     <th>Windows</th>
-    <td ng-bind="::topicModel.windowCount"></td>
+    <td ng-bind="::topicModel.windowCount || 0"></td>
   </tr>
   <tr>
     <th>Last generated</th>
-    <td ng-bind-template="{{::Vipra.formatDateTime(topicModel.lastGenerated)}}"></td>
+    <td ng-bind-template="{{::topicModel.lastGeneratedString}}"></td>
   </tr>
   <tr>
     <th>Last indexed</th>
-    <td ng-bind-template="{{::Vipra.formatDateTime(topicModel.lastIndexed)}}"></td>
+    <td ng-bind-template="{{::topicModel.lastIndexedString}}"></td>
   </tr>
 </table>
\ No newline at end of file
diff --git a/vipra-ui/app/html/topics/index.html b/vipra-ui/app/html/topics/index.html
index 2ffc0069a225a8235f6d9f9cb03b0dd20edcb691..aeefbc3a78234797e3bc53e488992b00dce556e8 100644
--- a/vipra-ui/app/html/topics/index.html
+++ b/vipra-ui/app/html/topics/index.html
@@ -53,11 +53,6 @@
         </div>
       </div>
     </div>
-    <div class="row">
-      <div class="col-md-12 text-center">
-        <pagination total="topicsTotal" page="topicsIndexModels.page" limit="topicsIndexModels.limit" />
-      </div>
-    </div>
   </div>
 </div>
 <div ng-cloak ui-view></div>
diff --git a/vipra-ui/app/html/words/index.html b/vipra-ui/app/html/words/index.html
index 2f6c91c889e18a2318132f720cde5c6e3d5b924a..33be9812ed3ef443bcfb1c24155b68681a587d44 100644
--- a/vipra-ui/app/html/words/index.html
+++ b/vipra-ui/app/html/words/index.html
@@ -51,11 +51,6 @@
         </div>
       </div>
     </div>
-    <div class="row">
-      <div class="col-md-12 text-center">
-        <pagination total="wordsTotal" page="wordsIndexModels.page" limit="wordsIndexModels.limit" />
-      </div>
-    </div>
   </div>
 </div>
 <div ng-cloak ui-view></div>
diff --git a/vipra-ui/app/index.html b/vipra-ui/app/index.html
index 4cbc1dc855c6ebccc7d4ec9d4502d8b2aff8275b..cf072fde2ade401475f1f8eb470269d0f1b171e1 100644
--- a/vipra-ui/app/index.html
+++ b/vipra-ui/app/index.html
@@ -5,7 +5,7 @@
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <base href="/">
-  <title>Vipra</title>
+  <title ng-bind-template="VIPRA - {{rootModels.title}}"></title>
   <!-- stylesheets -->
   <link href="css/vendor.css" rel="stylesheet">
   <link href="css/app.css" rel="stylesheet">
@@ -85,10 +85,7 @@
         </div>
         <div class="modal-body">
           <ul class="list-group nomargin" ng-show="topicModels.length" ng-cloak>
-            <button type="button" class="list-group-item topic-model" ng-repeat="topicModel in topicModels" ng-click="changeTopicModel(topicModel)" ng-class="{'active selected-model':rootModels.topicModel.id===topicModel.id}" analytics-on analytics-event="Topic model" analytics-category="Topic model actions">
-              <span class="badge" bs-popover popover-title="{{::topicModel.name}}" popover-template="partials/topicmodel-popover.html" popover-placement="left">
-                <i class="fa fa-info"></i>
-              </span>
+            <button type="button" class="list-group-item topic-model" ng-repeat="topicModel in topicModels" ng-click="changeTopicModel(topicModel)" ng-class="{'active selected-model':rootModels.topicModel.id===topicModel.id}" analytics-on analytics-event="Topic model" analytics-category="Topic model actions" bs-popover popover-title="{{::topicModel.name}}" popover-template="partials/topicmodel-popover.html" popover-placement="bottom">
               <span class="badge badge-group">
                 <span class="badge-part" ng-if="!topicModel.lastGenerated" title="Model was never generated">Non-generated</span>
                 <span class="badge-part" ng-bind="::topicModel.articleCount" ng-show="topicModel.articleCount" ng-attr-title="{{::topicModel.articleCount + ' article(s)'}}" ng-cloak></span>
diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js
index 397e7b01c659fc95129d08b577fc3790f93e3ecb..b66a6f1d301689b4ccbbae6b9acd09efe4c7df4a 100644
--- a/vipra-ui/app/js/controllers.js
+++ b/vipra-ui/app/js/controllers.js
@@ -14,7 +14,8 @@
 
       $scope.rootModels = {
         topicModel: null,
-        search: null
+        search: null,
+        title: 'Home'
       };
 
       var prevTopicModelLoading = false;
@@ -48,6 +49,10 @@
         TopicModelFactory.query({
           fields: '_all'
         }, function(data) {
+          for(var i = 0; i < data.length; i++) {
+            data[i].lastGeneratedString = data[i].lastGenerated ? Vipra.formatDateTime(data[i].lastGenerated) : 'never';
+            data[i].lastIndexedString = data[i].lastIndexed ? Vipra.formatDateTime(data[i].lastIndexed) : 'never';
+          }
           $scope.topicModels = data;
         });
       };
@@ -67,6 +72,7 @@
       $scope.changeTopicModel = function(topicModel) {
         $scope.rootModels.topicModel = topicModel;
         $('#topicModelModal').modal('hide');
+        $('.popover').popover('hide');
         localStorage.tm = topicModel.id;
         if($state.current.name != 'index')
           $state.transitionTo('index');
@@ -94,7 +100,7 @@
             $state.transitionTo('index');
           });
         }
-      }
+      };
 
       $scope.goBack = function() {
         $window.history.back();
@@ -197,6 +203,7 @@
    */
   app.controller('IndexController', ['$scope', '$stateParams', '$location', '$timeout', 'ArticleFactory', 'TopicFactory', 'SearchFactory',
     function($scope, $stateParams, $location, $timeout, ArticleFactory, TopicFactory, SearchFactory) {
+      $scope.rootModels.title = 'Home';
 
       if (!$scope.rootModels.topicModel)
         $scope.chooseTopicModel();
@@ -268,6 +275,7 @@
    */
   app.controller('AboutController', ['$scope', 'InfoFactory',
     function($scope, InfoFactory) {
+      $scope.rootModels.title = 'About';
 
       InfoFactory.get(function(data) {
         $scope.info = data;
@@ -283,6 +291,7 @@
    */
   app.controller('NetworkController', ['$scope', '$state', '$stateParams', '$timeout', 'ArticleFactory', 'TopicFactory', 'WordFactory',
     function($scope, $state, $stateParams, $timeout, ArticleFactory, TopicFactory, WordFactory) {
+      $scope.rootModels.title = 'Network';
 
       var id = 0,
         ids = {},
@@ -663,10 +672,11 @@
 
   app.controller('ExplorerController', ['$scope', '$state', '$templateCache', '$timeout', 'TopicFactory',
     function($scope, $state, $templateCache, $timeout, TopicFactory) {
+      $scope.rootModels.title = 'Explorer';
 
       $scope.checkTopicModel('explorer', function() {
         TopicFactory.query({
-          fields: 'name,sequences,avgRelevance,varRelevance,risingRelevance,fallingRelevance,risingDecayRelevance,words',
+          fields: 'name,sequences,avgRelevance,varRelevance,risingRelevance,fallingRelevance,risingDecayRelevance,words,color',
           topicModel: $scope.rootModels.topicModel.id
         }, function(data) {
           $scope.topics = data;
@@ -675,7 +685,7 @@
           });
           for (var i = 0, t; i < $scope.topics.length; i++) {
             t = $scope.topics[i];
-            t.color = colors[i];
+            t.color = t.color || colors[i];
             avgMax = Math.max(t.avgRelevance, avgMax);
             varMax = Math.max(t.varRelevance, varMax);
             fallingMax = Math.max(t.fallingRelevance, fallingMax);
@@ -941,6 +951,7 @@
    */
   app.controller('ArticlesIndexController', ['$scope', '$state', 'ArticleFactory',
     function($scope, $state, ArticleFactory) {
+      $scope.rootModels.title = 'Articles';
 
       $scope.checkTopicModel('articles', function() {
         $scope.articlesIndexModels = {
@@ -988,6 +999,7 @@
    */
   app.controller('ArticlesShowController', ['$scope', '$state', '$stateParams', '$timeout', 'ArticleFactory',
     function($scope, $state, $stateParams, $timeout, ArticleFactory) {
+      $scope.rootModels.title = 'Article';
 
       $scope.articlesShowModels = {
         topicsSort: '-share',
@@ -1003,6 +1015,7 @@
         $scope.articleDate = Vipra.formatDate($scope.article.date);
         $scope.articleCreated = Vipra.formatDateTime($scope.article.created);
         $scope.articleModified = Vipra.formatDateTime($scope.article.modified);
+        $scope.rootModels.title = $scope.article.title;
 
         // calculate share from divergence
         if ($scope.article.similarArticles) {
@@ -1027,12 +1040,12 @@
             d = {
               name: topics[topicIndex].topic.name,
               y: topics[topicIndex].share,
-              color: colors[topicIndex],
+              color: topics[topicIndex].topic.color || colors[topicIndex],
               id: topics[topicIndex].topic.id
             };
 
             topicShareSeries.push(d);
-            $scope.article.topics[topicIndex].color = colors[topicIndex];
+            $scope.article.topics[topicIndex].color = $scope.article.topics[topicIndex].color || colors[topicIndex];
           }
         }
 
@@ -1118,6 +1131,7 @@
    */
   app.controller('TopicsIndexController', ['$scope', '$state', 'TopicFactory',
     function($scope, $state, TopicFactory) {
+      $scope.rootModels.title = 'Topics';
 
       $scope.checkTopicModel('topics', function() {
         $scope.topicsIndexModels = {
@@ -1165,6 +1179,7 @@
    */
   app.controller('TopicsShowController', ['$scope', '$state', '$stateParams', '$timeout', 'TopicFactory', 'SequenceFactory',
     function($scope, $state, $stateParams, $timeout, TopicFactory, SequenceFactory) {
+      $scope.rootModels.title = 'Topic';
 
       $scope.topicsShowModels = {
         relSeqstyle: 'absolute',
@@ -1180,6 +1195,7 @@
         $scope.topic = data;
         $scope.topicCreated = Vipra.formatDateTime($scope.topic.created);
         $scope.topicModified = Vipra.formatDateTime($scope.topic.modified);
+        $scope.rootModels.title = $scope.topic.name;
 
         // take topic model from topic
         if (!angular.isObject($scope.rootModels.topicModel))
@@ -1355,6 +1371,7 @@
 
   app.controller('EntitiesIndexController', ['$scope', '$state', 'EntityFactory',
     function($scope, $state, EntityFactory) {
+      $scope.rootModels.title = 'Entities';
 
       $scope.checkTopicModel('entities', function() {
         $scope.entitiesIndexModels = {
@@ -1399,6 +1416,7 @@
 
   app.controller('EntitiesShowController', ['$scope', '$state', '$stateParams', 'EntityFactory',
     function($scope, $state, $stateParams, EntityFactory) {
+      $scope.rootModels.title = 'Entity';
 
       $scope.checkTopicModel('entities.show', function() {
         EntityFactory.get({
@@ -1408,6 +1426,7 @@
           $scope.entity = data;
           $scope.entityCreated = Vipra.formatDateTime($scope.entity.created);
           $scope.entityModified = Vipra.formatDateTime($scope.entity.modified);
+          $scope.rootModels.title = $scope.entity.entity;
         });
       });
     }
@@ -1418,6 +1437,7 @@
 
       $scope.checkTopicModel('entities.show.articles', function() {
         $scope.entity = $stateParams.id;
+        $scope.rootModels.title = $scope.entity;
 
         $scope.entitiesArticlesModels = {
           sortkey: 'date',
@@ -1453,6 +1473,7 @@
 
   app.controller('WordsIndexController', ['$scope', '$state', 'WordFactory',
     function($scope, $state, WordFactory) {
+      $scope.rootModels.title = 'Words';
 
       $scope.checkTopicModel('words', function() {
         $scope.wordsIndexModels = {
@@ -1495,6 +1516,7 @@
 
   app.controller('WordsShowController', ['$scope', '$state', '$stateParams', 'WordFactory',
     function($scope, $state, $stateParams, WordFactory) {
+      $scope.rootModels.title = 'Word';
 
       $scope.checkTopicModel('words.show', function() {
         WordFactory.get({
@@ -1502,6 +1524,7 @@
           topicModel: $scope.rootModels.topicModel.id
         }, function(data) {
           $scope.word = data;
+          $scope.rootModels.title = $scope.word.word;
         });
       });
     }
@@ -1512,6 +1535,7 @@
 
       $scope.checkTopicModel('words.show.topics', function() {
         $scope.word = $stateParams.id;
+        $scope.rootModels.title = $scope.word;
 
         $scope.wordsTopicsModels = {
           sortkey: 'name',
@@ -1542,6 +1566,7 @@
 
       $scope.checkTopicModel('words.show.articles', function() {
         $scope.word = $stateParams.id;
+        $scope.rootModels.title = $scope.word;
 
         $scope.wordsArticlesModels = {
           sortkey: 'date',
@@ -1573,6 +1598,7 @@
 
   app.controller('SlidesController', ['$scope', function($scope) {
 
+      $scope.rootModels.title = 'Slides';
       $scope.current = 1;
 
       var folder = '//ftp.cochu.io/vipra/slides/';
@@ -1601,6 +1627,7 @@
     function($scope, $state, $stateParams) {
       $scope.code = $stateParams.code;
       $scope.title = Vipra.statusMsg($scope.code);
+      $scope.rootModels.title = 'Error: ' + $scope.code;
     }
   ]);
 
diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less
index abe1031957f48f889cb89ae46511ab5b525caa3f..92792d737fe6b9ae14f982d3430c805686d43d51 100644
--- a/vipra-ui/app/less/app.less
+++ b/vipra-ui/app/less/app.less
@@ -699,7 +699,7 @@ entity-menu {
   }
 
   .pagination {
-    margin: 10px 0;
+    margin: 5px 0;
   }
 }
 
@@ -964,6 +964,14 @@ entity-menu {
   position: relative;
 }
 
+.topic-badge + .topic-badge {
+  margin-left: 3px;
+}
+
+.text-outline {
+  text-shadow: 1px 1px 1px #888, 1px -1px 1px #888, -1px 1px 1px #888, -1px -1px 1px #888;
+}
+
 @keyframes spin {
   100% {
     -webkit-transform: rotateY(360deg);
diff --git a/vipra-util/src/main/java/de/vipra/util/ColorUtils.java b/vipra-util/src/main/java/de/vipra/util/ColorUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..534d7783e39b77b0a9eae741cd34151f6dd03f0f
--- /dev/null
+++ b/vipra-util/src/main/java/de/vipra/util/ColorUtils.java
@@ -0,0 +1,83 @@
+package de.vipra.util;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class ColorUtils {
+
+	/**
+	 * @see {@link #generateRandomColor(Long)}
+	 */
+	public static Color generateRandomColor() {
+		return generateRandomColor(null);
+	}
+
+	/**
+	 * Generate a random color, using an optional seed.
+	 * 
+	 * @param seed
+	 *            optional random seed, null for default seed
+	 * @return generated random color
+	 */
+	public static Color generateRandomColor(final Long seed) {
+		Random random = seed != null ? new Random(seed) : new Random();
+		int red = (random.nextInt(256) + 255) / 2;
+		int green = (random.nextInt(256) + 255) / 2;
+		int blue = (random.nextInt(256) + 255) / 2;
+		Color color = new Color(red, green, blue);
+		return color;
+	}
+
+	/**
+	 * @see {@link #generateRandomColors(int, Long)}
+	 */
+	public static List<Color> generateRandomColors(final int number) {
+		return generateRandomColors(number, null);
+	}
+
+	/**
+	 * Generate random colors, using an optional seed.
+	 * 
+	 * @param number
+	 *            how many colors to generate
+	 * @param seed
+	 *            optional random seed, null for default seed
+	 * @return generated random colors
+	 */
+	public static List<Color> generateRandomColors(final int number, final Long seed) {
+		Random random = seed != null ? new Random(seed) : new Random();
+		final List<Color> colors = new ArrayList<>(number);
+		for (int i = 0; i < number; i++) {
+			int red = (random.nextInt(256) + 255) / 2;
+			int green = (random.nextInt(256) + 255) / 2;
+			int blue = (random.nextInt(256) + 255) / 2;
+			colors.add(new Color(red, green, blue));
+		}
+		return colors;
+	}
+
+	/**
+	 * Turns a Color object into a hexadecimal string of format #ABCDEF.
+	 * 
+	 * @param color
+	 *            Color to transform into hex string
+	 * @return hex string
+	 */
+	public static String toHexString(final Color color) {
+		return String.format("#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue());
+	}
+
+	/**
+	 * Parses a hexadecimal string into a Color object.
+	 * 
+	 * @param hex
+	 *            hexadecimal string
+	 * @return Color object
+	 */
+	public static Color fromHexString(final String hex) {
+		return Color.decode(hex);
+	}
+
+}
diff --git a/vipra-util/src/main/java/de/vipra/util/model/Topic.java b/vipra-util/src/main/java/de/vipra/util/model/Topic.java
index ef9696e175b1e6f0f96924ad2abdd352f380221c..2f6e0b880e8faa63c2275b9d0784e6fbb331c4c1 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/Topic.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/Topic.java
@@ -25,17 +25,14 @@ public class Topic implements Model<ObjectId>, Serializable {
 
 	private Integer articlesCount;
 
+	private String color;
+
 	public Topic() {}
 
 	public Topic(final ObjectId id) {
 		this.id = id;
 	}
 
-	public Topic(final ObjectId id, final String name) {
-		this.id = id;
-		this.name = name;
-	}
-
 	@Override
 	public ObjectId getId() {
 		return id;
@@ -66,6 +63,14 @@ public class Topic implements Model<ObjectId>, Serializable {
 		this.articlesCount = articlesCount;
 	}
 
+	public String getColor() {
+		return color;
+	}
+
+	public void setColor(String color) {
+		this.color = color;
+	}
+
 	@Override
 	public boolean equals(final Object o) {
 		if (o == null)
diff --git a/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java b/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java
index d74dc24cec63e1fab2e5274619e7b7059c2596ad..2e61b2743564e0da1dfe499899c9f582a7bf682e 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java
@@ -66,6 +66,8 @@ public class TopicFull implements Model<ObjectId>, Serializable {
 
 	private Integer articlesCount;
 
+	private String color;
+
 	@QueryIgnore(multi = true)
 	private Date created;
 
@@ -174,6 +176,14 @@ public class TopicFull implements Model<ObjectId>, Serializable {
 		this.articlesCount = articlesCount;
 	}
 
+	public String getColor() {
+		return color;
+	}
+
+	public void setColor(String color) {
+		this.color = color;
+	}
+
 	public Date getCreated() {
 		return created;
 	}
diff --git a/vipra-util/src/main/java/de/vipra/util/model/TopicModelFull.java b/vipra-util/src/main/java/de/vipra/util/model/TopicModelFull.java
index 8ae0afae9919186bbcc67860659b0d4e1a638220..50c7e4b23494a3d0e0d8cb5274b3a6925cb9cbf3 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/TopicModelFull.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/TopicModelFull.java
@@ -39,6 +39,8 @@ public class TopicModelFull implements Model<ObjectId>, Comparable<TopicModelFul
 
 	private Date lastIndexed;
 
+	private Long colorSeed;
+
 	@Embedded
 	@QueryIgnore(multi = true)
 	private List<Window> windows;
@@ -146,6 +148,14 @@ public class TopicModelFull implements Model<ObjectId>, Comparable<TopicModelFul
 		this.lastIndexed = lastIndexed;
 	}
 
+	public Long getColorSeed() {
+		return colorSeed;
+	}
+
+	public void setColorSeed(Long colorSeed) {
+		this.colorSeed = colorSeed;
+	}
+
 	public List<Window> getWindows() {
 		return windows;
 	}