Slim frontend for API and serialization tests
diff --git a/lib/Korap/Example.pm b/lib/Korap/Example.pm
new file mode 100644
index 0000000..58fbd8b
--- /dev/null
+++ b/lib/Korap/Example.pm
@@ -0,0 +1,12 @@
+package Korap::Example;
+use Mojo::Base 'Mojolicious::Controller';
+
+# This action will render a template
+sub welcome {
+  my $self = shift;
+
+  # Render template "example/welcome.html.ep" with message
+  $self->render(msg => 'Welcome to the Mojolicious real-time web framework!');
+}
+
+1;
diff --git a/lib/Korap/Plugin/KorapSearch.pm b/lib/Korap/Plugin/KorapSearch.pm
new file mode 100644
index 0000000..0245e5d
--- /dev/null
+++ b/lib/Korap/Plugin/KorapSearch.pm
@@ -0,0 +1,136 @@
+package Korap::Plugin::KorapSearch;
+use Mojo::Base 'Mojolicious::Plugin';
+use Mojo::ByteStream 'b';
+
+sub register {
+  my ($plugin, $mojo, $param) = @_;
+  $param ||= {};
+
+  # Load parameter from Config file
+  if (my $config_param = $mojo->config('KorAP')) {
+    $param = { %$config_param, %$param };
+  };
+
+  my $api = $param->{api};
+
+  $mojo->helper(
+    search => sub {
+      my $c = shift;
+
+      # Todo: If there is no callback, return the hits object!
+      my $cb = pop if ref $_[-1] && ref $_[-1] eq 'CODE';
+
+      my %param = @_;
+
+      $c->stash(
+	'search.count' =>
+	  delete($param{count}) //
+	    scalar($c->param('count')) //
+	      $param->{count}
+	    );
+
+      $c->stash('search.startPage' =>
+		  (delete($param{startPage}) //
+		     scalar $c->param('p') //
+		       1
+		     ));
+
+      my $query = $param{query} // scalar $c->param('q');
+
+      return '' unless $query;
+
+      # Get stash information
+      my $count = $c->stash('search.count');
+      my $start_page = $c->stash('search.startPage');
+
+      foreach ($start_page, $count) {
+	$_ = undef if (!$_ || $_ !~ /^\d+$/);
+      };
+
+      my $url = Mojo::URL->new($api);
+      $url->path('resource/search');
+      #if ($c->stash('resource')) {
+      #$url->path($c->stash('resource'));
+      #if ($c->stash('cid')) {
+      #$url->path($c->stash('cid'));
+      #};
+      #};
+
+      my %query = (q => $query);
+      $query{ql} = scalar $c->param('ql') // 'poliqarp';
+      $query{startPage} = $start_page if $start_page;
+      $query{count} = $count if $count;
+
+      $query{lctx} = 'chars';
+      $query{lctxs} = 120;
+      $query{rctx} = 'chars';
+      $query{rctxs} = 120;
+
+      $url->query(\%query);
+
+      $c->stash('search.totalResults' => 0);
+      $c->stash('search.itemsPerPage' => $count);
+
+      my $ua = Mojo::UserAgent->new($url);
+      $c->app->log->debug($url->to_string);
+      my $tx = $ua->get($url);
+
+      if (my $res = $tx->success) {
+	my $json = $res->json;
+
+	my $b_hit = $json->{benchmarkHitCounter};
+	my $b_search = $json->{benchmarkSearchResults};
+
+	if ($b_hit =~ s/\s+(m)?s$//) {
+	  $b_hit = sprintf("%.2f", $b_hit) . ($1 ? $1 : '') . 's';
+	};
+	if ($b_search =~ s/\s+(m)?s$//) {
+	  $b_search = sprintf("%.2f", $b_search) . ($1 ? $1 : '') . 's';
+	};
+
+	$c->stash('search.bm.hit' => $b_hit);
+	$c->stash('search.bm.result' => $b_search);
+	$c->stash('search.itemsPerPage' => $json->{itemsPerPage});
+	if ($json->{error}) {
+	  $c->notify('error' => $json->{error});
+	};
+	$c->stash('search.query' => $json->{request}->{query});
+	$c->stash('search.hits' => $json->{matches});
+	$c->stash('search.totalResults' => $json->{totalResults});
+      }
+      else {
+	my $res = $tx->res;
+	$c->notify('error' =>  $res->code . ': ' . $res->message);
+      };
+      my $v = $cb->();
+      foreach (qw/hits totalResults bm.hit bm.result itemsPerPage error query/) {
+	delete $c->stash->{'search.' . $_};
+      };
+      return $v;
+    }
+  );
+
+  $mojo->helper(
+    search_hits => sub {
+      my $c = shift;
+      my $cb = pop;
+
+      if (!ref $cb || !(ref $cb eq 'CODE')) {
+	$mojo->log->error("search_hits expects a code block");
+	return '';
+      };
+
+      my $hits = delete $c->stash->{'search.hits'};
+      my $string;
+      foreach (@$hits) {
+	local $_ = $_;
+	$c->stash('search.hit' => $_);
+	$string .= $cb->($_);
+      };
+      delete $c->stash->{'lucy.hit'};
+      return b($string || '');
+    }
+  );
+};
+
+1;
diff --git a/lib/Korap/Plugin/Notifications.pm b/lib/Korap/Plugin/Notifications.pm
new file mode 100644
index 0000000..c07df47
--- /dev/null
+++ b/lib/Korap/Plugin/Notifications.pm
@@ -0,0 +1,136 @@
+package Korap::Plugin::Notifications;
+use Mojo::Base 'Mojolicious::Plugin';
+use Mojo::Util qw/camelize/;
+
+our $TYPE_RE = qr/^[-a-zA-Z_]+$/;
+
+# Todo: Support multiple notification center,
+#       so the notifications can be part of
+#       json as well as XML
+# Possibly use 'n!.a' for flash as this will be in the cookie!
+
+sub register {
+  my ($plugin, $mojo, $param) = @_;
+
+  $param ||= {};
+
+  # Load parameter from Config file
+  if (my $config_param = $mojo->config('Notifications')) {
+    $param = { %$config_param, %$param };
+  };
+
+  my $debug = $mojo->mode eq 'development' ? 1 : 0;
+
+  my $center = camelize(delete $param->{use} // __PACKAGE__ . '::HTML');
+
+  if (index($center,'::') < 0) {
+    $center = __PACKAGE__ . '::' . $center;
+  };
+
+  my $center_plugin = $mojo->plugins->load_plugin($center);
+  $center_plugin->register($mojo, $param);
+
+  # Add notifications
+  $mojo->helper(
+    notify => sub {
+      my $c = shift;
+      my $type = shift;
+
+      return if $type !~ $TYPE_RE || (!$debug && $type eq 'debug');
+
+      my $array;
+
+      # New notifications
+      if ($array = $c->stash('notify.array')) {
+	push @$array, [$type => @_];
+      }
+
+      # Notifications already set
+      else {
+	$c->stash->{'notify.array'} = [[$type => @_]];
+      };
+    }
+  );
+
+  # Make notifications flash in case of a redirect
+  $mojo->hook(
+    after_dispatch => sub {
+      my ($c) = @_;
+      if ($c->stash('notify.array') && $c->res->is_status_class(300)) {
+	$c->flash('notify.array' => delete $c->stash->{'notify.array'});
+      };
+    }
+  );
+
+  # Embed notification display
+  $mojo->helper(
+    notifications => sub {
+      my $c = shift;
+
+      my @notify_array;
+
+      # Get flash notifications
+      my $flash = $c->flash('notify.array');
+      if ($flash && ref $flash eq 'ARRAY') {
+
+	# Ensure that no harmful types are injected
+	push @notify_array, grep { $_->[0] =~ $TYPE_RE } @$flash;
+
+	$c->flash('notify.array' => undef);
+      };
+
+      # Get stash notifications
+      if ($c->stash('notify.array')) {
+	push @notify_array, @{ delete $c->stash->{'notify.array'} };
+      };
+
+      # Nothing to do
+      return '' unless @notify_array or @_;
+
+      # Forward messages to notification center
+      $center_plugin->notifications($c, \@notify_array, @_);
+    }
+  );
+};
+
+
+1;
+
+
+__END__
+
+=head2 notify
+
+  $c->notify(error => 'Something went wrong');
+  $c->notify(error => { timeout => 4000 } => 'Something went wrong');
+
+=head2 notifications
+
+  %= include_notification_center
+  %= include_notification_center qw/warn error success/
+
+The notification won't be included in case no notifications are
+in the queue and no parameters are passed.
+
+
+
+% if (flash('fine') || flash('alert') || stash('fine') || stash('alert')) {
+%= javascript '/js/humane.min.js'
+%= javascript begin
+  humane.baseCls = 'humane-libnotify';
+%   if (flash('fine') || stash('fine')) {
+  humane.log("<%= l(flash('fine') || stash('fine')) %>", {
+    timeout: 3000,
+    clickToClose: true,
+    addnCls: 'humane-libnotify-success'
+  });
+%   };
+%   if (flash('alert') || stash('alert')) {
+  humane.log("<%= l(flash('alert') || stash('alert')) %>", {
+    timeout: 3000,
+    clickToClose: true,
+    addnCls: 'humane-libnotify-error'
+  });
+%   };
+%= end
+% };
diff --git a/lib/Korap/Plugin/Notifications/HTML.pm b/lib/Korap/Plugin/Notifications/HTML.pm
new file mode 100644
index 0000000..24a0623
--- /dev/null
+++ b/lib/Korap/Plugin/Notifications/HTML.pm
@@ -0,0 +1,33 @@
+package Korap::Plugin::Notifications::HTML;
+use Mojo::Base 'Mojolicious::Plugin';
+use Mojo::Collection 'c';
+use Mojo::ByteStream 'b';
+use Mojo::Util qw/xml_escape/;
+
+# Nothing to register
+sub register {};
+
+
+# Notification method
+sub notifications {
+  my ($self, $c, $notify_array) = @_;
+
+  my $html = '';
+  foreach my $note (@$notify_array) {
+    my $type = shift @$note;
+    $html .= qq{<div class="notify-$type">};
+    $html .= c(@$note)->grep(sub { !ref $_ })
+                      ->map(sub { return xml_escape($_) })
+                      ->join('<br />')
+                      ->to_string;
+    $html .= "</div>\n";
+  };
+
+  return b $html;
+};
+
+
+1;
+
+
+__END__
diff --git a/lib/Korap/Plugin/Notifications/Humane.pm b/lib/Korap/Plugin/Notifications/Humane.pm
new file mode 100644
index 0000000..44810c3
--- /dev/null
+++ b/lib/Korap/Plugin/Notifications/Humane.pm
@@ -0,0 +1,76 @@
+package Korap::Plugin::Notifications::Humane;
+use Mojo::Base 'Mojolicious::Plugin';
+use Mojo::ByteStream 'b';
+use Mojo::JSON;
+use File::Spec;
+use File::Basename;
+
+has json => sub {
+  state $json = Mojo::JSON->new
+};
+
+has [qw/base_class base_timeout/];
+
+# Register plugin
+sub register {
+  my ($plugin, $mojo, $param) = @_;
+
+  # Set config
+  $plugin->base_class(   $param->{base_class}   // 'libnotify' );
+  $plugin->base_timeout( $param->{base_timeout} // 3000 );
+
+  # Add static path to JavaScript
+  push @{$mojo->static->paths},
+    File::Spec->catdir( File::Basename::dirname(__FILE__), 'Humane' );
+};
+
+
+# Notification method
+sub notifications {
+  my ($self, $c, $notify_array, @classes) = @_;
+
+  my $base_class = $self->base_class;
+
+  my %rule;
+  while ($classes[0] && index($classes[0], '-') == 0) {
+    $rule{shift @classes} = 1;
+  };
+
+  return unless @$notify_array || @classes;
+
+  my $js = '';
+  unless ($rule{-no_include}) {
+    $js .= $c->javascript('/humane.min.js');
+  };
+
+  unless ($rule{-no_css}) {
+    $js .= $c->stylesheet("/$base_class.css");;
+  };
+
+  # Start JavaScript snippet
+  $js .= qq{<script>\n} .
+    qq!var notify=humane.create({baseCls:'humane-$base_class',timeout:! .
+      $self->base_timeout . ",clickToClose:true});\n";
+
+  my ($log, %notify) = ('');
+
+  # Add notifications
+  foreach (@$notify_array) {
+    $notify{$_->[0]} = 1;
+    $log .= '.' . $_->[0] . '(' . $self->json->encode($_->[1])  . ')';
+  };
+  $log = "notify$log;\n" if $log;
+
+  # Ceate notification classes
+  foreach (sort(keys %notify), @classes) {
+    $js .= "notify.$_=notify.spawn({addnCls:'humane-$base_class-$_'});\n";
+  };
+
+  return b($js . $log . '</script>');
+};
+
+
+1;
+
+
+__END__
diff --git a/lib/Korap/Plugin/Notifications/Humane/bigbox.css b/lib/Korap/Plugin/Notifications/Humane/bigbox.css
new file mode 100644
index 0000000..8d369bb
--- /dev/null
+++ b/lib/Korap/Plugin/Notifications/Humane/bigbox.css
@@ -0,0 +1,123 @@
+html,
+body {
+  min-height: 100%;
+}
+.humane,
+.humane-bigbox {
+  position: fixed;
+  -moz-transition: all 0.3s ease-out;
+  -webkit-transition: all 0.3s ease-out;
+  -ms-transition: all 0.3s ease-out;
+  -o-transition: all 0.3s ease-out;
+  transition: all 0.3s ease-out;
+  z-index: 100000;
+  filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
+}
+.humane,
+.humane-bigbox {
+  font-family: Ubuntu, Verdana, sans-serif;
+  line-height: 40px;
+  font-size: 35px;
+  top: 25%;
+  left: 25%;
+  opacity: 0;
+  width: 50%;
+  min-height: 40px;
+  padding: 30px;
+  text-align: center;
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADICAYAAAAp8ov1AAAABmJLR0QA/wD/AP+gvaeTAAAAc0lEQVQokb2RQQ6EMAwDx/7/n80BtIEC3RYhLlXrVLGTAYiBWBIGtkPSP01SfreTVoV5re9Rcee1scwDk9NurbR62sZJcpzy9O+2X5KsXabyPaQFYNuvkqkRviDTp9Vs8opC0TpkHvJtVjeReW/5kEyX1gKeLEKE9peeWAAAAABJRU5ErkJggg==');
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #000), color-stop(1, rgba(0,0,0,0.9))) no-repeat;
+  background: -moz-linear-gradient(top, #000 0%, rgba(0,0,0,0.9) 100%) no-repeat;
+  background: -webkit-linear-gradient(top, #000 0%, rgba(0,0,0,0.9) 100%) no-repeat;
+  background: -ms-linear-gradient(top, #000 0%, rgba(0,0,0,0.9) 100%) no-repeat;
+  background: -o-linear-gradient(top, #000 0%, rgba(0,0,0,0.9) 100%) no-repeat;
+  background: linear-gradient(top, #000 0%, rgba(0,0,0,0.9) 100%) no-repeat;
+  *background-color: #000;
+  color: #fff;
+  -webkit-border-radius: 15px;
+  border-radius: 15px;
+  text-shadow: 0 -1px 1px #ddd;
+  -webkit-box-shadow: 0 15px 15px -15px #000;
+  box-shadow: 0 15px 15px -15px #000;
+  -moz-transform: scale(0.1);
+  -webkit-transform: scale(0.1);
+  -ms-transform: scale(0.1);
+  -o-transform: scale(0.1);
+  transform: scale(0.1);
+}
+.humane p,
+.humane-bigbox p,
+.humane ul,
+.humane-bigbox ul {
+  margin: 0;
+  padding: 0;
+}
+.humane ul,
+.humane-bigbox ul {
+  list-style: none;
+}
+.humane.humane-bigbox-info,
+.humane-bigbox.humane-bigbox-info {
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADICAYAAAAp8ov1AAAABmJLR0QA/wD/AP+gvaeTAAAAQElEQVQokWNgYEj5z8TAwPCfiYGBgQGVIEKMTG2DTYwRVez/IHIaNcUGyBnYgpORel6gpvFEJhBqpxIaG8/AAADsKDq/HhYQ2AAAAABJRU5ErkJggg==');
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #000064), color-stop(1, rgba(0,0,100,0.9))) no-repeat;
+  background: -moz-linear-gradient(top, #000064 0%, rgba(0,0,100,0.9) 100%) no-repeat;
+  background: -webkit-linear-gradient(top, #000064 0%, rgba(0,0,100,0.9) 100%) no-repeat;
+  background: -ms-linear-gradient(top, #000064 0%, rgba(0,0,100,0.9) 100%) no-repeat;
+  background: -o-linear-gradient(top, #000064 0%, rgba(0,0,100,0.9) 100%) no-repeat;
+  background: linear-gradient(top, #000064 0%, rgba(0,0,100,0.9) 100%) no-repeat;
+  *background-color: #030;
+}
+.humane.humane-bigbox-success,
+.humane-bigbox.humane-bigbox-success {
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADICAYAAAAp8ov1AAAABmJLR0QA/wD/AP+gvaeTAAAAPklEQVQokWNgSGH4z8TAACEYUAkixMjUNsjEGFHF/g8ip1FVbGCcgS04GannBaoaT1wCwWkvmXbQ2HgGBgYA8Yw6v+m4Kh8AAAAASUVORK5CYII=');
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #006400), color-stop(1, rgba(0,100,0,0.9))) no-repeat;
+  background: -moz-linear-gradient(top, #006400 0%, rgba(0,100,0,0.9) 100%) no-repeat;
+  background: -webkit-linear-gradient(top, #006400 0%, rgba(0,100,0,0.9) 100%) no-repeat;
+  background: -ms-linear-gradient(top, #006400 0%, rgba(0,100,0,0.9) 100%) no-repeat;
+  background: -o-linear-gradient(top, #006400 0%, rgba(0,100,0,0.9) 100%) no-repeat;
+  background: linear-gradient(top, #006400 0%, rgba(0,100,0,0.9) 100%) no-repeat;
+  *background-color: #030;
+}
+.humane.humane-bigbox-error,
+.humane-bigbox.humane-bigbox-error {
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADICAYAAAAp8ov1AAAABmJLR0QA/wD/AP+gvaeTAAAAPklEQVQokWNIYWD4z8QAJRhQCSLEyNQ2uMQYUcX+DyKnUVdsQJyBLTgZqecF6hpPVALBaS+ZdtDYeAYGBgYA9vA6v4OR3MkAAAAASUVORK5CYII=');
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #640000), color-stop(1, rgba(100,0,0,0.9))) no-repeat;
+  background: -moz-linear-gradient(top, #640000 0%, rgba(100,0,0,0.9) 100%) no-repeat;
+  background: -webkit-linear-gradient(top, #640000 0%, rgba(100,0,0,0.9) 100%) no-repeat;
+  background: -ms-linear-gradient(top, #640000 0%, rgba(100,0,0,0.9) 100%) no-repeat;
+  background: -o-linear-gradient(top, #640000 0%, rgba(100,0,0,0.9) 100%) no-repeat;
+  background: linear-gradient(top, #640000 0%, rgba(100,0,0,0.9) 100%) no-repeat;
+  *background-color: #300;
+}
+.humane.humane-animate,
+.humane-bigbox.humane-bigbox-animate {
+  opacity: 1;
+  -moz-transform: scale(1);
+  -webkit-transform: scale(1);
+  -ms-transform: scale(1);
+  -o-transform: scale(1);
+  transform: scale(1);
+}
+.humane.humane-animate:hover,
+.humane-bigbox.humane-bigbox-animate:hover {
+  opacity: 0.6;
+  -moz-transform: scale(0.8);
+  -webkit-transform: scale(0.8);
+  -ms-transform: scale(0.8);
+  -o-transform: scale(0.8);
+  transform: scale(0.8);
+}
+.humane.humane-js-animate,
+.humane-bigbox.humane-bigbox-js-animate {
+  opacity: 1;
+  -moz-transform: scale(1);
+  -webkit-transform: scale(1);
+  -ms-transform: scale(1);
+  -o-transform: scale(1);
+  transform: scale(1);
+}
+.humane.humane-js-animate:hover,
+.humane-bigbox.humane-bigbox-js-animate:hover {
+  opacity: 0.6;
+  filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=60);
+}
diff --git a/lib/Korap/Plugin/Notifications/Humane/boldlight.css b/lib/Korap/Plugin/Notifications/Humane/boldlight.css
new file mode 100644
index 0000000..d338ed2
--- /dev/null
+++ b/lib/Korap/Plugin/Notifications/Humane/boldlight.css
@@ -0,0 +1,122 @@
+html,
+body {
+  min-height: 100%;
+}
+.humane,
+.humane-boldlight {
+  position: fixed;
+  -moz-transition: all 0.3s ease-out;
+  -webkit-transition: all 0.3s ease-out;
+  -ms-transition: all 0.3s ease-out;
+  -o-transition: all 0.3s ease-out;
+  transition: all 0.3s ease-out;
+  z-index: 100000;
+  filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
+}
+.humane,
+.humane-boldlight {
+  font-family: Ubuntu, Verdana, sans-serif;
+  font-size: 25px;
+  letter-spacing: -1px;
+  top: 25%;
+  left: 25%;
+  opacity: 0;
+  width: 50%;
+  color: #000;
+  padding: 10px;
+  text-align: center;
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAACWCAYAAAAfduJyAAAABmJLR0QA/wD/AP+gvaeTAAAAPElEQVQokWP4////Gab///8zQAgGBgYo8e/fP2QxSpSgydJNCYJLRSVoPqeOkgEIYop9TrGbSfcWpW4GAFeF/7lb/oWBAAAAAElFTkSuQmCC');
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255,255,255,0.8)), color-stop(1, rgba(150,150,150,0.8))) no-repeat;
+  background: -moz-linear-gradient(top, rgba(255,255,255,0.8) 0%, rgba(150,150,150,0.8) 100%) no-repeat;
+  background: -webkit-linear-gradient(top, rgba(255,255,255,0.8) 0%, rgba(150,150,150,0.8) 100%) no-repeat;
+  background: -ms-linear-gradient(top, rgba(255,255,255,0.8) 0%, rgba(150,150,150,0.8) 100%) no-repeat;
+  background: -o-linear-gradient(top, rgba(255,255,255,0.8) 0%, rgba(150,150,150,0.8) 100%) no-repeat;
+  background: linear-gradient(top, rgba(255,255,255,0.8) 0%, rgba(150,150,150,0.8) 100%) no-repeat;
+  *background-color: #fff;
+  -webkit-border-radius: 15px;
+  border-radius: 15px;
+  text-shadow: 0 -1px 1px rgba(221,221,221,0.4);
+  -webkit-box-shadow: 0 4px 4px -4px #eee;
+  box-shadow: 0 4px 4px -4px #eee;
+  -moz-transform: scale(1.1);
+  -webkit-transform: scale(1.1);
+  -ms-transform: scale(1.1);
+  -o-transform: scale(1.1);
+  transform: scale(1.1);
+}
+.humane p,
+.humane-boldlight p,
+.humane ul,
+.humane-boldlight ul {
+  margin: 0;
+  padding: 0;
+}
+.humane ul,
+.humane-boldlight ul {
+  list-style: none;
+}
+.humane.humane-boldlight-info,
+.humane-boldlight.humane-boldlight-info {
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADICAYAAAAp8ov1AAAABmJLR0QA/wD/AP+gvaeTAAAAR0lEQVQokWNISfn/n4mBgeE/EwMDAwMqQYQYmdoGlxgjI4rY//+Dx2nUFRsQZ2ALTrQQp8QL1DWeqASC014y7aCx8QwMDAwA1aZBIulmpvwAAAAASUVORK5CYII=');
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #6464ff), color-stop(1, rgba(100,100,255,0.8))) no-repeat;
+  background: -moz-linear-gradient(top, #6464ff 0%, rgba(100,100,255,0.8) 100%) no-repeat;
+  background: -webkit-linear-gradient(top, #6464ff 0%, rgba(100,100,255,0.8) 100%) no-repeat;
+  background: -ms-linear-gradient(top, #6464ff 0%, rgba(100,100,255,0.8) 100%) no-repeat;
+  background: -o-linear-gradient(top, #6464ff 0%, rgba(100,100,255,0.8) 100%) no-repeat;
+  background: linear-gradient(top, #6464ff 0%, rgba(100,100,255,0.8) 100%) no-repeat;
+  *background-color: #6464ff;
+}
+.humane.humane-boldlight-success,
+.humane-boldlight.humane-boldlight-success {
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADICAYAAAAp8ov1AAAABmJLR0QA/wD/AP+gvaeTAAAAGklEQVQokWNI+Z9yhomBgYFhlBglRonhSgAAFX0EItSd0k8AAAAASUVORK5CYII=');
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #64ff64), color-stop(1, rgba(100,255,100,0.8))) no-repeat;
+  background: -moz-linear-gradient(top, #64ff64 0%, rgba(100,255,100,0.8) 100%) no-repeat;
+  background: -webkit-linear-gradient(top, #64ff64 0%, rgba(100,255,100,0.8) 100%) no-repeat;
+  background: -ms-linear-gradient(top, #64ff64 0%, rgba(100,255,100,0.8) 100%) no-repeat;
+  background: -o-linear-gradient(top, #64ff64 0%, rgba(100,255,100,0.8) 100%) no-repeat;
+  background: linear-gradient(top, #64ff64 0%, rgba(100,255,100,0.8) 100%) no-repeat;
+  *background-color: #64ff64;
+}
+.humane.humane-boldlight-error,
+.humane-boldlight.humane-boldlight-error {
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADICAYAAAAp8ov1AAAABmJLR0QA/wD/AP+gvaeTAAAAGklEQVQokWP4n5JyhomBgYFhlBglRonhSgAAFhgEIhjGqQkAAAAASUVORK5CYII=');
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #ff6464), color-stop(1, rgba(255,100,100,0.8))) no-repeat;
+  background: -moz-linear-gradient(top, #ff6464 0%, rgba(255,100,100,0.8) 100%) no-repeat;
+  background: -webkit-linear-gradient(top, #ff6464 0%, rgba(255,100,100,0.8) 100%) no-repeat;
+  background: -ms-linear-gradient(top, #ff6464 0%, rgba(255,100,100,0.8) 100%) no-repeat;
+  background: -o-linear-gradient(top, #ff6464 0%, rgba(255,100,100,0.8) 100%) no-repeat;
+  background: linear-gradient(top, #ff6464 0%, rgba(255,100,100,0.8) 100%) no-repeat;
+  *background-color: #ff6464;
+}
+.humane.humane-animate,
+.humane-boldlight.humane-boldlight-animate {
+  opacity: 1;
+  -moz-transform: scale(1);
+  -webkit-transform: scale(1);
+  -ms-transform: scale(1);
+  -o-transform: scale(1);
+  transform: scale(1);
+}
+.humane.humane-animate:hover,
+.humane-boldlight.humane-boldlight-animate:hover {
+  opacity: 0.4;
+  -moz-transform: scale(1.8);
+  -webkit-transform: scale(1.8);
+  -ms-transform: scale(1.8);
+  -o-transform: scale(1.8);
+  transform: scale(1.8);
+}
+.humane.humane-animate,
+.humane-boldlight.humane-boldlight-js-animate {
+  opacity: 1;
+  -moz-transform: scale(1);
+  -webkit-transform: scale(1);
+  -ms-transform: scale(1);
+  -o-transform: scale(1);
+  transform: scale(1);
+}
+.humane.humane-animate:hover,
+.humane-boldlight.humane-boldlight-js-animate:hover {
+  opacity: 0.4;
+  filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
+}
diff --git a/lib/Korap/Plugin/Notifications/Humane/flatty.css b/lib/Korap/Plugin/Notifications/Humane/flatty.css
new file mode 100644
index 0000000..74d94f8
--- /dev/null
+++ b/lib/Korap/Plugin/Notifications/Humane/flatty.css
@@ -0,0 +1,94 @@
+html,
+body {
+  min-height: 100%;
+}
+.humane,
+.humane-flatty {
+  position: fixed;
+  -moz-transition: all 0.4s ease-in-out;
+  -webkit-transition: all 0.4s ease-in-out;
+  -ms-transition: all 0.4s ease-in-out;
+  -o-transition: all 0.4s ease-in-out;
+  transition: all 0.4s ease-in-out;
+  z-index: 100000;
+  filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
+}
+.humane,
+.humane-flatty {
+  font-family: Helvetica Neue, Helvetica, san-serif;
+  font-size: 16px;
+  top: 0;
+  left: 30%;
+  opacity: 0;
+  width: 40%;
+  color: #444;
+  padding: 10px;
+  text-align: center;
+  background-color: #fff;
+  -webkit-border-bottom-right-radius: 3px;
+  -webkit-border-bottom-left-radius: 3px;
+  -moz-border-radius-bottomright: 3px;
+  -moz-border-radius-bottomleft: 3px;
+  border-bottom-right-radius: 3px;
+  border-bottom-left-radius: 3px;
+  -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.5);
+  box-shadow: 0 1px 2px rgba(0,0,0,0.5);
+  -moz-transform: translateY(-100px);
+  -webkit-transform: translateY(-100px);
+  -ms-transform: translateY(-100px);
+  -o-transform: translateY(-100px);
+  transform: translateY(-100px);
+}
+.humane p,
+.humane-flatty p,
+.humane ul,
+.humane-flatty ul {
+  margin: 0;
+  padding: 0;
+}
+.humane ul,
+.humane-flatty ul {
+  list-style: none;
+}
+.humane.humane-flatty-info,
+.humane-flatty.humane-flatty-info {
+  background-color: #3498db;
+  color: #FFF;
+}
+.humane.humane-flatty-success,
+.humane-flatty.humane-flatty-success {
+  background-color: #18bc9c;
+  color: #FFF;
+}
+.humane.humane-flatty-error,
+.humane-flatty.humane-flatty-error {
+  background-color: #e74c3c;
+  color: #FFF;
+}
+.humane-animate,
+.humane-flatty.humane-flatty-animate {
+  opacity: 1;
+  -moz-transform: translateY(0);
+  -webkit-transform: translateY(0);
+  -ms-transform: translateY(0);
+  -o-transform: translateY(0);
+  transform: translateY(0);
+}
+.humane-animate:hover,
+.humane-flatty.humane-flatty-animate:hover {
+  opacity: 0.7;
+}
+.humane-js-animate,
+.humane-flatty.humane-flatty-js-animate {
+  opacity: 1;
+  -moz-transform: translateY(0);
+  -webkit-transform: translateY(0);
+  -ms-transform: translateY(0);
+  -o-transform: translateY(0);
+  transform: translateY(0);
+}
+.humane-js-animate:hover,
+.humane-flatty.humane-flatty-js-animate:hover {
+  opacity: 0.7;
+  filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70);
+}
diff --git a/lib/Korap/Plugin/Notifications/Humane/humane.min.js b/lib/Korap/Plugin/Notifications/Humane/humane.min.js
new file mode 100644
index 0000000..59a7453
--- /dev/null
+++ b/lib/Korap/Plugin/Notifications/Humane/humane.min.js
@@ -0,0 +1 @@
+!function(t,e,i){"undefined"!=typeof module?module.exports=i(t,e):"function"==typeof define&&"object"==typeof define.amd?define(i):e[t]=i(t,e)}("humane",this,function(){var t=window,e=document,i={on:function(e,i,n){"addEventListener"in t?e.addEventListener(i,n,!1):e.attachEvent("on"+i,n)},off:function(e,i,n){"removeEventListener"in t?e.removeEventListener(i,n,!1):e.detachEvent("on"+i,n)},bind:function(t,e){return function(){t.apply(e,arguments)}},isArray:Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)},config:function(t,e){return null!=t?t:e},transSupport:!1,useFilter:/msie [678]/i.test(navigator.userAgent),_checkTransition:function(){var t=e.createElement("div"),i={webkit:"webkit",Moz:"",O:"o",ms:"MS"};for(var n in i)n+"Transition"in t.style&&(this.vendorPrefix=i[n],this.transSupport=!0)}};i._checkTransition();var n=function(e){e||(e={}),this.queue=[],this.baseCls=e.baseCls||"humane",this.addnCls=e.addnCls||"",this.timeout="timeout"in e?e.timeout:2500,this.waitForMove=e.waitForMove||!1,this.clickToClose=e.clickToClose||!1,this.timeoutAfterMove=e.timeoutAfterMove||!1,this.container=e.container;try{this._setupEl()}catch(n){i.on(t,"load",i.bind(this._setupEl,this))}};return n.prototype={constructor:n,_setupEl:function(){var t=e.createElement("div");if(t.style.display="none",!this.container){if(!e.body)throw"document.body is null";this.container=e.body}this.container.appendChild(t),this.el=t,this.removeEvent=i.bind(function(){this.timeoutAfterMove?setTimeout(i.bind(this.remove,this),this.timeout):this.remove()},this),this.transEvent=i.bind(this._afterAnimation,this),this._run()},_afterTimeout:function(){i.config(this.currentMsg.waitForMove,this.waitForMove)?this.removeEventsSet||(i.on(e.body,"mousemove",this.removeEvent),i.on(e.body,"click",this.removeEvent),i.on(e.body,"keypress",this.removeEvent),i.on(e.body,"touchstart",this.removeEvent),this.removeEventsSet=!0):this.remove()},_run:function(){if(!this._animating&&this.queue.length&&this.el){this._animating=!0,this.currentTimer&&(clearTimeout(this.currentTimer),this.currentTimer=null);var t=this.queue.shift(),e=i.config(t.clickToClose,this.clickToClose);e&&(i.on(this.el,"click",this.removeEvent),i.on(this.el,"touchstart",this.removeEvent));var n=i.config(t.timeout,this.timeout);n>0&&(this.currentTimer=setTimeout(i.bind(this._afterTimeout,this),n)),i.isArray(t.html)&&(t.html="<ul><li>"+t.html.join("<li>")+"</ul>"),this.el.innerHTML=t.html,this.currentMsg=t,this.el.className=this.baseCls,i.transSupport?(this.el.style.display="block",setTimeout(i.bind(this._showMsg,this),50)):this._showMsg()}},_setOpacity:function(t){if(i.useFilter)try{this.el.filters.item("DXImageTransform.Microsoft.Alpha").Opacity=100*t}catch(e){}else this.el.style.opacity=String(t)},_showMsg:function(){var t=i.config(this.currentMsg.addnCls,this.addnCls);if(i.transSupport)this.el.className=this.baseCls+" "+t+" "+this.baseCls+"-animate";else{var e=0;this.el.className=this.baseCls+" "+t+" "+this.baseCls+"-js-animate",this._setOpacity(0),this.el.style.display="block";var n=this,s=setInterval(function(){1>e?(e+=.1,e>1&&(e=1),n._setOpacity(e)):clearInterval(s)},30)}},_hideMsg:function(){var t=i.config(this.currentMsg.addnCls,this.addnCls);if(i.transSupport)this.el.className=this.baseCls+" "+t,i.on(this.el,i.vendorPrefix?i.vendorPrefix+"TransitionEnd":"transitionend",this.transEvent);else var e=1,n=this,s=setInterval(function(){e>0?(e-=.1,0>e&&(e=0),n._setOpacity(e)):(n.el.className=n.baseCls+" "+t,clearInterval(s),n._afterAnimation())},30)},_afterAnimation:function(){i.transSupport&&i.off(this.el,i.vendorPrefix?i.vendorPrefix+"TransitionEnd":"transitionend",this.transEvent),this.currentMsg.cb&&this.currentMsg.cb(),this.el.style.display="none",this._animating=!1,this._run()},remove:function(t){var n="function"==typeof t?t:null;i.off(e.body,"mousemove",this.removeEvent),i.off(e.body,"click",this.removeEvent),i.off(e.body,"keypress",this.removeEvent),i.off(e.body,"touchstart",this.removeEvent),i.off(this.el,"click",this.removeEvent),i.off(this.el,"touchstart",this.removeEvent),this.removeEventsSet=!1,n&&this.currentMsg&&(this.currentMsg.cb=n),this._animating?this._hideMsg():n&&n()},log:function(t,e,i,n){var s={};if(n)for(var o in n)s[o]=n[o];if("function"==typeof e)i=e;else if(e)for(var o in e)s[o]=e[o];return s.html=t,i&&(s.cb=i),this.queue.push(s),this._run(),this},spawn:function(t){var e=this;return function(i,n,s){return e.log.call(e,i,n,s,t),e}},create:function(t){return new n(t)}},new n});
\ No newline at end of file
diff --git a/lib/Korap/Plugin/Notifications/Humane/jackedup.css b/lib/Korap/Plugin/Notifications/Humane/jackedup.css
new file mode 100644
index 0000000..cbc6aa9
--- /dev/null
+++ b/lib/Korap/Plugin/Notifications/Humane/jackedup.css
@@ -0,0 +1,123 @@
+html,
+body {
+  min-height: 100%;
+}
+.humane,
+.humane-jackedup {
+  position: fixed;
+  -moz-transition: all 0.6s ease-in-out;
+  -webkit-transition: all 0.6s ease-in-out;
+  -ms-transition: all 0.6s ease-in-out;
+  -o-transition: all 0.6s ease-in-out;
+  transition: all 0.6s ease-in-out;
+  z-index: 100000;
+  filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
+}
+.humane,
+.humane-jackedup {
+  font-family: Helvetica Neue, Helvetica, san-serif;
+  font-size: 18px;
+  letter-spacing: -1px;
+  top: 20px;
+  left: 30%;
+  opacity: 0;
+  width: 40%;
+  color: #333;
+  padding: 10px;
+  text-align: center;
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAACWCAYAAAAfduJyAAAABmJLR0QA/wD/AP+gvaeTAAAAIklEQVQokWNgYGCQZGJgYGDARTDSQnboGDqsnDt0DKWNLAAkiQFdC+vZNQAAAABJRU5ErkJggg==');
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(0,0,0,0.1)), color-stop(1, rgba(0,0,0,0.2))) no-repeat;
+  background: -moz-linear-gradient(top, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.2) 100%) no-repeat;
+  background: -webkit-linear-gradient(top, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.2) 100%) no-repeat;
+  background: -ms-linear-gradient(top, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.2) 100%) no-repeat;
+  background: -o-linear-gradient(top, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.2) 100%) no-repeat;
+  background: linear-gradient(top, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.2) 100%) no-repeat;
+  background-color: #fff;
+  -webkit-border-radius: 3px;
+  border-radius: 3px;
+  text-shadow: 0 1px 1px rgba(255,255,255,0.8);
+  -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.5);
+  box-shadow: 0 1px 2px rgba(0,0,0,0.5);
+  -moz-transform: translateY(-100px);
+  -webkit-transform: translateY(-100px);
+  -ms-transform: translateY(-100px);
+  -o-transform: translateY(-100px);
+  transform: translateY(-100px);
+}
+.humane p,
+.humane-jackedup p,
+.humane ul,
+.humane-jackedup ul {
+  margin: 0;
+  padding: 0;
+}
+.humane ul,
+.humane-jackedup ul {
+  list-style: none;
+}
+.humane.humane-jackedup-info,
+.humane-jackedup.humane-jackedup-info {
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADICAYAAAAp8ov1AAAABmJLR0QA/wD/AP+gvaeTAAAAR0lEQVQokWNISfn/n4mBgeE/EwMDAwMqQYQYmdoGlxgjI4rY//+Dx2nUFRsQZ2ALTrQQp8QL1DWeqASC014y7aCx8QwMDAwA1aZBIulmpvwAAAAASUVORK5CYII=');
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(0,0,0,0.7)), color-stop(1, rgba(0,0,0,0.85))) no-repeat;
+  background: -moz-linear-gradient(top, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.85) 100%) no-repeat;
+  background: -webkit-linear-gradient(top, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.85) 100%) no-repeat;
+  background: -ms-linear-gradient(top, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.85) 100%) no-repeat;
+  background: -o-linear-gradient(top, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.85) 100%) no-repeat;
+  background: linear-gradient(top, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.85) 100%) no-repeat;
+  background-color: #fff;
+  color: #fff;
+  text-shadow: 0 -1px 1px rgba(0,0,0,0.35);
+}
+.humane.humane-jackedup-success,
+.humane-jackedup.humane-jackedup-success {
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADICAYAAAAp8ov1AAAABmJLR0QA/wD/AP+gvaeTAAAASElEQVQokc2SMQ4AIAgDD9/K/79QVzWaENTownAJbWnA5SqACkA/Aiy59hczrGVC30Q7y57EmNU5NL5zwln50IMsfZMel+UBKtFBQSLWM9wLAAAAAElFTkSuQmCC');
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #62c462), color-stop(1, #57a957)) no-repeat;
+  background: -moz-linear-gradient(top, #62c462 0%, #57a957 100%) no-repeat;
+  background: -webkit-linear-gradient(top, #62c462 0%, #57a957 100%) no-repeat;
+  background: -ms-linear-gradient(top, #62c462 0%, #57a957 100%) no-repeat;
+  background: -o-linear-gradient(top, #62c462 0%, #57a957 100%) no-repeat;
+  background: linear-gradient(top, #62c462 0%, #57a957 100%) no-repeat;
+  background-color: #64ff64;
+  color: #fff;
+  text-shadow: 0 -1px 1px rgba(0,0,0,0.35);
+}
+.humane.humane-jackedup-error,
+.humane-jackedup.humane-jackedup-error {
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADICAIAAACmkByiAAAABmJLR0QA/wD/AP+gvaeTAAAAf0lEQVQokY2TOQ7AIAwER/5mivy/yRc2RQDhA0jhghFYO5bhuS+TZMAoIUMEhhH4loGhfu71cenM3DutWMsaeGKjv3zO5N17KLPJ0+fQD8cpv5uVLPo4vnX0PpXj0nuaaeVzdmw+yXG1O96n2p3kozB757Ni1Z5UPsU9SP8AeAG1kHXE+7RlPAAAAABJRU5ErkJggg==');
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #ee5f5b), color-stop(1, #c43c35)) no-repeat;
+  background: -moz-linear-gradient(top, #ee5f5b 0%, #c43c35 100%) no-repeat;
+  background: -webkit-linear-gradient(top, #ee5f5b 0%, #c43c35 100%) no-repeat;
+  background: -ms-linear-gradient(top, #ee5f5b 0%, #c43c35 100%) no-repeat;
+  background: -o-linear-gradient(top, #ee5f5b 0%, #c43c35 100%) no-repeat;
+  background: linear-gradient(top, #ee5f5b 0%, #c43c35 100%) no-repeat;
+  background-color: #ee5f5b;
+  color: #fff;
+  text-shadow: 0 -1px 1px rgba(0,0,0,0.35);
+}
+.humane-animate,
+.humane-jackedup.humane-jackedup-animate {
+  opacity: 1;
+  -moz-transform: translateY(0);
+  -webkit-transform: translateY(0);
+  -ms-transform: translateY(0);
+  -o-transform: translateY(0);
+  transform: translateY(0);
+}
+.humane-animate:hover,
+.humane-jackedup.humane-jackedup-animate:hover {
+  opacity: 0.7;
+}
+.humane-js-animate,
+.humane-jackedup.humane-jackedup-js-animate {
+  opacity: 1;
+  -moz-transform: translateY(0);
+  -webkit-transform: translateY(0);
+  -ms-transform: translateY(0);
+  -o-transform: translateY(0);
+  transform: translateY(0);
+}
+.humane-js-animate:hover,
+.humane-jackedup.humane-jackedup-js-animate:hover {
+  opacity: 0.7;
+  filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70);
+}
diff --git a/lib/Korap/Plugin/Notifications/Humane/libnotify.css b/lib/Korap/Plugin/Notifications/Humane/libnotify.css
new file mode 100644
index 0000000..7e70c1c
--- /dev/null
+++ b/lib/Korap/Plugin/Notifications/Humane/libnotify.css
@@ -0,0 +1,115 @@
+html,
+body {
+  min-height: 100%;
+}
+.humane,
+.humane-libnotify {
+  position: fixed;
+  -moz-transition: all 0.3s ease-out;
+  -webkit-transition: all 0.3s ease-out;
+  -ms-transition: all 0.3s ease-out;
+  -o-transition: all 0.3s ease-out;
+  transition: all 0.3s ease-out;
+  z-index: 100000;
+  filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
+}
+.humane,
+.humane-libnotify {
+  font-family: Ubuntu, Arial, sans-serif;
+  text-align: center;
+  font-size: 15px;
+  top: 10px;
+  right: 10px;
+  opacity: 0;
+  width: 150px;
+  color: #fff;
+  padding: 10px;
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAABQCAYAAADYxx/bAAAABmJLR0QA/wD/AP+gvaeTAAAANElEQVQYlWNgYGB4ysTAwMDAxMjICCUQXDQWAwMDAxMTExMedcRyB6d5CAMQ5hGrjSrmAQBQdgIXlosSTwAAAABJRU5ErkJggg==');
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(0,0,0,0.9)), color-stop(1, rgba(50,50,50,0.9))) no-repeat;
+  background: -moz-linear-gradient(top, rgba(0,0,0,0.9) 0%, rgba(50,50,50,0.9) 100%) no-repeat;
+  background: -webkit-linear-gradient(top, rgba(0,0,0,0.9) 0%, rgba(50,50,50,0.9) 100%) no-repeat;
+  background: -ms-linear-gradient(top, rgba(0,0,0,0.9) 0%, rgba(50,50,50,0.9) 100%) no-repeat;
+  background: -o-linear-gradient(top, rgba(0,0,0,0.9) 0%, rgba(50,50,50,0.9) 100%) no-repeat;
+  background: linear-gradient(top, rgba(0,0,0,0.9) 0%, rgba(50,50,50,0.9) 100%) no-repeat;
+  *background-color: #000;
+  -webkit-border-radius: 5px;
+  border-radius: 5px;
+  -webkit-box-shadow: 0 4px 4px -4px #000;
+  box-shadow: 0 4px 4px -4px #000;
+  -moz-transform: translateY(-40px);
+  -webkit-transform: translateY(-40px);
+  -ms-transform: translateY(-40px);
+  -o-transform: translateY(-40px);
+  transform: translateY(-40px);
+}
+.humane p,
+.humane-libnotify p,
+.humane ul,
+.humane-libnotify ul {
+  margin: 0;
+  padding: 0;
+}
+.humane ul,
+.humane-libnotify ul {
+  list-style: none;
+}
+.humane.humane-libnotify-info,
+.humane-libnotify.humane-libnotify-info {
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAABQCAYAAADYxx/bAAAABmJLR0QA/wD/AP+gvaeTAAAAMUlEQVQYlWNgYDB6ysTAwMDAxMDACCcYUFkMDEwMDEwMBNVhkxg65jGhmke6M6hgHgBSdgHnpZwADwAAAABJRU5ErkJggg==');
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(0,0,50,0.9)), color-stop(1, rgba(0,0,100,0.9))) no-repeat;
+  background: -moz-linear-gradient(top, rgba(0,0,50,0.9) 0%, rgba(0,0,100,0.9) 100%) no-repeat;
+  background: -webkit-linear-gradient(top, rgba(0,0,50,0.9) 0%, rgba(0,0,100,0.9) 100%) no-repeat;
+  background: -ms-linear-gradient(top, rgba(0,0,50,0.9) 0%, rgba(0,0,100,0.9) 100%) no-repeat;
+  background: -o-linear-gradient(top, rgba(0,0,50,0.9) 0%, rgba(0,0,100,0.9) 100%) no-repeat;
+  background: linear-gradient(top, rgba(0,0,50,0.9) 0%, rgba(0,0,100,0.9) 100%) no-repeat;
+  *background-color: #030;
+}
+.humane.humane-libnotify-success,
+.humane-libnotify.humane-libnotify-success {
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAABQCAYAAADYxx/bAAAABmJLR0QA/wD/AP+gvaeTAAAAMUlEQVQYlWNgMGJ4ysTAwMDAxMAIJxhQWQwMDEwMTKgS2NRhkxg65jGhmke6M6hhHgBS2QHn2LzhygAAAABJRU5ErkJggg==');
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(0,50,0,0.9)), color-stop(1, rgba(0,100,0,0.9))) no-repeat;
+  background: -moz-linear-gradient(top, rgba(0,50,0,0.9) 0%, rgba(0,100,0,0.9) 100%) no-repeat;
+  background: -webkit-linear-gradient(top, rgba(0,50,0,0.9) 0%, rgba(0,100,0,0.9) 100%) no-repeat;
+  background: -ms-linear-gradient(top, rgba(0,50,0,0.9) 0%, rgba(0,100,0,0.9) 100%) no-repeat;
+  background: -o-linear-gradient(top, rgba(0,50,0,0.9) 0%, rgba(0,100,0,0.9) 100%) no-repeat;
+  background: linear-gradient(top, rgba(0,50,0,0.9) 0%, rgba(0,100,0,0.9) 100%) no-repeat;
+  *background-color: #030;
+}
+.humane.humane-libnotify-error,
+.humane-libnotify.humane-libnotify-error {
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADICAYAAAAp8ov1AAAABmJLR0QA/wD/AP+gvaeTAAAAPklEQVQokWMwYmB4ysTAwMCATjASFsOmBBvBRJ7x+O0g0wCS7CDTH/RwH7X9MVDuwyaG032D2M2UeIYO7gMAqt8C19Bn7+YAAAAASUVORK5CYII=');
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(50,0,0,0.9)), color-stop(1, rgba(100,0,0,0.9))) no-repeat;
+  background: -moz-linear-gradient(top, rgba(50,0,0,0.9) 0%, rgba(100,0,0,0.9) 100%) no-repeat;
+  background: -webkit-linear-gradient(top, rgba(50,0,0,0.9) 0%, rgba(100,0,0,0.9) 100%) no-repeat;
+  background: -ms-linear-gradient(top, rgba(50,0,0,0.9) 0%, rgba(100,0,0,0.9) 100%) no-repeat;
+  background: -o-linear-gradient(top, rgba(50,0,0,0.9) 0%, rgba(100,0,0,0.9) 100%) no-repeat;
+  background: linear-gradient(top, rgba(50,0,0,0.9) 0%, rgba(100,0,0,0.9) 100%) no-repeat;
+  *background-color: #300;
+}
+.humane.humane-animate,
+.humane-libnotify.humane-libnotify-animate {
+  opacity: 1;
+  -moz-transform: translateY(0);
+  -webkit-transform: translateY(0);
+  -ms-transform: translateY(0);
+  -o-transform: translateY(0);
+  transform: translateY(0);
+}
+.humane.humane-animate:hover,
+.humane-libnotify.humane-libnotify-animate:hover {
+  opacity: 0.2;
+}
+.humane.humane-animate,
+.humane-libnotify.humane-libnotify-js-animate {
+  opacity: 1;
+  -moz-transform: translateY(0);
+  -webkit-transform: translateY(0);
+  -ms-transform: translateY(0);
+  -o-transform: translateY(0);
+  transform: translateY(0);
+}
+.humane.humane-animate:hover,
+.humane-libnotify.humane-libnotify-js-animate:hover {
+  opacity: 0.2;
+  filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=20);
+}
diff --git a/lib/Korap/Plugin/Notifications/Humane/original.css b/lib/Korap/Plugin/Notifications/Humane/original.css
new file mode 100644
index 0000000..60393de
--- /dev/null
+++ b/lib/Korap/Plugin/Notifications/Humane/original.css
@@ -0,0 +1,72 @@
+html,
+body {
+  min-height: 100%;
+}
+.humane,
+.humane-original {
+  position: fixed;
+  -moz-transition: all 0.2s ease-out;
+  -webkit-transition: all 0.2s ease-out;
+  -ms-transition: all 0.2s ease-out;
+  -o-transition: all 0.2s ease-out;
+  transition: all 0.2s ease-out;
+  z-index: 100000;
+  filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
+}
+.humane,
+.humane-original {
+  font-family: Ubuntu, Verdana, sans-serif;
+  line-height: 40px;
+  font-size: 25px;
+  top: 25%;
+  left: 25%;
+  opacity: 0;
+  width: 50%;
+  min-height: 40px;
+  padding: 10px;
+  text-align: center;
+  background-color: #000;
+  color: #fff;
+  -webkit-border-radius: 15px;
+  border-radius: 15px;
+}
+.humane p,
+.humane-original p,
+.humane ul,
+.humane-original ul {
+  margin: 0;
+  padding: 0;
+}
+.humane ul,
+.humane-original ul {
+  list-style: none;
+}
+.humane.humane-original-info,
+.humane-original.humane-original-info {
+  background-color: #030;
+}
+.humane.humane-original-success,
+.humane-original.humane-original-success {
+  background-color: #030;
+}
+.humane.humane-original-error,
+.humane-original.humane-original-error {
+  background-color: #300;
+}
+.humane.humane-animate,
+.humane-original.humane-original-animate {
+  opacity: 0.8;
+}
+.humane.humane-animate:hover,
+.humane-original.humane-original-animate:hover {
+  opacity: 0.6;
+}
+.humane.humane-js-animate,
+.humane-original.humane-original-js-animate {
+  opacity: 0.8;
+}
+.humane.humane-js-animate:hover,
+.humane-original.humane-original-js-animate:hover {
+  opacity: 0.6;
+  filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=60);
+}
diff --git a/lib/Korap/Plugin/Notifications/Humane/readme.md b/lib/Korap/Plugin/Notifications/Humane/readme.md
new file mode 100644
index 0000000..cf4938b
--- /dev/null
+++ b/lib/Korap/Plugin/Notifications/Humane/readme.md
@@ -0,0 +1,85 @@
+# humane.js
+This project was heavily inspired by [humanmsg](http://code.google.com/p/humanmsg/) project for jQuery.  I really
+liked the project but I wanted to remove the jQuery dependency, add support for CSS transitions, and have more 
+control over the timing.
+
+## About
+humane.js tries to be as unobtrusive as possible to the user experience while providing helpful information that is
+clear and grabs the users attention.  It is framework independent.  Customizable.
+
+## Setup
+Setup is simple:
+
+  - Download tar/zip
+  - Select a [theme](https://github.com/wavded/humane-js/wiki/Themes) from `themes` dir.
+  - Include the theme CSS in your page
+  - Include `humane.min.js` in your page
+
+## Demo/Usage
+
+You can see a [demo and usage here](http://wavded.github.com/humane-js/)
+
+## Custom Themes
+
+Got a neat theme/animation, love to see it.  View `theme-src/bigbox.styl` for an template to get started (uses [Stylus](http://learnboost.github.com/stylus/) w/ Nib and Canvas).
+
+To get setup with Stylus use [npm](http://npmjs.org):
+
+```sh
+(sudo) npm install -g stylus nib canvas
+```
+
+With Stylus installed you can watch for changes and compile into CSS by running:
+
+```sh
+make watch
+```
+
+## Desktop and Mobile Browser Support
+
+humane.js has been tested for the following browsers.
+
+  - Internet Explorer 7+
+  - Firefox 3+
+  - Chrome 9+
+  - Safari 3+
+  - Opera 10+
+  - iOS 4+
+  - Android 2+
+
+## AMD and Node.js Support
+
+humane.js works for AMD systems like [require.js](http://requirejs.org/) and within Node.js CommonJS module system like [browserify](https://github.com/substack/node-browserify).
+
+## Contributers
+
+Many thanks to the JS/Browser wizards that helped make this better, community rocks!
+
+- @bga_ (Alexander)
+- @joseanpg (Jose)
+- @OiNutter (Will McKenzie)
+- @ianmassey (Ian Massey)
+
+## License
+
+(The MIT License)
+
+Copyright (c) 2011 Marc Harter &lt;wavded@gmail.com&gt;
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/lib/Korap/Search.pm b/lib/Korap/Search.pm
new file mode 100644
index 0000000..e548ace
--- /dev/null
+++ b/lib/Korap/Search.pm
@@ -0,0 +1,14 @@
+package Korap::Search;
+use Mojo::Base 'Mojolicious::Controller';
+
+# This action will render a template
+sub remote {
+  my $c = shift;
+
+  # Render template "example/welcome.html.ep" with message
+  $c->render(template => 'search');
+};
+
+
+
+1;