diff --git a/web/gulpfile.js b/web/gulpfile.js
index f920f195fac99afa76558a2860bc945dea354da7..63f6fa7e8adb5719283e2e53c8604c2f4998e6a9 100644
--- a/web/gulpfile.js
+++ b/web/gulpfile.js
@@ -1,8 +1,10 @@
 // load all plugins in 'devDependencies' into the variable $
 var gulp = require('gulp');
+
 const $ = require('gulp-load-plugins')({
         rename: {
-            'gulp-add-src': 'add_src'
+            'gulp-add-src': 'add_src',
+            'webpack-stream': 'webpack_stream',
         },
         pattern: ['*'],
         scope: ['devDependencies']
@@ -29,10 +31,17 @@ gulp.task('scss', () => {
         .pipe(gulp.dest(pkg.paths.dist.css));
 });
 
+gulp.task('webpack', () => {
+    return gulp.src(pkg.main)
+        .pipe($.webpack_stream())
+        .pipe(gulp.dest(pkg.paths.dist.js));
+});
+
 gulp.task('watch', function() {
   // gulp.watch(kubedir + 'src/_js/*.js', ['scripts']);
   gulp.watch(pkg.paths.src.scss + pkg.vars.scssName, ['scss']);
   gulp.watch('./package.json', ['scss']);
+  gulp.watch(pkg.paths.src.js, ['webpack']);
 });
 
-gulp.task('default', ['scss', 'watch'])
+gulp.task('default', ['scss', 'webpack', 'watch'])
diff --git a/web/package.json b/web/package.json
index 4b6b4f4f0eccd876b757e454934099975490fcea..e1e2000c14c35ffbd7a9e249c09305f14a667169 100644
--- a/web/package.json
+++ b/web/package.json
@@ -1,155 +1,164 @@
 {
-    "name": "opengeosys.org",
-    "version": "0.0.1",
-    "main": "index.js",
-    "repository": "https://github.com/ufz/ogs",
-    "author": "Lars Bilke",
-    "license": "BSD",
-    "paths": {
-        "src": {
-            "base": "./src/",
-            "css": "./src/css/",
-            "fontello": "./src/fontello/",
-            "fonts": "./src/fonts/",
-            "json": "./src/json/",
-            "js": "./src/js/",
-            "img": "./src/img/",
-            "scss": "./src/scss/"
-        },
-        "dist": {
-            "base": "./themes/ogs/static/",
-            "css": "./themes/ogs/static/css/",
-            "js": "./themes/ogs/static/js/",
-            "fonts": "./themes/ogs/static/fonts/",
-            "img": "./themes/ogs/static/img/"
-        },
-        "build": {
-            "base": "./build/",
-            "css": "./build/css/",
-            "fontello": "./build/fonts/fontello/",
-            "fonts": "./build/fonts/",
-            "js": "./build/js/",
-            "html": "./build/html/",
-            "img": "./build/img/"
-        },
-        "favicon": {
-            "src": "./src/img/favicon_src.png",
-            "dest": "./themes/ogs/static/img/site/",
-            "path": "/img/site/"
-        },
-        "scss": [],
-        "templates": "./craft/templates/"
+  "name": "opengeosys.org",
+  "version": "0.0.1",
+  "main": "src/js/app.js",
+  "repository": "https://github.com/ufz/ogs",
+  "author": "Lars Bilke",
+  "license": "BSD",
+  "scripts": {
+    "build": "webpack",
+    "build:release": "export NODE_ENV=production && webpack -p",
+    "start": "webpack-dev-server --content-base ./public"
+  },
+  "paths": {
+    "src": {
+      "base": "./src/",
+      "css": "./src/css/",
+      "fontello": "./src/fontello/",
+      "fonts": "./src/fonts/",
+      "json": "./src/json/",
+      "js": "./src/js/",
+      "img": "./src/img/",
+      "scss": "./src/scss/"
     },
-    "urls": {
-        "live": "https://opengeosys.org/",
-        "local": "https://opengeosys.dev/",
-        "critical": "https://opengeosys.org/"
+    "dist": {
+      "base": "./themes/ogs/static/",
+      "css": "./themes/ogs/static/css/",
+      "js": "./themes/ogs/static/js/",
+      "fonts": "./themes/ogs/static/fonts/",
+      "img": "./themes/ogs/static/img/"
     },
-    "vars": {
-        "siteCssName": "site.combined.min.css",
-        "scssName": "main.scss",
-        "cssName": "main.css"
+    "build": {
+      "base": "./build/",
+      "css": "./build/css/",
+      "fontello": "./build/fonts/fontello/",
+      "fonts": "./build/fonts/",
+      "js": "./build/js/",
+      "html": "./build/html/",
+      "img": "./build/img/"
     },
-    "globs": {
-        "distCss": [
-            "./node_modules/normalize.css/normalize.css",
-            "./node_modules/flexboxgrid/dist/flexboxgrid.min.css",
-            "./src/css/prism-theme.css",
-            "./build/fonts/fontello/css/fontello-codes.css",
-            "./build/css/*.css",
-            "./src/css/*.css"
-        ],
-        "img": [
-            "./public/img/"
-        ],
-        "components": [
-            "./src/components/**/*.vue"
-        ],
-        "fonts": [
-            "./build/fonts/fontello/font/*.{eot,ttf,woff,woff2}",
-            "./src/fonts/*.{eot,ttf,woff,woff2}"
-        ],
-        "critical": [
-            {
-                "url": "",
-                "template": "index"
-            },
-            {
-                "url": "blog/stop-using-htaccess-files-no-really",
-                "template": "blog/_entry"
-            },
-            {
-                "url": "blog/stop-using-htaccess-files-no-really",
-                "template": "blog/_amp_entry"
-            },
-            {
-                "url": "blog",
-                "template": "blog/index"
-            },
-            {
-                "url": "blog",
-                "template": "blog/amp_index"
-            },
-            {
-                "url": "wordpress",
-                "template": "wordpress"
-            },
-            {
-                "url": "404",
-                "template": "404"
-            }
-        ],
-        "distJs": [
-            "./build/js/*.js",
-            "./node_modules/lazysizes/lazysizes.min.js",
-            "./node_modules/lazysizes/plugins/bgset/ls.bgset.min.js",
-            "./node_modules/picturefill/dist/picturefill.min.js",
-            "./node_modules/vue/dist/vue.min.js",
-            "./node_modules/vue-resource/dist/vue-resource.min.js"
-        ],
-        "prismJs": [
-            "./node_modules/prismjs/prism.js",
-            "./node_modules/prismjs/components/prism-markup.js",
-            "./node_modules/prismjs/components/prism-apacheconf.js",
-            "./node_modules/prismjs/components/prism-css.js",
-            "./node_modules/prismjs/components/prism-json.js",
-            "./node_modules/prismjs/components/prism-twig.js",
-            "./node_modules/prismjs/components/prism-php.js",
-            "./node_modules/prismjs/components/prism-bash.js",
-            "./node_modules/prismjs/components/prism-javascript.js",
-            "./node_modules/prismjs/plugins/line-numbers/prism-line-numbers.min.js"
-        ],
-        "systemJs": [
-            "./node_modules/systemjs/dist/system-polyfills.js",
-            "./node_modules/systemjs/dist/system.js",
-            "./src/js/system-config.js"
-        ],
-        "babelJs": [
-            "./src/js/*.js"
-        ],
-        "inlineJs": [
-            "./node_modules/fg-loadcss/src/loadCSS.js",
-            "./node_modules/fg-loadcss/src/cssrelpreload.js",
-            "./node_modules/fontfaceobserver/fontfaceobserver.js",
-            "./src/js/asyncload-blog-fonts.js",
-            "./src/js/asyncload-site-fonts.js"
-        ],
-        "siteIcon": "./public/img/site/favicon.*"
+    "favicon": {
+      "src": "./src/img/favicon_src.png",
+      "dest": "./themes/ogs/static/img/site/",
+      "path": "/img/site/"
     },
-    "devDependencies": {
-        "fancy-log": "^1.3.0",
-        "flexboxgrid": "^6.3.1",
-        "gulp": "^3.9.1",
-        "gulp-add-src": "^0.2.0",
-        "gulp-autoprefixer": "^3.1.1",
-        "gulp-cached": "^1.1.1",
-        "gulp-concat": "^2.6.1",
-        "gulp-load-plugins": "^1.4.0",
-        "gulp-plumber": "^1.1.0",
-        "gulp-rename": "^1.2.2",
-        "gulp-sass": "^3.1.0",
-        "gulp-size": "^2.1.0",
-        "normalize.css": "^5.0.0",
-        "pygments-css": "^1.0.0"
-    }
+    "scss": [],
+    "templates": "./craft/templates/"
+  },
+  "urls": {
+    "live": "https://opengeosys.org/",
+    "local": "https://opengeosys.dev/",
+    "critical": "https://opengeosys.org/"
+  },
+  "vars": {
+    "siteCssName": "site.combined.min.css",
+    "scssName": "main.scss",
+    "cssName": "main.css"
+  },
+  "globs": {
+    "distCss": [
+      "./node_modules/normalize.css/normalize.css",
+      "./node_modules/flexboxgrid/dist/flexboxgrid.min.css",
+      "./src/css/prism-theme.css",
+      "./build/fonts/fontello/css/fontello-codes.css",
+      "./build/css/*.css",
+      "./src/css/*.css"
+    ],
+    "img": [
+      "./public/img/"
+    ],
+    "components": [
+      "./src/components/**/*.vue"
+    ],
+    "fonts": [
+      "./build/fonts/fontello/font/*.{eot,ttf,woff,woff2}",
+      "./src/fonts/*.{eot,ttf,woff,woff2}"
+    ],
+    "critical": [
+      {
+        "url": "",
+        "template": "index"
+      },
+      {
+        "url": "blog/stop-using-htaccess-files-no-really",
+        "template": "blog/_entry"
+      },
+      {
+        "url": "blog/stop-using-htaccess-files-no-really",
+        "template": "blog/_amp_entry"
+      },
+      {
+        "url": "blog",
+        "template": "blog/index"
+      },
+      {
+        "url": "blog",
+        "template": "blog/amp_index"
+      },
+      {
+        "url": "wordpress",
+        "template": "wordpress"
+      },
+      {
+        "url": "404",
+        "template": "404"
+      }
+    ],
+    "distJs": [
+      "./build/js/*.js",
+      "./node_modules/lazysizes/lazysizes.min.js",
+      "./node_modules/lazysizes/plugins/bgset/ls.bgset.min.js",
+      "./node_modules/picturefill/dist/picturefill.min.js",
+      "./node_modules/vue/dist/vue.min.js",
+      "./node_modules/vue-resource/dist/vue-resource.min.js"
+    ],
+    "prismJs": [
+      "./node_modules/prismjs/prism.js",
+      "./node_modules/prismjs/components/prism-markup.js",
+      "./node_modules/prismjs/components/prism-apacheconf.js",
+      "./node_modules/prismjs/components/prism-css.js",
+      "./node_modules/prismjs/components/prism-json.js",
+      "./node_modules/prismjs/components/prism-twig.js",
+      "./node_modules/prismjs/components/prism-php.js",
+      "./node_modules/prismjs/components/prism-bash.js",
+      "./node_modules/prismjs/components/prism-javascript.js",
+      "./node_modules/prismjs/plugins/line-numbers/prism-line-numbers.min.js"
+    ],
+    "systemJs": [
+      "./node_modules/systemjs/dist/system-polyfills.js",
+      "./node_modules/systemjs/dist/system.js",
+      "./src/js/system-config.js"
+    ],
+    "babelJs": [
+      "./src/js/*.js"
+    ],
+    "inlineJs": [
+      "./node_modules/fg-loadcss/src/loadCSS.js",
+      "./node_modules/fg-loadcss/src/cssrelpreload.js",
+      "./node_modules/fontfaceobserver/fontfaceobserver.js",
+      "./src/js/asyncload-blog-fonts.js",
+      "./src/js/asyncload-site-fonts.js"
+    ],
+    "siteIcon": "./public/img/site/favicon.*"
+  },
+  "devDependencies": {
+    "fancy-log": "^1.3.0",
+    "flexboxgrid": "^6.3.1",
+    "gulp": "^3.9.1",
+    "gulp-add-src": "^0.2.0",
+    "gulp-autoprefixer": "^3.1.1",
+    "gulp-cached": "^1.1.1",
+    "gulp-concat": "^2.6.1",
+    "gulp-load-plugins": "^1.4.0",
+    "gulp-plumber": "^1.1.0",
+    "gulp-rename": "^1.2.2",
+    "gulp-sass": "^3.1.0",
+    "gulp-size": "^2.1.0",
+    "kw-web-suite": "^2.2.1",
+    "normalize.css": "^5.0.0",
+    "pygments-css": "^1.0.0",
+    "vtk.js": "^2.10.0",
+    "webpack": "^1.14.0",
+    "webpack-stream": "^3.2.0"
+  }
 }
diff --git a/web/src/js/app.js b/web/src/js/app.js
new file mode 100644
index 0000000000000000000000000000000000000000..974e4e9c34eaf2204231aee2b88ceca08cbe82e4
--- /dev/null
+++ b/web/src/js/app.js
@@ -0,0 +1,102 @@
+
+import vtkFullScreenRenderWindow from 'vtk.js/Sources/Rendering/Misc/FullScreenRenderWindow';
+import vtkActor           from 'vtk.js/Sources/Rendering/Core/Actor';
+import vtkLookupTable     from 'vtk.js/Sources/Common/Core/LookupTable';
+import vtkMapper          from 'vtk.js/Sources/Rendering/Core/Mapper';
+import { ColorMode, ScalarMode }  from 'vtk.js/Sources/Rendering/Core/Mapper/Constants';
+import { AttributeTypes } from 'vtk.js/Sources/Common/DataModel/DataSetAttributes/Constants';
+import { FieldDataTypes } from 'vtk.js/Sources/Common/DataModel/DataSet/Constants';
+import vtkHttpDataSetReader       from 'vtk.js/Sources/IO/Core/HttpDataSetReader';
+import vtkInteractorStyleTrackballCamera from 'vtk.js/Sources/Interaction/Style/InteractorStyleTrackballCamera'
+import controlPanel from './controller.html';
+// ----------------------------------------------------------------------------
+// Standard rendering code setup
+// ----------------------------------------------------------------------------
+const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance();
+const renderer = fullScreenRenderer.getRenderer();
+const renderWindow = fullScreenRenderer.getRenderWindow();
+
+// -------------
+// DataSetReader
+// -------------
+const reader = vtkHttpDataSetReader.newInstance({ enableArray: true, fetchGzip: true });
+// reader.setUrl(`./data/square_1e2_neumann_pcs_0_ts_1_t_1.000000.vtu`).then((reader, dataset) => {
+reader.setUrl(`./data/square_1e4_pcs_0_ts_1_t_1.000000.vtu`).then((reader, dataset) => {
+  console.log('Metadata loaded with the geometry', dataset);
+  reader.loadData().then((reader, dataset) =>{
+    var indexPointData = 0;
+    var indexCellData = 0;
+    reader.getArrays().forEach((array, i) => {
+      console.log('-', array.name, array.location, ':', array.enable);
+      if(array.location == 'pointData') {
+        console.log('  - Range: ',
+          array.ds[0].pointData.arrays[indexPointData].data.ranges[0].min,
+          '-',
+          array.ds[0].pointData.arrays[indexPointData].data.ranges[0].max
+        );
+        indexPointData++;
+      } else {
+          console.log('  - Range: ',
+            array.ds[0].cellData.arrays[indexCellData].data.ranges[0].min,
+            '-',
+            array.ds[0].cellData.arrays[indexCellData].data.ranges[0].max
+          );
+          indexCellData++;
+      }
+    });
+  });
+});
+
+const lookupTable = vtkLookupTable.newInstance({
+  hueRange: [0.0, 0.33],
+  mappingRange: [-1, 1],
+});
+
+const mapper = vtkMapper.newInstance({
+  interpolateScalarsBeforeMapping: true,
+  colorMode: ColorMode.MAP_SCALARS,
+  scalarMode: ScalarMode.USE_POINT_FIELD_DATA,
+  useLookupTableScalarRange: true,
+  lookupTable,
+});
+mapper.setInputConnection(reader.getOutputPort());
+mapper.setColorByArrayName('pressure')
+
+const actor = vtkActor.newInstance();
+var property = actor.getProperty();
+property.setEdgeVisibility(true);
+
+actor.setMapper(mapper);
+renderer.addActor(actor);
+
+renderer.resetCamera();
+renderWindow.render();
+
+
+// -----------------------------------------------------------
+// UI control handling
+// -----------------------------------------------------------
+fullScreenRenderer.addController(controlPanel);
+const representationSelector = document.querySelector('.representations');
+const resolutionChange = document.querySelector('.resolution');
+representationSelector.addEventListener('change', (e) => {
+  const newRepValue = Number(e.target.value);
+  actor.getProperty().setRepresentation(newRepValue);
+  renderWindow.render();
+});
+resolutionChange.addEventListener('input', (e) => {
+  const value = Number(e.target.value);
+  actor.getProperty().setOpacity(value / 100.0)
+  renderWindow.render();
+});
+
+// -----------------------------------------------------------
+// globals for inspecting
+// -----------------------------------------------------------
+global.mapper = mapper;
+global.actor = actor;
+global.renderer = renderer;
+global.renderWindow = renderWindow;
+
+global.reader = reader;
+global.lut = lookupTable;
diff --git a/web/src/js/controller.html b/web/src/js/controller.html
new file mode 100644
index 0000000000000000000000000000000000000000..fd93578a90af474280225232d2d155e8115dafbf
--- /dev/null
+++ b/web/src/js/controller.html
@@ -0,0 +1,16 @@
+<table>
+  <tr>
+    <td>
+      <select class='representations' style="width: 100%">
+        <option value='0'>Points</option>
+        <option value='1'>Wireframe</option>
+        <option value='2' selected>Surface</option>
+      </select>
+    </td>
+  </tr>
+  <tr>
+    <td>
+      <input class='resolution' type='range' min='0' max='100' value='100' />
+    </td>
+  </tr>
+</table>
diff --git a/web/themes/ogs/layouts/partials/footer.html b/web/themes/ogs/layouts/partials/footer.html
index e88706a756b878a9619080a21036e4c1babaedf1..7cfc95dd6bbfb52599ae1c1874d5c7388905ffea 100644
--- a/web/themes/ogs/layouts/partials/footer.html
+++ b/web/themes/ogs/layouts/partials/footer.html
@@ -1,5 +1,6 @@
 <!-- <script src="js/scripts.js"></script> -->
     </div> <!-- row -->
   </div> <!-- container -->
+<script src="/js/bundle.js"></script>
 </body>
 </html>
diff --git a/web/webpack.config.js b/web/webpack.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..b1acb055462c1cbbacf5088ac89b37a6b531e978
--- /dev/null
+++ b/web/webpack.config.js
@@ -0,0 +1,31 @@
+const pkg = require('./package.json');
+
+var path = require('path'),
+    webpack = require('webpack'),
+    loaders = require('./node_modules/vtk.js/Utilities/config/webpack.loaders.js'),
+    plugins = [];
+if(process.env.NODE_ENV === 'production') {
+    console.log('==> Production build');
+    plugins.push(new webpack.DefinePlugin({
+        "process.env": {
+            NODE_ENV: JSON.stringify("production"),
+        },
+    }));
+}
+
+module.exports = {
+  plugins: plugins,
+  entry: pkg.paths.src.js + '/app.js',
+  output: {
+    path: pkg.paths.dist.js,
+    filename: 'bundle.js',
+  },
+  module: {
+      loaders: [
+          { test: require.resolve("./src/js/app.js"), loader: "expose-loader?MyWebApp" },
+      ].concat(loaders),
+    },
+    postcss: [
+      require('autoprefixer')({ browsers: ['last 2 versions'] }),
+    ],
+};