diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 914ce163f..ca0a02824 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -55,7 +55,7 @@ jobs:
     - name: Checkout
       uses: actions/checkout@v2
     - name: Install Ubuntu packages
-      run: sudo ./utils/searx.sh install packages
+      run: sudo ./utils/searx.sh install buildhost
     - name: Install node dependencies
       run: make V=1 node.env
     - name: Build themes
diff --git a/manage b/manage
index 5237e6e1b..7019f429b 100755
--- a/manage
+++ b/manage
@@ -13,7 +13,6 @@ source_dot_config
 
 PYOBJECTS="searx"
 PY_SETUP_EXTRAS='[test]'
-NPM_PACKAGES="less@2.7 less-plugin-clean-css grunt-cli"
 GECKODRIVER_VERSION="v0.28.0"
 # SPHINXOPTS=
 
@@ -296,43 +295,29 @@ gecko.driver() {
 }
 
 node.env() {
-    local err=0
-    pushd "${REPO_ROOT}" &> /dev/null
-    # shellcheck disable=SC2230
-    which npm &> /dev/null || die 1 'node.env - npm is not found!'
+    if ! required_commands npm fontforge ttfautohint; then
+        info_msg "to install build tools use::"
+        info_msg "   sudo -H ./utils/searx.sh install buildhost"
+        die 1 "install needed build tools first"
+    fi
 
     (   set -e
-        # shellcheck disable=SC2030
-        PATH="$(npm bin):$PATH"
-        export PATH
 
-        build_msg INSTALL "npm install $NPM_PACKAGES"
-        # shellcheck disable=SC2086
-        npm install $NPM_PACKAGES
+        build_msg INSTALL "searx/static/themes/oscar/package.json"
+        npm --prefix searx/static/themes/oscar install
 
-        cd "${REPO_ROOT}/searx/static/themes/oscar"
-        build_msg INSTALL "($(pwd)) npm install"
-        npm install
-
-        build_msg INSTALL "($(pwd)) npm install"
-        cd "${REPO_ROOT}/searx/static/themes/simple"
-        npm install
+        build_msg INSTALL "searx/static/themes/simple/package.json"
+        npm --prefix searx/static/themes/simple install
     )
-    err=$?
-    popd &> /dev/null
-    dump_return "$err"
+    dump_return $?
 }
 
 node.clean() {
-
     build_msg CLEAN "locally installed npm dependencies"
-    rm -rf \
-       ./node_modules  \
-       ./package-lock.json \
-       ./searx/static/themes/oscar/package-lock.json \
-       ./searx/static/themes/oscar/node_modules \
-       ./searx/static/themes/simple/package-lock.json \
-       ./searx/static/themes/simple/node_modules
+    (   set -e
+        npm --prefix searx/static/themes/oscar run clean
+        npm --prefix searx/static/themes/simple run clean
+    )
     dump_return $?
 }
 
@@ -481,16 +466,14 @@ themes.all() {
 }
 
 themes.oscar() {
-    local gruntfile=searx/static/themes/oscar/gruntfile.js
-    build_msg GRUNT "${gruntfile}"
-    PATH="$(npm bin):$PATH" grunt --gruntfile "${gruntfile}"
+    build_msg GRUNT "theme: oscar"
+    npm --prefix searx/static/themes/oscar run build
     dump_return $?
 }
 
 themes.simple() {
-    local gruntfile=searx/static/themes/simple/gruntfile.js
-    build_msg GRUNT "${gruntfile}"
-    PATH="$(npm bin):$PATH" grunt --gruntfile "${gruntfile}"
+    build_msg GRUNT "theme: simple"
+    npm --prefix searx/static/themes/simple run build
     dump_return $?
 }
 
diff --git a/searx/static/themes/oscar/package.json b/searx/static/themes/oscar/package.json
index 8a3d48787..94883873d 100644
--- a/searx/static/themes/oscar/package.json
+++ b/searx/static/themes/oscar/package.json
@@ -1,23 +1,27 @@
 {
     "devDependencies": {
-        "grunt": "^1.3.0",
-        "grunt-cli": "^1.3.2",
+        "grunt": "~1.4.1",
+        "grunt-cli": "^1.4.3",
         "grunt-contrib-concat": "~1.0.1",
         "grunt-contrib-copy": "^1.0.0",
         "grunt-contrib-jshint": "~3.0.0",
         "grunt-contrib-less": "~3.0.0",
-        "grunt-contrib-uglify": "~5.0.0",
-        "grunt-contrib-watch": "~1.1.0"
-    },
-    "scripts": {
-        "build": "npm install && grunt",
-        "start": "grunt watch",
-        "test": "grunt"
+        "grunt-contrib-uglify": "~5.0.1",
+        "grunt-contrib-watch": "~1.1.0",
+        "jslint": "^0.12.1",
+        "less": "^4.1.1",
+        "less-plugin-clean-css": "^1.5.1"
     },
     "dependencies": {
         "bootstrap": "^3.4.1",
         "corejs-typeahead": "^1.3.1",
         "jquery": "^3.6.0",
         "leaflet": "^1.7.1"
+    },
+    "scripts": {
+        "all": "npm install && grunt",
+        "build": "grunt",
+        "watch": "grunt watch",
+        "clean": "rm -Rf node_modules package-lock.json"
     }
 }
diff --git a/searx/static/themes/simple/.gitignore b/searx/static/themes/simple/.gitignore
index 07e6e472c..2c1954c3c 100644
--- a/searx/static/themes/simple/.gitignore
+++ b/searx/static/themes/simple/.gitignore
@@ -1 +1,2 @@
 /node_modules
+ion.less
diff --git a/searx/static/themes/simple/leaflet/images/layers-2x.png b/searx/static/themes/simple/css/images/layers-2x.png
similarity index 100%
rename from searx/static/themes/simple/leaflet/images/layers-2x.png
rename to searx/static/themes/simple/css/images/layers-2x.png
diff --git a/searx/static/themes/simple/leaflet/images/layers.png b/searx/static/themes/simple/css/images/layers.png
similarity index 100%
rename from searx/static/themes/simple/leaflet/images/layers.png
rename to searx/static/themes/simple/css/images/layers.png
diff --git a/searx/static/themes/simple/leaflet/images/marker-icon-2x.png b/searx/static/themes/simple/css/images/marker-icon-2x.png
similarity index 100%
rename from searx/static/themes/simple/leaflet/images/marker-icon-2x.png
rename to searx/static/themes/simple/css/images/marker-icon-2x.png
diff --git a/searx/static/themes/simple/leaflet/images/marker-icon.png b/searx/static/themes/simple/css/images/marker-icon.png
similarity index 100%
rename from searx/static/themes/simple/leaflet/images/marker-icon.png
rename to searx/static/themes/simple/css/images/marker-icon.png
diff --git a/searx/static/themes/simple/leaflet/images/marker-shadow.png b/searx/static/themes/simple/css/images/marker-shadow.png
similarity index 100%
rename from searx/static/themes/simple/leaflet/images/marker-shadow.png
rename to searx/static/themes/simple/css/images/marker-shadow.png
diff --git a/searx/static/themes/simple/css/leaflet.css b/searx/static/themes/simple/css/leaflet.css
new file mode 100644
index 000000000..601476fe6
Binary files /dev/null and b/searx/static/themes/simple/css/leaflet.css differ
diff --git a/searx/static/themes/simple/css/searx-rtl.css b/searx/static/themes/simple/css/searx-rtl.css
index 62f5470cf..2f79f82df 100644
--- a/searx/static/themes/simple/css/searx-rtl.css
+++ b/searx/static/themes/simple/css/searx-rtl.css
@@ -1,4 +1,4 @@
-/*! searx | 09-06-2021 | https://github.com/searxng/searxng */
+/*! searx | 16-06-2021 | https://github.com/searxng/searxng */
 /*
 * searx, A privacy-respecting, hackable metasearch engine
 *
@@ -1672,14 +1672,13 @@ td:hover .engine-tooltip,
 }
 @font-face {
   font-family: "ion";
-  src: url("../fonts/ion.eot?ce7a0ead692560b4405a96d5b8471f51");
-  src: url("../fonts/ion.eot?#iefix") format("embedded-opentype"), url("../fonts/ion.woff2?ce7a0ead692560b4405a96d5b8471f51") format("woff2"), url("../fonts/ion.woff?ce7a0ead692560b4405a96d5b8471f51") format("woff"), url("../fonts/ion.ttf?ce7a0ead692560b4405a96d5b8471f51") format("truetype"), url("../fonts/ion.svg?ce7a0ead692560b4405a96d5b8471f51#ion") format("svg");
+  src: url("../fonts/ion.eot?14747ff3e5db3853c40bc9540e9f6c3a");
+  src: url("../fonts/ion.eot?14747ff3e5db3853c40bc9540e9f6c3a#iefix") format("embedded-opentype"), url("../fonts/ion.woff2?14747ff3e5db3853c40bc9540e9f6c3a") format("woff2"), url("../fonts/ion.woff?14747ff3e5db3853c40bc9540e9f6c3a") format("woff"), url("../fonts/ion.ttf?14747ff3e5db3853c40bc9540e9f6c3a") format("truetype"), url("../fonts/ion.svg?14747ff3e5db3853c40bc9540e9f6c3a#ion") format("svg");
   font-weight: normal;
   font-style: normal;
 }
 .ion-icon {
   display: inline-block;
-  vertical-align: middle;
   line-height: 1;
   font-weight: normal;
   font-style: normal;
@@ -1758,7 +1757,6 @@ td:hover .engine-tooltip,
 }
 .ion-icon-big {
   display: inline-block;
-  vertical-align: middle;
   line-height: 1;
   font-weight: normal;
   font-style: normal;
diff --git a/searx/static/themes/simple/css/searx-rtl.min.css b/searx/static/themes/simple/css/searx-rtl.min.css
index 1509e313f..1a555fc8e 100644
Binary files a/searx/static/themes/simple/css/searx-rtl.min.css and b/searx/static/themes/simple/css/searx-rtl.min.css differ
diff --git a/searx/static/themes/simple/css/searx-rtl.min.css.map b/searx/static/themes/simple/css/searx-rtl.min.css.map
index 6bbf567c9..137bc5dff 100644
Binary files a/searx/static/themes/simple/css/searx-rtl.min.css.map and b/searx/static/themes/simple/css/searx-rtl.min.css.map differ
diff --git a/searx/static/themes/simple/css/searx.css b/searx/static/themes/simple/css/searx.css
index fc9bf5e3e..77038f988 100644
--- a/searx/static/themes/simple/css/searx.css
+++ b/searx/static/themes/simple/css/searx.css
@@ -1,4 +1,4 @@
-/*! searx | 09-06-2021 | https://github.com/searxng/searxng */
+/*! searx | 16-06-2021 | https://github.com/searxng/searxng */
 /*
 * searx, A privacy-respecting, hackable metasearch engine
 *
@@ -1672,14 +1672,13 @@ td:hover .engine-tooltip,
 }
 @font-face {
   font-family: "ion";
-  src: url("../fonts/ion.eot?ce7a0ead692560b4405a96d5b8471f51");
-  src: url("../fonts/ion.eot?#iefix") format("embedded-opentype"), url("../fonts/ion.woff2?ce7a0ead692560b4405a96d5b8471f51") format("woff2"), url("../fonts/ion.woff?ce7a0ead692560b4405a96d5b8471f51") format("woff"), url("../fonts/ion.ttf?ce7a0ead692560b4405a96d5b8471f51") format("truetype"), url("../fonts/ion.svg?ce7a0ead692560b4405a96d5b8471f51#ion") format("svg");
+  src: url("../fonts/ion.eot?14747ff3e5db3853c40bc9540e9f6c3a");
+  src: url("../fonts/ion.eot?14747ff3e5db3853c40bc9540e9f6c3a#iefix") format("embedded-opentype"), url("../fonts/ion.woff2?14747ff3e5db3853c40bc9540e9f6c3a") format("woff2"), url("../fonts/ion.woff?14747ff3e5db3853c40bc9540e9f6c3a") format("woff"), url("../fonts/ion.ttf?14747ff3e5db3853c40bc9540e9f6c3a") format("truetype"), url("../fonts/ion.svg?14747ff3e5db3853c40bc9540e9f6c3a#ion") format("svg");
   font-weight: normal;
   font-style: normal;
 }
 .ion-icon {
   display: inline-block;
-  vertical-align: middle;
   line-height: 1;
   font-weight: normal;
   font-style: normal;
@@ -1758,7 +1757,6 @@ td:hover .engine-tooltip,
 }
 .ion-icon-big {
   display: inline-block;
-  vertical-align: middle;
   line-height: 1;
   font-weight: normal;
   font-style: normal;
diff --git a/searx/static/themes/simple/css/searx.min.css b/searx/static/themes/simple/css/searx.min.css
index 94d65bcb2..ea8c5ab1d 100644
Binary files a/searx/static/themes/simple/css/searx.min.css and b/searx/static/themes/simple/css/searx.min.css differ
diff --git a/searx/static/themes/simple/css/searx.min.css.map b/searx/static/themes/simple/css/searx.min.css.map
index c44daf4c3..b0c850c1c 100644
Binary files a/searx/static/themes/simple/css/searx.min.css.map and b/searx/static/themes/simple/css/searx.min.css.map differ
diff --git a/searx/static/themes/simple/fonts/ion.css b/searx/static/themes/simple/fonts/ion.css
index ebf6c6259..b65aca967 100644
--- a/searx/static/themes/simple/fonts/ion.css
+++ b/searx/static/themes/simple/fonts/ion.css
@@ -1,14 +1,15 @@
 /* Generated by grunt-webfont */
 
 
+
 @font-face {
 	font-family:"ion";
-	src:url("../fonts/ion.eot?ce7a0ead692560b4405a96d5b8471f51");
-	src:url("../fonts/ion.eot?#iefix") format("embedded-opentype"),
-		url("../fonts/ion.woff2?ce7a0ead692560b4405a96d5b8471f51") format("woff2"),
-		url("../fonts/ion.woff?ce7a0ead692560b4405a96d5b8471f51") format("woff"),
-		url("../fonts/ion.ttf?ce7a0ead692560b4405a96d5b8471f51") format("truetype"),
-		url("../fonts/ion.svg?ce7a0ead692560b4405a96d5b8471f51#ion") format("svg");
+	src:url("../fonts/ion.eot?14747ff3e5db3853c40bc9540e9f6c3a");
+	src:url("../fonts/ion.eot?14747ff3e5db3853c40bc9540e9f6c3a#iefix") format("embedded-opentype"),
+		url("../fonts/ion.woff2?14747ff3e5db3853c40bc9540e9f6c3a") format("woff2"),
+		url("../fonts/ion.woff?14747ff3e5db3853c40bc9540e9f6c3a") format("woff"),
+		url("../fonts/ion.ttf?14747ff3e5db3853c40bc9540e9f6c3a") format("truetype"),
+		url("../fonts/ion.svg?14747ff3e5db3853c40bc9540e9f6c3a#ion") format("svg");
 	font-weight:normal;
 	font-style:normal;
 }
@@ -18,7 +19,6 @@
 		font-family:"ion";
 	
 	display:inline-block;
-	vertical-align:middle;
 	line-height:1;
 	font-weight:normal;
 	font-style:normal;
diff --git a/searx/static/themes/simple/fonts/ion.eot b/searx/static/themes/simple/fonts/ion.eot
deleted file mode 100644
index 96b3ee98c..000000000
Binary files a/searx/static/themes/simple/fonts/ion.eot and /dev/null differ
diff --git a/searx/static/themes/simple/fonts/ion.html b/searx/static/themes/simple/fonts/ion.html
index d92237994..011a63170 100644
--- a/searx/static/themes/simple/fonts/ion.html
+++ b/searx/static/themes/simple/fonts/ion.html
@@ -58,14 +58,15 @@
 		/* Generated by grunt-webfont */
 
 
+
 @font-face {
 	font-family:"ion";
-	src:url("ion.eot?ce7a0ead692560b4405a96d5b8471f51");
-	src:url("ion.eot?#iefix") format("embedded-opentype"),
-		url("ion.woff2?ce7a0ead692560b4405a96d5b8471f51") format("woff2"),
-		url("ion.woff?ce7a0ead692560b4405a96d5b8471f51") format("woff"),
-		url("ion.ttf?ce7a0ead692560b4405a96d5b8471f51") format("truetype"),
-		url("ion.svg?ce7a0ead692560b4405a96d5b8471f51#ion") format("svg");
+	src:url("ion.eot?14747ff3e5db3853c40bc9540e9f6c3a");
+	src:url("ion.eot?14747ff3e5db3853c40bc9540e9f6c3a#iefix") format("embedded-opentype"),
+		url("ion.woff2?14747ff3e5db3853c40bc9540e9f6c3a") format("woff2"),
+		url("ion.woff?14747ff3e5db3853c40bc9540e9f6c3a") format("woff"),
+		url("ion.ttf?14747ff3e5db3853c40bc9540e9f6c3a") format("truetype"),
+		url("ion.svg?14747ff3e5db3853c40bc9540e9f6c3a#ion") format("svg");
 	font-weight:normal;
 	font-style:normal;
 }
@@ -75,7 +76,6 @@
 		font-family:"ion";
 	
 	display:inline-block;
-	vertical-align:middle;
 	line-height:1;
 	font-weight:normal;
 	font-style:normal;
diff --git a/searx/static/themes/simple/fonts/ion.svg b/searx/static/themes/simple/fonts/ion.svg
index 97ca930bb..c16155aab 100644
Binary files a/searx/static/themes/simple/fonts/ion.svg and b/searx/static/themes/simple/fonts/ion.svg differ
diff --git a/searx/static/themes/simple/fonts/ion.ttf b/searx/static/themes/simple/fonts/ion.ttf
index ccc5482b5..8334ba424 100644
Binary files a/searx/static/themes/simple/fonts/ion.ttf and b/searx/static/themes/simple/fonts/ion.ttf differ
diff --git a/searx/static/themes/simple/fonts/ion.woff b/searx/static/themes/simple/fonts/ion.woff
index 0b7fe877a..dec02f7d6 100644
Binary files a/searx/static/themes/simple/fonts/ion.woff and b/searx/static/themes/simple/fonts/ion.woff differ
diff --git a/searx/static/themes/simple/fonts/ion.woff2 b/searx/static/themes/simple/fonts/ion.woff2
index 30b902da2..17ef58b9c 100644
Binary files a/searx/static/themes/simple/fonts/ion.woff2 and b/searx/static/themes/simple/fonts/ion.woff2 differ
diff --git a/searx/static/themes/simple/gruntfile.js b/searx/static/themes/simple/gruntfile.js
index 4c14116b2..1dea80bd4 100644
--- a/searx/static/themes/simple/gruntfile.js
+++ b/searx/static/themes/simple/gruntfile.js
@@ -6,12 +6,12 @@ module.exports = function(grunt) {
     pkg: grunt.file.readJSON('package.json'),
     watch: {
       scripts: {
-        files: ['<%= jshint.files %>', 'less/*.less'],
+        files: ['src/**'],
         tasks: ['jshint', 'concat', 'uglify', 'webfont', 'less:development', 'less:production']
       }
     },
     jshint: {
-      files: ['js/searx_src/*.js', 'js/searx_header/*.js', '../__common__/js/*.js'],
+      files: ['src/js/main/*.js', 'src/js/head/*.js', '../__common__/js/*.js'],
       options: {
         reporterOutput: "",
         proto: true,
@@ -23,14 +23,49 @@ module.exports = function(grunt) {
         }
       }
     },
+    copy: {
+      js: {
+        expand: true,
+        cwd: './node_modules',
+        dest: './js/',
+        flatten: true,
+        filter: 'isFile',
+        timestamp: true,
+        src: [
+          './leaflet/dist/leaflet.js',
+        ]
+      },
+      css: {
+        expand: true,
+        cwd: './node_modules',
+        dest: './css/',
+        flatten: true,
+        filter: 'isFile',
+        timestamp: true,
+        src: [
+          './leaflet/dist/leaflet.css',
+        ]
+      },
+      leaflet_images: {
+        expand: true,
+        cwd: './node_modules',
+        dest: './css/images/',
+        flatten: true,
+        filter: 'isFile',
+        timestamp: true,
+        src: [
+          './leaflet/dist/images/*.png',
+        ]
+      },
+    },
     concat: {
       head_and_body: {
         options: {
           separator: ';'
         },
         files: {
-          'js/searx.head.js': ['js/searx_head/*.js'],
-          'js/searx.js': ['js/searx_src/*.js', '../__common__/js/*.js']
+          'js/searx.head.js': ['src/js/head/*.js'],
+          'js/searx.js': ['src/js/main/*.js', '../__common__/js/*.js', './node_modules/autocomplete-js/dist/autocomplete.js']
         }
       }
     },
@@ -53,35 +88,6 @@ module.exports = function(grunt) {
         }
       }
     },
-    less: {
-      development: {
-        options: {
-          paths: ["less"],
-          banner: '/*! searx | <%= grunt.template.today("dd-mm-yyyy") %> | <%= process.env.GIT_URL %> */\n'
-        },
-        files: {
-          "css/searx.css": "less/style.less",
-          "css/searx-rtl.css": "less/style-rtl.less"
-        }
-      },
-      production: {
-        options: {
-          paths: ["less"],
-          plugins: [
-            new (require('less-plugin-clean-css'))()
-          ],
-          sourceMap: true,
-          sourceMapURL: (name) => { const s = name.split('/'); return s[s.length - 1] + '.map';},
-          outputSourceFiles: false,
-          sourceMapRootpath: '../',
-          banner: '/*! searx | <%= grunt.template.today("dd-mm-yyyy") %> | <%= process.env.GIT_URL %> */\n'
-        },
-        files: {
-          "css/searx.min.css": "less/style.less",
-          "css/searx-rtl.min.css": "less/style-rtl.less"
-        }
-      },
-    },
     webfont: {
       icons: {
         // src: 'node_modules/ionicons-npm/src/*.svg',
@@ -107,11 +113,11 @@ module.exports = function(grunt) {
           'node_modules/ionicons-npm/src/music-note.svg',
           'node_modules/ionicons-npm/src/ion-close-round.svg',
           'node_modules/ionicons-npm/src/android-more-vertical.svg',
-          'magnet.svg',
+          'src/fonts/magnet.svg',
           'node_modules/ionicons-npm/src/android-close.svg',	  
         ],
         dest: 'fonts',
-        destLess: 'less',
+        destLess: '.',
         options: {
           font: 'ion',
           hashes : true,
@@ -145,10 +151,40 @@ module.exports = function(grunt) {
           }
         }
       }
-    }
+    },
+    less: {
+      development: {
+        options: {
+          paths: ["less"],
+          banner: '/*! searx | <%= grunt.template.today("dd-mm-yyyy") %> | <%= process.env.GIT_URL %> */\n'
+        },
+        files: {
+          "css/searx.css": "src/less/style.less",
+          "css/searx-rtl.css": "src/less/style-rtl.less"
+        }
+      },
+      production: {
+        options: {
+          paths: ["less"],
+          plugins: [
+            new (require('less-plugin-clean-css'))()
+          ],
+          sourceMap: true,
+          sourceMapURL: (name) => { const s = name.split('/'); return s[s.length - 1] + '.map';},
+          outputSourceFiles: false,
+          sourceMapRootpath: '../',
+          banner: '/*! searx | <%= grunt.template.today("dd-mm-yyyy") %> | <%= process.env.GIT_URL %> */\n'
+        },
+        files: {
+          "css/searx.min.css": "src/less/style.less",
+          "css/searx-rtl.min.css": "src/less/style-rtl.less"
+        }
+      },
+    },
   });
 
   grunt.loadNpmTasks('grunt-contrib-watch');
+  grunt.loadNpmTasks('grunt-contrib-copy');
   grunt.loadNpmTasks('grunt-contrib-uglify');
   grunt.loadNpmTasks('grunt-contrib-jshint');
   grunt.loadNpmTasks('grunt-contrib-concat');
@@ -158,5 +194,5 @@ module.exports = function(grunt) {
 
   grunt.registerTask('test', ['jshint']);
 
-  grunt.registerTask('default', ['jshint', 'concat', 'uglify', 'less:development', 'less:production']);
+  grunt.registerTask('default', ['jshint', 'copy', 'concat', 'uglify', 'webfont', 'less:development', 'less:production']);
 };
diff --git a/searx/static/themes/simple/js/leaflet.js b/searx/static/themes/simple/js/leaflet.js
new file mode 100644
index 000000000..21f499c3e
Binary files /dev/null and b/searx/static/themes/simple/js/leaflet.js differ
diff --git a/searx/static/themes/simple/js/searx.head.js b/searx/static/themes/simple/js/searx.head.js
index e6964400b..be7560451 100644
--- a/searx/static/themes/simple/js/searx.head.js
+++ b/searx/static/themes/simple/js/searx.head.js
@@ -33,7 +33,7 @@
         infinite_scroll: script.getAttribute('data-infinite-scroll') === 'true',
         static_path: script.getAttribute('data-static-path'),
         translations: JSON.parse(script.getAttribute('data-translations')),
-    }
+    };
 
     // update the css
     d.getElementsByTagName("html")[0].className = (w.searx.touch)?"js touch":"js";
diff --git a/searx/static/themes/simple/js/searx.head.min.js b/searx/static/themes/simple/js/searx.head.min.js
index 9c74c87a4..070d1c3ab 100644
Binary files a/searx/static/themes/simple/js/searx.head.min.js and b/searx/static/themes/simple/js/searx.head.min.js differ
diff --git a/searx/static/themes/simple/js/searx.head.min.js.map b/searx/static/themes/simple/js/searx.head.min.js.map
index a233d297b..7a4dc8ca4 100644
Binary files a/searx/static/themes/simple/js/searx.head.min.js.map and b/searx/static/themes/simple/js/searx.head.min.js.map differ
diff --git a/searx/static/themes/simple/js/searx.js b/searx/static/themes/simple/js/searx.js
index 92c99340c..5ded864cb 100644
--- a/searx/static/themes/simple/js/searx.js
+++ b/searx/static/themes/simple/js/searx.js
@@ -162,542 +162,6 @@ window.searx = (function(w, d) {
   
   return searx;
 })(window, document);
-;(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.AutoComplete = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
-/*
- * @license MIT
- *
- * Autocomplete.js v2.6.3
- * Developed by Baptiste Donaux
- * http://autocomplete-js.com
- *
- * (c) 2017, Baptiste Donaux
- */
-"use strict";
-var ConditionOperator;
-(function (ConditionOperator) {
-    ConditionOperator[ConditionOperator["AND"] = 0] = "AND";
-    ConditionOperator[ConditionOperator["OR"] = 1] = "OR";
-})(ConditionOperator || (ConditionOperator = {}));
-var EventType;
-(function (EventType) {
-    EventType[EventType["KEYDOWN"] = 0] = "KEYDOWN";
-    EventType[EventType["KEYUP"] = 1] = "KEYUP";
-})(EventType || (EventType = {}));
-/**
- * Core
- *
- * @class
- * @author Baptiste Donaux <baptiste.donaux@gmail.com> @baptistedonaux
- */
-var AutoComplete = (function () {
-    // Constructor
-    function AutoComplete(params, selector) {
-        if (params === void 0) { params = {}; }
-        if (selector === void 0) { selector = "[data-autocomplete]"; }
-        if (Array.isArray(selector)) {
-            selector.forEach(function (s) {
-                new AutoComplete(params, s);
-            });
-        }
-        else if (typeof selector == "string") {
-            var elements = document.querySelectorAll(selector);
-            Array.prototype.forEach.call(elements, function (input) {
-                new AutoComplete(params, input);
-            });
-        }
-        else {
-            var specificParams = AutoComplete.merge(AutoComplete.defaults, params, {
-                DOMResults: document.createElement("div")
-            });
-            AutoComplete.prototype.create(specificParams, selector);
-            return specificParams;
-        }
-    }
-    AutoComplete.prototype.create = function (params, element) {
-        params.Input = element;
-        if (params.Input.nodeName.match(/^INPUT$/i) && (params.Input.hasAttribute("type") === false || params.Input.getAttribute("type").match(/^TEXT|SEARCH$/i))) {
-            params.Input.setAttribute("autocomplete", "off");
-            params._Position(params);
-            params.Input.parentNode.appendChild(params.DOMResults);
-            params.$Listeners = {
-                blur: params._Blur.bind(params),
-                destroy: AutoComplete.prototype.destroy.bind(null, params),
-                focus: params._Focus.bind(params),
-                keyup: AutoComplete.prototype.event.bind(null, params, EventType.KEYUP),
-                keydown: AutoComplete.prototype.event.bind(null, params, EventType.KEYDOWN),
-                position: params._Position.bind(params)
-            };
-            for (var event in params.$Listeners) {
-                params.Input.addEventListener(event, params.$Listeners[event]);
-            }
-        }
-    };
-    AutoComplete.prototype.getEventsByType = function (params, type) {
-        var mappings = {};
-        for (var key in params.KeyboardMappings) {
-            var event = EventType.KEYUP;
-            if (params.KeyboardMappings[key].Event !== undefined) {
-                event = params.KeyboardMappings[key].Event;
-            }
-            if (event == type) {
-                mappings[key] = params.KeyboardMappings[key];
-            }
-        }
-        return mappings;
-    };
-    AutoComplete.prototype.event = function (params, type, event) {
-        var eventIdentifier = function (condition) {
-            if ((match === true && mapping.Operator == ConditionOperator.AND) || (match === false && mapping.Operator == ConditionOperator.OR)) {
-                condition = AutoComplete.merge({
-                    Not: false
-                }, condition);
-                if (condition.hasOwnProperty("Is")) {
-                    if (condition.Is == event.keyCode) {
-                        match = !condition.Not;
-                    }
-                    else {
-                        match = condition.Not;
-                    }
-                }
-                else if (condition.hasOwnProperty("From") && condition.hasOwnProperty("To")) {
-                    if (event.keyCode >= condition.From && event.keyCode <= condition.To) {
-                        match = !condition.Not;
-                    }
-                    else {
-                        match = condition.Not;
-                    }
-                }
-            }
-        };
-        for (var name in AutoComplete.prototype.getEventsByType(params, type)) {
-            var mapping = AutoComplete.merge({
-                Operator: ConditionOperator.AND
-            }, params.KeyboardMappings[name]), match = ConditionOperator.AND == mapping.Operator;
-            mapping.Conditions.forEach(eventIdentifier);
-            if (match === true) {
-                mapping.Callback.call(params, event);
-            }
-        }
-    };
-    AutoComplete.prototype.makeRequest = function (params, callback) {
-        var propertyHttpHeaders = Object.getOwnPropertyNames(params.HttpHeaders), request = new XMLHttpRequest(), method = params._HttpMethod(), url = params._Url(), queryParams = params._Pre(), queryParamsStringify = encodeURIComponent(params._QueryArg()) + "=" + encodeURIComponent(queryParams);
-        if (method.match(/^GET$/i)) {
-            if (url.indexOf("?") !== -1) {
-                url += "&" + queryParamsStringify;
-            }
-            else {
-                url += "?" + queryParamsStringify;
-            }
-        }
-        request.open(method, url, true);
-        for (var i = propertyHttpHeaders.length - 1; i >= 0; i--) {
-            request.setRequestHeader(propertyHttpHeaders[i], params.HttpHeaders[propertyHttpHeaders[i]]);
-        }
-        request.onreadystatechange = function () {
-            if (request.readyState == 4 && request.status == 200) {
-                params.$Cache[queryParams] = request.response;
-                callback(request.response);
-            }
-        };
-        return request;
-    };
-    AutoComplete.prototype.ajax = function (params, request, timeout) {
-        if (timeout === void 0) { timeout = true; }
-        if (params.$AjaxTimer) {
-            window.clearTimeout(params.$AjaxTimer);
-        }
-        if (timeout === true) {
-            params.$AjaxTimer = window.setTimeout(AutoComplete.prototype.ajax.bind(null, params, request, false), params.Delay);
-        }
-        else {
-            if (params.Request) {
-                params.Request.abort();
-            }
-            params.Request = request;
-            params.Request.send(params._QueryArg() + "=" + params._Pre());
-        }
-    };
-    AutoComplete.prototype.cache = function (params, callback) {
-        var response = params._Cache(params._Pre());
-        if (response === undefined) {
-            var request = AutoComplete.prototype.makeRequest(params, callback);
-            AutoComplete.prototype.ajax(params, request);
-        }
-        else {
-            callback(response);
-        }
-    };
-    AutoComplete.prototype.destroy = function (params) {
-        for (var event in params.$Listeners) {
-            params.Input.removeEventListener(event, params.$Listeners[event]);
-        }
-        params.DOMResults.parentNode.removeChild(params.DOMResults);
-    };
-    return AutoComplete;
-}());
-AutoComplete.merge = function () {
-    var merge = {}, tmp;
-    for (var i = 0; i < arguments.length; i++) {
-        for (tmp in arguments[i]) {
-            merge[tmp] = arguments[i][tmp];
-        }
-    }
-    return merge;
-};
-AutoComplete.defaults = {
-    Delay: 150,
-    EmptyMessage: "No result here",
-    Highlight: {
-        getRegex: function (value) {
-            return new RegExp(value, "ig");
-        },
-        transform: function (value) {
-            return "<strong>" + value + "</strong>";
-        }
-    },
-    HttpHeaders: {
-        "Content-type": "application/x-www-form-urlencoded"
-    },
-    Limit: 0,
-    MinChars: 0,
-    HttpMethod: "GET",
-    QueryArg: "q",
-    Url: null,
-    KeyboardMappings: {
-        "Enter": {
-            Conditions: [{
-                    Is: 13,
-                    Not: false
-                }],
-            Callback: function (event) {
-                if (this.DOMResults.getAttribute("class").indexOf("open") != -1) {
-                    var liActive = this.DOMResults.querySelector("li.active");
-                    if (liActive !== null) {
-                        event.preventDefault();
-                        this._Select(liActive);
-                        this.DOMResults.setAttribute("class", "autocomplete");
-                    }
-                }
-            },
-            Operator: ConditionOperator.AND,
-            Event: EventType.KEYDOWN
-        },
-        "KeyUpAndDown_down": {
-            Conditions: [{
-                    Is: 38,
-                    Not: false
-                },
-                {
-                    Is: 40,
-                    Not: false
-                }],
-            Callback: function (event) {
-                event.preventDefault();
-            },
-            Operator: ConditionOperator.OR,
-            Event: EventType.KEYDOWN
-        },
-        "KeyUpAndDown_up": {
-            Conditions: [{
-                    Is: 38,
-                    Not: false
-                },
-                {
-                    Is: 40,
-                    Not: false
-                }],
-            Callback: function (event) {
-                event.preventDefault();
-                var first = this.DOMResults.querySelector("li:first-child:not(.locked)"), last = this.DOMResults.querySelector("li:last-child:not(.locked)"), active = this.DOMResults.querySelector("li.active");
-                if (active) {
-                    var currentIndex = Array.prototype.indexOf.call(active.parentNode.children, active), position = currentIndex + (event.keyCode - 39), lisCount = this.DOMResults.getElementsByTagName("li").length;
-                    if (position < 0) {
-                        position = lisCount - 1;
-                    }
-                    else if (position >= lisCount) {
-                        position = 0;
-                    }
-                    active.classList.remove("active");
-                    active.parentElement.children.item(position).classList.add("active");
-                }
-                else if (last && event.keyCode == 38) {
-                    last.classList.add("active");
-                }
-                else if (first) {
-                    first.classList.add("active");
-                }
-            },
-            Operator: ConditionOperator.OR,
-            Event: EventType.KEYUP
-        },
-        "AlphaNum": {
-            Conditions: [{
-                    Is: 13,
-                    Not: true
-                }, {
-                    From: 35,
-                    To: 40,
-                    Not: true
-                }],
-            Callback: function () {
-                var oldValue = this.Input.getAttribute("data-autocomplete-old-value"), currentValue = this._Pre();
-                if (currentValue !== "" && currentValue.length >= this._MinChars()) {
-                    if (!oldValue || currentValue != oldValue) {
-                        this.DOMResults.setAttribute("class", "autocomplete open");
-                    }
-                    AutoComplete.prototype.cache(this, function (response) {
-                        this._Render(this._Post(response));
-                        this._Open();
-                    }.bind(this));
-                }
-            },
-            Operator: ConditionOperator.AND,
-            Event: EventType.KEYUP
-        }
-    },
-    DOMResults: null,
-    Request: null,
-    Input: null,
-    /**
-     * Return the message when no result returns
-     */
-    _EmptyMessage: function () {
-        var emptyMessage = "";
-        if (this.Input.hasAttribute("data-autocomplete-empty-message")) {
-            emptyMessage = this.Input.getAttribute("data-autocomplete-empty-message");
-        }
-        else if (this.EmptyMessage !== false) {
-            emptyMessage = this.EmptyMessage;
-        }
-        else {
-            emptyMessage = "";
-        }
-        return emptyMessage;
-    },
-    /**
-     * Returns the maximum number of results
-     */
-    _Limit: function () {
-        var limit = this.Input.getAttribute("data-autocomplete-limit");
-        if (isNaN(limit) || limit === null) {
-            return this.Limit;
-        }
-        return parseInt(limit, 10);
-    },
-    /**
-     * Returns the minimum number of characters entered before firing ajax
-     */
-    _MinChars: function () {
-        var minchars = this.Input.getAttribute("data-autocomplete-minchars");
-        if (isNaN(minchars) || minchars === null) {
-            return this.MinChars;
-        }
-        return parseInt(minchars, 10);
-    },
-    /**
-     * Apply transformation on labels response
-     */
-    _Highlight: function (label) {
-        return label.replace(this.Highlight.getRegex(this._Pre()), this.Highlight.transform);
-    },
-    /**
-     * Returns the HHTP method to use
-     */
-    _HttpMethod: function () {
-        if (this.Input.hasAttribute("data-autocomplete-method")) {
-            return this.Input.getAttribute("data-autocomplete-method");
-        }
-        return this.HttpMethod;
-    },
-    /**
-     * Returns the query param to use
-     */
-    _QueryArg: function () {
-        if (this.Input.hasAttribute("data-autocomplete-param-name")) {
-            return this.Input.getAttribute("data-autocomplete-param-name");
-        }
-        return this.QueryArg;
-    },
-    /**
-     * Returns the URL to use for AJAX request
-     */
-    _Url: function () {
-        if (this.Input.hasAttribute("data-autocomplete")) {
-            return this.Input.getAttribute("data-autocomplete");
-        }
-        return this.Url;
-    },
-    /**
-     * Manage the close
-     */
-    _Blur: function (now) {
-        if (now === true) {
-            this.DOMResults.setAttribute("class", "autocomplete");
-            this.Input.setAttribute("data-autocomplete-old-value", this.Input.value);
-        }
-        else {
-            var params = this;
-            setTimeout(function () {
-                params._Blur(true);
-            }, 150);
-        }
-    },
-    /**
-     * Manage the cache
-     */
-    _Cache: function (value) {
-        return this.$Cache[value];
-    },
-    /**
-     * Manage the open
-     */
-    _Focus: function () {
-        var oldValue = this.Input.getAttribute("data-autocomplete-old-value");
-        if ((!oldValue || this.Input.value != oldValue) && this._MinChars() <= this.Input.value.length) {
-            this.DOMResults.setAttribute("class", "autocomplete open");
-        }
-    },
-    /**
-     * Bind all results item if one result is opened
-     */
-    _Open: function () {
-        var params = this;
-        Array.prototype.forEach.call(this.DOMResults.getElementsByTagName("li"), function (li) {
-            if (li.getAttribute("class") != "locked") {
-		            li.onclick = function (event) {
-                    params._Select(li);
-                };
-                li.onmouseenter = function () {
-                    var active = params.DOMResults.querySelector("li.active");
-                    if (active !== li) {
-                        if (active !== null) {
-                            active.classList.remove("active");
-                        }
-                        li.classList.add("active");
-                    }
-                };
-            }
-        });
-    },
-    /**
-     * Position the results HTML element
-     */
-    _Position: function () {
-        this.DOMResults.setAttribute("class", "autocomplete");
-        this.DOMResults.setAttribute("style", "top:" + (this.Input.offsetTop + this.Input.offsetHeight) + "px;left:" + this.Input.offsetLeft + "px;width:" + this.Input.clientWidth + "px;");
-    },
-    /**
-     * Execute the render of results DOM element
-     */
-    _Render: function (response) {
-        var ul;
-        if (typeof response == "string") {
-            ul = this._RenderRaw(response);
-        }
-        else {
-            ul = this._RenderResponseItems(response);
-        }
-        if (this.DOMResults.hasChildNodes()) {
-            this.DOMResults.removeChild(this.DOMResults.childNodes[0]);
-        }
-        this.DOMResults.appendChild(ul);
-    },
-    /**
-     * ResponseItems[] rendering
-     */
-    _RenderResponseItems: function (response) {
-        var ul = document.createElement("ul"), li = document.createElement("li"), limit = this._Limit();
-        // Order
-        if (limit < 0) {
-            response = response.reverse();
-        }
-        else if (limit === 0) {
-            limit = response.length;
-        }
-        for (var item = 0; item < Math.min(Math.abs(limit), response.length); item++) {
-            li.innerHTML = response[item].Label;
-            li.setAttribute("data-autocomplete-value", response[item].Value);
-            ul.appendChild(li);
-            li = document.createElement("li");
-        }
-        return ul;
-    },
-    /**
-     * string response rendering (RAW HTML)
-     */
-    _RenderRaw: function (response) {
-        var ul = document.createElement("ul"), li = document.createElement("li");
-        if (response.length > 0) {
-            this.DOMResults.innerHTML = response;
-        }
-        else {
-            var emptyMessage = this._EmptyMessage();
-            if (emptyMessage !== "") {
-                li.innerHTML = emptyMessage;
-                li.setAttribute("class", "locked");
-                ul.appendChild(li);
-            }
-        }
-        return ul;
-    },
-    /**
-     * Deal with request response
-     */
-    _Post: function (response) {
-        try {
-            var returnResponse = [];
-            //JSON return
-            var json = JSON.parse(response);
-            if (Object.keys(json).length === 0) {
-                return "";
-            }
-            if (Array.isArray(json)) {
-                for (var i = 0; i < Object.keys(json).length; i++) {
-                    returnResponse[returnResponse.length] = { "Value": json[i], "Label": this._Highlight(json[i]) };
-                }
-            }
-            else {
-                for (var value in json) {
-                    returnResponse.push({
-                        "Value": value,
-                        "Label": this._Highlight(json[value])
-                    });
-                }
-            }
-            return returnResponse;
-        }
-        catch (event) {
-            //HTML return
-            return response;
-        }
-    },
-    /**
-     * Return the autocomplete value to send (before request)
-     */
-    _Pre: function () {
-        return this.Input.value;
-    },
-    /**
-     * Choice one result item
-     */
-    _Select: function (item) {
-	console.log('test test test');
-        if (item.hasAttribute("data-autocomplete-value")) {
-            this.Input.value = item.getAttribute("data-autocomplete-value");
-        }
-        else {
-            this.Input.value = item.innerHTML;
-        }
-        this.Input.setAttribute("data-autocomplete-old-value", this.Input.value);
-    },
-    $AjaxTimer: null,
-    $Cache: {},
-    $Listeners: {}
-};
-module.exports = AutoComplete;
-
-},{}]},{},[1])(1)
-});
 ;searx.ready(function() {
 
   searx.on('.result', 'click', function() {
@@ -1097,8 +561,8 @@ module.exports = AutoComplete;
       var map_boundingbox = JSON.parse(this.dataset.mapBoundingbox);
       var map_geojson = JSON.parse(this.dataset.mapGeojson);
 
-      searx.loadStyle('leaflet/leaflet.css');
-      searx.loadScript('leaflet/leaflet.js', function() {
+      searx.loadStyle('css/leaflet.css');
+      searx.loadScript('js/leaflet.js', function() {
         var map_bounds = null;
         if(map_boundingbox) {
           var southWest = L.latLng(map_boundingbox[0], map_boundingbox[2]);
@@ -1500,3 +964,543 @@ module.exports = AutoComplete;
   w.searx.ImageLayout = ImageLayout;
 
 }(window, document));
+;(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.AutoComplete = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
+/*
+ * @license MIT
+ *
+ * Autocomplete.js v2.7.1
+ * Developed by Baptiste Donaux
+ * http://autocomplete-js.com
+ *
+ * (c) 2017, Baptiste Donaux
+ */
+"use strict";
+var ConditionOperator;
+(function (ConditionOperator) {
+    ConditionOperator[ConditionOperator["AND"] = 0] = "AND";
+    ConditionOperator[ConditionOperator["OR"] = 1] = "OR";
+})(ConditionOperator || (ConditionOperator = {}));
+var EventType;
+(function (EventType) {
+    EventType[EventType["KEYDOWN"] = 0] = "KEYDOWN";
+    EventType[EventType["KEYUP"] = 1] = "KEYUP";
+})(EventType || (EventType = {}));
+/**
+ * Core
+ *
+ * @class
+ * @author Baptiste Donaux <baptiste.donaux@gmail.com> @baptistedonaux
+ */
+var AutoComplete = /** @class */ (function () {
+    // Constructor
+    function AutoComplete(params, selector) {
+        if (params === void 0) { params = {}; }
+        if (selector === void 0) { selector = "[data-autocomplete]"; }
+        if (Array.isArray(selector)) {
+            selector.forEach(function (s) {
+                new AutoComplete(params, s);
+            });
+        }
+        else if (typeof selector == "string") {
+            var elements = document.querySelectorAll(selector);
+            Array.prototype.forEach.call(elements, function (input) {
+                new AutoComplete(params, input);
+            });
+        }
+        else {
+            var specificParams = AutoComplete.merge(AutoComplete.defaults, params, {
+                DOMResults: document.createElement("div")
+            });
+            AutoComplete.prototype.create(specificParams, selector);
+            return specificParams;
+        }
+    }
+    AutoComplete.prototype.create = function (params, element) {
+        params.Input = element;
+        if (params.Input.nodeName.match(/^INPUT$/i) && (params.Input.hasAttribute("type") === false || params.Input.getAttribute("type").match(/^TEXT|SEARCH$/i))) {
+            params.Input.setAttribute("autocomplete", "off");
+            params._Position(params);
+            params.Input.parentNode.appendChild(params.DOMResults);
+            params.$Listeners = {
+                blur: params._Blur.bind(params),
+                destroy: AutoComplete.prototype.destroy.bind(null, params),
+                focus: params._Focus.bind(params),
+                keyup: AutoComplete.prototype.event.bind(null, params, EventType.KEYUP),
+                keydown: AutoComplete.prototype.event.bind(null, params, EventType.KEYDOWN),
+                position: params._Position.bind(params)
+            };
+            for (var event in params.$Listeners) {
+                params.Input.addEventListener(event, params.$Listeners[event]);
+            }
+        }
+    };
+    AutoComplete.prototype.getEventsByType = function (params, type) {
+        var mappings = {};
+        for (var key in params.KeyboardMappings) {
+            var event = EventType.KEYUP;
+            if (params.KeyboardMappings[key].Event !== undefined) {
+                event = params.KeyboardMappings[key].Event;
+            }
+            if (event == type) {
+                mappings[key] = params.KeyboardMappings[key];
+            }
+        }
+        return mappings;
+    };
+    AutoComplete.prototype.event = function (params, type, event) {
+        var eventIdentifier = function (condition) {
+            if ((match === true && mapping.Operator == ConditionOperator.AND) || (match === false && mapping.Operator == ConditionOperator.OR)) {
+                condition = AutoComplete.merge({
+                    Not: false
+                }, condition);
+                if (condition.hasOwnProperty("Is")) {
+                    if (condition.Is == event.keyCode) {
+                        match = !condition.Not;
+                    }
+                    else {
+                        match = condition.Not;
+                    }
+                }
+                else if (condition.hasOwnProperty("From") && condition.hasOwnProperty("To")) {
+                    if (event.keyCode >= condition.From && event.keyCode <= condition.To) {
+                        match = !condition.Not;
+                    }
+                    else {
+                        match = condition.Not;
+                    }
+                }
+            }
+        };
+        for (var name in AutoComplete.prototype.getEventsByType(params, type)) {
+            var mapping = AutoComplete.merge({
+                Operator: ConditionOperator.AND
+            }, params.KeyboardMappings[name]), match = ConditionOperator.AND == mapping.Operator;
+            mapping.Conditions.forEach(eventIdentifier);
+            if (match === true) {
+                mapping.Callback.call(params, event);
+            }
+        }
+    };
+    AutoComplete.prototype.makeRequest = function (params, callback, callbackErr) {
+        var propertyHttpHeaders = Object.getOwnPropertyNames(params.HttpHeaders), request = new XMLHttpRequest(), method = params._HttpMethod(), url = params._Url(), queryParams = params._Pre(), queryParamsStringify = encodeURIComponent(params._QueryArg()) + "=" + encodeURIComponent(queryParams);
+        if (method.match(/^GET$/i)) {
+            if (url.indexOf("?") !== -1) {
+                url += "&" + queryParamsStringify;
+            }
+            else {
+                url += "?" + queryParamsStringify;
+            }
+        }
+        request.open(method, url, true);
+        for (var i = propertyHttpHeaders.length - 1; i >= 0; i--) {
+            request.setRequestHeader(propertyHttpHeaders[i], params.HttpHeaders[propertyHttpHeaders[i]]);
+        }
+        request.onreadystatechange = function () {
+            if (request.readyState == 4 && request.status == 200) {
+                params.$Cache[queryParams] = request.response;
+                callback(request.response);
+            }
+            else if (request.status >= 400) {
+                callbackErr();
+            }
+        };
+        return request;
+    };
+    AutoComplete.prototype.ajax = function (params, request, timeout) {
+        if (timeout === void 0) { timeout = true; }
+        if (params.$AjaxTimer) {
+            window.clearTimeout(params.$AjaxTimer);
+        }
+        if (timeout === true) {
+            params.$AjaxTimer = window.setTimeout(AutoComplete.prototype.ajax.bind(null, params, request, false), params.Delay);
+        }
+        else {
+            if (params.Request) {
+                params.Request.abort();
+            }
+            params.Request = request;
+            params.Request.send(params._QueryArg() + "=" + params._Pre());
+        }
+    };
+    AutoComplete.prototype.cache = function (params, callback, callbackErr) {
+        var response = params._Cache(params._Pre());
+        if (response === undefined) {
+            var request = AutoComplete.prototype.makeRequest(params, callback, callbackErr);
+            AutoComplete.prototype.ajax(params, request);
+        }
+        else {
+            callback(response);
+        }
+    };
+    AutoComplete.prototype.destroy = function (params) {
+        for (var event in params.$Listeners) {
+            params.Input.removeEventListener(event, params.$Listeners[event]);
+        }
+        params.DOMResults.parentNode.removeChild(params.DOMResults);
+    };
+    AutoComplete.merge = function () {
+        var merge = {}, tmp;
+        for (var i = 0; i < arguments.length; i++) {
+            for (tmp in arguments[i]) {
+                merge[tmp] = arguments[i][tmp];
+            }
+        }
+        return merge;
+    };
+    AutoComplete.defaults = {
+        Delay: 150,
+        EmptyMessage: "No result here",
+        Highlight: {
+            getRegex: function (value) {
+                return new RegExp(value, "ig");
+            },
+            transform: function (value) {
+                return "<strong>" + value + "</strong>";
+            }
+        },
+        HttpHeaders: {
+            "Content-type": "application/x-www-form-urlencoded"
+        },
+        Limit: 0,
+        MinChars: 0,
+        HttpMethod: "GET",
+        QueryArg: "q",
+        Url: null,
+        KeyboardMappings: {
+            "Enter": {
+                Conditions: [{
+                        Is: 13,
+                        Not: false
+                    }],
+                Callback: function (event) {
+                    if (this.DOMResults.getAttribute("class").indexOf("open") != -1) {
+                        var liActive = this.DOMResults.querySelector("li.active");
+                        if (liActive !== null) {
+                            event.preventDefault();
+                            this._Select(liActive);
+                            this.DOMResults.setAttribute("class", "autocomplete");
+                        }
+                    }
+                },
+                Operator: ConditionOperator.AND,
+                Event: EventType.KEYDOWN
+            },
+            "KeyUpAndDown_down": {
+                Conditions: [{
+                        Is: 38,
+                        Not: false
+                    },
+                    {
+                        Is: 40,
+                        Not: false
+                    }],
+                Callback: function (event) {
+                    event.preventDefault();
+                },
+                Operator: ConditionOperator.OR,
+                Event: EventType.KEYDOWN
+            },
+            "KeyUpAndDown_up": {
+                Conditions: [{
+                        Is: 38,
+                        Not: false
+                    },
+                    {
+                        Is: 40,
+                        Not: false
+                    }],
+                Callback: function (event) {
+                    event.preventDefault();
+                    var first = this.DOMResults.querySelector("li:first-child:not(.locked)"), last = this.DOMResults.querySelector("li:last-child:not(.locked)"), active = this.DOMResults.querySelector("li.active");
+                    if (active) {
+                        var currentIndex = Array.prototype.indexOf.call(active.parentNode.children, active), position = currentIndex + (event.keyCode - 39), lisCount = this.DOMResults.getElementsByTagName("li").length;
+                        if (position < 0) {
+                            position = lisCount - 1;
+                        }
+                        else if (position >= lisCount) {
+                            position = 0;
+                        }
+                        active.classList.remove("active");
+                        active.parentElement.children.item(position).classList.add("active");
+                    }
+                    else if (last && event.keyCode == 38) {
+                        last.classList.add("active");
+                    }
+                    else if (first) {
+                        first.classList.add("active");
+                    }
+                },
+                Operator: ConditionOperator.OR,
+                Event: EventType.KEYUP
+            },
+            "AlphaNum": {
+                Conditions: [{
+                        Is: 13,
+                        Not: true
+                    }, {
+                        From: 35,
+                        To: 40,
+                        Not: true
+                    }],
+                Callback: function () {
+                    var oldValue = this.Input.getAttribute("data-autocomplete-old-value"), currentValue = this._Pre();
+                    if (currentValue !== "" && currentValue.length >= this._MinChars()) {
+                        if (!oldValue || currentValue != oldValue) {
+                            this.DOMResults.setAttribute("class", "autocomplete open");
+                        }
+                        AutoComplete.prototype.cache(this, function (response) {
+                            this._Render(this._Post(response));
+                            this._Open();
+                        }.bind(this), this._Error);
+                    }
+                    else {
+                        this._Close();
+                    }
+                },
+                Operator: ConditionOperator.AND,
+                Event: EventType.KEYUP
+            }
+        },
+        DOMResults: null,
+        Request: null,
+        Input: null,
+        /**
+         * Return the message when no result returns
+         */
+        _EmptyMessage: function () {
+            var emptyMessage = "";
+            if (this.Input.hasAttribute("data-autocomplete-empty-message")) {
+                emptyMessage = this.Input.getAttribute("data-autocomplete-empty-message");
+            }
+            else if (this.EmptyMessage !== false) {
+                emptyMessage = this.EmptyMessage;
+            }
+            else {
+                emptyMessage = "";
+            }
+            return emptyMessage;
+        },
+        /**
+         * Returns the maximum number of results
+         */
+        _Limit: function () {
+            var limit = this.Input.getAttribute("data-autocomplete-limit");
+            if (isNaN(limit) || limit === null) {
+                return this.Limit;
+            }
+            return parseInt(limit, 10);
+        },
+        /**
+         * Returns the minimum number of characters entered before firing ajax
+         */
+        _MinChars: function () {
+            var minchars = this.Input.getAttribute("data-autocomplete-minchars");
+            if (isNaN(minchars) || minchars === null) {
+                return this.MinChars;
+            }
+            return parseInt(minchars, 10);
+        },
+        /**
+         * Apply transformation on labels response
+         */
+        _Highlight: function (label) {
+            return label.replace(this.Highlight.getRegex(this._Pre()), this.Highlight.transform);
+        },
+        /**
+         * Returns the HHTP method to use
+         */
+        _HttpMethod: function () {
+            if (this.Input.hasAttribute("data-autocomplete-method")) {
+                return this.Input.getAttribute("data-autocomplete-method");
+            }
+            return this.HttpMethod;
+        },
+        /**
+         * Returns the query param to use
+         */
+        _QueryArg: function () {
+            if (this.Input.hasAttribute("data-autocomplete-param-name")) {
+                return this.Input.getAttribute("data-autocomplete-param-name");
+            }
+            return this.QueryArg;
+        },
+        /**
+         * Returns the URL to use for AJAX request
+         */
+        _Url: function () {
+            if (this.Input.hasAttribute("data-autocomplete")) {
+                return this.Input.getAttribute("data-autocomplete");
+            }
+            return this.Url;
+        },
+        /**
+         * Manage the close
+         */
+        _Blur: function (now) {
+            if (now === void 0) { now = false; }
+            if (now) {
+                this._Close();
+            }
+            else {
+                var params = this;
+                setTimeout(function () {
+                    params._Blur(true);
+                }, 150);
+            }
+        },
+        /**
+         * Manage the cache
+         */
+        _Cache: function (value) {
+            return this.$Cache[value];
+        },
+        /**
+         * Manage the open
+         */
+        _Focus: function () {
+            var oldValue = this.Input.getAttribute("data-autocomplete-old-value");
+            if ((!oldValue || this.Input.value != oldValue) && this._MinChars() <= this.Input.value.length) {
+                this.DOMResults.setAttribute("class", "autocomplete open");
+            }
+        },
+        /**
+         * Bind all results item if one result is opened
+         */
+        _Open: function () {
+            var params = this;
+            Array.prototype.forEach.call(this.DOMResults.getElementsByTagName("li"), function (li) {
+                if (li.getAttribute("class") != "locked") {
+                    li.onclick = function () {
+                        params._Select(li);
+                    };
+                }
+            });
+        },
+        _Close: function () {
+            this.DOMResults.setAttribute("class", "autocomplete");
+        },
+        /**
+         * Position the results HTML element
+         */
+        _Position: function () {
+            this.DOMResults.setAttribute("class", "autocomplete");
+            this.DOMResults.setAttribute("style", "top:" + (this.Input.offsetTop + this.Input.offsetHeight) + "px;left:" + this.Input.offsetLeft + "px;width:" + this.Input.clientWidth + "px;");
+        },
+        /**
+         * Execute the render of results DOM element
+         */
+        _Render: function (response) {
+            var ul;
+            if (typeof response == "string") {
+                ul = this._RenderRaw(response);
+            }
+            else {
+                ul = this._RenderResponseItems(response);
+            }
+            if (this.DOMResults.hasChildNodes()) {
+                this.DOMResults.removeChild(this.DOMResults.childNodes[0]);
+            }
+            this.DOMResults.appendChild(ul);
+        },
+        /**
+         * ResponseItems[] rendering
+         */
+        _RenderResponseItems: function (response) {
+            var ul = document.createElement("ul"), li = document.createElement("li"), limit = this._Limit();
+            // Order
+            if (limit < 0) {
+                response = response.reverse();
+            }
+            else if (limit === 0) {
+                limit = response.length;
+            }
+            for (var item = 0; item < Math.min(Math.abs(limit), response.length); item++) {
+                li.innerHTML = response[item].Label;
+                li.setAttribute("data-autocomplete-value", response[item].Value);
+                ul.appendChild(li);
+                li = document.createElement("li");
+            }
+            return ul;
+        },
+        /**
+         * string response rendering (RAW HTML)
+         */
+        _RenderRaw: function (response) {
+            var ul = document.createElement("ul"), li = document.createElement("li");
+            if (response.length > 0) {
+                this.DOMResults.innerHTML = response;
+            }
+            else {
+                var emptyMessage = this._EmptyMessage();
+                if (emptyMessage !== "") {
+                    li.innerHTML = emptyMessage;
+                    li.setAttribute("class", "locked");
+                    ul.appendChild(li);
+                }
+            }
+            return ul;
+        },
+        /**
+         * Deal with request response
+         */
+        _Post: function (response) {
+            try {
+                var returnResponse = [];
+                //JSON return
+                var json = JSON.parse(response);
+                if (Object.keys(json).length === 0) {
+                    return "";
+                }
+                if (Array.isArray(json)) {
+                    for (var i = 0; i < Object.keys(json).length; i++) {
+                        returnResponse[returnResponse.length] = { "Value": json[i], "Label": this._Highlight(json[i]) };
+                    }
+                }
+                else {
+                    for (var value in json) {
+                        returnResponse.push({
+                            "Value": value,
+                            "Label": this._Highlight(json[value])
+                        });
+                    }
+                }
+                return returnResponse;
+            }
+            catch (event) {
+                //HTML return
+                return response;
+            }
+        },
+        /**
+         * Return the autocomplete value to send (before request)
+         */
+        _Pre: function () {
+            return this.Input.value;
+        },
+        /**
+         * Choice one result item
+         */
+        _Select: function (item) {
+            if (item.hasAttribute("data-autocomplete-value")) {
+                this.Input.value = item.getAttribute("data-autocomplete-value");
+            }
+            else {
+                this.Input.value = item.innerHTML;
+            }
+            this.Input.setAttribute("data-autocomplete-old-value", this.Input.value);
+        },
+        /**
+         * Handle HTTP error on the request
+         */
+        _Error: function () {
+        },
+        $AjaxTimer: null,
+        $Cache: {},
+        $Listeners: {}
+    };
+    return AutoComplete;
+}());
+module.exports = AutoComplete;
+
+},{}]},{},[1])(1)
+});
diff --git a/searx/static/themes/simple/js/searx.min.js b/searx/static/themes/simple/js/searx.min.js
index ce21068dc..b3057c324 100644
Binary files a/searx/static/themes/simple/js/searx.min.js and b/searx/static/themes/simple/js/searx.min.js differ
diff --git a/searx/static/themes/simple/js/searx.min.js.map b/searx/static/themes/simple/js/searx.min.js.map
index 511f22d67..beb478449 100644
Binary files a/searx/static/themes/simple/js/searx.min.js.map and b/searx/static/themes/simple/js/searx.min.js.map differ
diff --git a/searx/static/themes/simple/js/searx_src/autocomplete.js b/searx/static/themes/simple/js/searx_src/autocomplete.js
deleted file mode 100644
index b95fbcfb2..000000000
--- a/searx/static/themes/simple/js/searx_src/autocomplete.js
+++ /dev/null
@@ -1,536 +0,0 @@
-(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.AutoComplete = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
-/*
- * @license MIT
- *
- * Autocomplete.js v2.6.3
- * Developed by Baptiste Donaux
- * http://autocomplete-js.com
- *
- * (c) 2017, Baptiste Donaux
- */
-"use strict";
-var ConditionOperator;
-(function (ConditionOperator) {
-    ConditionOperator[ConditionOperator["AND"] = 0] = "AND";
-    ConditionOperator[ConditionOperator["OR"] = 1] = "OR";
-})(ConditionOperator || (ConditionOperator = {}));
-var EventType;
-(function (EventType) {
-    EventType[EventType["KEYDOWN"] = 0] = "KEYDOWN";
-    EventType[EventType["KEYUP"] = 1] = "KEYUP";
-})(EventType || (EventType = {}));
-/**
- * Core
- *
- * @class
- * @author Baptiste Donaux <baptiste.donaux@gmail.com> @baptistedonaux
- */
-var AutoComplete = (function () {
-    // Constructor
-    function AutoComplete(params, selector) {
-        if (params === void 0) { params = {}; }
-        if (selector === void 0) { selector = "[data-autocomplete]"; }
-        if (Array.isArray(selector)) {
-            selector.forEach(function (s) {
-                new AutoComplete(params, s);
-            });
-        }
-        else if (typeof selector == "string") {
-            var elements = document.querySelectorAll(selector);
-            Array.prototype.forEach.call(elements, function (input) {
-                new AutoComplete(params, input);
-            });
-        }
-        else {
-            var specificParams = AutoComplete.merge(AutoComplete.defaults, params, {
-                DOMResults: document.createElement("div")
-            });
-            AutoComplete.prototype.create(specificParams, selector);
-            return specificParams;
-        }
-    }
-    AutoComplete.prototype.create = function (params, element) {
-        params.Input = element;
-        if (params.Input.nodeName.match(/^INPUT$/i) && (params.Input.hasAttribute("type") === false || params.Input.getAttribute("type").match(/^TEXT|SEARCH$/i))) {
-            params.Input.setAttribute("autocomplete", "off");
-            params._Position(params);
-            params.Input.parentNode.appendChild(params.DOMResults);
-            params.$Listeners = {
-                blur: params._Blur.bind(params),
-                destroy: AutoComplete.prototype.destroy.bind(null, params),
-                focus: params._Focus.bind(params),
-                keyup: AutoComplete.prototype.event.bind(null, params, EventType.KEYUP),
-                keydown: AutoComplete.prototype.event.bind(null, params, EventType.KEYDOWN),
-                position: params._Position.bind(params)
-            };
-            for (var event in params.$Listeners) {
-                params.Input.addEventListener(event, params.$Listeners[event]);
-            }
-        }
-    };
-    AutoComplete.prototype.getEventsByType = function (params, type) {
-        var mappings = {};
-        for (var key in params.KeyboardMappings) {
-            var event = EventType.KEYUP;
-            if (params.KeyboardMappings[key].Event !== undefined) {
-                event = params.KeyboardMappings[key].Event;
-            }
-            if (event == type) {
-                mappings[key] = params.KeyboardMappings[key];
-            }
-        }
-        return mappings;
-    };
-    AutoComplete.prototype.event = function (params, type, event) {
-        var eventIdentifier = function (condition) {
-            if ((match === true && mapping.Operator == ConditionOperator.AND) || (match === false && mapping.Operator == ConditionOperator.OR)) {
-                condition = AutoComplete.merge({
-                    Not: false
-                }, condition);
-                if (condition.hasOwnProperty("Is")) {
-                    if (condition.Is == event.keyCode) {
-                        match = !condition.Not;
-                    }
-                    else {
-                        match = condition.Not;
-                    }
-                }
-                else if (condition.hasOwnProperty("From") && condition.hasOwnProperty("To")) {
-                    if (event.keyCode >= condition.From && event.keyCode <= condition.To) {
-                        match = !condition.Not;
-                    }
-                    else {
-                        match = condition.Not;
-                    }
-                }
-            }
-        };
-        for (var name in AutoComplete.prototype.getEventsByType(params, type)) {
-            var mapping = AutoComplete.merge({
-                Operator: ConditionOperator.AND
-            }, params.KeyboardMappings[name]), match = ConditionOperator.AND == mapping.Operator;
-            mapping.Conditions.forEach(eventIdentifier);
-            if (match === true) {
-                mapping.Callback.call(params, event);
-            }
-        }
-    };
-    AutoComplete.prototype.makeRequest = function (params, callback) {
-        var propertyHttpHeaders = Object.getOwnPropertyNames(params.HttpHeaders), request = new XMLHttpRequest(), method = params._HttpMethod(), url = params._Url(), queryParams = params._Pre(), queryParamsStringify = encodeURIComponent(params._QueryArg()) + "=" + encodeURIComponent(queryParams);
-        if (method.match(/^GET$/i)) {
-            if (url.indexOf("?") !== -1) {
-                url += "&" + queryParamsStringify;
-            }
-            else {
-                url += "?" + queryParamsStringify;
-            }
-        }
-        request.open(method, url, true);
-        for (var i = propertyHttpHeaders.length - 1; i >= 0; i--) {
-            request.setRequestHeader(propertyHttpHeaders[i], params.HttpHeaders[propertyHttpHeaders[i]]);
-        }
-        request.onreadystatechange = function () {
-            if (request.readyState == 4 && request.status == 200) {
-                params.$Cache[queryParams] = request.response;
-                callback(request.response);
-            }
-        };
-        return request;
-    };
-    AutoComplete.prototype.ajax = function (params, request, timeout) {
-        if (timeout === void 0) { timeout = true; }
-        if (params.$AjaxTimer) {
-            window.clearTimeout(params.$AjaxTimer);
-        }
-        if (timeout === true) {
-            params.$AjaxTimer = window.setTimeout(AutoComplete.prototype.ajax.bind(null, params, request, false), params.Delay);
-        }
-        else {
-            if (params.Request) {
-                params.Request.abort();
-            }
-            params.Request = request;
-            params.Request.send(params._QueryArg() + "=" + params._Pre());
-        }
-    };
-    AutoComplete.prototype.cache = function (params, callback) {
-        var response = params._Cache(params._Pre());
-        if (response === undefined) {
-            var request = AutoComplete.prototype.makeRequest(params, callback);
-            AutoComplete.prototype.ajax(params, request);
-        }
-        else {
-            callback(response);
-        }
-    };
-    AutoComplete.prototype.destroy = function (params) {
-        for (var event in params.$Listeners) {
-            params.Input.removeEventListener(event, params.$Listeners[event]);
-        }
-        params.DOMResults.parentNode.removeChild(params.DOMResults);
-    };
-    return AutoComplete;
-}());
-AutoComplete.merge = function () {
-    var merge = {}, tmp;
-    for (var i = 0; i < arguments.length; i++) {
-        for (tmp in arguments[i]) {
-            merge[tmp] = arguments[i][tmp];
-        }
-    }
-    return merge;
-};
-AutoComplete.defaults = {
-    Delay: 150,
-    EmptyMessage: "No result here",
-    Highlight: {
-        getRegex: function (value) {
-            return new RegExp(value, "ig");
-        },
-        transform: function (value) {
-            return "<strong>" + value + "</strong>";
-        }
-    },
-    HttpHeaders: {
-        "Content-type": "application/x-www-form-urlencoded"
-    },
-    Limit: 0,
-    MinChars: 0,
-    HttpMethod: "GET",
-    QueryArg: "q",
-    Url: null,
-    KeyboardMappings: {
-        "Enter": {
-            Conditions: [{
-                    Is: 13,
-                    Not: false
-                }],
-            Callback: function (event) {
-                if (this.DOMResults.getAttribute("class").indexOf("open") != -1) {
-                    var liActive = this.DOMResults.querySelector("li.active");
-                    if (liActive !== null) {
-                        event.preventDefault();
-                        this._Select(liActive);
-                        this.DOMResults.setAttribute("class", "autocomplete");
-                    }
-                }
-            },
-            Operator: ConditionOperator.AND,
-            Event: EventType.KEYDOWN
-        },
-        "KeyUpAndDown_down": {
-            Conditions: [{
-                    Is: 38,
-                    Not: false
-                },
-                {
-                    Is: 40,
-                    Not: false
-                }],
-            Callback: function (event) {
-                event.preventDefault();
-            },
-            Operator: ConditionOperator.OR,
-            Event: EventType.KEYDOWN
-        },
-        "KeyUpAndDown_up": {
-            Conditions: [{
-                    Is: 38,
-                    Not: false
-                },
-                {
-                    Is: 40,
-                    Not: false
-                }],
-            Callback: function (event) {
-                event.preventDefault();
-                var first = this.DOMResults.querySelector("li:first-child:not(.locked)"), last = this.DOMResults.querySelector("li:last-child:not(.locked)"), active = this.DOMResults.querySelector("li.active");
-                if (active) {
-                    var currentIndex = Array.prototype.indexOf.call(active.parentNode.children, active), position = currentIndex + (event.keyCode - 39), lisCount = this.DOMResults.getElementsByTagName("li").length;
-                    if (position < 0) {
-                        position = lisCount - 1;
-                    }
-                    else if (position >= lisCount) {
-                        position = 0;
-                    }
-                    active.classList.remove("active");
-                    active.parentElement.children.item(position).classList.add("active");
-                }
-                else if (last && event.keyCode == 38) {
-                    last.classList.add("active");
-                }
-                else if (first) {
-                    first.classList.add("active");
-                }
-            },
-            Operator: ConditionOperator.OR,
-            Event: EventType.KEYUP
-        },
-        "AlphaNum": {
-            Conditions: [{
-                    Is: 13,
-                    Not: true
-                }, {
-                    From: 35,
-                    To: 40,
-                    Not: true
-                }],
-            Callback: function () {
-                var oldValue = this.Input.getAttribute("data-autocomplete-old-value"), currentValue = this._Pre();
-                if (currentValue !== "" && currentValue.length >= this._MinChars()) {
-                    if (!oldValue || currentValue != oldValue) {
-                        this.DOMResults.setAttribute("class", "autocomplete open");
-                    }
-                    AutoComplete.prototype.cache(this, function (response) {
-                        this._Render(this._Post(response));
-                        this._Open();
-                    }.bind(this));
-                }
-            },
-            Operator: ConditionOperator.AND,
-            Event: EventType.KEYUP
-        }
-    },
-    DOMResults: null,
-    Request: null,
-    Input: null,
-    /**
-     * Return the message when no result returns
-     */
-    _EmptyMessage: function () {
-        var emptyMessage = "";
-        if (this.Input.hasAttribute("data-autocomplete-empty-message")) {
-            emptyMessage = this.Input.getAttribute("data-autocomplete-empty-message");
-        }
-        else if (this.EmptyMessage !== false) {
-            emptyMessage = this.EmptyMessage;
-        }
-        else {
-            emptyMessage = "";
-        }
-        return emptyMessage;
-    },
-    /**
-     * Returns the maximum number of results
-     */
-    _Limit: function () {
-        var limit = this.Input.getAttribute("data-autocomplete-limit");
-        if (isNaN(limit) || limit === null) {
-            return this.Limit;
-        }
-        return parseInt(limit, 10);
-    },
-    /**
-     * Returns the minimum number of characters entered before firing ajax
-     */
-    _MinChars: function () {
-        var minchars = this.Input.getAttribute("data-autocomplete-minchars");
-        if (isNaN(minchars) || minchars === null) {
-            return this.MinChars;
-        }
-        return parseInt(minchars, 10);
-    },
-    /**
-     * Apply transformation on labels response
-     */
-    _Highlight: function (label) {
-        return label.replace(this.Highlight.getRegex(this._Pre()), this.Highlight.transform);
-    },
-    /**
-     * Returns the HHTP method to use
-     */
-    _HttpMethod: function () {
-        if (this.Input.hasAttribute("data-autocomplete-method")) {
-            return this.Input.getAttribute("data-autocomplete-method");
-        }
-        return this.HttpMethod;
-    },
-    /**
-     * Returns the query param to use
-     */
-    _QueryArg: function () {
-        if (this.Input.hasAttribute("data-autocomplete-param-name")) {
-            return this.Input.getAttribute("data-autocomplete-param-name");
-        }
-        return this.QueryArg;
-    },
-    /**
-     * Returns the URL to use for AJAX request
-     */
-    _Url: function () {
-        if (this.Input.hasAttribute("data-autocomplete")) {
-            return this.Input.getAttribute("data-autocomplete");
-        }
-        return this.Url;
-    },
-    /**
-     * Manage the close
-     */
-    _Blur: function (now) {
-        if (now === true) {
-            this.DOMResults.setAttribute("class", "autocomplete");
-            this.Input.setAttribute("data-autocomplete-old-value", this.Input.value);
-        }
-        else {
-            var params = this;
-            setTimeout(function () {
-                params._Blur(true);
-            }, 150);
-        }
-    },
-    /**
-     * Manage the cache
-     */
-    _Cache: function (value) {
-        return this.$Cache[value];
-    },
-    /**
-     * Manage the open
-     */
-    _Focus: function () {
-        var oldValue = this.Input.getAttribute("data-autocomplete-old-value");
-        if ((!oldValue || this.Input.value != oldValue) && this._MinChars() <= this.Input.value.length) {
-            this.DOMResults.setAttribute("class", "autocomplete open");
-        }
-    },
-    /**
-     * Bind all results item if one result is opened
-     */
-    _Open: function () {
-        var params = this;
-        Array.prototype.forEach.call(this.DOMResults.getElementsByTagName("li"), function (li) {
-            if (li.getAttribute("class") != "locked") {
-		            li.onclick = function (event) {
-                    params._Select(li);
-                };
-                li.onmouseenter = function () {
-                    var active = params.DOMResults.querySelector("li.active");
-                    if (active !== li) {
-                        if (active !== null) {
-                            active.classList.remove("active");
-                        }
-                        li.classList.add("active");
-                    }
-                };
-            }
-        });
-    },
-    /**
-     * Position the results HTML element
-     */
-    _Position: function () {
-        this.DOMResults.setAttribute("class", "autocomplete");
-        this.DOMResults.setAttribute("style", "top:" + (this.Input.offsetTop + this.Input.offsetHeight) + "px;left:" + this.Input.offsetLeft + "px;width:" + this.Input.clientWidth + "px;");
-    },
-    /**
-     * Execute the render of results DOM element
-     */
-    _Render: function (response) {
-        var ul;
-        if (typeof response == "string") {
-            ul = this._RenderRaw(response);
-        }
-        else {
-            ul = this._RenderResponseItems(response);
-        }
-        if (this.DOMResults.hasChildNodes()) {
-            this.DOMResults.removeChild(this.DOMResults.childNodes[0]);
-        }
-        this.DOMResults.appendChild(ul);
-    },
-    /**
-     * ResponseItems[] rendering
-     */
-    _RenderResponseItems: function (response) {
-        var ul = document.createElement("ul"), li = document.createElement("li"), limit = this._Limit();
-        // Order
-        if (limit < 0) {
-            response = response.reverse();
-        }
-        else if (limit === 0) {
-            limit = response.length;
-        }
-        for (var item = 0; item < Math.min(Math.abs(limit), response.length); item++) {
-            li.innerHTML = response[item].Label;
-            li.setAttribute("data-autocomplete-value", response[item].Value);
-            ul.appendChild(li);
-            li = document.createElement("li");
-        }
-        return ul;
-    },
-    /**
-     * string response rendering (RAW HTML)
-     */
-    _RenderRaw: function (response) {
-        var ul = document.createElement("ul"), li = document.createElement("li");
-        if (response.length > 0) {
-            this.DOMResults.innerHTML = response;
-        }
-        else {
-            var emptyMessage = this._EmptyMessage();
-            if (emptyMessage !== "") {
-                li.innerHTML = emptyMessage;
-                li.setAttribute("class", "locked");
-                ul.appendChild(li);
-            }
-        }
-        return ul;
-    },
-    /**
-     * Deal with request response
-     */
-    _Post: function (response) {
-        try {
-            var returnResponse = [];
-            //JSON return
-            var json = JSON.parse(response);
-            if (Object.keys(json).length === 0) {
-                return "";
-            }
-            if (Array.isArray(json)) {
-                for (var i = 0; i < Object.keys(json).length; i++) {
-                    returnResponse[returnResponse.length] = { "Value": json[i], "Label": this._Highlight(json[i]) };
-                }
-            }
-            else {
-                for (var value in json) {
-                    returnResponse.push({
-                        "Value": value,
-                        "Label": this._Highlight(json[value])
-                    });
-                }
-            }
-            return returnResponse;
-        }
-        catch (event) {
-            //HTML return
-            return response;
-        }
-    },
-    /**
-     * Return the autocomplete value to send (before request)
-     */
-    _Pre: function () {
-        return this.Input.value;
-    },
-    /**
-     * Choice one result item
-     */
-    _Select: function (item) {
-	console.log('test test test');
-        if (item.hasAttribute("data-autocomplete-value")) {
-            this.Input.value = item.getAttribute("data-autocomplete-value");
-        }
-        else {
-            this.Input.value = item.innerHTML;
-        }
-        this.Input.setAttribute("data-autocomplete-old-value", this.Input.value);
-    },
-    $AjaxTimer: null,
-    $Cache: {},
-    $Listeners: {}
-};
-module.exports = AutoComplete;
-
-},{}]},{},[1])(1)
-});
diff --git a/searx/static/themes/simple/leaflet/images/marker-icon-2x-green.png b/searx/static/themes/simple/leaflet/images/marker-icon-2x-green.png
deleted file mode 100644
index 7446bb031..000000000
Binary files a/searx/static/themes/simple/leaflet/images/marker-icon-2x-green.png and /dev/null differ
diff --git a/searx/static/themes/simple/leaflet/images/marker-icon-2x-orange.png b/searx/static/themes/simple/leaflet/images/marker-icon-2x-orange.png
deleted file mode 100644
index ecd67736f..000000000
Binary files a/searx/static/themes/simple/leaflet/images/marker-icon-2x-orange.png and /dev/null differ
diff --git a/searx/static/themes/simple/leaflet/images/marker-icon-2x-red.png b/searx/static/themes/simple/leaflet/images/marker-icon-2x-red.png
deleted file mode 100644
index 1d2e197c6..000000000
Binary files a/searx/static/themes/simple/leaflet/images/marker-icon-2x-red.png and /dev/null differ
diff --git a/searx/static/themes/simple/leaflet/images/marker-icon-green.png b/searx/static/themes/simple/leaflet/images/marker-icon-green.png
deleted file mode 100644
index f48ef41df..000000000
Binary files a/searx/static/themes/simple/leaflet/images/marker-icon-green.png and /dev/null differ
diff --git a/searx/static/themes/simple/leaflet/images/marker-icon-orange.png b/searx/static/themes/simple/leaflet/images/marker-icon-orange.png
deleted file mode 100644
index d0d22205c..000000000
Binary files a/searx/static/themes/simple/leaflet/images/marker-icon-orange.png and /dev/null differ
diff --git a/searx/static/themes/simple/leaflet/images/marker-icon-red.png b/searx/static/themes/simple/leaflet/images/marker-icon-red.png
deleted file mode 100644
index 7a92b9e04..000000000
Binary files a/searx/static/themes/simple/leaflet/images/marker-icon-red.png and /dev/null differ
diff --git a/searx/static/themes/simple/leaflet/leaflet.css b/searx/static/themes/simple/leaflet/leaflet.css
deleted file mode 100644
index d1b47a125..000000000
Binary files a/searx/static/themes/simple/leaflet/leaflet.css and /dev/null differ
diff --git a/searx/static/themes/simple/leaflet/leaflet.js b/searx/static/themes/simple/leaflet/leaflet.js
deleted file mode 100644
index 02ae624a7..000000000
Binary files a/searx/static/themes/simple/leaflet/leaflet.js and /dev/null differ
diff --git a/searx/static/themes/simple/less/ion.less b/searx/static/themes/simple/less/ion.less
deleted file mode 100644
index c9e715591..000000000
--- a/searx/static/themes/simple/less/ion.less
+++ /dev/null
@@ -1,181 +0,0 @@
-// Generated by grunt-webfont
-
-
-@font-face {
-	font-family:"ion";
-	src:url("../fonts/ion.eot?ce7a0ead692560b4405a96d5b8471f51");
-	src:url("../fonts/ion.eot?#iefix") format("embedded-opentype"),
-		url("../fonts/ion.woff2?ce7a0ead692560b4405a96d5b8471f51") format("woff2"),
-		url("../fonts/ion.woff?ce7a0ead692560b4405a96d5b8471f51") format("woff"),
-		url("../fonts/ion.ttf?ce7a0ead692560b4405a96d5b8471f51") format("truetype"),
-		url("../fonts/ion.svg?ce7a0ead692560b4405a96d5b8471f51#ion") format("svg");
-	font-weight:normal;
-	font-style:normal;
-}
-
-.ion-icon {
-	&:before {
-		font-family:"ion";
-	}
-	display:inline-block;
-	vertical-align:middle;
-	line-height:1;
-	font-weight:normal;
-	font-style:normal;
-	speak:none;
-	text-decoration:inherit;
-	text-transform:none;
-	text-rendering:auto;
-	-webkit-font-smoothing:antialiased;
-	-moz-osx-font-smoothing:grayscale;
-}
-
-
-// Icons
-
-.ion-navicon-round {
-	&:before {
-		content:"\f101";
-	}
-}
-
-
-.ion-search {
-	&:before {
-		content:"\f102";
-	}
-}
-
-
-.ion-play {
-	&:before {
-		content:"\f103";
-	}
-}
-
-
-.ion-link {
-	&:before {
-		content:"\f104";
-	}
-}
-
-
-.ion-chevron-up {
-	&:before {
-		content:"\f105";
-	}
-}
-
-
-.ion-chevron-left {
-	&:before {
-		content:"\f106";
-	}
-}
-
-
-.ion-chevron-right {
-	&:before {
-		content:"\f107";
-	}
-}
-
-
-.ion-arrow-down-a {
-	&:before {
-		content:"\f108";
-	}
-}
-
-
-.ion-arrow-up-a {
-	&:before {
-		content:"\f109";
-	}
-}
-
-
-.ion-arrow-swap {
-	&:before {
-		content:"\f10a";
-	}
-}
-
-
-.ion-arrow-dropdown {
-	&:before {
-		content:"\f10b";
-	}
-}
-
-
-.ion-globe {
-	&:before {
-		content:"\f10c";
-	}
-}
-
-
-.ion-time {
-	&:before {
-		content:"\f10d";
-	}
-}
-
-
-.ion-location {
-	&:before {
-		content:"\f10e";
-	}
-}
-
-
-.ion-warning {
-	&:before {
-		content:"\f10f";
-	}
-}
-
-
-.ion-error {
-	&:before {
-		content:"\f110";
-	}
-}
-
-
-.ion-film-outline {
-	&:before {
-		content:"\f111";
-	}
-}
-
-
-.ion-music-note {
-	&:before {
-		content:"\f112";
-	}
-}
-
-
-.ion-more-vertical {
-	&:before {
-		content:"\f113";
-	}
-}
-
-
-.ion-magnet {
-	&:before {
-		content:"\f114";
-	}
-}
-
-
-.ion-close {
-	&:before {
-		content:"\f115";
-	}
-}
-
diff --git a/searx/static/themes/simple/package.json b/searx/static/themes/simple/package.json
index e84489947..22357a4c7 100644
--- a/searx/static/themes/simple/package.json
+++ b/searx/static/themes/simple/package.json
@@ -1,20 +1,28 @@
 {
   "devDependencies": {
-    "grunt": "~1.0.3",
+    "grunt-cli": "^1.4.3",
+    "grunt": "~1.4.1",
+    "grunt-contrib-copy": "^1.0.0",
     "grunt-contrib-concat": "~1.0.1",
-    "grunt-contrib-cssmin": "^2.2.1",
-    "grunt-contrib-jshint": "~1.1.0",
+    "grunt-contrib-cssmin": "^4.0.0",
+    "grunt-contrib-jshint": "~3.0.0",
     "grunt-contrib-less": "~3.0.0",
-    "grunt-contrib-uglify": "~3.4.0",
+    "grunt-contrib-uglify": "~5.0.1",
     "grunt-contrib-watch": "~1.1.0",
-    "grunt-webfont": "^1.7.1",
+    "grunt-webfont": "^1.7.2",
     "ionicons-npm": "^2.0.1",
-    "jslint": "^0.12.0",
+    "jslint": "^0.12.1",
+    "less": "^4.1.1",
     "less-plugin-clean-css": "^1.5.1"
   },
+  "dependencies": {
+    "autocomplete-js": "2.7.1",
+    "leaflet": "^1.7.1"
+  },
   "scripts": {
-    "build": "npm install && grunt",
-    "start": "grunt watch",
-    "test": "grunt"
+    "all": "npm install && grunt",
+    "build": "grunt",
+    "watch": "grunt watch",
+    "clean": "rm -Rf node_modules package-lock.json ion.less"
   }
 }
diff --git a/searx/static/themes/simple/magnet.svg b/searx/static/themes/simple/src/fonts/magnet.svg
similarity index 100%
rename from searx/static/themes/simple/magnet.svg
rename to searx/static/themes/simple/src/fonts/magnet.svg
diff --git a/searx/static/themes/simple/js/searx_head/00_init.js b/searx/static/themes/simple/src/js/head/00_init.js
similarity index 99%
rename from searx/static/themes/simple/js/searx_head/00_init.js
rename to searx/static/themes/simple/src/js/head/00_init.js
index e6964400b..be7560451 100644
--- a/searx/static/themes/simple/js/searx_head/00_init.js
+++ b/searx/static/themes/simple/src/js/head/00_init.js
@@ -33,7 +33,7 @@
         infinite_scroll: script.getAttribute('data-infinite-scroll') === 'true',
         static_path: script.getAttribute('data-static-path'),
         translations: JSON.parse(script.getAttribute('data-translations')),
-    }
+    };
 
     // update the css
     d.getElementsByTagName("html")[0].className = (w.searx.touch)?"js touch":"js";
diff --git a/searx/static/themes/simple/js/searx_src/00_searx_toolkit.js b/searx/static/themes/simple/src/js/main/00_searx_toolkit.js
similarity index 100%
rename from searx/static/themes/simple/js/searx_src/00_searx_toolkit.js
rename to searx/static/themes/simple/src/js/main/00_searx_toolkit.js
diff --git a/searx/static/themes/simple/js/searx_src/searx_keyboard.js b/searx/static/themes/simple/src/js/main/searx_keyboard.js
similarity index 100%
rename from searx/static/themes/simple/js/searx_src/searx_keyboard.js
rename to searx/static/themes/simple/src/js/main/searx_keyboard.js
diff --git a/searx/static/themes/simple/js/searx_src/searx_mapresult.js b/searx/static/themes/simple/src/js/main/searx_mapresult.js
similarity index 97%
rename from searx/static/themes/simple/js/searx_src/searx_mapresult.js
rename to searx/static/themes/simple/src/js/main/searx_mapresult.js
index 2ccdbd1c7..7b3982c11 100644
--- a/searx/static/themes/simple/js/searx_src/searx_mapresult.js
+++ b/searx/static/themes/simple/src/js/main/searx_mapresult.js
@@ -31,8 +31,8 @@
       var map_boundingbox = JSON.parse(this.dataset.mapBoundingbox);
       var map_geojson = JSON.parse(this.dataset.mapGeojson);
 
-      searx.loadStyle('leaflet/leaflet.css');
-      searx.loadScript('leaflet/leaflet.js', function() {
+      searx.loadStyle('css/leaflet.css');
+      searx.loadScript('js/leaflet.js', function() {
         var map_bounds = null;
         if(map_boundingbox) {
           var southWest = L.latLng(map_boundingbox[0], map_boundingbox[2]);
diff --git a/searx/static/themes/simple/js/searx_src/searx_results.js b/searx/static/themes/simple/src/js/main/searx_results.js
similarity index 100%
rename from searx/static/themes/simple/js/searx_src/searx_results.js
rename to searx/static/themes/simple/src/js/main/searx_results.js
diff --git a/searx/static/themes/simple/js/searx_src/searx_search.js b/searx/static/themes/simple/src/js/main/searx_search.js
similarity index 100%
rename from searx/static/themes/simple/js/searx_src/searx_search.js
rename to searx/static/themes/simple/src/js/main/searx_search.js
diff --git a/searx/static/themes/simple/less/autocomplete.less b/searx/static/themes/simple/src/less/autocomplete.less
similarity index 100%
rename from searx/static/themes/simple/less/autocomplete.less
rename to searx/static/themes/simple/src/less/autocomplete.less
diff --git a/searx/static/themes/simple/less/code.less b/searx/static/themes/simple/src/less/code.less
similarity index 100%
rename from searx/static/themes/simple/less/code.less
rename to searx/static/themes/simple/src/less/code.less
diff --git a/searx/static/themes/simple/less/definitions.less b/searx/static/themes/simple/src/less/definitions.less
similarity index 100%
rename from searx/static/themes/simple/less/definitions.less
rename to searx/static/themes/simple/src/less/definitions.less
diff --git a/searx/static/themes/simple/less/index.less b/searx/static/themes/simple/src/less/index.less
similarity index 100%
rename from searx/static/themes/simple/less/index.less
rename to searx/static/themes/simple/src/less/index.less
diff --git a/searx/static/themes/simple/less/mixins.less b/searx/static/themes/simple/src/less/mixins.less
similarity index 100%
rename from searx/static/themes/simple/less/mixins.less
rename to searx/static/themes/simple/src/less/mixins.less
diff --git a/searx/static/themes/simple/less/normalize.less b/searx/static/themes/simple/src/less/normalize.less
similarity index 100%
rename from searx/static/themes/simple/less/normalize.less
rename to searx/static/themes/simple/src/less/normalize.less
diff --git a/searx/static/themes/simple/less/preferences.less b/searx/static/themes/simple/src/less/preferences.less
similarity index 100%
rename from searx/static/themes/simple/less/preferences.less
rename to searx/static/themes/simple/src/less/preferences.less
diff --git a/searx/static/themes/simple/less/pygments.less b/searx/static/themes/simple/src/less/pygments.less
similarity index 100%
rename from searx/static/themes/simple/less/pygments.less
rename to searx/static/themes/simple/src/less/pygments.less
diff --git a/searx/static/themes/simple/less/search.less b/searx/static/themes/simple/src/less/search.less
similarity index 100%
rename from searx/static/themes/simple/less/search.less
rename to searx/static/themes/simple/src/less/search.less
diff --git a/searx/static/themes/simple/less/style-rtl.less b/searx/static/themes/simple/src/less/style-rtl.less
similarity index 100%
rename from searx/static/themes/simple/less/style-rtl.less
rename to searx/static/themes/simple/src/less/style-rtl.less
diff --git a/searx/static/themes/simple/less/style.less b/searx/static/themes/simple/src/less/style.less
similarity index 99%
rename from searx/static/themes/simple/less/style.less
rename to searx/static/themes/simple/src/less/style.less
index 61c0a93de..82fcb00ed 100644
--- a/searx/static/themes/simple/less/style.less
+++ b/searx/static/themes/simple/src/less/style.less
@@ -22,7 +22,7 @@
 @import "autocomplete.less";
 
 // ion-icon
-@import "ion.less";
+@import "../../ion.less";
 
 .ion-icon-big {
   .ion-icon;
diff --git a/searx/static/themes/simple/less/toolkit.less b/searx/static/themes/simple/src/less/toolkit.less
similarity index 100%
rename from searx/static/themes/simple/less/toolkit.less
rename to searx/static/themes/simple/src/less/toolkit.less
diff --git a/searx/static/themes/simple/less/toolkit_loader.less b/searx/static/themes/simple/src/less/toolkit_loader.less
similarity index 100%
rename from searx/static/themes/simple/less/toolkit_loader.less
rename to searx/static/themes/simple/src/less/toolkit_loader.less
diff --git a/utils/searx.sh b/utils/searx.sh
index a5345c075..69068b914 100755
--- a/utils/searx.sh
+++ b/utils/searx.sh
@@ -51,7 +51,8 @@ shellcheck"
 BUILD_PACKAGES_debian="\
 firefox graphviz imagemagick texlive-xetex librsvg2-bin
 texlive-latex-recommended texlive-extra-utils fonts-dejavu
-latexmk"
+latexmk fontforge ttfautohint
+npm"
 
 # pacman packages
 SEARX_PACKAGES_arch="\
@@ -62,7 +63,8 @@ shellcheck"
 
 BUILD_PACKAGES_arch="\
 firefox graphviz imagemagick texlive-bin extra/librsvg
-texlive-core texlive-latexextra ttf-dejavu"
+texlive-core texlive-latexextra ttf-dejavu fontforge ttfautohint
+npm"
 
 # dnf packages
 SEARX_PACKAGES_fedora="\
@@ -75,9 +77,16 @@ BUILD_PACKAGES_fedora="\
 firefox graphviz graphviz-gd ImageMagick librsvg2-tools
 texlive-xetex-bin texlive-collection-fontsrecommended
 texlive-collection-latex dejavu-sans-fonts dejavu-serif-fonts
-dejavu-sans-mono-fonts"
+dejavu-sans-mono-fonts fontforge ttfautohint
+npm"
 
 # yum packages
+#
+# hint: We do no longer support yum packages, it is to complex to maintain
+#       automate installation of packages like npm.  In the firts step we ignore
+#       CentOS-7 as developer & build platform (the inital patch which brought
+#       CentOS-7 supports was not intended to be a developer platform).
+
 SEARX_PACKAGES_centos="\
 python36 python36-pip python36-lxml python-babel
 uwsgi uwsgi-plugin-python3