diff --git a/package-lock.json b/package-lock.json
index e8d174db64f94695fff7d142ab262682782e2d2e..7e574f630d3b78f7f2f79cd0b910a5f38116cbf4 100644
Binary files a/package-lock.json and b/package-lock.json differ
diff --git a/package.json b/package.json
index 2a60147a8a2c4f21c85e8a287ddb62b204c4fa0f..4ec4c0c13a5389960600ee76b858fb7d1aafb13b 100644
--- a/package.json
+++ b/package.json
@@ -12,13 +12,12 @@
   },
   "scripts": {
     "lint": "tslint src/**/*.ts",
-    "build": "cpx \"src/**/*.{vert,frag,glsl}\" build/node_modules/ && tsc",
+    "build": "cpx \"src/**/*.{vert,frag,glsl,scss,woff,woff2,ttf,otf,eot,svg,html}\" build/node_modules/ && tsc",
     "watch": "tsc -watch",
-    "watch-shader": "cpx \"src/**/*.{vert,frag,glsl}\" build/node_modules/ --watch",
+    "watch-extra": "cpx \"src/**/*.{vert,frag,glsl,scss,woff,woff2,ttf,otf,eot,svg,html}\" build/node_modules/ --watch",
     "test": "jest",
-    "script": "node build/node_modules/script.js",
-    "app-render-test": "webpack build/node_modules/apps/render-test/index.js --mode development -o web/render-test/index.js",
-    "app-render-test-watch": "webpack build/node_modules/apps/render-test/index.js -w --mode development -o web/render-test/index.js"
+    "build-viewer": "webpack build/node_modules/apps/viewer/index.js --mode development -o build/viewer/index.js",
+    "watch-viewer": "webpack build/node_modules/apps/viewer/index.js -w --mode development -o build/viewer/index.js"
   },
   "jest": {
     "moduleFileExtensions": [
@@ -34,6 +33,7 @@
       "build/node_modules"
     ],
     "moduleNameMapper": {
+      "mol-app($|/.*)": "<rootDir>/src/mol-app$1",
       "mol-data($|/.*)": "<rootDir>/src/mol-data$1",
       "mol-geo($|/.*)": "<rootDir>/src/mol-geo$1",
       "mol-gl($|/.*)": "<rootDir>/src/mol-gl$1",
@@ -60,35 +60,42 @@
     "@types/benchmark": "^1.0.31",
     "@types/express": "^4.11.1",
     "@types/jest": "^22.2.3",
-    "@types/node": "^9.6.8",
+    "@types/node": "^9.6.16",
     "@types/node-fetch": "^1.6.9",
-    "@types/react": "^16.3.13",
+    "@types/react": "^16.3.14",
     "@types/react-dom": "^16.0.5",
     "benchmark": "^2.1.4",
     "copyfiles": "^2.0.0",
     "cpx": "^1.5.0",
+    "css-loader": "^0.28.11",
     "extra-watch-webpack-plugin": "^1.0.3",
+    "extract-text-webpack-plugin": "^4.0.0-beta.0",
+    "file-loader": "^1.1.11",
     "glslify-import": "^3.1.0",
     "glslify-loader": "^1.0.2",
     "jest": "^22.4.3",
     "jest-raw-loader": "^1.0.1",
+    "node-sass": "^4.9.0",
     "raw-loader": "^0.5.1",
-    "ts-jest": "^22.4.4",
-    "tslint": "^5.9.1",
+    "resolve-url-loader": "^2.3.0",
+    "sass-loader": "^7.0.1",
+    "style-loader": "^0.21.0",
+    "ts-jest": "^22.4.6",
+    "tslint": "^5.10.0",
     "typescript": "^2.8.3",
-    "uglify-js": "^3.3.23",
+    "uglify-js": "^3.3.25",
     "util.promisify": "^1.0.0",
-    "webpack": "^4.6.0",
-    "webpack-cli": "^2.1.2"
+    "webpack": "^4.8.3",
+    "webpack-cli": "^2.1.3"
   },
   "dependencies": {
     "argparse": "^1.0.10",
     "express": "^4.16.3",
     "gl": "^4.0.4",
-    "material-ui": "^1.0.0-beta.43",
+    "immutable": "^4.0.0-rc.9",
     "node-fetch": "^2.1.2",
     "react": "^16.3.2",
     "react-dom": "^16.3.2",
-    "rxjs": "^6.0.0"
+    "rxjs": "^6.1.0"
   }
 }
diff --git a/src/apps/cif2bcif/converter.ts b/src/apps/cif2bcif/converter.ts
index 1a3a08221bdb79e9808e69dad4651aa141e2cd21..8a0530bc5a9acb1dc25e351ba017fbe066307a25 100644
--- a/src/apps/cif2bcif/converter.ts
+++ b/src/apps/cif2bcif/converter.ts
@@ -5,7 +5,7 @@
  */
 
 import Iterator from 'mol-data/iterator'
-import CIF, { Category } from 'mol-io/reader/cif'
+import CIF, { CifCategory } from 'mol-io/reader/cif'
 import * as Encoder from 'mol-io/writer/cif'
 import * as fs from 'fs'
 import classify from './field-classifier'
@@ -20,14 +20,14 @@ async function getCIF(path: string) {
     return parsed.result;
 }
 
-function createDefinition(cat: Category): Encoder.CategoryDefinition {
+function createDefinition(cat: CifCategory): Encoder.CategoryDefinition {
     return {
         name: cat.name,
         fields: cat.fieldNames.map(n => classify(n, cat.getField(n)!))
     }
 }
 
-function getCategoryInstanceProvider(cat: Category): Encoder.CategoryProvider {
+function getCategoryInstanceProvider(cat: CifCategory): Encoder.CategoryProvider {
     return function (ctx: any) {
         return {
             data: cat,
@@ -50,4 +50,3 @@ export default async function convert(path: string, asText = false) {
     }
     return encoder.getData();
 }
-
diff --git a/src/apps/cif2bcif/field-classifier.ts b/src/apps/cif2bcif/field-classifier.ts
index 9d8d322f6b424884773f9bbbaf26d924fd2a766c..d0725993f29c05f1326ce2ffead2a45ec0083ab5 100644
--- a/src/apps/cif2bcif/field-classifier.ts
+++ b/src/apps/cif2bcif/field-classifier.ts
@@ -5,7 +5,7 @@
  */
 
 import { Column } from 'mol-data/db'
-import { Field } from 'mol-io/reader/cif/data-model'
+import { CifField } from 'mol-io/reader/cif/data-model'
 import { FieldDefinition, FieldType } from 'mol-io/writer/cif/encoder'
 
 const intRegex = /^-?\d+$/
@@ -13,7 +13,7 @@ const floatRegex = /^-?(([0-9]+)[.]?|([0-9]*[.][0-9]+))([(][0-9]+[)])?([eE][+-]?
 
 // Classify a cif field as str, int or float based the data it contains.
 // To classify a field as int or float all items are checked.
-function classify(name: string, field: Field): FieldDefinition {
+function classify(name: string, field: CifField): FieldDefinition {
     let floatCount = 0, hasString = false;
     for (let i = 0, _i = field.rowCount; i < _i; i++) {
         const k = field.valueKind(i);
diff --git a/src/apps/render-test/components/assemblies.tsx b/src/apps/render-test/components/assemblies.tsx
deleted file mode 100644
index ab0bae997994a229b33d5ac3419e72e484f218f9..0000000000000000000000000000000000000000
--- a/src/apps/render-test/components/assemblies.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import * as React from 'react'
-import { WithStyles } from 'material-ui/styles';
-import { MenuItem } from 'material-ui/Menu';
-import { InputLabel } from 'material-ui/Input';
-import { FormControl } from 'material-ui/Form';
-import Select from 'material-ui/Select';
-
-import State from '../state'
-import Observer from './observer';
-import { Assembly } from 'mol-model/structure/model/properties/symmetry';
-
-interface AssemblyState {
-    loading: boolean
-    assemblies: ReadonlyArray<Assembly>
-    value: string
-}
-
-export default class Assemblies extends Observer<{ state: State } & WithStyles, AssemblyState> {
-    state: AssemblyState = { loading: false, assemblies: [], value: '' }
-
-    componentDidMount() {
-        this.subscribe(this.props.state.loading, value => {
-           this.setState({ loading: value });
-        });
-        this.subscribe(this.props.state.model, value => {
-            this.setState({ assemblies: value ? value.symmetry.assemblies : [] });
-        });
-        this.subscribe(this.props.state.assembly, value => {
-            this.setState({ value });
-        });
-    }
-
-    handleValueChange = (event: React.ChangeEvent<any>) => {
-        this.props.state.assembly.next(event.target.value)
-    }
-
-    render() {
-        const { classes } = this.props;
-
-        const items = this.state.assemblies.map((value, idx) => {
-            return <MenuItem key={idx} value={value.id}>{value.details}</MenuItem>
-        })
-
-        return <FormControl className={classes.formControl}>
-            <InputLabel htmlFor='assembly-value'>Assembly</InputLabel>
-            <Select
-                className={classes.selectField}
-                value={this.state.value}
-                onChange={this.handleValueChange}
-                inputProps={{
-                    name: 'value',
-                    id: 'assembly-value',
-                }}
-            >
-                {items}
-            </Select>
-        </FormControl>
-    }
-}
\ No newline at end of file
diff --git a/src/apps/render-test/components/color-theme.tsx b/src/apps/render-test/components/color-theme.tsx
deleted file mode 100644
index 701247c5db1bc0e2c0ed79caa486f711b2dc99ac..0000000000000000000000000000000000000000
--- a/src/apps/render-test/components/color-theme.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import * as React from 'react'
-import { WithStyles } from 'material-ui/styles';
-import { MenuItem } from 'material-ui/Menu';
-import { InputLabel } from 'material-ui/Input';
-import { FormControl } from 'material-ui/Form';
-import Select from 'material-ui/Select';
-
-import State, { ColorTheme as _ColorTheme } from '../state'
-import Observer from './observer';
-import { Color, ColorNames } from 'mol-util/color';
-
-interface ColorThemeState {
-    loading: boolean
-    name: _ColorTheme
-    value: Color
-}
-
-export default class ColorTheme extends Observer<{ state: State } & WithStyles, ColorThemeState> {
-    state: ColorThemeState = { loading: false, name: 'element-symbol' as _ColorTheme, value: 0xFF0000 }
-
-    componentDidMount() {
-        this.subscribe(this.props.state.loading, value => {
-           this.setState({ loading: value });
-        });
-        this.subscribe(this.props.state.colorTheme, value => {
-            this.setState({ name: value });
-         });
-         this.subscribe(this.props.state.colorValue, value => {
-            this.setState({ value: value });
-         });
-    }
-
-    handleNameChange = (event: React.ChangeEvent<any>) => {
-        this.props.state.colorTheme.next(event.target.value)
-    }
-
-    handleValueChange = (event: React.ChangeEvent<any>) => {
-        this.props.state.colorValue.next(event.target.value)
-    }
-
-    render() {
-        const { classes } = this.props;
-
-        const colorThemeItems = Object.keys(_ColorTheme).map((name, idx) => {
-            return <MenuItem key={idx} value={name}>{name}</MenuItem>
-        })
-
-        const colorValueItems = Object.keys(ColorNames).map((name, idx) => {
-            return <MenuItem key={idx} value={(ColorNames as any)[name]}>{name}</MenuItem>
-        })
-
-        return <div>
-            <FormControl className={classes.formControl}>
-                <InputLabel htmlFor='color-theme-name'>Color Theme</InputLabel>
-                <Select
-                    className={classes.selectField}
-                    value={this.state.name}
-                    onChange={this.handleNameChange}
-                    inputProps={{
-                        name: 'name',
-                        id: 'color-theme-name',
-                    }}
-                >
-                    {colorThemeItems}
-                </Select>
-            </FormControl>
-            <FormControl className={classes.formControl}>
-                    <InputLabel htmlFor='uniform-color-value'>Color Value</InputLabel>
-                    <Select
-                        className={classes.selectField}
-                        value={this.state.value}
-                        onChange={this.handleValueChange}
-                        inputProps={{
-                            name: 'value',
-                            id: 'uniform-color-value',
-                        }}
-                    >
-                        {colorValueItems}
-                    </Select>
-            </FormControl>
-        </div>
-    }
-}
\ No newline at end of file
diff --git a/src/apps/render-test/components/file-input.tsx b/src/apps/render-test/components/file-input.tsx
deleted file mode 100644
index 850ba452e38fe881a9a0c32dc723589e7e4c5658..0000000000000000000000000000000000000000
--- a/src/apps/render-test/components/file-input.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import * as React from 'react'
-import { WithStyles } from 'material-ui/styles';
-import TextField from 'material-ui/TextField';
-import Button from 'material-ui/Button';
-
-import State from '../state'
-import Observer from './observer';
-
-export default class FileInput extends Observer<{ state: State } & WithStyles, { loading: boolean }> {
-    state = { loading: false }
-
-    componentDidMount() {
-        this.subscribe(this.props.state.loading, value => {
-           this.setState({ loading: value });
-        });
-    }
-
-    render() {
-        const { classes, state } = this.props;
-
-        return <div>
-            <TextField
-                label='PDB ID'
-                className={classes.textField}
-                disabled={this.state.loading}
-                margin='normal'
-                onChange={(event) => {
-                    state.pdbId = event.target.value
-                }}
-                onKeyPress={(event) => {
-                    if (event.key === 'Enter') state.loadPdbId()
-                }}
-            />
-            <input
-                accept='*.cif'
-                className={classes.input}
-                id='button-file'
-                type='file'
-                onChange={(event) => {
-                    if (event.target.files) {
-                        state.loadFile(event.target.files[0])
-                    }
-                }}
-            />
-            <label htmlFor='button-file'>
-                <Button
-                    variant='raised'
-                    component='span'
-                    className={classes.button}
-                >
-                    Open CIF
-                </Button>
-            </label>
-        </div>
-    }
-}
\ No newline at end of file
diff --git a/src/apps/render-test/components/observer.tsx b/src/apps/render-test/components/observer.tsx
deleted file mode 100644
index 67e48fb23485513daf57f4b7f57fc9bba76bd26a..0000000000000000000000000000000000000000
--- a/src/apps/render-test/components/observer.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import * as React from 'react'
-import { Observable, Subscription } from 'rxjs';
-
-export default class Observer<S, P> extends React.Component<S, P> {
-    private _subs: Subscription[] = []
-
-    subscribe<T>(obs: Observable<T>, onNext: (v: T) => void) {
-        this._subs.push(obs.subscribe(onNext));
-    }
-
-    componentWillUnmount() {
-        for (const s of this._subs) s.unsubscribe();
-        this._subs = [];
-    }
-}
\ No newline at end of file
diff --git a/src/apps/render-test/components/sphere-detail.tsx b/src/apps/render-test/components/sphere-detail.tsx
deleted file mode 100644
index 1b19cca359238d3b5d4a0e99dfb80c7d4ef03c99..0000000000000000000000000000000000000000
--- a/src/apps/render-test/components/sphere-detail.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import * as React from 'react'
-import { WithStyles } from 'material-ui/styles';
-import { MenuItem } from 'material-ui/Menu';
-import { InputLabel } from 'material-ui/Input';
-import { FormControl } from 'material-ui/Form';
-import Select from 'material-ui/Select';
-
-import State from '../state'
-import Observer from './observer';
-
-interface SphereDetailState {
-    loading: boolean
-    value: number
-}
-
-export default class SphereDetail extends Observer<{ state: State } & WithStyles, SphereDetailState> {
-    state: SphereDetailState = { loading: false, value: 2 }
-
-    componentDidMount() {
-        this.subscribe(this.props.state.loading, value => {
-           this.setState({ loading: value });
-        });
-        this.subscribe(this.props.state.sphereDetail, value => {
-            this.setState({ value });
-         });
-    }
-
-    handleValueChange = (event: React.ChangeEvent<any>) => {
-        this.props.state.sphereDetail.next(event.target.value)
-    }
-
-    render() {
-        const { classes } = this.props;
-
-        const items = [0, 1, 2, 3].map((value, idx) => {
-            return <MenuItem key={idx} value={value}>{value.toString()}</MenuItem>
-        })
-
-        return <FormControl className={classes.formControl}>
-            <InputLabel htmlFor='sphere-detail-value'>Sphere Detail</InputLabel>
-            <Select
-                className={classes.selectField}
-                value={this.state.value}
-                onChange={this.handleValueChange}
-                inputProps={{
-                    name: 'value',
-                    id: 'sphere-detail-value',
-                }}
-            >
-                {items}
-            </Select>
-        </FormControl>
-    }
-}
\ No newline at end of file
diff --git a/src/apps/render-test/components/viewport.tsx b/src/apps/render-test/components/viewport.tsx
deleted file mode 100644
index 27b0cd63c8cef15ffb5b1c38733cca8c3371c181..0000000000000000000000000000000000000000
--- a/src/apps/render-test/components/viewport.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import * as React from 'react'
-import State from '../state'
-
-export default class Viewport extends React.Component<{ state: State }, { initialized: boolean }> {
-    private container: HTMLDivElement | null = null;
-    private canvas: HTMLCanvasElement | null = null;
-    state = { initialized: false }
-
-    componentDidMount() {
-        if (this.container && this.canvas) {
-            this.props.state.initRenderer(this.canvas, this.container).then(() => {
-                this.setState({ initialized: true })
-            })
-        }
-    }
-
-    render() {
-        return <div ref={elm => this.container = elm} style={{ height: '100%' }}>
-            <canvas ref={elm => this.canvas = elm}></canvas>
-        </div>
-    }
-}
\ No newline at end of file
diff --git a/src/apps/render-test/components/visibility.tsx b/src/apps/render-test/components/visibility.tsx
deleted file mode 100644
index a93a9b123ba729f5488cfb159339f731cfc921c0..0000000000000000000000000000000000000000
--- a/src/apps/render-test/components/visibility.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import * as React from 'react'
-import { WithStyles } from 'material-ui/styles';
-import { FormLabel, FormControl, FormGroup, FormControlLabel } from 'material-ui/Form';
-import Checkbox from 'material-ui/Checkbox';
-
-import State from '../state'
-import Observer from './observer';
-
-interface VisibilityState {
-    loading: boolean
-    spacefill: boolean
-    point: boolean
-}
-
-export default class Visibility extends Observer<{ state: State } & WithStyles, VisibilityState> {
-    state: VisibilityState = { loading: false, spacefill: true, point: true }
-
-    componentDidMount() {
-        this.subscribe(this.props.state.loading, value => {
-           this.setState({ loading: value });
-        });
-        this.subscribe(this.props.state.spacefillVisibility, value => {
-            this.setState({ spacefill: value });
-        });
-        this.subscribe(this.props.state.pointVisibility, value => {
-            this.setState({ point: value });
-        });
-    }
-
-    handleChange = (event: React.ChangeEvent<any>) => {
-        switch (event.target.name) {
-            case 'point': this.props.state.pointVisibility.next(event.target.checked); break;
-            case 'spacefill': this.props.state.spacefillVisibility.next(event.target.checked); break;
-        }
-    }
-
-    render() {
-        const { classes } = this.props
-
-        return <div className={classes.formControl}>
-            <FormControl component='fieldset'>
-                <FormLabel component='legend'>Visibility</FormLabel>
-                <FormGroup>
-                    <FormControlLabel
-                        control={<Checkbox
-                            checked={this.state.point}
-                            onChange={this.handleChange}
-                            name='point'
-                        />}
-                        label='Point'
-                    />
-                    <FormControlLabel
-                        control={<Checkbox
-                            checked={this.state.spacefill}
-                            onChange={this.handleChange}
-                            name='spacefill'
-                        />}
-                        label='Spacefill'
-                    />
-                </FormGroup>
-            </FormControl>
-        </div>
-    }
-}
\ No newline at end of file
diff --git a/src/apps/render-test/index.tsx b/src/apps/render-test/index.tsx
deleted file mode 100644
index 2e87d291faecababed3fca35c7a2799742704ce9..0000000000000000000000000000000000000000
--- a/src/apps/render-test/index.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import UI from './ui'
-import State from './state'
-import * as React from 'react'
-import * as ReactDOM from 'react-dom'
-
-const state = new State()
-ReactDOM.render(<UI state={ state } />, document.getElementById('app'));
\ No newline at end of file
diff --git a/src/apps/render-test/state.ts b/src/apps/render-test/state.ts
deleted file mode 100644
index 1e284a0a12b47738f2d32ca9dd550e07f9fbdbcd..0000000000000000000000000000000000000000
--- a/src/apps/render-test/state.ts
+++ /dev/null
@@ -1,226 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import { BehaviorSubject } from 'rxjs';
-
-// import { ValueCell } from 'mol-util/value-cell'
-
-// import { Vec3, Mat4 } from 'mol-math/linear-algebra'
-import Viewer from 'mol-view/viewer'
-// import { createColorTexture } from 'mol-gl/util';
-// import Icosahedron from 'mol-geo/primitive/icosahedron'
-// import Box from 'mol-geo/primitive/box'
-import Spacefill, { SpacefillProps } from 'mol-geo/representation/structure/spacefill'
-import Point, { PointProps } from 'mol-geo/representation/structure/point'
-
-import { Run } from 'mol-task'
-import { Symmetry, Structure, Model } from 'mol-model/structure'
-
-// import mcubes from './utils/mcubes'
-import { getModelFromPdbId, getModelFromFile, log, Volume, getVolumeFromEmdId } from './utils'
-import { StructureRepresentation } from 'mol-geo/representation/structure';
-import { Color } from 'mol-util/color';
-import Surface, { SurfaceProps } from 'mol-geo/representation/volume/surface';
-import { VolumeIsoValue } from 'mol-model/volume';
-import { VolumeRepresentation } from 'mol-geo/representation/volume';
-// import Cylinder from 'mol-geo/primitive/cylinder';
-
-
-export const ColorTheme = {
-    'atom-index': {},
-    'chain-id': {},
-    'element-symbol': {},
-    'instance-index': {},
-    'uniform': {}
-}
-export type ColorTheme = keyof typeof ColorTheme
-
-export default class State {
-    viewer: Viewer
-    pdbId = '1crn'
-    // pdbId = '5ire'
-    emdId = '8116'
-    // pdbId = '6G1K'
-    // emdId = '4339'
-    // pdbId = '4cup'
-    // emdId = ''
-    model = new BehaviorSubject<Model | undefined>(undefined)
-    volume = new BehaviorSubject<Volume | undefined>(undefined)
-    initialized = new BehaviorSubject<boolean>(false)
-    loading = new BehaviorSubject<boolean>(false)
-
-    colorTheme = new BehaviorSubject<ColorTheme>('element-symbol')
-    colorValue = new BehaviorSubject<Color>(0xFF4411)
-    sphereDetail = new BehaviorSubject<number>(0)
-    assembly = new BehaviorSubject<string>('')
-
-    pointVisibility = new BehaviorSubject<boolean>(true)
-    spacefillVisibility = new BehaviorSubject<boolean>(true)
-
-    pointRepr: StructureRepresentation<PointProps>
-    spacefillRepr: StructureRepresentation<SpacefillProps>
-    surfaceRepr: VolumeRepresentation<SurfaceProps>
-
-    constructor() {
-        this.colorTheme.subscribe(() => this.update())
-        this.colorValue.subscribe(() => this.update())
-        this.sphereDetail.subscribe(() => this.update())
-        this.assembly.subscribe(() => this.initStructure())
-
-        this.pointVisibility.subscribe(() => this.updateVisibility())
-        this.spacefillVisibility.subscribe(() => this.updateVisibility())
-    }
-
-    getSpacefillProps (): SpacefillProps {
-        const colorThemeName = this.colorTheme.getValue()
-        return {
-            doubleSided: true,
-            detail: this.sphereDetail.getValue(),
-            colorTheme: colorThemeName === 'uniform' ?
-                { name: colorThemeName, value: this.colorValue.getValue() } :
-                { name: colorThemeName }
-        }
-    }
-
-    getPointProps (): PointProps {
-        const colorThemeName = this.colorTheme.getValue()
-        return {
-            sizeTheme: { name: 'uniform', value: 0.1 },
-            colorTheme: colorThemeName === 'uniform' ?
-                { name: colorThemeName, value: this.colorValue.getValue() } :
-                { name: colorThemeName }
-        }
-    }
-
-    async initRenderer (canvas: HTMLCanvasElement, container: HTMLDivElement) {
-        this.viewer = Viewer.create(canvas, container)
-        this.initialized.next(true)
-        this.loadPdbId()
-        this.loadEmdId()
-        this.viewer.animate()
-    }
-
-    async getStructure () {
-        const model = this.model.getValue()
-        if (!model) return
-        const assembly = this.assembly.getValue()
-        let structure: Structure
-        const assemblies = model.symmetry.assemblies
-        if (assemblies.length) {
-            structure = await Run(Symmetry.buildAssembly(Structure.ofModel(model), assembly || '1'), log, 500)
-        } else {
-            structure = Structure.ofModel(model)
-        }
-        return structure
-    }
-
-    async initStructure () {
-        const { viewer } = this
-        if (!viewer || !this.model.getValue()) return
-
-        if (this.pointRepr) this.viewer.remove(this.pointRepr)
-        if (this.spacefillRepr) this.viewer.remove(this.spacefillRepr)
-
-        const structure = await this.getStructure()
-        if (!structure) return
-
-        this.pointRepr = StructureRepresentation(Point)
-        await Run(this.pointRepr.create(structure, this.getPointProps()), log, 500)
-        viewer.add(this.pointRepr)
-
-        this.spacefillRepr = StructureRepresentation(Spacefill)
-        await Run(this.spacefillRepr.create(structure, this.getSpacefillProps()), log, 500)
-        viewer.add(this.spacefillRepr)
-
-        this.updateVisibility()
-        viewer.requestDraw()
-        console.log(viewer.stats)
-    }
-
-    setModel(model: Model) {
-        this.model.next(model)
-        this.initStructure()
-        this.loading.next(false)
-    }
-
-    async loadFile (file: File) {
-        this.viewer.clear()
-        this.loading.next(true)
-        this.setModel((await getModelFromFile(file))[0])
-    }
-
-    async initVolume () {
-        const { viewer } = this
-        const v = this.volume.getValue()
-        if (!viewer || !v) return
-
-        if (this.surfaceRepr) this.viewer.remove(this.surfaceRepr)
-
-        this.surfaceRepr = VolumeRepresentation(Surface)
-        await Run(this.surfaceRepr.create(v.volume, {
-            isoValue: VolumeIsoValue.relative(v.volume.dataStats, 3.0),
-            alpha: 0.5,
-            flatShaded: false,
-            flipSided: true,
-            doubleSided: true
-        }), log, 500)
-        viewer.add(this.surfaceRepr)
-
-        viewer.requestDraw()
-        console.log(viewer.stats)
-    }
-
-    async loadPdbId () {
-        if (this.pointRepr) this.viewer.remove(this.pointRepr)
-        if (this.spacefillRepr) this.viewer.remove(this.spacefillRepr)
-        if (this.pdbId.length !== 4) return
-        this.loading.next(true)
-        this.setModel((await getModelFromPdbId(this.pdbId))[0])
-    }
-
-    setVolume(volume: Volume) {
-        this.volume.next(volume)
-        this.initVolume()
-        this.loading.next(false)
-    }
-
-    async loadEmdId () {
-        if (this.surfaceRepr) this.viewer.remove(this.surfaceRepr)
-        if (this.emdId.length !== 4) return
-        this.loading.next(true)
-        this.setVolume(await getVolumeFromEmdId(this.emdId))
-    }
-
-    async update () {
-        if (!this.spacefillRepr) return
-        await Run(this.spacefillRepr.update(this.getSpacefillProps()), log, 500)
-        await Run(this.pointRepr.update(this.getPointProps()), log, 500)
-        this.viewer.add(this.spacefillRepr)
-        this.viewer.add(this.pointRepr)
-        this.viewer.update()
-        this.viewer.requestDraw()
-        console.log(this.viewer.stats)
-    }
-
-    updateVisibility () {
-        if (!this.viewer) return
-        if (this.pointRepr) {
-            if (this.pointVisibility.getValue()) {
-                this.viewer.show(this.pointRepr)
-            } else {
-                this.viewer.hide(this.pointRepr)
-            }
-        }
-        if (this.spacefillRepr) {
-            if (this.spacefillVisibility.getValue()) {
-                this.viewer.show(this.spacefillRepr)
-            } else {
-                this.viewer.hide(this.spacefillRepr)
-            }
-        }
-        this.viewer.requestDraw()
-    }
-}
\ No newline at end of file
diff --git a/src/apps/render-test/ui.tsx b/src/apps/render-test/ui.tsx
deleted file mode 100644
index b302fe512d682a377e6f054c92e85fd9dcd48433..0000000000000000000000000000000000000000
--- a/src/apps/render-test/ui.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import * as React from 'react'
-import { withStyles, WithStyles, Theme, StyleRulesCallback } from 'material-ui/styles';
-import Typography from 'material-ui/Typography';
-import Toolbar from 'material-ui/Toolbar';
-import AppBar from 'material-ui/AppBar';
-import Drawer from 'material-ui/Drawer';
-
-import State from './state'
-
-import Viewport from './components/viewport'
-import FileInput from './components/file-input'
-import ColorTheme from './components/color-theme'
-import SphereDetail from './components/sphere-detail'
-import Visibility from './components/visibility'
-import Assemblies from './components/assemblies'
-
-
-const styles: StyleRulesCallback = (theme: Theme) => ({
-    root: {
-        flexGrow: 1,
-        height: 830,
-        zIndex: 1,
-        overflow: 'hidden',
-        position: 'relative',
-        display: 'flex',
-    },
-    appBar: {
-        zIndex: theme.zIndex.drawer + 1,
-    },
-    drawerPaper: {
-        position: 'relative',
-        width: 240,
-    },
-    content: {
-        flexGrow: 1,
-        backgroundColor: theme.palette.background.default,
-        padding: theme.spacing.unit * 3,
-        minWidth: 0, // So the Typography noWrap works
-    },
-    toolbar: theme.mixins.toolbar,
-    formControl: {
-        margin: theme.spacing.unit,
-        width: 200,
-    },
-    textField: {
-        marginLeft: theme.spacing.unit,
-        marginRight: theme.spacing.unit,
-        width: 200,
-    },
-    button: {
-        margin: theme.spacing.unit,
-    },
-    input: {
-        display: 'none',
-    },
-} as any);
-
-const decorate = withStyles(styles);
-
-interface Props {
-    state: State;
-};
-
-class UI extends React.Component<{ state: State } & WithStyles, {  }> {
-    render() {
-        const { classes, state } = this.props;
-        return (
-            <div className={classes.root}>
-                <AppBar position='absolute' className={classes.appBar}>
-                    <Toolbar>
-                    <Typography variant='title' color='inherit' noWrap>
-                        Mol* Render Test
-                    </Typography>
-                    </Toolbar>
-                </AppBar>
-                <Drawer variant='permanent' classes={{ paper: classes.drawerPaper, }}>
-                    <div className={classes.toolbar} />
-                    <FileInput state={state} classes={classes}></FileInput>
-                    <form className={classes.root} autoComplete='off'>
-                        <div>
-                            <Assemblies state={state} classes={classes}></Assemblies>
-                            <ColorTheme state={state} classes={classes}></ColorTheme>
-                            <SphereDetail state={state} classes={classes}></SphereDetail>
-                            <Visibility state={state} classes={classes}></Visibility>
-                        </div>
-                    </form>
-                </Drawer>
-                <main className={classes.content}>
-                    <div className={classes.toolbar} />
-                    <Viewport state={state}></Viewport>
-                </main>
-            </div>
-        );
-    }
-}
-
-export default decorate<Props>(UI)
\ No newline at end of file
diff --git a/src/apps/render-test/utils/index.ts b/src/apps/render-test/utils/index.ts
deleted file mode 100644
index 54486ffb3323d4808327e4ed013c332e38ed04de..0000000000000000000000000000000000000000
--- a/src/apps/render-test/utils/index.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import CIF from 'mol-io/reader/cif'
-import { Run, Progress } from 'mol-task'
-import { Model } from 'mol-model/structure'
-import { VolumeData, parseDensityServerData } from 'mol-model/volume'
-import { DensityServer_Data_Database } from 'mol-io/reader/cif/schema/density-server';
-
-export function log(progress: Progress) {
-    const p = progress.root.progress
-    console.log(`${p.message} ${(p.current/p.max*100).toFixed(2)}%`)
-}
-
-export async function downloadCif(url: string, isBinary: boolean) {
-    const data = await fetch(url);
-    return parseCif(isBinary ? new Uint8Array(await data.arrayBuffer()) : await data.text());
-}
-
-export async function parseCif(data: string|Uint8Array) {
-    const comp = CIF.parse(data)
-    const parsed = await Run(comp, log, 500);
-    if (parsed.isError) throw parsed;
-    return parsed.result
-}
-
-export async function getModelFromPdbId(pdbid: string) {
-    const cif = await downloadCif(`https://files.rcsb.org/download/${pdbid}.cif`, false)
-    return Model.create({ kind: 'mmCIF', data: CIF.schema.mmCIF(cif.blocks[0]) })
-}
-
-const readFileAsText = (file: File) => {
-    const fileReader = new FileReader()
-    return new Promise<string>((resolve, reject) => {
-        fileReader.onerror = () => {
-            fileReader.abort()
-            reject(new DOMException('Error parsing input file.'))
-        }
-        fileReader.onload = () => resolve(fileReader.result)
-        fileReader.readAsText(file)
-    })
-}
-
-export async function getModelFromFile(file: File) {
-    const cif = await parseCif(await readFileAsText(file))
-    return Model.create({ kind: 'mmCIF', data: CIF.schema.mmCIF(cif.blocks[0]) })
-}
-
-export type Volume = { source: DensityServer_Data_Database, volume: VolumeData }
-
-export async function getVolumeFromEmdId(emdid: string): Promise<Volume> {
-    const cif = await downloadCif(`https://webchem.ncbr.muni.cz/DensityServer/em/emd-${emdid}/cell?detail=4`, true)
-    const data = CIF.schema.densityServer(cif.blocks[1])
-    return { source: data, volume: await Run(parseDensityServerData(data)) }
-}
\ No newline at end of file
diff --git a/src/apps/render-test/utils/mcubes.ts b/src/apps/render-test/utils/mcubes.ts
deleted file mode 100644
index 92905c7b6f5d111486ee7a1bd6fab7b95a5cd5e6..0000000000000000000000000000000000000000
--- a/src/apps/render-test/utils/mcubes.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { Run } from 'mol-task'
-import { computeMarchingCubes } from 'mol-geo/util/marching-cubes/algorithm'
-import { Mesh } from 'mol-geo/shape/mesh'
-import { Tensor, Mat4, Vec3 } from 'mol-math/linear-algebra'
-
-function fillField(tensor: Tensor, f: (x: number, y: number, z: number) => number, min: number[], max: number[]): Tensor {
-    const { space: { set, dimensions: [ii, jj, kk] }, data } = tensor;
-
-    const dx = (max[0] - min[0]) / (ii - 1);
-    const dy = (max[1] - min[1]) / (jj - 1);
-    const dz = (max[2] - min[2]) / (kk - 1);
-
-    for (let i = 0, x = min[0]; i < ii; i++, x += dx) {
-        for (let j = 0, y = min[1]; j < jj; j++, y += dy) {
-            for (let k = 0, z = min[2]; k < kk; k++, z += dz) {
-                set(data, i, j, k, f(x, y, z));
-            }
-        }
-    }
-
-    return tensor
-}
-
-export default async function computeSurface(f: (x: number, y: number, z: number) => number, data?: { field: Tensor, surface: Mesh }) {
-    let field: Tensor;
-    if (data) field = data.field;
-    else {
-        const space = Tensor.Space([30, 30, 30], [0, 1, 2]);
-        field = Tensor.create(space, space.create(Float32Array));
-    }
-
-    const min = Vec3.create(-1.1, -1.1, -1.1), max = Vec3.create(1.1, 1.1, 1.1);
-
-    fillField(field, f, min, max);
-    const surface = await Run(computeMarchingCubes({
-        scalarField: field,
-        isoLevel: 0,
-        oldSurface: data ? data.surface : void 0
-    }));
-
-    const translation = Mat4.fromTranslation(Mat4.zero(), min);
-    const grid = Vec3.zero();
-    Vec3.fromArray(grid, field.space.dimensions as any, 0);
-    const size = Vec3.sub(Vec3.zero(), max, min);
-    const scale = Mat4.fromScaling(Mat4.zero(), Vec3.create(size[0] / (grid[0] - 1), size[1] / (grid[1] - 1), size[2] / (grid[2] - 1)));
-
-    const transform = Mat4.mul(Mat4.zero(), translation, scale);
-    Mesh.transformImmediate(surface, transform);
-    Mesh.computeNormalsImmediate(surface);
-    return { surface, field };
-}
\ No newline at end of file
diff --git a/src/apps/schema-generator/schema-from-mmcif-dic.ts b/src/apps/schema-generator/schema-from-mmcif-dic.ts
index a6fc5fed2b9cc9a0a022055f77ded38923d5e82e..eb1d7e74ba4a336e5fc70860a753d1bf8a4ec78e 100644
--- a/src/apps/schema-generator/schema-from-mmcif-dic.ts
+++ b/src/apps/schema-generator/schema-from-mmcif-dic.ts
@@ -10,7 +10,7 @@ import * as fs from 'fs'
 import fetch from 'node-fetch'
 
 import Csv from 'mol-io/reader/csv/parser'
-import CIF, { Frame } from 'mol-io/reader/cif'
+import CIF, { CifFrame } from 'mol-io/reader/cif'
 import { generateSchema } from './util/cif-dic'
 import { generate } from './util/generate'
 import { Filter } from './util/json-schema'
@@ -29,7 +29,7 @@ async function runGenerateSchema(name: string, fieldNamesPath?: string, typescri
     const ihmDicVersion = CIF.schema.dic(ihmDic.result.blocks[0]).dictionary.version.value(0)
     const version = `Dictionary versions: mmCIF ${mmcifDicVersion}, IHM ${ihmDicVersion}.`
 
-    const frames: Frame[] = [...mmcifDic.result.blocks[0].saveFrames, ...ihmDic.result.blocks[0].saveFrames]
+    const frames: CifFrame[] = [...mmcifDic.result.blocks[0].saveFrames, ...ihmDic.result.blocks[0].saveFrames]
     const schema = generateSchema(frames)
 
     const filter = fieldNamesPath ? await getFieldNamesFilter(fieldNamesPath) : undefined
diff --git a/src/apps/schema-generator/util/cif-dic.ts b/src/apps/schema-generator/util/cif-dic.ts
index 5cc6016cfba541708448d83d1881846ee659579c..718c9348394ba52907534642303be0382640ad1e 100644
--- a/src/apps/schema-generator/util/cif-dic.ts
+++ b/src/apps/schema-generator/util/cif-dic.ts
@@ -6,7 +6,7 @@
 
 import { Database, ValueColumn, ListColumn } from './json-schema'
 import * as Data from 'mol-io/reader/cif/data-model'
-import { Frame } from 'mol-io/reader/cif/data-model';
+import { CifFrame } from 'mol-io/reader/cif/data-model';
 
 export function getFieldType (type: string, values?: string[]): ValueColumn|ListColumn {
     switch (type) {
@@ -76,7 +76,7 @@ export function getFieldType (type: string, values?: string[]): ValueColumn|List
     return 'str'
 }
 
-type FrameCategories = { [category: string]: Data.Frame }
+type FrameCategories = { [category: string]: Data.CifFrame }
 type FrameLinks = { [k: string]: string }
 
 interface FrameData {
@@ -85,7 +85,7 @@ interface FrameData {
 }
 
 // get field from given or linked category
-function getField ( category: string, field: string, d: Data.Frame, ctx: FrameData): Data.Field|undefined {
+function getField ( category: string, field: string, d: Data.CifFrame, ctx: FrameData): Data.CifField|undefined {
     const { categories, links } = ctx
 
     const cat = d.categories[category]
@@ -105,7 +105,7 @@ function getField ( category: string, field: string, d: Data.Frame, ctx: FrameDa
     }
 }
 
-function getEnums (d: Data.Frame, ctx: FrameData) {
+function getEnums (d: Data.CifFrame, ctx: FrameData) {
     const value = getField('item_enumeration', 'value', d, ctx)
     const enums: string[] = []
     if (value) {
@@ -119,7 +119,7 @@ function getEnums (d: Data.Frame, ctx: FrameData) {
     }
 }
 
-function getCode (d: Data.Frame, ctx: FrameData): [string, string[]|undefined]|undefined {
+function getCode (d: Data.CifFrame, ctx: FrameData): [string, string[]|undefined]|undefined {
     const code = getField('item_type', 'code', d, ctx)
     if (code) {
         return [ code.str(0), getEnums(d, ctx) ]
@@ -128,7 +128,7 @@ function getCode (d: Data.Frame, ctx: FrameData): [string, string[]|undefined]|u
     }
 }
 
-function getSubCategory (d: Data.Frame, ctx: FrameData): string|undefined {
+function getSubCategory (d: Data.CifFrame, ctx: FrameData): string|undefined {
     const value = getField('item_sub_category', 'id', d, ctx)
     if (value) {
         return value.str(0)
@@ -178,7 +178,7 @@ const SPACE_SEPARATED_LIST_FIELDS = [
     '_pdbx_soln_scatter.data_analysis_software_list', // SCTPL5 GNOM
 ];
 
-export function generateSchema (frames: Frame[]) {
+export function generateSchema (frames: CifFrame[]) {
     const schema: Database = {}
 
     const categories: FrameCategories = {}
diff --git a/src/apps/viewer/index.html b/src/apps/viewer/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..f3faa04f95f9e915ba493d25fdc471b69489e913
--- /dev/null
+++ b/src/apps/viewer/index.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8" />
+        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+        <title>Mol* Render Test</title>
+        <link href='./app.css', rel="stylesheet">
+    </head>
+    <body>
+        <div id="app"></div>
+        <script type="text/javascript" src="./index.js"></script>
+    </body>
+</html>
\ No newline at end of file
diff --git a/src/apps/viewer/index.tsx b/src/apps/viewer/index.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f828e597af544499d74c9c85afc8dea6e4bd7fd7
--- /dev/null
+++ b/src/apps/viewer/index.tsx
@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as React from 'react'
+import * as ReactDOM from 'react-dom'
+
+import './index.html'
+import 'mol-app/skin/molstar-light.scss'
+
+import { Context } from 'mol-app/context/context';
+import { Viewport } from 'mol-app/ui/visualization/viewport'
+import { makeEmptyTargets, LayoutRegion } from 'mol-app/controller/layout';
+import { Layout } from 'mol-app/ui/layout';
+import { LogController } from 'mol-app/controller/misc/log';
+import { Log } from 'mol-app/ui/misc/log';
+import { JobsController } from 'mol-app/controller/misc/jobs';
+import { BackgroundJobs, Overlay } from 'mol-app/ui/misc/jobs';
+import { EntityTree } from 'mol-app/ui/entity/tree';
+import { EntityTreeController } from 'mol-app/controller/entity/tree';
+import { TransformListController } from 'mol-app/controller/transform/list';
+import { TransformList } from 'mol-app/ui/transform/list';
+
+const elm = document.getElementById('app')
+if (!elm) throw new Error('Can not find element with id "app".')
+
+const ctx = new Context()
+const targets = makeEmptyTargets();
+
+targets[LayoutRegion.Main].components.push({
+    key: 'molstar-internal-viewport',
+    controller: ctx.viewport,
+    region: LayoutRegion.Main,
+    view: Viewport,
+    isStatic: true
+});
+
+targets[LayoutRegion.Bottom].components.push({
+    key: 'molstar-log',
+    controller: new LogController(ctx),
+    region: LayoutRegion.Bottom,
+    view: Log,
+    isStatic: true
+});
+
+targets[LayoutRegion.Main].components.push({
+    key: 'molstar-background-jobs',
+    controller: new JobsController(ctx, 'Background'),
+    region: LayoutRegion.Main,
+    view: BackgroundJobs,
+    isStatic: true
+});
+
+targets[LayoutRegion.Root].components.push({
+    key: 'molstar-overlay',
+    controller: new JobsController(ctx, 'Normal'),
+    region: LayoutRegion.Root,
+    view: Overlay,
+    isStatic: true
+});
+
+targets[LayoutRegion.Right].components.push({
+    key: 'molstar-transform-list',
+    controller: new TransformListController(ctx),
+    region: LayoutRegion.Right,
+    view: TransformList,
+    isStatic: false
+});
+
+targets[LayoutRegion.Left].components.push({
+    key: 'molstar-entity-tree',
+    controller: new EntityTreeController(ctx),
+    region: LayoutRegion.Left,
+    view: EntityTree,
+    isStatic: true
+});
+
+ctx.createLayout(targets, elm)
+ctx.layout.setState({
+    isExpanded: true,
+    hideControls: false,
+    collapsedControlsLayout: 0
+})
+// ctx.viewport.setState()
+
+ReactDOM.render(React.createElement(Layout, { controller: ctx.layout }), elm);
diff --git a/src/mol-app/context/context.ts b/src/mol-app/context/context.ts
new file mode 100644
index 0000000000000000000000000000000000000000..92a317aad2dc583dcf9f1f9051a6295c26536596
--- /dev/null
+++ b/src/mol-app/context/context.ts
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import { UUID } from 'mol-util'
+import { PerformanceMonitor } from 'mol-util/performance-monitor';
+import { Dispatcher } from '../service/dispatcher'
+import { Logger } from '../service/logger'
+import { LayoutTarget, LayoutController } from '../controller/layout';
+import { ViewportController } from '../controller/visualization/viewport';
+import { Stage } from 'mol-view/stage';
+import { AnyTransform } from 'mol-view/state/transform';
+import { BehaviorSubject } from 'rxjs';
+import { AnyEntity } from 'mol-view/state/entity';
+
+export class Settings {
+    private settings = new Map<string, any>();
+
+    set(key: string, value: any) {
+        this.settings.set(key, value);
+    }
+
+    get(key: string) {
+        return this.settings.get(key);
+    }
+}
+
+export class Context {
+    id = UUID.create()
+
+    dispatcher = new Dispatcher();
+    logger = new Logger(this);
+    performance = new PerformanceMonitor();
+
+    stage = new Stage();
+    viewport = new ViewportController(this);
+    layout: LayoutController;
+    settings = new Settings();
+
+    currentEntity = new BehaviorSubject(undefined) as BehaviorSubject<AnyEntity | undefined>
+    currentTransforms = new BehaviorSubject([] as AnyTransform[])
+
+    createLayout(targets: LayoutTarget[], target: HTMLElement) {
+        this.layout = new LayoutController(this, targets, target);
+    }
+
+    initStage(canvas: HTMLCanvasElement, container: HTMLDivElement) {
+        this.stage.initRenderer(canvas, container)
+        return true
+    }
+
+    destroy() {
+        if (this.stage) {
+            this.stage.dispose()
+            this.stage = null as any
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/controller/controller.ts b/src/mol-app/controller/controller.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bfd6127f6dd7efbb395557f071be100bb4c3b68b
--- /dev/null
+++ b/src/mol-app/controller/controller.ts
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import { BehaviorSubject } from 'rxjs';
+import { merge } from 'mol-util';
+import { Context } from '../context/context'
+import { LayoutRegion } from './layout';
+
+export class Controller<State> {
+
+    private _state = new BehaviorSubject<State>(<any>void 0);
+    private _latestState: State = <any>void 0;
+
+    get dispatcher() {
+        return this.context.dispatcher;
+    }
+
+    setState(...states: Partial<State>[]) {
+        let s = merge(this._latestState, ...states);
+        if (s !== this._latestState) {
+            this._latestState = s;
+            this._state.next(s);
+        }
+    }
+
+    get state() {
+        return this._state;
+    }
+
+    get latestState() {
+        return this._latestState;
+    }
+
+    constructor(public context: Context, initialState: State) {
+        this._latestState = initialState;
+    }
+}
+
+export interface ControllerInfo {
+    key: string;
+    controller: Controller<any>;
+    view: any;
+    region: LayoutRegion;
+    isStatic?: boolean;
+}
\ No newline at end of file
diff --git a/src/mol-app/controller/entity/tree.ts b/src/mol-app/controller/entity/tree.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fdcb620e550079976e69b6595d58ac55c56ebe28
--- /dev/null
+++ b/src/mol-app/controller/entity/tree.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import { Context } from '../../context/context'
+import { Controller } from '../controller';
+import { AnyEntity } from 'mol-view/state/entity';
+
+export interface EntityTreeState {
+    entities: Set<AnyEntity>
+}
+
+export class EntityTreeController extends Controller<EntityTreeState> {
+    constructor(context: Context) {
+        super(context, { entities: new Set() });
+
+        context.stage.ctx.change.subscribe(() => {
+            if (context.stage.ctx) {
+                this.state.next({ entities: context.stage.ctx.entities }) // TODO
+                this.setState({ entities: context.stage.ctx.entities })
+            }
+        })
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/controller/layout.ts b/src/mol-app/controller/layout.ts
new file mode 100644
index 0000000000000000000000000000000000000000..388f57788aecd7465ca1d66a40bae2032b2d120b
--- /dev/null
+++ b/src/mol-app/controller/layout.ts
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import { Context } from '../context/context'
+import { Controller, ControllerInfo } from './controller'
+import { CommonEvents, LayoutEvents } from '../event/basic';
+
+export enum LayoutRegion {
+    Main     = 0,
+    Top      = 1,
+    Right    = 2,
+    Bottom   = 3,
+    Left     = 4,
+    Root     = 5
+}
+
+export enum CollapsedControlsLayout {
+    Outside   = 0,
+    Landscape = 1,
+    Portrait  = 2
+}
+
+export class LayoutTarget {
+    components: ControllerInfo[] = [];
+    constructor(public cssClass: string) {
+    }
+}
+
+export function makeEmptyTargets() {
+    let ret: LayoutTarget[] = [];
+    for (let i = 0; i <= LayoutRegion.Root; i++) {
+        ret.push(new LayoutTarget(LayoutRegion[i].toLowerCase()));
+    }
+    return ret;
+}
+
+export type RegionState = 'Hidden' | 'Sticky' | 'Default'
+
+export interface LayoutState {
+    isExpanded: boolean,
+    hideControls: boolean,
+    collapsedControlsLayout: CollapsedControlsLayout,
+    regionStates?: { [region: number]: RegionState }
+}
+
+interface RootState {
+    top: string | null,
+    bottom: string | null,
+    left: string | null,
+    right: string | null,
+
+    width: string | null;
+    height: string | null;
+    maxWidth: string | null;
+    maxHeight: string | null;
+    margin: string | null;
+    marginLeft: string | null;
+    marginRight: string | null;
+    marginTop: string | null;
+    marginBottom: string | null;
+
+    scrollTop: number,
+    scrollLeft: number,
+    position: string | null,
+    overflow: string | null,
+    viewports: HTMLElement[],
+    zindex: string | null
+}
+
+export class LayoutController extends Controller<LayoutState> {
+
+    update(state: Partial<LayoutState>) {
+        let prevExpanded = !!this.latestState.isExpanded;
+        this.setState(state);
+        if (typeof state.isExpanded === 'boolean' && state.isExpanded !== prevExpanded) this.handleExpand();
+
+        this.dispatcher.schedule(() => CommonEvents.LayoutChanged.dispatch(this.context, {}));
+    }
+
+    private rootState: RootState | undefined = void 0;
+    private expandedViewport: HTMLMetaElement;
+
+    private getScrollElement() {
+        if ((document as any).scrollingElement) return (document as any).scrollingElement;
+        if (document.documentElement) return document.documentElement;
+        return document.body;
+    }
+
+    private handleExpand() {
+        try {
+            let body = document.getElementsByTagName('body')[0];
+            let head = document.getElementsByTagName('head')[0];
+
+            if (!body || !head) return;
+
+            if (this.latestState.isExpanded) {
+
+                let children = head.children;
+                let hasExp = false;
+                let viewports: HTMLElement[] = [];
+                for (let i = 0; i < children.length; i++) {
+                    if (children[i] === this.expandedViewport) {
+                        hasExp = true;
+                    } else if (((children[i] as any).name || '').toLowerCase() === 'viewport') {
+                        viewports.push(children[i] as any);
+                    }
+                }
+
+                for (let v of viewports) {
+                    head.removeChild(v);
+                }
+
+                if (!hasExp) head.appendChild(this.expandedViewport);
+
+
+                let s = body.style;
+
+                let doc = this.getScrollElement();
+                let scrollLeft = doc.scrollLeft;
+                let scrollTop = doc.scrollTop;
+
+                this.rootState = {
+                    top: s.top, bottom: s.bottom, right: s.right, left: s.left, scrollTop, scrollLeft, position: s.position, overflow: s.overflow, viewports, zindex: this.root.style.zIndex,
+                    width: s.width, height: s.height,
+                    maxWidth: s.maxWidth, maxHeight: s.maxHeight,
+                    margin: s.margin, marginLeft: s.marginLeft, marginRight: s.marginRight, marginTop: s.marginTop, marginBottom: s.marginBottom
+                };
+
+                s.overflow = 'hidden';
+                s.position = 'fixed';
+                s.top = '0';
+                s.bottom = '0';
+                s.right = '0';
+                s.left = '0';
+
+                s.width = '100%';
+                s.height = '100%';
+                s.maxWidth = '100%';
+                s.maxHeight = '100%';
+                s.margin = '0';
+                s.marginLeft = '0';
+                s.marginRight = '0';
+                s.marginTop = '0';
+                s.marginBottom = '0';
+
+                this.root.style.zIndex = '100000';
+            } else {
+                // root.style.overflow = rootOverflow;
+                let children = head.children;
+                for (let i = 0; i < children.length; i++) {
+                    if (children[i] === this.expandedViewport) {
+                        head.removeChild(this.expandedViewport);
+                        break;
+                    }
+                }
+
+                if (this.rootState) {
+                    let s = body.style, t = this.rootState;
+                    for (let v of t.viewports) {
+                        head.appendChild(v);
+                    }
+                    s.top = t.top;
+                    s.bottom = t.bottom;
+                    s.left = t.left;
+                    s.right = t.right;
+
+                    s.width = t.width;
+                    s.height = t.height;
+                    s.maxWidth = t.maxWidth;
+                    s.maxHeight = t.maxHeight;
+                    s.margin = t.margin;
+                    s.marginLeft = t.marginLeft;
+                    s.marginRight = t.marginRight;
+                    s.marginTop = t.marginTop;
+                    s.marginBottom = t.marginBottom;
+
+                    s.position = t.position;
+                    s.overflow = t.overflow;
+                    let doc = this.getScrollElement();
+                    doc.scrollTop = t.scrollTop;
+                    doc.scrollLeft = t.scrollLeft;
+                    this.rootState = void 0;
+                    this.root.style.zIndex = t.zindex;
+                }
+            }
+        } catch (e) {
+            this.context.logger.error('Layout change error, you might have to reload the page.');
+            console.log('Layout change error, you might have to reload the page.', e);
+        }
+    }
+
+    updateTargets(targets: LayoutTarget[]) {
+        this.targets = targets;
+        this.dispatcher.schedule(() => CommonEvents.ComponentsChanged.dispatch(this.context, {}));
+    }
+
+    constructor(context: Context, public targets: LayoutTarget[], private root: HTMLElement) {
+        super(context, {
+            isExpanded: false,
+            hideControls: false,
+            collapsedControlsLayout: CollapsedControlsLayout.Outside,
+            regionStates: { }
+        });
+
+        LayoutEvents.SetState.getStream(this.context).subscribe(e => this.update(e.data));
+
+        // <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
+        this.expandedViewport = document.createElement('meta') as any;
+        this.expandedViewport.name = 'viewport';
+        this.expandedViewport.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0';
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/controller/misc/jobs.ts b/src/mol-app/controller/misc/jobs.ts
new file mode 100644
index 0000000000000000000000000000000000000000..075c32e0ffb926fa471b9ff2b7050851de8f506a
--- /dev/null
+++ b/src/mol-app/controller/misc/jobs.ts
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import { Map } from 'immutable'
+import { filter } from 'rxjs/operators';
+
+import { Controller } from '../controller'
+import { JobEvents } from '../../event/basic';
+import { Context } from '../../context/context';
+import { Job } from '../../service/job';
+
+
+export interface JobInfo {
+    name: string;
+    message: string;
+    abort?: () => void
+}
+
+export interface JobsState {
+    jobs: Map<number, JobInfo>
+}
+
+export class JobsController extends Controller<JobsState> {
+    private updated(state: Job.State) {
+        let isWatched = state.type === this.type;
+        let jobs = this.latestState.jobs!;
+
+        if (!isWatched) {
+            if (jobs.has(state.jobId)) {
+                jobs = jobs.delete(state.jobId);
+                this.setState({ jobs });
+            }
+            return;
+        }
+
+        jobs = jobs.set(state.jobId, {
+            name: state.name,
+            message: state.message,
+            abort: state.abort
+        });
+        this.setState({ jobs });
+    }
+
+    private started(job: Job.Info) {
+        this.setState({
+            jobs: this.latestState.jobs!.set(job.id, { name: job.name, message: 'Running...' })
+        });
+    }
+
+    private completed(taskId: number) {
+        if (!this.latestState.jobs!.has(taskId)) return;
+
+        this.setState({
+            jobs: this.latestState.jobs!.delete(taskId)
+        });
+    }
+
+    constructor(context: Context, private type: Job.Type) {
+        super(context, {
+            jobs: Map<number, JobInfo>()
+        });
+
+        JobEvents.StateUpdated.getStream(this.context)
+            .subscribe(e => this.updated(e.data));
+
+        JobEvents.Started.getStream(this.context).pipe(
+            filter(e => e.data.type === type))
+            .subscribe(e => this.started(e.data));
+
+        JobEvents.Completed.getStream(this.context)
+            .subscribe(e => this.completed(e.data));
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/controller/misc/log.ts b/src/mol-app/controller/misc/log.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c694612ec490bce090543c0ba11c24d72d3a3226
--- /dev/null
+++ b/src/mol-app/controller/misc/log.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import { List } from 'immutable'
+
+import { Controller } from '../controller'
+import { Context } from '../../context/context';
+import { LogEvent } from '../../event/basic';
+import { Logger } from '../../service/logger';
+
+export class LogController extends Controller<{ entries: List<Logger.Entry> }> {
+    constructor(context: Context) {
+        super(context, { entries: List<Logger.Entry>() });
+
+        LogEvent.getStream(this.context)
+            .subscribe(e => this.setState({ entries: this.latestState.entries.push(e.data) }))
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/controller/transform/list.ts b/src/mol-app/controller/transform/list.ts
new file mode 100644
index 0000000000000000000000000000000000000000..75ee2b5c23d3b0553e87af5582cb9079bfee2484
--- /dev/null
+++ b/src/mol-app/controller/transform/list.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import { Context } from '../../context/context'
+import { Controller } from '../controller';
+import { AnyTransform } from 'mol-view/state/transform';
+import { AnyEntity } from 'mol-view/state/entity';
+
+export interface TransformListState {
+    entity?: AnyEntity
+    transforms: AnyTransform[]
+}
+
+export class TransformListController extends Controller<TransformListState> {
+    constructor(context: Context) {
+        super(context, { transforms: [], entity: undefined });
+
+        context.currentTransforms.subscribe((transforms) => {
+            this.state.next({ transforms, entity: context.currentEntity.getValue() }) // TODO
+            this.setState({ transforms, entity: context.currentEntity.getValue() })
+        })
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/controller/visualization/viewport.ts b/src/mol-app/controller/visualization/viewport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..40a4ac53a3726f7cdf4a22a6867ab4b1fd6d8404
--- /dev/null
+++ b/src/mol-app/controller/visualization/viewport.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+// import { throttle } from 'rxjs/operators';
+// import { interval } from 'rxjs';
+
+import { shallowClone } from 'mol-util';
+import { Context } from '../../context/context'
+import { Controller } from '../controller';
+
+export const DefaultViewportOptions = {
+    clearColor: { r: 1, g: 1, b: 1 },
+    enableFog: true,
+    cameraFOV: 30,
+    cameraSpeed: 4
+}
+export type ViewportOptions = typeof DefaultViewportOptions
+
+export class ViewportController extends Controller<ViewportOptions> {
+    constructor(context: Context) {
+        super(context, shallowClone(DefaultViewportOptions));
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/event/basic.ts b/src/mol-app/event/basic.ts
new file mode 100644
index 0000000000000000000000000000000000000000..99cf366f9c83e66e05af729e3224d74ec7f252de
--- /dev/null
+++ b/src/mol-app/event/basic.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import { Event } from './event'
+import { Logger } from '../service/logger';
+import { Dispatcher } from '../service/dispatcher'
+import { LayoutState } from '../controller/layout';
+import { ViewportOptions } from '../controller/visualization/viewport';
+import { Job } from '../service/job';
+
+const Lane = Dispatcher.Lane;
+
+export const LogEvent = Event.create<Logger.Entry>('bs.Log', Lane.Log);
+
+export namespace CommonEvents {
+    export const LayoutChanged = Event.create('bs.Common.LayoutChanged', Lane.Slow);
+    export const ComponentsChanged = Event.create('bs.Common.ComponentsChanged', Lane.Slow);
+}
+
+export namespace JobEvents {
+    export const Started = Event.create<Job.Info>('bs.Jobs.Started', Lane.Job);
+    export const Completed = Event.create<number>('bs.Jobs.Completed', Lane.Job);
+    export const StateUpdated = Event.create<Job.State>('bs.Jobs.StateUpdated', Lane.Busy);
+}
+
+export namespace LayoutEvents {
+    export const SetState = Event.create<Partial<LayoutState>>('lm.cmd.Layout.SetState', Lane.Slow);
+    export const SetViewportOptions = Event.create<ViewportOptions>('bs.cmd.Layout.SetViewportOptions', Lane.Slow);
+}
diff --git a/src/mol-app/event/event.ts b/src/mol-app/event/event.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4e6affecc6620074f229179e54ee8c12624846f6
--- /dev/null
+++ b/src/mol-app/event/event.ts
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import { Observable } from 'rxjs';
+import { Context } from '../context/context'
+import { Dispatcher } from '../service/dispatcher'
+
+export interface Event<T> {
+    type: Event.Type<T>;
+    data: T;
+}
+
+export namespace Event {
+    export type Stream<T> = Observable<Event<T>>;
+
+    import Lane = Dispatcher.Lane
+
+    export type Any = Event<any>
+    export type AnyType = Type<any>
+
+    export interface Type<T> {
+        name: string,
+        lane: Lane,
+        dispatch(context: Context, data: T): void;
+        getStream(context: Context): Stream<T>;
+    }
+
+    const EventPrototype = {
+        dispatch<T>(this: any, context: Context, data: T) { context.dispatcher.dispatch({ type: this, data }) },
+        getStream(this: any, context: Context) { return context.dispatcher.getStream(this); }
+    }
+
+    export function create<T>(name: string, lane: Dispatcher.Lane): Type<T> {
+        return Object.create(EventPrototype, {
+            name: { writable: false, configurable: false, value: name },
+            lane: { writable: false, configurable: false, value: lane }
+        });
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/service/dispatcher.ts b/src/mol-app/service/dispatcher.ts
new file mode 100644
index 0000000000000000000000000000000000000000..58a6345c601a7dfc16cbc6d162c814235efdda2f
--- /dev/null
+++ b/src/mol-app/service/dispatcher.ts
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import { Subject } from 'rxjs';
+import { filter } from 'rxjs/operators';
+import { Event } from '../event/event'
+
+export class Dispatcher {
+    LOG_DISPATCH_STREAM = false;
+
+    private lanes: Subject<Event<any>>[] = [];
+    constructor() {
+        for (let i = 0; i <= Dispatcher.Lane.Job; i++) {
+            this.lanes.push(new Subject<Event<any>>());
+        }
+    }
+
+    dispatch<T>(event: Event<T>) {
+        if (this.LOG_DISPATCH_STREAM) console.log(event.type.name, Dispatcher.Lane[event.type.lane], event.data);
+        this.lanes[event.type.lane].next(event);
+    }
+
+    schedule(action: () => void, onError?: (e: string) => void, timeout = 1000 / 31) {
+        return setTimeout(() => {
+            if (onError) {
+                try {
+                    action.call(null)
+                } catch (e) {
+                    onError.call(null, '' + e);
+                }
+            } else {
+                action.call(null);
+            }
+        }, timeout);
+    }
+
+    getStream<T>(type: Event.Type<T>): Event.Stream<T> {
+        return this.lanes[type.lane].pipe(filter(e => e.type === type));
+    }
+
+    finished() {
+        this.lanes.forEach(l => l.complete());
+    }
+}
+
+export namespace Dispatcher {
+    export enum Lane {
+        Slow = 0,
+        Fast = 1,
+        Log = 2,
+        Busy = 3,
+        Transformer = 4,
+        Job = 5
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/service/job.ts b/src/mol-app/service/job.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ed2ec6a5da0a65538d2890c587d706f12e901687
--- /dev/null
+++ b/src/mol-app/service/job.ts
@@ -0,0 +1,132 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import { Context } from '../context/context'
+import { JobEvents } from '../event/basic';
+import { PerformanceMonitor } from 'mol-util/performance-monitor';
+import { formatProgress } from 'mol-util';
+import { Progress, Task, Run } from 'mol-task';
+
+export class Job<T> {
+    private info: Job.Info;
+    get id() { return this.info.id; }
+    get reportTime() { return this.info.reportTime; }
+
+    run(context: Context) {
+        return this.runWithContext(context).result;
+    }
+
+    runWithContext(context: Context): Job.Running<T> {
+        return new Job.Running(context, this.task, this.info);
+    }
+
+    setReportTime(report: boolean) {
+        this.info.reportTime = report;
+        return this;
+    }
+
+    constructor(public name: string, public type: Job.Type, private task: Task<T>) {
+        this.info = {
+            id: serialJobId++,
+            name,
+            type,
+            reportTime: false
+        };
+    }
+}
+
+let serialJobId = 0;
+export namespace Job {
+    export let __DEBUG_MODE__ = false;
+
+    export type Type = 'Normal' | 'Background' | 'Silent';
+
+    export interface Info {
+        id: number,
+        type: Type,
+        name: string,
+        reportTime: boolean
+    }
+
+    export class Running<T> {
+        result: Promise<T>;
+
+        private progressUpdated(progress: Progress) {
+            JobEvents.StateUpdated.dispatch(this.context, {
+                jobId: this.info.id,
+                type: this.info.type,
+                name: this.info.name,
+                message: formatProgress(progress),
+                abort: progress.requestAbort
+            });
+        }
+
+        private resolved() {
+            try {
+                this.context.performance.end('job' + this.info.id);
+                if (this.info.reportTime) {
+                    let time = this.context.performance.time('job' + this.info.id);
+                    if (this.info.type !== 'Silent') this.context.logger.info(`${this.info.name} finished in ${PerformanceMonitor.format(time)}.`)
+                }
+            } finally {
+                JobEvents.Completed.dispatch(this.context, this.info.id);
+            }
+        }
+
+        private rejected(err: any) {
+            this.context.performance.end('job' + this.info.id);
+            this.context.performance.formatTime('job' + this.info.id);
+
+            if (__DEBUG_MODE__) {
+                console.error(err);
+            }
+
+            try {
+                if (this.info.type === 'Silent') {
+                    if (err.warn)  this.context.logger.warning(`Warning (${this.info.name}): ${err.message}`);
+                    else console.error(`Error (${this.info.name})`, err);
+                } else {
+                    if (err.warn) {
+                        this.context.logger.warning(`Warning (${this.info.name}): ${err.message}`);
+                    } else {
+                        let e = '' + err;
+                        if (e.indexOf('Aborted') >= 0) this.context.logger.info(`${this.info.name}: Aborted.`);
+                        else this.context.logger.error(`Error (${this.info.name}): ${err}`);
+                    }
+                }
+            } catch (e) {
+                console.error(e);
+            } finally {
+                JobEvents.Completed.dispatch(this.context, this.info.id);
+            }
+        }
+
+        private run() {
+            JobEvents.Started.dispatch(this.context, this.info);
+            this.context.performance.start('job' + this.info.id);
+
+            this.result = Run(this.task, (p: Progress) => this.progressUpdated(p), 250)
+            this.result.then(() => this.resolved()).catch(e => this.rejected(e));
+        }
+
+        constructor(private context: Context, private task: Task<T>, private info: Info) {
+            this.run();
+        }
+    }
+
+    export interface State {
+        jobId: number,
+        type: Type,
+        name: string,
+        message: string,
+        abort?: () => void
+    }
+
+    export function create<T>(name: string, type: Type, task: Task<T>) {
+        return new Job<T>(name, type, task);
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/service/logger.ts b/src/mol-app/service/logger.ts
new file mode 100644
index 0000000000000000000000000000000000000000..901596719a035024e00f5a3c117ba4dc4e135ed2
--- /dev/null
+++ b/src/mol-app/service/logger.ts
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import { LogEvent } from '../event/basic'
+import { Context } from '../context/context'
+
+export class Logger {
+
+    private log(e: Logger.Entry) {
+        LogEvent.dispatch(this.context, e);
+    }
+
+    message(m: string) {
+        this.log({ type: Logger.EntryType.Message, timestamp: new Date(), message: m });
+    }
+
+    error(m: string) {
+        this.log({ type: Logger.EntryType.Error, timestamp: new Date(), message: m });
+    }
+
+    warning(m: string) {
+        this.log({ type: Logger.EntryType.Warning, timestamp: new Date(), message: m });
+    }
+
+    info(m: string) {
+        this.log({ type: Logger.EntryType.Info, timestamp: new Date(), message: m });
+    }
+
+    constructor(private context: Context) {
+
+    }
+}
+
+export namespace Logger {
+    export enum EntryType {
+        Message,
+        Error,
+        Warning,
+        Info
+    }
+
+    export interface Entry {
+        type: EntryType;
+        timestamp: Date;
+        message: any
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/base.scss b/src/mol-app/skin/base.scss
new file mode 100644
index 0000000000000000000000000000000000000000..355f0eccabe5e385cf87fb59e1d0b23c6bfe72fb
--- /dev/null
+++ b/src/mol-app/skin/base.scss
@@ -0,0 +1,45 @@
+
+@font-face {
+    font-family: 'fontello';
+    src: url('./fonts/fontello.eot');
+    src: url('./fonts/fontello.eot#iefix') format('embedded-opentype'),
+         url('./fonts/fontello.woff2') format('woff2'),
+         url('./fonts/fontello.woff') format('woff'),
+         url('./fonts/fontello.ttf') format('truetype'),
+         url('./fonts/fontello.svg#fontello') format('svg');
+    font-weight: normal;
+    font-style: normal;
+}
+
+@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400italic,700);
+
+.molstar-plugin {
+    font-family: "Helvetica Neue", "Source Sans Pro", Helvetica, Arial, sans-serif;
+    font-size: 14px;
+    line-height: 1.42857143;
+
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    @import 'variables';
+
+    // for bootstrap
+    $border-radius-base:        0;
+    $border-radius-large:       0;
+    $border-radius-small:       0;
+
+    @import 'bootstrap';
+
+    @import 'icons';
+    @import 'layout';
+    @import 'ui';
+    @import 'logo';
+
+    .molstar-plugin-content {
+        color: $font-color;
+    }
+
+    background: $default-background;
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap.scss b/src/mol-app/skin/bootstrap.scss
new file mode 100644
index 0000000000000000000000000000000000000000..f26fdba911a808e6de47e05e6226d84f956a84d8
--- /dev/null
+++ b/src/mol-app/skin/bootstrap.scss
@@ -0,0 +1,25 @@
+/*!
+ * Bootstrap v3.3.6 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+// Core variables and mixins
+@import "bootstrap/variables";
+@import "bootstrap/mixins";
+
+// Reset and dependencies
+@import "bootstrap/normalize";
+
+// Core CSS
+@import "bootstrap/scaffolding";
+@import "bootstrap/type";
+@import "bootstrap/forms";
+@import "bootstrap/buttons";
+
+// Components
+@import "bootstrap/button-groups";
+@import "bootstrap/input-groups";
+
+@import "bootstrap/labels";
+@import "bootstrap/badges";
diff --git a/src/mol-app/skin/bootstrap/badges.scss b/src/mol-app/skin/bootstrap/badges.scss
new file mode 100644
index 0000000000000000000000000000000000000000..b03cc5562b6aa3a752dea7960206ad146facd24f
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/badges.scss
@@ -0,0 +1,68 @@
+//
+// Badges
+// --------------------------------------------------
+
+
+// Base class
+.badge {
+    display: inline-block;
+    min-width: 10px;
+    padding: 3px 7px;
+    font-size: $font-size-small;
+    font-weight: $badge-font-weight;
+    color: $badge-color;
+    line-height: $badge-line-height;
+    vertical-align: middle;
+    white-space: nowrap;
+    text-align: center;
+    background-color: $badge-bg;
+    border-radius: $badge-border-radius;
+
+    // Empty badges collapse automatically (not available in IE8)
+    &:empty {
+      display: none;
+    }
+
+    // Quick fix for badges in buttons
+    .molstar-btn & {
+      position: relative;
+      top: -1px;
+    }
+
+    .molstar-btn-xs &,
+    .molstar-btn-group-xs > .molstar-btn & {
+      top: 0;
+      padding: 1px 5px;
+    }
+
+    // [converter] extracted a& to a.badge
+
+    // Account for badges in navs
+    .list-group-item.active > &,
+    .nav-pills > .active > a > & {
+      color: $badge-active-color;
+      background-color: $badge-active-bg;
+    }
+
+    .list-group-item > & {
+      float: right;
+    }
+
+    .list-group-item > & + & {
+      margin-right: 5px;
+    }
+
+    .nav-pills > li > a > & {
+      margin-left: 3px;
+    }
+  }
+
+  // Hover state, but only for links
+  a.badge {
+    &:hover,
+    &:focus {
+      color: $badge-link-hover-color;
+      text-decoration: none;
+      cursor: pointer;
+    }
+  }
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/button-groups.scss b/src/mol-app/skin/bootstrap/button-groups.scss
new file mode 100644
index 0000000000000000000000000000000000000000..3fd6d085e2760ba38823f4a32c1de9aaf8e0894f
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/button-groups.scss
@@ -0,0 +1,244 @@
+//
+// Button groups
+// --------------------------------------------------
+
+// Make the div behave like a button
+.molstar-btn-group,
+.molstar-btn-group-vertical {
+  position: relative;
+  display: inline-block;
+  vertical-align: middle; // match .molstar-btn alignment given font-size hack above
+  > .molstar-btn {
+    position: relative;
+    float: left;
+    // Bring the "active" button to the front
+    &:hover,
+    &:focus,
+    &:active,
+    &.active {
+      z-index: 2;
+    }
+  }
+}
+
+// Prevent double borders when buttons are next to each other
+.molstar-btn-group {
+  .molstar-btn + .molstar-btn,
+  .molstar-btn + .molstar-btn-group,
+  .molstar-btn-group + .molstar-btn,
+  .molstar-btn-group + .molstar-btn-group {
+    margin-left: -1px;
+  }
+}
+
+// Optional: Group multiple button groups together for a toolbar
+.molstar-btn-toolbar {
+  margin-left: -5px; // Offset the first child's margin
+  @include clearfix;
+
+  .molstar-btn,
+  .molstar-btn-group,
+  .input-group {
+    float: left;
+  }
+  > .molstar-btn,
+  > .molstar-btn-group,
+  > .input-group {
+    margin-left: 5px;
+  }
+}
+
+.molstar-btn-group > .molstar-btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
+  border-radius: 0;
+}
+
+// Set corners individual because sometimes a single button can be in a .molstar-btn-group and we need :first-child and :last-child to both match
+.molstar-btn-group > .molstar-btn:first-child {
+  margin-left: 0;
+  &:not(:last-child):not(.dropdown-toggle) {
+    @include border-right-radius(0);
+  }
+}
+// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it
+.molstar-btn-group > .molstar-btn:last-child:not(:first-child),
+.molstar-btn-group > .dropdown-toggle:not(:first-child) {
+  @include border-left-radius(0);
+}
+
+// Custom edits for including molstar-btn-groups within molstar-btn-groups (useful for including dropdown buttons within a molstar-btn-group)
+.molstar-btn-group > .molstar-btn-group {
+  float: left;
+}
+.molstar-btn-group > .molstar-btn-group:not(:first-child):not(:last-child) > .molstar-btn {
+  border-radius: 0;
+}
+.molstar-btn-group > .molstar-btn-group:first-child:not(:last-child) {
+  > .molstar-btn:last-child,
+  > .dropdown-toggle {
+    @include border-right-radius(0);
+  }
+}
+.molstar-btn-group > .molstar-btn-group:last-child:not(:first-child) > .molstar-btn:first-child {
+  @include border-left-radius(0);
+}
+
+// On active and open, don't show outline
+.molstar-btn-group .dropdown-toggle:active,
+.molstar-btn-group.open .dropdown-toggle {
+  outline: 0;
+}
+
+
+// Sizing
+//
+// Remix the default button sizing classes into new ones for easier manipulation.
+
+.molstar-btn-group-xs > .molstar-btn { @extend .molstar-btn-xs; }
+.molstar-btn-group-sm > .molstar-btn { @extend .molstar-btn-sm; }
+.molstar-btn-group-lg > .molstar-btn { @extend .molstar-btn-lg; }
+
+
+// Split button dropdowns
+// ----------------------
+
+// Give the line between buttons some depth
+.molstar-btn-group > .molstar-btn + .dropdown-toggle {
+  padding-left: 8px;
+  padding-right: 8px;
+}
+.molstar-btn-group > .molstar-btn-lg + .dropdown-toggle {
+  padding-left: 12px;
+  padding-right: 12px;
+}
+
+// The clickable button for toggling the menu
+// Remove the gradient and set the same inset shadow as the :active state
+.molstar-btn-group.open .dropdown-toggle {
+  @include box-shadow(inset 0 3px 5px rgba(0,0,0,.125));
+
+  // Show no shadow for `.molstar-btn-link` since it has no other button styles.
+  &.molstar-btn-link {
+    @include box-shadow(none);
+  }
+}
+
+
+// Reposition the caret
+.molstar-btn .caret {
+  margin-left: 0;
+}
+// Carets in other button sizes
+.molstar-btn-lg .caret {
+  border-width: $caret-width-large $caret-width-large 0;
+  border-bottom-width: 0;
+}
+// Upside down carets for .dropup
+.dropup .molstar-btn-lg .caret {
+  border-width: 0 $caret-width-large $caret-width-large;
+}
+
+
+// Vertical button groups
+// ----------------------
+
+.molstar-btn-group-vertical {
+  > .molstar-btn,
+  > .molstar-btn-group,
+  > .molstar-btn-group > .molstar-btn {
+    display: block;
+    float: none;
+    width: 100%;
+    max-width: 100%;
+  }
+
+  // Clear floats so dropdown menus can be properly placed
+  > .molstar-btn-group {
+    @include clearfix;
+    > .molstar-btn {
+      float: none;
+    }
+  }
+
+  > .molstar-btn + .molstar-btn,
+  > .molstar-btn + .molstar-btn-group,
+  > .molstar-btn-group + .molstar-btn,
+  > .molstar-btn-group + .molstar-btn-group {
+    margin-top: -1px;
+    margin-left: 0;
+  }
+}
+
+.molstar-btn-group-vertical > .molstar-btn {
+  &:not(:first-child):not(:last-child) {
+    border-radius: 0;
+  }
+  &:first-child:not(:last-child) {
+    @include border-top-radius($molstar-btn-border-radius-base);
+    @include border-bottom-radius(0);
+  }
+  &:last-child:not(:first-child) {
+    @include border-top-radius(0);
+    @include border-bottom-radius($molstar-btn-border-radius-base);
+  }
+}
+.molstar-btn-group-vertical > .molstar-btn-group:not(:first-child):not(:last-child) > .molstar-btn {
+  border-radius: 0;
+}
+.molstar-btn-group-vertical > .molstar-btn-group:first-child:not(:last-child) {
+  > .molstar-btn:last-child,
+  > .dropdown-toggle {
+    @include border-bottom-radius(0);
+  }
+}
+.molstar-btn-group-vertical > .molstar-btn-group:last-child:not(:first-child) > .molstar-btn:first-child {
+  @include border-top-radius(0);
+}
+
+
+// Justified button groups
+// ----------------------
+
+.molstar-btn-group-justified {
+  display: table;
+  width: 100%;
+  table-layout: fixed;
+  border-collapse: separate;
+  > .molstar-btn,
+  > .molstar-btn-group {
+    float: none;
+    display: table-cell;
+    width: 1%;
+  }
+  > .molstar-btn-group .molstar-btn {
+    width: 100%;
+  }
+
+  > .molstar-btn-group .dropdown-menu {
+    left: auto;
+  }
+}
+
+
+// Checkbox and radio options
+//
+// In order to support the browser's form validation feedback, powered by the
+// `required` attribute, we have to "hide" the inputs via `clip`. We cannot use
+// `display: none;` or `visibility: hidden;` as that also hides the popover.
+// Simply visually hiding the inputs via `opacity` would leave them clickable in
+// certain cases which is prevented by using `clip` and `pointer-events`.
+// This way, we ensure a DOM element is visible to position the popover from.
+//
+// See https://github.com/twbs/bootstrap/pull/12794 and
+// https://github.com/twbs/bootstrap/pull/14559 for more information.
+
+[data-toggle="buttons"] {
+  > .molstar-btn,
+  > .molstar-btn-group > .molstar-btn {
+    input[type="radio"],
+    input[type="checkbox"] {
+      position: absolute;
+      clip: rect(0,0,0,0);
+      pointer-events: none;
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/buttons.scss b/src/mol-app/skin/bootstrap/buttons.scss
new file mode 100644
index 0000000000000000000000000000000000000000..f775ee279506b373097a9aa27beb2a91c3d6950c
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/buttons.scss
@@ -0,0 +1,168 @@
+//
+// Buttons
+// --------------------------------------------------
+
+
+// Base styles
+// --------------------------------------------------
+
+.molstar-btn {
+    display: inline-block;
+    margin-bottom: 0; // For input.molstar-btn
+    font-weight: $molstar-btn-font-weight;
+    text-align: center;
+    vertical-align: middle;
+    touch-action: manipulation;
+    cursor: pointer;
+    background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214
+    border: 1px solid transparent;
+    white-space: nowrap;
+    @include button-size($padding-base-vertical, $padding-base-horizontal, $font-size-base, $line-height-base, $molstar-btn-border-radius-base);
+    @include user-select(none);
+
+    &,
+    &:active,
+    &.active {
+      &:focus,
+      &.focus {
+        @include tab-focus;
+      }
+    }
+
+    &:hover,
+    &:focus,
+    &.focus {
+      color: $molstar-btn-default-color;
+      text-decoration: none;
+    }
+
+    &:active,
+    &.active {
+      outline: 0;
+      background-image: none;
+      @include box-shadow(inset 0 3px 5px rgba(0,0,0,.125));
+    }
+
+    &.disabled,
+    &[disabled],
+    fieldset[disabled] & {
+      cursor: $cursor-disabled;
+      @include opacity(.65);
+      @include box-shadow(none);
+    }
+
+    // [converter] extracted a& to a.molstar-btn
+  }
+
+  a.molstar-btn {
+    &.disabled,
+    fieldset[disabled] & {
+      pointer-events: none; // Future-proof disabling of clicks on `<a>` elements
+    }
+  }
+
+
+  // Alternate buttons
+  // --------------------------------------------------
+
+  .molstar-btn-default {
+    @include button-variant($molstar-btn-default-color, $molstar-btn-default-bg, $molstar-btn-default-border);
+  }
+  .molstar-btn-primary {
+    @include button-variant($molstar-btn-primary-color, $molstar-btn-primary-bg, $molstar-btn-primary-border);
+  }
+  // Success appears as green
+  .molstar-btn-success {
+    @include button-variant($molstar-btn-success-color, $molstar-btn-success-bg, $molstar-btn-success-border);
+  }
+  // Info appears as blue-green
+  .molstar-btn-info {
+    @include button-variant($molstar-btn-info-color, $molstar-btn-info-bg, $molstar-btn-info-border);
+  }
+  // Warning appears as orange
+  .molstar-btn-warning {
+    @include button-variant($molstar-btn-warning-color, $molstar-btn-warning-bg, $molstar-btn-warning-border);
+  }
+  // Danger and error appear as red
+  .molstar-btn-danger {
+    @include button-variant($molstar-btn-danger-color, $molstar-btn-danger-bg, $molstar-btn-danger-border);
+  }
+
+
+  // Link buttons
+  // -------------------------
+
+  // Make a button look and behave like a link
+  .molstar-btn-link {
+    color: $link-color;
+    font-weight: normal;
+    border-radius: 0;
+
+    &,
+    &:active,
+    &.active,
+    &[disabled],
+    fieldset[disabled] & {
+      background-color: transparent;
+      @include box-shadow(none);
+    }
+    &,
+    &:hover,
+    &:focus,
+    &:active {
+      border-color: transparent;
+    }
+    &:hover,
+    &:focus {
+      color: $link-hover-color;
+      text-decoration: $link-hover-decoration;
+      background-color: transparent;
+    }
+    &[disabled],
+    fieldset[disabled] & {
+      &:hover,
+      &:focus {
+        color: $molstar-btn-link-disabled-color;
+        text-decoration: none;
+      }
+    }
+  }
+
+
+  // Button Sizes
+  // --------------------------------------------------
+
+  .molstar-btn-lg {
+    // line-height: ensure even-numbered height of button next to large input
+    @include button-size($padding-large-vertical, $padding-large-horizontal, $font-size-large, $line-height-large, $molstar-btn-border-radius-large);
+  }
+  .molstar-btn-sm {
+    // line-height: ensure proper height of button next to small input
+    @include button-size($padding-small-vertical, $padding-small-horizontal, $font-size-small, $line-height-small, $molstar-btn-border-radius-small);
+  }
+  .molstar-btn-xs {
+    @include button-size($padding-xs-vertical, $padding-xs-horizontal, $font-size-small, $line-height-small, $molstar-btn-border-radius-small);
+  }
+
+
+  // Block button
+  // --------------------------------------------------
+
+  .molstar-btn-block {
+    display: block;
+    width: 100%;
+  }
+
+  // Vertically space out multiple block buttons
+  .molstar-btn-block + .molstar-btn-block {
+    margin-top: 5px;
+  }
+
+  // Specificity overrides
+  input[type="submit"],
+  input[type="reset"],
+  input[type="button"] {
+    &.molstar-btn-block {
+      width: 100%;
+    }
+  }
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/forms.scss b/src/mol-app/skin/bootstrap/forms.scss
new file mode 100644
index 0000000000000000000000000000000000000000..b51eb372bc104ddf1fc19d35a212012308634fbc
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/forms.scss
@@ -0,0 +1,617 @@
+//
+// Forms
+// --------------------------------------------------
+
+
+// Normalize non-controls
+//
+// Restyle and baseline non-control form elements.
+
+fieldset {
+    padding: 0;
+    margin: 0;
+    border: 0;
+    // Chrome and Firefox set a `min-width: min-content;` on fieldsets,
+    // so we reset that to ensure it behaves more like a standard block element.
+    // See https://github.com/twbs/bootstrap/issues/12359.
+    min-width: 0;
+  }
+
+  legend {
+    display: block;
+    width: 100%;
+    padding: 0;
+    margin-bottom: $line-height-computed;
+    font-size: ($font-size-base * 1.5);
+    line-height: inherit;
+    color: $legend-color;
+    border: 0;
+    border-bottom: 1px solid $legend-border-color;
+  }
+
+  label {
+    display: inline-block;
+    max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141)
+    margin-bottom: 5px;
+    font-weight: bold;
+  }
+
+
+  // Normalize form controls
+  //
+  // While most of our form styles require extra classes, some basic normalization
+  // is required to ensure optimum display with or without those classes to better
+  // address browser inconsistencies.
+
+  // Override content-box in Normalize (* isn't specific enough)
+  input[type="search"] {
+    @include box-sizing(border-box);
+  }
+
+  // Position radios and checkboxes better
+  input[type="radio"],
+  input[type="checkbox"] {
+    margin: 4px 0 0;
+    margin-top: 1px \9; // IE8-9
+    line-height: normal;
+  }
+
+  input[type="file"] {
+    display: block;
+  }
+
+  // Make range inputs behave like textual form controls
+  input[type="range"] {
+    display: block;
+    width: 100%;
+  }
+
+  // Make multiple select elements height not fixed
+  select[multiple],
+  select[size] {
+    height: auto;
+  }
+
+  // Focus for file, radio, and checkbox
+  input[type="file"]:focus,
+  input[type="radio"]:focus,
+  input[type="checkbox"]:focus {
+    @include tab-focus;
+  }
+
+  // Adjust output element
+  output {
+    display: block;
+    padding-top: ($padding-base-vertical + 1);
+    font-size: $font-size-base;
+    line-height: $line-height-base;
+    color: $input-color;
+  }
+
+
+  // Common form controls
+  //
+  // Shared size and type resets for form controls. Apply `.molstar-form-control` to any
+  // of the following form controls:
+  //
+  // select
+  // textarea
+  // input[type="text"]
+  // input[type="password"]
+  // input[type="datetime"]
+  // input[type="datetime-local"]
+  // input[type="date"]
+  // input[type="month"]
+  // input[type="time"]
+  // input[type="week"]
+  // input[type="number"]
+  // input[type="email"]
+  // input[type="url"]
+  // input[type="search"]
+  // input[type="tel"]
+  // input[type="color"]
+
+  .molstar-form-control {
+    display: block;
+    width: 100%;
+    height: $input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)
+    padding: $padding-base-vertical $padding-base-horizontal;
+    font-size: $font-size-base;
+    line-height: $line-height-base;
+    color: $input-color;
+    background-color: $input-bg;
+    background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214
+    border: 1px solid $input-border;
+    border-radius: $input-border-radius; // Note: This has no effect on <select>s in some browsers, due to the limited stylability of <select>s in CSS.
+    //@include box-shadow(none);//inset 0 1px 1px rgba(0,0,0,.075));
+    //@include transition(border-color ease-in-out .15s, box-shadow ease-in-out .15s);
+
+    // Customize the `:focus` state to imitate native WebKit styles.
+    @include molstar-form-control-focus;
+
+    // Placeholder
+    @include placeholder;
+
+    // Unstyle the caret on `<select>`s in IE10+.
+    &::-ms-expand {
+      border: 0;
+      background-color: transparent;
+    }
+
+    // Disabled and read-only inputs
+    //
+    // HTML5 says that controls under a fieldset > legend:first-child won't be
+    // disabled if the fieldset is disabled. Due to implementation difficulty, we
+    // don't honor that edge case; we style them as disabled anyway.
+    &[disabled],
+    &[readonly],
+    fieldset[disabled] & {
+      background-color: $input-bg-disabled;
+      opacity: 1; // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655
+    }
+
+    &[disabled],
+    fieldset[disabled] & {
+      cursor: $cursor-disabled;
+    }
+
+    // [converter] extracted textarea& to textarea.molstar-form-control
+  }
+
+  // Reset height for `textarea`s
+  textarea.molstar-form-control {
+    height: auto;
+  }
+
+
+  // Search inputs in iOS
+  //
+  // This overrides the extra rounded corners on search inputs in iOS so that our
+  // `.molstar-form-control` class can properly style them. Note that this cannot simply
+  // be added to `.molstar-form-control` as it's not specific enough. For details, see
+  // https://github.com/twbs/bootstrap/issues/11586.
+
+  input[type="search"] {
+    -webkit-appearance: none;
+  }
+
+
+  // Special styles for iOS temporal inputs
+  //
+  // In Mobile Safari, setting `display: block` on temporal inputs causes the
+  // text within the input to become vertically misaligned. As a workaround, we
+  // set a pixel line-height that matches the given height of the input, but only
+  // for Safari. See https://bugs.webkit.org/show_bug.cgi?id=139848
+  //
+  // Note that as of 8.3, iOS doesn't support `datetime` or `week`.
+
+  @media screen and (-webkit-min-device-pixel-ratio: 0) {
+    input[type="date"],
+    input[type="time"],
+    input[type="datetime-local"],
+    input[type="month"] {
+      &.molstar-form-control {
+        line-height: $input-height-base;
+      }
+
+      &.input-sm,
+      .input-group-sm & {
+        line-height: $input-height-small;
+      }
+
+      &.input-lg,
+      .input-group-lg & {
+        line-height: $input-height-large;
+      }
+    }
+  }
+
+
+  // Form groups
+  //
+  // Designed to help with the organization and spacing of vertical forms. For
+  // horizontal forms, use the predefined grid classes.
+
+  .form-group {
+    margin-bottom: $form-group-margin-bottom;
+  }
+
+
+  // Checkboxes and radios
+  //
+  // Indent the labels to position radios/checkboxes as hanging controls.
+
+  .radio,
+  .checkbox {
+    position: relative;
+    display: block;
+    margin-top: 10px;
+    margin-bottom: 10px;
+
+    label {
+      min-height: $line-height-computed; // Ensure the input doesn't jump when there is no text
+      padding-left: 20px;
+      margin-bottom: 0;
+      font-weight: normal;
+      cursor: pointer;
+    }
+  }
+  .radio input[type="radio"],
+  .radio-inline input[type="radio"],
+  .checkbox input[type="checkbox"],
+  .checkbox-inline input[type="checkbox"] {
+    position: absolute;
+    margin-left: -20px;
+    margin-top: 4px \9;
+  }
+
+  .radio + .radio,
+  .checkbox + .checkbox {
+    margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing
+  }
+
+  // Radios and checkboxes on same line
+  .radio-inline,
+  .checkbox-inline {
+    position: relative;
+    display: inline-block;
+    padding-left: 20px;
+    margin-bottom: 0;
+    vertical-align: middle;
+    font-weight: normal;
+    cursor: pointer;
+  }
+  .radio-inline + .radio-inline,
+  .checkbox-inline + .checkbox-inline {
+    margin-top: 0;
+    margin-left: 10px; // space out consecutive inline controls
+  }
+
+  // Apply same disabled cursor tweak as for inputs
+  // Some special care is needed because <label>s don't inherit their parent's `cursor`.
+  //
+  // Note: Neither radios nor checkboxes can be readonly.
+  input[type="radio"],
+  input[type="checkbox"] {
+    &[disabled],
+    &.disabled,
+    fieldset[disabled] & {
+      cursor: $cursor-disabled;
+    }
+  }
+  // These classes are used directly on <label>s
+  .radio-inline,
+  .checkbox-inline {
+    &.disabled,
+    fieldset[disabled] & {
+      cursor: $cursor-disabled;
+    }
+  }
+  // These classes are used on elements with <label> descendants
+  .radio,
+  .checkbox {
+    &.disabled,
+    fieldset[disabled] & {
+      label {
+        cursor: $cursor-disabled;
+      }
+    }
+  }
+
+
+  // Static form control text
+  //
+  // Apply class to a `p` element to make any string of text align with labels in
+  // a horizontal form layout.
+
+  .molstar-form-control-static {
+    // Size it appropriately next to real form controls
+    padding-top: ($padding-base-vertical + 1);
+    padding-bottom: ($padding-base-vertical + 1);
+    // Remove default margin from `p`
+    margin-bottom: 0;
+    min-height: ($line-height-computed + $font-size-base);
+
+    &.input-lg,
+    &.input-sm {
+      padding-left: 0;
+      padding-right: 0;
+    }
+  }
+
+
+  // Form control sizing
+  //
+  // Build on `.molstar-form-control` with modifier classes to decrease or increase the
+  // height and font-size of form controls.
+  //
+  // The `.form-group-* molstar-form-control` variations are sadly duplicated to avoid the
+  // issue documented in https://github.com/twbs/bootstrap/issues/15074.
+
+  @include input-size('.input-sm', $input-height-small, $padding-small-vertical, $padding-small-horizontal, $font-size-small, $line-height-small, $input-border-radius-small);
+  .form-group-sm {
+    .molstar-form-control {
+      height: $input-height-small;
+      padding: $padding-small-vertical $padding-small-horizontal;
+      font-size: $font-size-small;
+      line-height: $line-height-small;
+      border-radius: $input-border-radius-small;
+    }
+    select.molstar-form-control {
+      height: $input-height-small;
+      line-height: $input-height-small;
+    }
+    textarea.molstar-form-control,
+    select[multiple].molstar-form-control {
+      height: auto;
+    }
+    .molstar-form-control-static {
+      height: $input-height-small;
+      min-height: ($line-height-computed + $font-size-small);
+      padding: ($padding-small-vertical + 1) $padding-small-horizontal;
+      font-size: $font-size-small;
+      line-height: $line-height-small;
+    }
+  }
+
+  @include input-size('.input-lg', $input-height-large, $padding-large-vertical, $padding-large-horizontal, $font-size-large, $line-height-large, $input-border-radius-large);
+  .form-group-lg {
+    .molstar-form-control {
+      height: $input-height-large;
+      padding: $padding-large-vertical $padding-large-horizontal;
+      font-size: $font-size-large;
+      line-height: $line-height-large;
+      border-radius: $input-border-radius-large;
+    }
+    select.molstar-form-control {
+      height: $input-height-large;
+      line-height: $input-height-large;
+    }
+    textarea.molstar-form-control,
+    select[multiple].molstar-form-control {
+      height: auto;
+    }
+    .molstar-form-control-static {
+      height: $input-height-large;
+      min-height: ($line-height-computed + $font-size-large);
+      padding: ($padding-large-vertical + 1) $padding-large-horizontal;
+      font-size: $font-size-large;
+      line-height: $line-height-large;
+    }
+  }
+
+
+  // Form control feedback states
+  //
+  // Apply contextual and semantic states to individual form controls.
+
+  .has-feedback {
+    // Enable absolute positioning
+    position: relative;
+
+    // Ensure icons don't overlap text
+    .molstar-form-control {
+      padding-right: ($input-height-base * 1.25);
+    }
+  }
+  // Feedback icon (requires .glyphicon classes)
+  .molstar-form-control-feedback {
+    position: absolute;
+    top: 0;
+    right: 0;
+    z-index: 2; // Ensure icon is above input groups
+    display: block;
+    width: $input-height-base;
+    height: $input-height-base;
+    line-height: $input-height-base;
+    text-align: center;
+    pointer-events: none;
+  }
+  .input-lg + .molstar-form-control-feedback,
+  .input-group-lg + .molstar-form-control-feedback,
+  .form-group-lg .molstar-form-control + .molstar-form-control-feedback {
+    width: $input-height-large;
+    height: $input-height-large;
+    line-height: $input-height-large;
+  }
+  .input-sm + .molstar-form-control-feedback,
+  .input-group-sm + .molstar-form-control-feedback,
+  .form-group-sm .molstar-form-control + .molstar-form-control-feedback {
+    width: $input-height-small;
+    height: $input-height-small;
+    line-height: $input-height-small;
+  }
+
+  // Feedback states
+  .has-success {
+    @include molstar-form-control-validation($state-success-text, $state-success-text, $state-success-bg);
+  }
+  .has-warning {
+    @include molstar-form-control-validation($state-warning-text, $state-warning-text, $state-warning-bg);
+  }
+  .has-error {
+    @include molstar-form-control-validation($state-danger-text, $state-danger-text, $state-danger-bg);
+  }
+
+  // Reposition feedback icon if input has visible label above
+  .has-feedback label {
+
+    & ~ .molstar-form-control-feedback {
+      top: ($line-height-computed + 5); // Height of the `label` and its margin
+    }
+    &.sr-only ~ .molstar-form-control-feedback {
+      top: 0;
+    }
+  }
+
+
+  // Help text
+  //
+  // Apply to any element you wish to create light text for placement immediately
+  // below a form control. Use for general help, formatting, or instructional text.
+
+  .help-block {
+    display: block; // account for any element using help-block
+    margin-top: 5px;
+    margin-bottom: 10px;
+    color: lighten($text-color, 25%); // lighten the text some for contrast
+  }
+
+
+  // Inline forms
+  //
+  // Make forms appear inline(-block) by adding the `.form-inline` class. Inline
+  // forms begin stacked on extra small (mobile) devices and then go inline when
+  // viewports reach <768px.
+  //
+  // Requires wrapping inputs and labels with `.form-group` for proper display of
+  // default HTML form controls and our custom form controls (e.g., input groups).
+  //
+  // Heads up! This is mixin-ed into `.navbar-form` in navbars.less.
+
+  // [converter] extracted from `.form-inline` for libsass compatibility
+  @mixin form-inline {
+
+    // Kick in the inline
+    @media (min-width: $screen-sm-min) {
+      // Inline-block all the things for "inline"
+      .form-group {
+        display: inline-block;
+        margin-bottom: 0;
+        vertical-align: middle;
+      }
+
+      // In navbar-form, allow folks to *not* use `.form-group`
+      .molstar-form-control {
+        display: inline-block;
+        width: auto; // Prevent labels from stacking above inputs in `.form-group`
+        vertical-align: middle;
+      }
+
+      // Make static controls behave like regular ones
+      .molstar-form-control-static {
+        display: inline-block;
+      }
+
+      .input-group {
+        display: inline-table;
+        vertical-align: middle;
+
+        .input-group-addon,
+        .input-group-molstar-btn,
+        .molstar-form-control {
+          width: auto;
+        }
+      }
+
+      // Input groups need that 100% width though
+      .input-group > .molstar-form-control {
+        width: 100%;
+      }
+
+      .control-label {
+        margin-bottom: 0;
+        vertical-align: middle;
+      }
+
+      // Remove default margin on radios/checkboxes that were used for stacking, and
+      // then undo the floating of radios and checkboxes to match.
+      .radio,
+      .checkbox {
+        display: inline-block;
+        margin-top: 0;
+        margin-bottom: 0;
+        vertical-align: middle;
+
+        label {
+          padding-left: 0;
+        }
+      }
+      .radio input[type="radio"],
+      .checkbox input[type="checkbox"] {
+        position: relative;
+        margin-left: 0;
+      }
+
+      // Re-override the feedback icon.
+      .has-feedback .molstar-form-control-feedback {
+        top: 0;
+      }
+    }
+  }
+  // [converter] extracted as `@mixin form-inline` for libsass compatibility
+  .form-inline {
+    @include form-inline;
+  }
+
+
+
+  // Horizontal forms
+  //
+  // Horizontal forms are built on grid classes and allow you to create forms with
+  // labels on the left and inputs on the right.
+
+  .form-horizontal {
+
+    // Consistent vertical alignment of radios and checkboxes
+    //
+    // Labels also get some reset styles, but that is scoped to a media query below.
+    .radio,
+    .checkbox,
+    .radio-inline,
+    .checkbox-inline {
+      margin-top: 0;
+      margin-bottom: 0;
+      padding-top: ($padding-base-vertical + 1); // Default padding plus a border
+    }
+    // Account for padding we're adding to ensure the alignment and of help text
+    // and other content below items
+    .radio,
+    .checkbox {
+      min-height: ($line-height-computed + ($padding-base-vertical + 1));
+    }
+
+    // Make form groups behave like rows
+    .form-group {
+      @include make-row;
+    }
+
+    // Reset spacing and right align labels, but scope to media queries so that
+    // labels on narrow viewports stack the same as a default form example.
+    @media (min-width: $screen-sm-min) {
+      .control-label {
+        text-align: right;
+        margin-bottom: 0;
+        padding-top: ($padding-base-vertical + 1); // Default padding plus a border
+      }
+    }
+
+    // Validation states
+    //
+    // Reposition the icon because it's now within a grid column and columns have
+    // `position: relative;` on them. Also accounts for the grid gutter padding.
+    .has-feedback .molstar-form-control-feedback {
+      right: floor(($grid-gutter-width / 2));
+    }
+
+    // Form group sizes
+    //
+    // Quick utility class for applying `.input-lg` and `.input-sm` styles to the
+    // inputs and labels within a `.form-group`.
+    .form-group-lg {
+      @media (min-width: $screen-sm-min) {
+        .control-label {
+          padding-top: ($padding-large-vertical + 1);
+          font-size: $font-size-large;
+        }
+      }
+    }
+    .form-group-sm {
+      @media (min-width: $screen-sm-min) {
+        .control-label {
+          padding-top: ($padding-small-vertical + 1);
+          font-size: $font-size-small;
+        }
+      }
+    }
+  }
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/input-groups.scss b/src/mol-app/skin/bootstrap/input-groups.scss
new file mode 100644
index 0000000000000000000000000000000000000000..5f57fc3df5e9c92cc30cb750ccee0182d7df9bd9
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/input-groups.scss
@@ -0,0 +1,171 @@
+//
+// Input groups
+// --------------------------------------------------
+
+// Base styles
+// -------------------------
+.input-group {
+    position: relative; // For dropdowns
+    display: table;
+    border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table
+
+    // Undo padding and float of grid classes
+    &[class*="col-"] {
+      float: none;
+      padding-left: 0;
+      padding-right: 0;
+    }
+
+    .molstar-form-control {
+      // Ensure that the input is always above the *appended* addon button for
+      // proper border colors.
+      position: relative;
+      z-index: 2;
+
+      // IE9 fubars the placeholder attribute in text inputs and the arrows on
+      // select elements in input groups. To fix it, we float the input. Details:
+      // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855
+      float: left;
+
+      width: 100%;
+      margin-bottom: 0;
+
+      &:focus {
+        z-index: 3;
+      }
+    }
+  }
+
+  // Sizing options
+  //
+  // Remix the default form control sizing classes into new ones for easier
+  // manipulation.
+
+  .input-group-lg > .molstar-form-control,
+  .input-group-lg > .input-group-addon,
+  .input-group-lg > .input-group-molstar-btn > .molstar-btn {
+    @extend .input-lg;
+  }
+  .input-group-sm > .molstar-form-control,
+  .input-group-sm > .input-group-addon,
+  .input-group-sm > .input-group-molstar-btn > .molstar-btn {
+    @extend .input-sm;
+  }
+
+
+  // Display as table-cell
+  // -------------------------
+  .input-group-addon,
+  .input-group-molstar-btn,
+  .input-group .molstar-form-control {
+    display: table-cell;
+
+    &:not(:first-child):not(:last-child) {
+      border-radius: 0;
+    }
+  }
+  // Addon and addon wrapper for buttons
+  .input-group-addon,
+  .input-group-molstar-btn {
+    width: 1%;
+    white-space: nowrap;
+    vertical-align: middle; // Match the inputs
+  }
+
+  // Text input groups
+  // -------------------------
+  .input-group-addon {
+    padding: $padding-base-vertical $padding-base-horizontal;
+    font-size: $font-size-base;
+    font-weight: normal;
+    line-height: 1;
+    color: $input-color;
+    text-align: center;
+    background-color: $input-group-addon-bg;
+    border: 1px solid $input-group-addon-border-color;
+    border-radius: $input-border-radius;
+
+    // Sizing
+    &.input-sm {
+      padding: $padding-small-vertical $padding-small-horizontal;
+      font-size: $font-size-small;
+      border-radius: $input-border-radius-small;
+    }
+    &.input-lg {
+      padding: $padding-large-vertical $padding-large-horizontal;
+      font-size: $font-size-large;
+      border-radius: $input-border-radius-large;
+    }
+
+    // Nuke default margins from checkboxes and radios to vertically center within.
+    input[type="radio"],
+    input[type="checkbox"] {
+      margin-top: 0;
+    }
+  }
+
+  // Reset rounded corners
+  .input-group .molstar-form-control:first-child,
+  .input-group-addon:first-child,
+  .input-group-molstar-btn:first-child > .molstar-btn,
+  .input-group-molstar-btn:first-child > .molstar-btn-group > .molstar-btn,
+  .input-group-molstar-btn:first-child > .dropdown-toggle,
+  .input-group-molstar-btn:last-child > .molstar-btn:not(:last-child):not(.dropdown-toggle),
+  .input-group-molstar-btn:last-child > .molstar-btn-group:not(:last-child) > .molstar-btn {
+    @include border-right-radius(0);
+  }
+  .input-group-addon:first-child {
+    border-right: 0;
+  }
+  .input-group .molstar-form-control:last-child,
+  .input-group-addon:last-child,
+  .input-group-molstar-btn:last-child > .molstar-btn,
+  .input-group-molstar-btn:last-child > .molstar-btn-group > .molstar-btn,
+  .input-group-molstar-btn:last-child > .dropdown-toggle,
+  .input-group-molstar-btn:first-child > .molstar-btn:not(:first-child),
+  .input-group-molstar-btn:first-child > .molstar-btn-group:not(:first-child) > .molstar-btn {
+    @include border-left-radius(0);
+  }
+  .input-group-addon:last-child {
+    border-left: 0;
+  }
+
+  // Button input groups
+  // -------------------------
+  .input-group-molstar-btn {
+    position: relative;
+    // Jankily prevent input button groups from wrapping with `white-space` and
+    // `font-size` in combination with `inline-block` on buttons.
+    font-size: 0;
+    white-space: nowrap;
+
+    // Negative margin for spacing, position for bringing hovered/focused/actived
+    // element above the siblings.
+    > .molstar-btn {
+      position: relative;
+      + .molstar-btn {
+        margin-left: -1px;
+      }
+      // Bring the "active" button to the front
+      &:hover,
+      &:focus,
+      &:active {
+        z-index: 2;
+      }
+    }
+
+    // Negative margin to only have a 1px border between the two
+    &:first-child {
+      > .molstar-btn,
+      > .molstar-btn-group {
+        margin-right: -1px;
+      }
+    }
+    &:last-child {
+      > .molstar-btn,
+      > .molstar-btn-group {
+        z-index: 2;
+        margin-left: -1px;
+      }
+    }
+  }
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/labels.scss b/src/mol-app/skin/bootstrap/labels.scss
new file mode 100644
index 0000000000000000000000000000000000000000..c453cd4173c362721e629c88e94c4de362b07fa1
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/labels.scss
@@ -0,0 +1,66 @@
+//
+// Labels
+// --------------------------------------------------
+
+.label {
+    display: inline;
+    padding: .2em .6em .3em;
+    font-size: 75%;
+    font-weight: bold;
+    line-height: 1;
+    color: $label-color;
+    text-align: center;
+    white-space: nowrap;
+    vertical-align: baseline;
+    border-radius: .25em;
+
+    // [converter] extracted a& to a.label
+
+    // Empty labels collapse automatically (not available in IE8)
+    &:empty {
+      display: none;
+    }
+
+    // Quick fix for labels in buttons
+    .molstar-btn & {
+      position: relative;
+      top: -1px;
+    }
+  }
+
+  // Add hover effects, but only for links
+  a.label {
+    &:hover,
+    &:focus {
+      color: $label-link-hover-color;
+      text-decoration: none;
+      cursor: pointer;
+    }
+  }
+
+  // Colors
+  // Contextual variations (linked labels get darker on :hover)
+
+  .label-default {
+    @include label-variant($label-default-bg);
+  }
+
+  .label-primary {
+    @include label-variant($label-primary-bg);
+  }
+
+  .label-success {
+    @include label-variant($label-success-bg);
+  }
+
+  .label-info {
+    @include label-variant($label-info-bg);
+  }
+
+  .label-warning {
+    @include label-variant($label-warning-bg);
+  }
+
+  .label-danger {
+    @include label-variant($label-danger-bg);
+  }
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/mixins.scss b/src/mol-app/skin/bootstrap/mixins.scss
new file mode 100644
index 0000000000000000000000000000000000000000..94b12fed20092a647d8ceaa9a0ad700f6bbad65a
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/mixins.scss
@@ -0,0 +1,40 @@
+// Mixins
+// --------------------------------------------------
+
+// Utilities
+// @import "mixins/hide-text";
+@import "mixins/opacity";
+@import "mixins/image";
+@import "mixins/labels";
+// @import "mixins/reset-filter";
+// @import "mixins/resize";
+// @import "mixins/responsive-visibility";
+// @import "mixins/size";
+@import "mixins/tab-focus";
+// @import "mixins/reset-text";
+@import "mixins/text-emphasis";
+@import "mixins/text-overflow";
+@import "mixins/vendor-prefixes";
+
+// Components
+// @import "mixins/alerts";
+@import "mixins/buttons";
+// @import "mixins/panels";
+// @import "mixins/pagination";
+// @import "mixins/list-group";
+// @import "mixins/nav-divider";
+@import "mixins/forms";
+// @import "mixins/progress-bar";
+// @import "mixins/table-row";
+
+// Skins
+@import "mixins/background-variant";
+@import "mixins/border-radius";
+// @import "mixins/gradients";
+
+// Layout
+@import "mixins/clearfix";
+// @import "mixins/center-block";
+// @import "mixins/nav-vertical-align";
+// @import "mixins/grid-framework";
+@import "mixins/grid";
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/mixins/background-variant.scss b/src/mol-app/skin/bootstrap/mixins/background-variant.scss
new file mode 100644
index 0000000000000000000000000000000000000000..a3044fff2190918a3f09b8ae795b78a5f45316a0
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/mixins/background-variant.scss
@@ -0,0 +1,12 @@
+// Contextual backgrounds
+
+// [converter] $parent hack
+@mixin bg-variant($parent, $color) {
+    #{$parent} {
+        background-color: $color;
+    }
+    a#{$parent}:hover,
+    a#{$parent}:focus {
+        background-color: darken($color, 10%);
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/mixins/border-radius.scss b/src/mol-app/skin/bootstrap/mixins/border-radius.scss
new file mode 100644
index 0000000000000000000000000000000000000000..2ba5d06e81ad3301f657b07e05ed8b9d9e604770
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/mixins/border-radius.scss
@@ -0,0 +1,18 @@
+// Single side border-radius
+
+@mixin border-top-radius($radius) {
+    border-top-right-radius: $radius;
+    border-top-left-radius: $radius;
+}
+@mixin border-right-radius($radius) {
+    border-bottom-right-radius: $radius;
+    border-top-right-radius: $radius;
+}
+@mixin border-bottom-radius($radius) {
+    border-bottom-right-radius: $radius;
+    border-bottom-left-radius: $radius;
+}
+@mixin border-left-radius($radius) {
+    border-bottom-left-radius: $radius;
+    border-top-left-radius: $radius;
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/mixins/buttons.scss b/src/mol-app/skin/bootstrap/mixins/buttons.scss
new file mode 100644
index 0000000000000000000000000000000000000000..c7be1237a5c12b198cd90091617e9bacb0107a62
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/mixins/buttons.scss
@@ -0,0 +1,65 @@
+// Button variants
+//
+// Easily pump out default styles, as well as :hover, :focus, :active,
+// and disabled options for all buttons
+
+@mixin button-variant($color, $background, $border) {
+    color: $color;
+    background-color: $background;
+    border-color: $border;
+
+    &:focus,
+    &.focus {
+      color: $color;
+      background-color: darken($background, 10%);
+          border-color: darken($border, 25%);
+    }
+    &:hover {
+      color: $color;
+      background-color: darken($background, 10%);
+          border-color: darken($border, 12%);
+    }
+    &:active,
+    &.active,
+    .open > &.dropdown-toggle {
+      color: $color;
+      background-color: darken($background, 10%);
+          border-color: darken($border, 12%);
+
+      &:hover,
+      &:focus,
+      &.focus {
+        color: $color;
+        background-color: darken($background, 17%);
+            border-color: darken($border, 25%);
+      }
+    }
+    &:active,
+    &.active,
+    .open > &.dropdown-toggle {
+      background-image: none;
+    }
+    &.disabled,
+    &[disabled],
+    fieldset[disabled] & {
+      &:hover,
+      &:focus,
+      &.focus {
+        background-color: $background;
+            border-color: $border;
+      }
+    }
+
+    .badge {
+      color: $background;
+      background-color: $color;
+    }
+  }
+
+  // Button sizes
+  @mixin button-size($padding-vertical, $padding-horizontal, $font-size, $line-height, $border-radius) {
+    padding: $padding-vertical $padding-horizontal;
+    font-size: $font-size;
+    line-height: $line-height;
+    border-radius: $border-radius;
+  }
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/mixins/clearfix.scss b/src/mol-app/skin/bootstrap/mixins/clearfix.scss
new file mode 100644
index 0000000000000000000000000000000000000000..12d42afa77016633e70bc238c2dd741129abf2f7
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/mixins/clearfix.scss
@@ -0,0 +1,22 @@
+// Clearfix
+//
+// For modern browsers
+// 1. The space content is one way to avoid an Opera bug when the
+//    contenteditable attribute is included anywhere else in the document.
+//    Otherwise it causes space to appear at the top and bottom of elements
+//    that are clearfixed.
+// 2. The use of `table` rather than `block` is only necessary if using
+//    `:before` to contain the top-margins of child elements.
+//
+// Source: http://nicolasgallagher.com/micro-clearfix-hack/
+
+@mixin clearfix() {
+    &:before,
+    &:after {
+        content: " "; // 1
+        display: table; // 2
+    }
+    &:after {
+        clear: both;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/mixins/forms.scss b/src/mol-app/skin/bootstrap/mixins/forms.scss
new file mode 100644
index 0000000000000000000000000000000000000000..2bfe4f00a75b7a0132a9412698bbdf7cf2e1cc47
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/mixins/forms.scss
@@ -0,0 +1,88 @@
+// Form validation states
+//
+// Used in forms.less to generate the form validation CSS for warnings, errors,
+// and successes.
+
+@mixin molstar-form-control-validation($text-color: #555, $border-color: #ccc, $background-color: #f5f5f5) {
+    // Color the label and help text
+    .help-block,
+    .control-label,
+    .radio,
+    .checkbox,
+    .radio-inline,
+    .checkbox-inline,
+    &.radio label,
+    &.checkbox label,
+    &.radio-inline label,
+    &.checkbox-inline label  {
+      color: $text-color;
+    }
+    // Set the border and box shadow on specific inputs to match
+    .molstar-form-control {
+      border-color: $border-color;
+      @include box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work
+      &:focus {
+        border-color: darken($border-color, 10%);
+        $shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten($border-color, 20%);
+        @include box-shadow($shadow);
+      }
+    }
+    // Set validation states also for addons
+    .input-group-addon {
+      color: $text-color;
+      border-color: $border-color;
+      background-color: $background-color;
+    }
+    // Optional feedback icon
+    .molstar-form-control-feedback {
+      color: $text-color;
+    }
+  }
+
+
+  // Form control focus state
+  //
+  // Generate a customized focus state and for any input with the specified color,
+  // which defaults to the `$input-border-focus` variable.
+  //
+  // We highly encourage you to not customize the default value, but instead use
+  // this to tweak colors on an as-needed basis. This aesthetic change is based on
+  // WebKit's default styles, but applicable to a wider range of browsers. Its
+  // usability and accessibility should be taken into account with any change.
+  //
+  // Example usage: change the default blue border and shadow to white for better
+  // contrast against a dark gray background.
+  @mixin molstar-form-control-focus($color: $input-border-focus) {
+    $color-rgba: rgba(red($color), green($color), blue($color), .6);
+    &:focus {
+      border-color: $color;
+      outline: 0;
+      @include box-shadow(inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px $color-rgba);
+    }
+  }
+
+  // Form control sizing
+  //
+  // Relative text size, padding, and border-radii changes for form controls. For
+  // horizontal sizing, wrap controls in the predefined grid classes. `<select>`
+  // element gets special love because it's special, and that's a fact!
+  // [converter] $parent hack
+  @mixin input-size($parent, $input-height, $padding-vertical, $padding-horizontal, $font-size, $line-height, $border-radius) {
+    #{$parent} {
+      height: $input-height;
+      padding: $padding-vertical $padding-horizontal;
+      font-size: $font-size;
+      line-height: $line-height;
+      border-radius: $border-radius;
+    }
+
+    select#{$parent} {
+      height: $input-height;
+      line-height: $input-height;
+    }
+
+    textarea#{$parent},
+    select[multiple]#{$parent} {
+      height: auto;
+    }
+  }
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/mixins/grid.scss b/src/mol-app/skin/bootstrap/mixins/grid.scss
new file mode 100644
index 0000000000000000000000000000000000000000..7457a0abaa5f59fd4a8128f969d61de347e315e7
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/mixins/grid.scss
@@ -0,0 +1,122 @@
+// Grid system
+//
+// Generate semantic grid columns with these mixins.
+
+// Centered container element
+@mixin container-fixed($gutter: $grid-gutter-width) {
+    margin-right: auto;
+    margin-left: auto;
+    padding-left:  floor(($gutter / 2));
+    padding-right: ceil(($gutter / 2));
+    @include clearfix;
+  }
+
+  // Creates a wrapper for a series of columns
+  @mixin make-row($gutter: $grid-gutter-width) {
+    margin-left:  ceil(($gutter / -2));
+    margin-right: floor(($gutter / -2));
+    @include clearfix;
+  }
+
+  // Generate the extra small columns
+  @mixin make-xs-column($columns, $gutter: $grid-gutter-width) {
+    position: relative;
+    float: left;
+    width: percentage(($columns / $grid-columns));
+    min-height: 1px;
+    padding-left:  ($gutter / 2);
+    padding-right: ($gutter / 2);
+  }
+  @mixin make-xs-column-offset($columns) {
+    margin-left: percentage(($columns / $grid-columns));
+  }
+  @mixin make-xs-column-push($columns) {
+    left: percentage(($columns / $grid-columns));
+  }
+  @mixin make-xs-column-pull($columns) {
+    right: percentage(($columns / $grid-columns));
+  }
+
+  // Generate the small columns
+  @mixin make-sm-column($columns, $gutter: $grid-gutter-width) {
+    position: relative;
+    min-height: 1px;
+    padding-left:  ($gutter / 2);
+    padding-right: ($gutter / 2);
+
+    @media (min-width: $screen-sm-min) {
+      float: left;
+      width: percentage(($columns / $grid-columns));
+    }
+  }
+  @mixin make-sm-column-offset($columns) {
+    @media (min-width: $screen-sm-min) {
+      margin-left: percentage(($columns / $grid-columns));
+    }
+  }
+  @mixin make-sm-column-push($columns) {
+    @media (min-width: $screen-sm-min) {
+      left: percentage(($columns / $grid-columns));
+    }
+  }
+  @mixin make-sm-column-pull($columns) {
+    @media (min-width: $screen-sm-min) {
+      right: percentage(($columns / $grid-columns));
+    }
+  }
+
+  // Generate the medium columns
+  @mixin make-md-column($columns, $gutter: $grid-gutter-width) {
+    position: relative;
+    min-height: 1px;
+    padding-left:  ($gutter / 2);
+    padding-right: ($gutter / 2);
+
+    @media (min-width: $screen-md-min) {
+      float: left;
+      width: percentage(($columns / $grid-columns));
+    }
+  }
+  @mixin make-md-column-offset($columns) {
+    @media (min-width: $screen-md-min) {
+      margin-left: percentage(($columns / $grid-columns));
+    }
+  }
+  @mixin make-md-column-push($columns) {
+    @media (min-width: $screen-md-min) {
+      left: percentage(($columns / $grid-columns));
+    }
+  }
+  @mixin make-md-column-pull($columns) {
+    @media (min-width: $screen-md-min) {
+      right: percentage(($columns / $grid-columns));
+    }
+  }
+
+  // Generate the large columns
+  @mixin make-lg-column($columns, $gutter: $grid-gutter-width) {
+    position: relative;
+    min-height: 1px;
+    padding-left:  ($gutter / 2);
+    padding-right: ($gutter / 2);
+
+    @media (min-width: $screen-lg-min) {
+      float: left;
+      width: percentage(($columns / $grid-columns));
+    }
+  }
+  @mixin make-lg-column-offset($columns) {
+    @media (min-width: $screen-lg-min) {
+      margin-left: percentage(($columns / $grid-columns));
+    }
+  }
+  @mixin make-lg-column-push($columns) {
+    @media (min-width: $screen-lg-min) {
+      left: percentage(($columns / $grid-columns));
+    }
+  }
+  @mixin make-lg-column-pull($columns) {
+    @media (min-width: $screen-lg-min) {
+      right: percentage(($columns / $grid-columns));
+    }
+  }
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/mixins/image.scss b/src/mol-app/skin/bootstrap/mixins/image.scss
new file mode 100644
index 0000000000000000000000000000000000000000..608cad5a535bf7db769366eef99d77c39a7283a2
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/mixins/image.scss
@@ -0,0 +1,33 @@
+// Image Mixins
+// - Responsive image
+// - Retina image
+
+
+// Responsive image
+//
+// Keep images from scaling beyond the width of their parents.
+@mixin img-responsive($display: block) {
+    display: $display;
+    max-width: 100%; // Part 1: Set a maximum relative to the parent
+    height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching
+}
+
+
+// Retina image
+//
+// Short retina mixin for setting background-image and -size. Note that the
+// spelling of `min--moz-device-pixel-ratio` is intentional.
+@mixin img-retina($file-1x, $file-2x, $width-1x, $height-1x) {
+    background-image: url(if($bootstrap-sass-asset-helper, twbs-image-path("#{$file-1x}"), "#{$file-1x}"));
+
+    @media
+    only screen and (-webkit-min-device-pixel-ratio: 2),
+    only screen and (   min--moz-device-pixel-ratio: 2),
+    only screen and (     -o-min-device-pixel-ratio: 2/1),
+    only screen and (        min-device-pixel-ratio: 2),
+    only screen and (                min-resolution: 192dpi),
+    only screen and (                min-resolution: 2dppx) {
+      background-image: url(if($bootstrap-sass-asset-helper, twbs-image-path("#{$file-2x}"), "#{$file-2x}"));
+      background-size: $width-1x $height-1x;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/mixins/labels.scss b/src/mol-app/skin/bootstrap/mixins/labels.scss
new file mode 100644
index 0000000000000000000000000000000000000000..1353b098dd8d4f89ff79da862d9e856f52dd674a
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/mixins/labels.scss
@@ -0,0 +1,12 @@
+// Labels
+
+@mixin label-variant($color) {
+    background-color: $color;
+
+    &[href] {
+        &:hover,
+        &:focus {
+            background-color: darken($color, 10%);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/mixins/opacity.scss b/src/mol-app/skin/bootstrap/mixins/opacity.scss
new file mode 100644
index 0000000000000000000000000000000000000000..f04356c6bb431f3c7f6e027e070fd1b39018c055
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/mixins/opacity.scss
@@ -0,0 +1,8 @@
+// Opacity
+
+@mixin opacity($opacity) {
+    opacity: $opacity;
+    // IE8 filter
+    $opacity-ie: ($opacity * 100);
+    filter: alpha(opacity=$opacity-ie);
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/mixins/tab-focus.scss b/src/mol-app/skin/bootstrap/mixins/tab-focus.scss
new file mode 100644
index 0000000000000000000000000000000000000000..414babf68561d780f937be3e206fd8ea7a49f160
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/mixins/tab-focus.scss
@@ -0,0 +1,9 @@
+// WebKit-style focus
+
+@mixin tab-focus() {
+    // Default
+    outline: thin dotted;
+    // WebKit
+    outline: 5px auto -webkit-focus-ring-color;
+    outline-offset: -2px;
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/mixins/text-emphasis.scss b/src/mol-app/skin/bootstrap/mixins/text-emphasis.scss
new file mode 100644
index 0000000000000000000000000000000000000000..dddb9ae2521c23982487c603f8606cc7c7d5495a
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/mixins/text-emphasis.scss
@@ -0,0 +1,12 @@
+// Typography
+
+// [converter] $parent hack
+@mixin text-emphasis-variant($parent, $color) {
+    #{$parent} {
+      color: $color;
+    }
+    a#{$parent}:hover,
+    a#{$parent}:focus {
+      color: darken($color, 10%);
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/mixins/text-overflow.scss b/src/mol-app/skin/bootstrap/mixins/text-overflow.scss
new file mode 100644
index 0000000000000000000000000000000000000000..b6ac5f707d4779f51d55e9657dc24e2a4e6cc77f
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/mixins/text-overflow.scss
@@ -0,0 +1,8 @@
+// Text overflow
+// Requires inline-block or block for proper styling
+
+@mixin text-overflow() {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/mixins/vendor-prefixes.scss b/src/mol-app/skin/bootstrap/mixins/vendor-prefixes.scss
new file mode 100644
index 0000000000000000000000000000000000000000..b657e7a26ef78da22d21d1a3a84b337d8b7463d0
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/mixins/vendor-prefixes.scss
@@ -0,0 +1,222 @@
+// Vendor Prefixes
+//
+// All vendor mixins are deprecated as of v3.2.0 due to the introduction of
+// Autoprefixer in our Gruntfile. They have been removed in v4.
+
+// - Animations
+// - Backface visibility
+// - Box shadow
+// - Box sizing
+// - Content columns
+// - Hyphens
+// - Placeholder text
+// - Transformations
+// - Transitions
+// - User Select
+
+
+// Animations
+@mixin animation($animation) {
+    -webkit-animation: $animation;
+         -o-animation: $animation;
+            animation: $animation;
+  }
+  @mixin animation-name($name) {
+    -webkit-animation-name: $name;
+            animation-name: $name;
+  }
+  @mixin animation-duration($duration) {
+    -webkit-animation-duration: $duration;
+            animation-duration: $duration;
+  }
+  @mixin animation-timing-function($timing-function) {
+    -webkit-animation-timing-function: $timing-function;
+            animation-timing-function: $timing-function;
+  }
+  @mixin animation-delay($delay) {
+    -webkit-animation-delay: $delay;
+            animation-delay: $delay;
+  }
+  @mixin animation-iteration-count($iteration-count) {
+    -webkit-animation-iteration-count: $iteration-count;
+            animation-iteration-count: $iteration-count;
+  }
+  @mixin animation-direction($direction) {
+    -webkit-animation-direction: $direction;
+            animation-direction: $direction;
+  }
+  @mixin animation-fill-mode($fill-mode) {
+    -webkit-animation-fill-mode: $fill-mode;
+            animation-fill-mode: $fill-mode;
+  }
+
+  // Backface visibility
+  // Prevent browsers from flickering when using CSS 3D transforms.
+  // Default value is `visible`, but can be changed to `hidden`
+
+  @mixin backface-visibility($visibility) {
+    -webkit-backface-visibility: $visibility;
+       -moz-backface-visibility: $visibility;
+            backface-visibility: $visibility;
+  }
+
+  // Drop shadows
+  //
+  // Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's
+  // supported browsers that have box shadow capabilities now support it.
+
+  @mixin box-shadow($shadow...) {
+    -webkit-box-shadow: $shadow; // iOS <4.3 & Android <4.1
+            box-shadow: $shadow;
+  }
+
+  // Box sizing
+  @mixin box-sizing($boxmodel) {
+    -webkit-box-sizing: $boxmodel;
+       -moz-box-sizing: $boxmodel;
+            box-sizing: $boxmodel;
+  }
+
+  // CSS3 Content Columns
+  @mixin content-columns($column-count, $column-gap: $grid-gutter-width) {
+    -webkit-column-count: $column-count;
+       -moz-column-count: $column-count;
+            column-count: $column-count;
+    -webkit-column-gap: $column-gap;
+       -moz-column-gap: $column-gap;
+            column-gap: $column-gap;
+  }
+
+  // Optional hyphenation
+  @mixin hyphens($mode: auto) {
+    word-wrap: break-word;
+    -webkit-hyphens: $mode;
+       -moz-hyphens: $mode;
+        -ms-hyphens: $mode; // IE10+
+         -o-hyphens: $mode;
+            hyphens: $mode;
+  }
+
+  // Placeholder text
+  @mixin placeholder($color: $input-color-placeholder) {
+    // Firefox
+    &::-moz-placeholder {
+      color: $color;
+      opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526
+    }
+    &:-ms-input-placeholder { color: $color; } // Internet Explorer 10+
+    &::-webkit-input-placeholder  { color: $color; } // Safari and Chrome
+  }
+
+  // Transformations
+  @mixin scale($ratio...) {
+    -webkit-transform: scale($ratio);
+        -ms-transform: scale($ratio); // IE9 only
+         -o-transform: scale($ratio);
+            transform: scale($ratio);
+  }
+
+  @mixin scaleX($ratio) {
+    -webkit-transform: scaleX($ratio);
+        -ms-transform: scaleX($ratio); // IE9 only
+         -o-transform: scaleX($ratio);
+            transform: scaleX($ratio);
+  }
+  @mixin scaleY($ratio) {
+    -webkit-transform: scaleY($ratio);
+        -ms-transform: scaleY($ratio); // IE9 only
+         -o-transform: scaleY($ratio);
+            transform: scaleY($ratio);
+  }
+  @mixin skew($x, $y) {
+    -webkit-transform: skewX($x) skewY($y);
+        -ms-transform: skewX($x) skewY($y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+
+         -o-transform: skewX($x) skewY($y);
+            transform: skewX($x) skewY($y);
+  }
+  @mixin translate($x, $y) {
+    -webkit-transform: translate($x, $y);
+        -ms-transform: translate($x, $y); // IE9 only
+         -o-transform: translate($x, $y);
+            transform: translate($x, $y);
+  }
+  @mixin translate3d($x, $y, $z) {
+    -webkit-transform: translate3d($x, $y, $z);
+            transform: translate3d($x, $y, $z);
+  }
+  @mixin rotate($degrees) {
+    -webkit-transform: rotate($degrees);
+        -ms-transform: rotate($degrees); // IE9 only
+         -o-transform: rotate($degrees);
+            transform: rotate($degrees);
+  }
+  @mixin rotateX($degrees) {
+    -webkit-transform: rotateX($degrees);
+        -ms-transform: rotateX($degrees); // IE9 only
+         -o-transform: rotateX($degrees);
+            transform: rotateX($degrees);
+  }
+  @mixin rotateY($degrees) {
+    -webkit-transform: rotateY($degrees);
+        -ms-transform: rotateY($degrees); // IE9 only
+         -o-transform: rotateY($degrees);
+            transform: rotateY($degrees);
+  }
+  @mixin perspective($perspective) {
+    -webkit-perspective: $perspective;
+       -moz-perspective: $perspective;
+            perspective: $perspective;
+  }
+  @mixin perspective-origin($perspective) {
+    -webkit-perspective-origin: $perspective;
+       -moz-perspective-origin: $perspective;
+            perspective-origin: $perspective;
+  }
+  @mixin transform-origin($origin) {
+    -webkit-transform-origin: $origin;
+       -moz-transform-origin: $origin;
+        -ms-transform-origin: $origin; // IE9 only
+            transform-origin: $origin;
+  }
+
+
+  // Transitions
+
+  @mixin transition($transition...) {
+    -webkit-transition: $transition;
+         -o-transition: $transition;
+            transition: $transition;
+  }
+  @mixin transition-property($transition-property...) {
+    -webkit-transition-property: $transition-property;
+            transition-property: $transition-property;
+  }
+  @mixin transition-delay($transition-delay) {
+    -webkit-transition-delay: $transition-delay;
+            transition-delay: $transition-delay;
+  }
+  @mixin transition-duration($transition-duration...) {
+    -webkit-transition-duration: $transition-duration;
+            transition-duration: $transition-duration;
+  }
+  @mixin transition-timing-function($timing-function) {
+    -webkit-transition-timing-function: $timing-function;
+            transition-timing-function: $timing-function;
+  }
+  @mixin transition-transform($transition...) {
+    -webkit-transition: -webkit-transform $transition;
+       -moz-transition: -moz-transform $transition;
+         -o-transition: -o-transform $transition;
+            transition: transform $transition;
+  }
+
+
+  // User select
+  // For selecting text on the page
+
+  @mixin user-select($select) {
+    -webkit-user-select: $select;
+       -moz-user-select: $select;
+        -ms-user-select: $select; // IE10+
+            user-select: $select;
+  }
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/normalize.scss b/src/mol-app/skin/bootstrap/normalize.scss
new file mode 100644
index 0000000000000000000000000000000000000000..7caf32b8c46f2f2926cdd3f4b86e91fdcf0e425f
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/normalize.scss
@@ -0,0 +1,424 @@
+/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
+
+//
+// 1. Set default font family to sans-serif.
+// 2. Prevent iOS and IE text size adjust after device orientation change,
+//    without disabling user zoom.
+//
+
+html {
+    font-family: sans-serif; // 1
+    -ms-text-size-adjust: 100%; // 2
+    -webkit-text-size-adjust: 100%; // 2
+  }
+
+  //
+  // Remove default margin.
+  //
+
+  body {
+    margin: 0;
+  }
+
+  // HTML5 display definitions
+  // ==========================================================================
+
+  //
+  // Correct `block` display not defined for any HTML5 element in IE 8/9.
+  // Correct `block` display not defined for `details` or `summary` in IE 10/11
+  // and Firefox.
+  // Correct `block` display not defined for `main` in IE 11.
+  //
+
+  article,
+  aside,
+  details,
+  figcaption,
+  figure,
+  footer,
+  header,
+  hgroup,
+  main,
+  menu,
+  nav,
+  section,
+  summary {
+    display: block;
+  }
+
+  //
+  // 1. Correct `inline-block` display not defined in IE 8/9.
+  // 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
+  //
+
+  audio,
+  canvas,
+  progress,
+  video {
+    display: inline-block; // 1
+    vertical-align: baseline; // 2
+  }
+
+  //
+  // Prevent modern browsers from displaying `audio` without controls.
+  // Remove excess height in iOS 5 devices.
+  //
+
+  audio:not([controls]) {
+    display: none;
+    height: 0;
+  }
+
+  //
+  // Address `[hidden]` styling not present in IE 8/9/10.
+  // Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
+  //
+
+  [hidden],
+  template {
+    display: none;
+  }
+
+  // Links
+  // ==========================================================================
+
+  //
+  // Remove the gray background color from active links in IE 10.
+  //
+
+  a {
+    background-color: transparent;
+  }
+
+  //
+  // Improve readability of focused elements when they are also in an
+  // active/hover state.
+  //
+
+  a:active,
+  a:hover {
+    outline: 0;
+  }
+
+  // Text-level semantics
+  // ==========================================================================
+
+  //
+  // Address styling not present in IE 8/9/10/11, Safari, and Chrome.
+  //
+
+  abbr[title] {
+    border-bottom: 1px dotted;
+  }
+
+  //
+  // Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
+  //
+
+  b,
+  strong {
+    font-weight: bold;
+  }
+
+  //
+  // Address styling not present in Safari and Chrome.
+  //
+
+  dfn {
+    font-style: italic;
+  }
+
+  //
+  // Address variable `h1` font-size and margin within `section` and `article`
+  // contexts in Firefox 4+, Safari, and Chrome.
+  //
+
+  h1 {
+    font-size: 2em;
+    margin: 0.67em 0;
+  }
+
+  //
+  // Address styling not present in IE 8/9.
+  //
+
+  mark {
+    background: #ff0;
+    color: #000;
+  }
+
+  //
+  // Address inconsistent and variable font size in all browsers.
+  //
+
+  small {
+    font-size: 80%;
+  }
+
+  //
+  // Prevent `sub` and `sup` affecting `line-height` in all browsers.
+  //
+
+  sub,
+  sup {
+    font-size: 75%;
+    line-height: 0;
+    position: relative;
+    vertical-align: baseline;
+  }
+
+  sup {
+    top: -0.5em;
+  }
+
+  sub {
+    bottom: -0.25em;
+  }
+
+  // Embedded content
+  // ==========================================================================
+
+  //
+  // Remove border when inside `a` element in IE 8/9/10.
+  //
+
+  img {
+    border: 0;
+  }
+
+  //
+  // Correct overflow not hidden in IE 9/10/11.
+  //
+
+  svg:not(:root) {
+    overflow: hidden;
+  }
+
+  // Grouping content
+  // ==========================================================================
+
+  //
+  // Address margin not present in IE 8/9 and Safari.
+  //
+
+  figure {
+    margin: 1em 40px;
+  }
+
+  //
+  // Address differences between Firefox and other browsers.
+  //
+
+  hr {
+    box-sizing: content-box;
+    height: 0;
+  }
+
+  //
+  // Contain overflow in all browsers.
+  //
+
+  pre {
+    overflow: auto;
+  }
+
+  //
+  // Address odd `em`-unit font size rendering in all browsers.
+  //
+
+  code,
+  kbd,
+  pre,
+  samp {
+    font-family: monospace, monospace;
+    font-size: 1em;
+  }
+
+  // Forms
+  // ==========================================================================
+
+  //
+  // Known limitation: by default, Chrome and Safari on OS X allow very limited
+  // styling of `select`, unless a `border` property is set.
+  //
+
+  //
+  // 1. Correct color not being inherited.
+  //    Known issue: affects color of disabled elements.
+  // 2. Correct font properties not being inherited.
+  // 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
+  //
+
+  button,
+  input,
+  optgroup,
+  select,
+  textarea {
+    color: inherit; // 1
+    font: inherit; // 2
+    margin: 0; // 3
+  }
+
+  //
+  // Address `overflow` set to `hidden` in IE 8/9/10/11.
+  //
+
+  button {
+    overflow: visible;
+  }
+
+  //
+  // Address inconsistent `text-transform` inheritance for `button` and `select`.
+  // All other form control elements do not inherit `text-transform` values.
+  // Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
+  // Correct `select` style inheritance in Firefox.
+  //
+
+  button,
+  select {
+    text-transform: none;
+  }
+
+  //
+  // 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+  //    and `video` controls.
+  // 2. Correct inability to style clickable `input` types in iOS.
+  // 3. Improve usability and consistency of cursor style between image-type
+  //    `input` and others.
+  //
+
+  button,
+  html input[type="button"], // 1
+  input[type="reset"],
+  input[type="submit"] {
+    -webkit-appearance: button; // 2
+    cursor: pointer; // 3
+  }
+
+  //
+  // Re-set default cursor for disabled elements.
+  //
+
+  button[disabled],
+  html input[disabled] {
+    cursor: default;
+  }
+
+  //
+  // Remove inner padding and border in Firefox 4+.
+  //
+
+  button::-moz-focus-inner,
+  input::-moz-focus-inner {
+    border: 0;
+    padding: 0;
+  }
+
+  //
+  // Address Firefox 4+ setting `line-height` on `input` using `!important` in
+  // the UA stylesheet.
+  //
+
+  input {
+    line-height: normal;
+  }
+
+  //
+  // It's recommended that you don't attempt to style these elements.
+  // Firefox's implementation doesn't respect box-sizing, padding, or width.
+  //
+  // 1. Address box sizing set to `content-box` in IE 8/9/10.
+  // 2. Remove excess padding in IE 8/9/10.
+  //
+
+  input[type="checkbox"],
+  input[type="radio"] {
+    box-sizing: border-box; // 1
+    padding: 0; // 2
+  }
+
+  //
+  // Fix the cursor style for Chrome's increment/decrement buttons. For certain
+  // `font-size` values of the `input`, it causes the cursor style of the
+  // decrement button to change from `default` to `text`.
+  //
+
+  input[type="number"]::-webkit-inner-spin-button,
+  input[type="number"]::-webkit-outer-spin-button {
+    height: auto;
+  }
+
+  //
+  // 1. Address `appearance` set to `searchfield` in Safari and Chrome.
+  // 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
+  //
+
+  input[type="search"] {
+    -webkit-appearance: textfield; // 1
+    box-sizing: content-box; //2
+  }
+
+  //
+  // Remove inner padding and search cancel button in Safari and Chrome on OS X.
+  // Safari (but not Chrome) clips the cancel button when the search input has
+  // padding (and `textfield` appearance).
+  //
+
+  input[type="search"]::-webkit-search-cancel-button,
+  input[type="search"]::-webkit-search-decoration {
+    -webkit-appearance: none;
+  }
+
+  //
+  // Define consistent border, margin, and padding.
+  //
+
+  fieldset {
+    border: 1px solid #c0c0c0;
+    margin: 0 2px;
+    padding: 0.35em 0.625em 0.75em;
+  }
+
+  //
+  // 1. Correct `color` not being inherited in IE 8/9/10/11.
+  // 2. Remove padding so people aren't caught out if they zero out fieldsets.
+  //
+
+  legend {
+    border: 0; // 1
+    padding: 0; // 2
+  }
+
+  //
+  // Remove default vertical scrollbar in IE 8/9/10/11.
+  //
+
+  textarea {
+    overflow: auto;
+  }
+
+  //
+  // Don't inherit the `font-weight` (applied by a rule above).
+  // NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
+  //
+
+  optgroup {
+    font-weight: bold;
+  }
+
+  // Tables
+  // ==========================================================================
+
+  //
+  // Remove most spacing between table cells.
+  //
+
+  table {
+    border-collapse: collapse;
+    border-spacing: 0;
+  }
+
+  td,
+  th {
+    padding: 0;
+  }
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/scaffolding.scss b/src/mol-app/skin/bootstrap/scaffolding.scss
new file mode 100644
index 0000000000000000000000000000000000000000..556b6acdfe77bec08b388e8ecf27bf86633fe3f9
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/scaffolding.scss
@@ -0,0 +1,161 @@
+//
+// Scaffolding
+// --------------------------------------------------
+
+
+// Reset the box-sizing
+//
+// Heads up! This reset may cause conflicts with some third-party widgets.
+// For recommendations on resolving such conflicts, see
+// http://getbootstrap.com/getting-started/#third-box-sizing
+* {
+    @include box-sizing(border-box);
+  }
+  *:before,
+  *:after {
+    @include box-sizing(border-box);
+  }
+
+
+  // Body reset
+
+  html {
+    font-size: 10px;
+    -webkit-tap-highlight-color: rgba(0,0,0,0);
+  }
+
+  body {
+    font-family: $font-family-base;
+    font-size: $font-size-base;
+    line-height: $line-height-base;
+    color: $text-color;
+    background-color: $body-bg;
+  }
+
+  // Reset fonts for relevant elements
+  input,
+  button,
+  select,
+  textarea {
+    font-family: inherit;
+    font-size: inherit;
+    line-height: inherit;
+  }
+
+
+  // Links
+
+  a {
+    color: $link-color;
+    text-decoration: none;
+
+    &:hover,
+    &:focus {
+      color: $link-hover-color;
+      text-decoration: $link-hover-decoration;
+    }
+
+    &:focus {
+      @include tab-focus;
+    }
+  }
+
+
+  // Figures
+  //
+  // We reset this here because previously Normalize had no `figure` margins. This
+  // ensures we don't break anyone's use of the element.
+
+  figure {
+    margin: 0;
+  }
+
+
+  // Images
+
+  img {
+    vertical-align: middle;
+  }
+
+  // Responsive images (ensure images don't scale beyond their parents)
+  .img-responsive {
+    @include img-responsive;
+  }
+
+  // Rounded corners
+  .img-rounded {
+    border-radius: $border-radius-large;
+  }
+
+  // Image thumbnails
+  //
+  // Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.
+  .img-thumbnail {
+    padding: $thumbnail-padding;
+    line-height: $line-height-base;
+    background-color: $thumbnail-bg;
+    border: 1px solid $thumbnail-border;
+    border-radius: $thumbnail-border-radius;
+    @include transition(all .2s ease-in-out);
+
+    // Keep them at most 100% wide
+    @include img-responsive(inline-block);
+  }
+
+  // Perfect circle
+  .img-circle {
+    border-radius: 50%; // set radius in percents
+  }
+
+
+  // Horizontal rules
+
+  hr {
+    margin-top:    $line-height-computed;
+    margin-bottom: $line-height-computed;
+    border: 0;
+    border-top: 1px solid $hr-border;
+  }
+
+
+  // Only display content to screen readers
+  //
+  // See: http://a11yproject.com/posts/how-to-hide-content/
+
+  .sr-only {
+    position: absolute;
+    width: 1px;
+    height: 1px;
+    margin: -1px;
+    padding: 0;
+    overflow: hidden;
+    clip: rect(0,0,0,0);
+    border: 0;
+  }
+
+  // Use in conjunction with .sr-only to only display content when it's focused.
+  // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
+  // Credit: HTML5 Boilerplate
+
+  .sr-only-focusable {
+    &:active,
+    &:focus {
+      position: static;
+      width: auto;
+      height: auto;
+      margin: 0;
+      overflow: visible;
+      clip: auto;
+    }
+  }
+
+
+  // iOS "clickable elements" fix for role="button"
+  //
+  // Fixes "clickability" issue (and more generally, the firing of events such as focus as well)
+  // for traditionally non-focusable elements with role="button"
+  // see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile
+
+  [role="button"] {
+    cursor: pointer;
+  }
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/type.scss b/src/mol-app/skin/bootstrap/type.scss
new file mode 100644
index 0000000000000000000000000000000000000000..92c0b63cac4e9c378be6c6fa1a41960fb5269634
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/type.scss
@@ -0,0 +1,298 @@
+//
+// Typography
+// --------------------------------------------------
+
+
+// Headings
+// -------------------------
+
+h1, h2, h3, h4, h5, h6,
+.h1, .h2, .h3, .h4, .h5, .h6 {
+  font-family: $headings-font-family;
+  font-weight: $headings-font-weight;
+  line-height: $headings-line-height;
+  color: $headings-color;
+
+  small,
+  .small {
+    font-weight: normal;
+    line-height: 1;
+    color: $headings-small-color;
+  }
+}
+
+h1, .h1,
+h2, .h2,
+h3, .h3 {
+  margin-top: $line-height-computed;
+  margin-bottom: ($line-height-computed / 2);
+
+  small,
+  .small {
+    font-size: 65%;
+  }
+}
+h4, .h4,
+h5, .h5,
+h6, .h6 {
+  margin-top: ($line-height-computed / 2);
+  margin-bottom: ($line-height-computed / 2);
+
+  small,
+  .small {
+    font-size: 75%;
+  }
+}
+
+h1, .h1 { font-size: $font-size-h1; }
+h2, .h2 { font-size: $font-size-h2; }
+h3, .h3 { font-size: $font-size-h3; }
+h4, .h4 { font-size: $font-size-h4; }
+h5, .h5 { font-size: $font-size-h5; }
+h6, .h6 { font-size: $font-size-h6; }
+
+
+// Body text
+// -------------------------
+
+p {
+  margin: 0 0 ($line-height-computed / 2);
+}
+
+.lead {
+  margin-bottom: $line-height-computed;
+  font-size: floor(($font-size-base * 1.15));
+  font-weight: 300;
+  line-height: 1.4;
+
+  @media (min-width: $screen-sm-min) {
+    font-size: ($font-size-base * 1.5);
+  }
+}
+
+
+// Emphasis & misc
+// -------------------------
+
+// Ex: (12px small font / 14px base font) * 100% = about 85%
+small,
+.small {
+  font-size: floor((100% * $font-size-small / $font-size-base));
+}
+
+mark,
+.mark {
+  background-color: $state-warning-bg;
+  padding: .2em;
+}
+
+// Alignment
+.text-left           { text-align: left; }
+.text-right          { text-align: right; }
+.text-center         { text-align: center; }
+.text-justify        { text-align: justify; }
+.text-nowrap         { white-space: nowrap; }
+
+// Transformation
+.text-lowercase      { text-transform: lowercase; }
+.text-uppercase      { text-transform: uppercase; }
+.text-capitalize     { text-transform: capitalize; }
+
+// Contextual colors
+.text-muted {
+  color: $text-muted;
+}
+
+@include text-emphasis-variant('.text-primary', $brand-primary);
+
+@include text-emphasis-variant('.text-success', $state-success-text);
+
+@include text-emphasis-variant('.text-info', $state-info-text);
+
+@include text-emphasis-variant('.text-warning', $state-warning-text);
+
+@include text-emphasis-variant('.text-danger', $state-danger-text);
+
+// Contextual backgrounds
+// For now we'll leave these alongside the text classes until v4 when we can
+// safely shift things around (per SemVer rules).
+.bg-primary {
+  // Given the contrast here, this is the only class to have its color inverted
+  // automatically.
+  color: #fff;
+}
+@include bg-variant('.bg-primary', $brand-primary);
+
+@include bg-variant('.bg-success', $state-success-bg);
+
+@include bg-variant('.bg-info', $state-info-bg);
+
+@include bg-variant('.bg-warning', $state-warning-bg);
+
+@include bg-variant('.bg-danger', $state-danger-bg);
+
+
+// Page header
+// -------------------------
+
+.page-header {
+  padding-bottom: (($line-height-computed / 2) - 1);
+  margin: ($line-height-computed * 2) 0 $line-height-computed;
+  border-bottom: 1px solid $page-header-border-color;
+}
+
+
+// Lists
+// -------------------------
+
+// Unordered and Ordered lists
+ul,
+ol {
+  margin-top: 0;
+  margin-bottom: ($line-height-computed / 2);
+  ul,
+  ol {
+    margin-bottom: 0;
+  }
+}
+
+// List options
+
+// [converter] extracted from `.lm-list-unstyled` for libsass compatibility
+@mixin lm-list-unstyled {
+  padding-left: 0;
+  list-style: none;
+}
+// [converter] extracted as `@mixin lm-list-unstyled` for libsass compatibility
+.lm-list-unstyled {
+  @include lm-list-unstyled;
+}
+
+
+// Inline turns list items into inline-block
+.list-inline {
+  @include lm-list-unstyled;
+  margin-left: -5px;
+
+  > li {
+    display: inline-block;
+    padding-left: 5px;
+    padding-right: 5px;
+  }
+}
+
+// Description Lists
+dl {
+  margin-top: 0; // Remove browser default
+  margin-bottom: $line-height-computed;
+}
+dt,
+dd {
+  line-height: $line-height-base;
+}
+dt {
+  font-weight: bold;
+}
+dd {
+  margin-left: 0; // Undo browser default
+}
+
+// Horizontal description lists
+//
+// Defaults to being stacked without any of the below styles applied, until the
+// grid breakpoint is reached (default of ~768px).
+
+.dl-horizontal {
+  dd {
+    @include clearfix; // Clear the floated `dt` if an empty `dd` is present
+  }
+
+  @media (min-width: $dl-horizontal-breakpoint) {
+    dt {
+      float: left;
+      width: ($dl-horizontal-offset - 20);
+      clear: left;
+      text-align: right;
+      @include text-overflow;
+    }
+    dd {
+      margin-left: $dl-horizontal-offset;
+    }
+  }
+}
+
+
+// Misc
+// -------------------------
+
+// Abbreviations and acronyms
+abbr[title],
+// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257
+abbr[data-original-title] {
+  cursor: help;
+  border-bottom: 1px dotted $abbr-border-color;
+}
+.initialism {
+  font-size: 90%;
+  @extend .text-uppercase;
+}
+
+// Blockquotes
+blockquote {
+  padding: ($line-height-computed / 2) $line-height-computed;
+  margin: 0 0 $line-height-computed;
+  font-size: $blockquote-font-size;
+  border-left: 5px solid $blockquote-border-color;
+
+  p,
+  ul,
+  ol {
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+
+  // Note: Deprecated small and .small as of v3.1.0
+  // Context: https://github.com/twbs/bootstrap/issues/11660
+  footer,
+  small,
+  .small {
+    display: block;
+    font-size: 80%; // back to default font-size
+    line-height: $line-height-base;
+    color: $blockquote-small-color;
+
+    &:before {
+      content: '\2014 \00A0'; // em dash, nbsp
+    }
+  }
+}
+
+// Opposite alignment of blockquote
+//
+// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0.
+.blockquote-reverse,
+blockquote.pull-right {
+  padding-right: 15px;
+  padding-left: 0;
+  border-right: 5px solid $blockquote-border-color;
+  border-left: 0;
+  text-align: right;
+
+  // Account for citation
+  footer,
+  small,
+  .small {
+    &:before { content: ''; }
+    &:after {
+      content: '\00A0 \2014'; // nbsp, em dash
+    }
+  }
+}
+
+// Addresses
+address {
+  margin-bottom: $line-height-computed;
+  font-style: normal;
+  line-height: $line-height-base;
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/bootstrap/variables.scss b/src/mol-app/skin/bootstrap/variables.scss
new file mode 100644
index 0000000000000000000000000000000000000000..ead0db3c4d7894471005eef2ce9f105ae82fa1b3
--- /dev/null
+++ b/src/mol-app/skin/bootstrap/variables.scss
@@ -0,0 +1,353 @@
+//== Colors
+//
+//## Gray and brand colors for use across Bootstrap.
+
+$gray-base:              #000 !default;
+$gray-darker:            lighten($gray-base, 13.5%) !default; // #222
+$gray-dark:              lighten($gray-base, 20%) !default;   // #333
+$gray:                   lighten($gray-base, 33.5%) !default; // #555
+$gray-light:             lighten($gray-base, 46.7%) !default; // #777
+$gray-lighter:           lighten($gray-base, 93.5%) !default; // #eee
+
+$brand-primary:         darken(#428bca, 6.5%) !default; // #337ab7
+$brand-success:         #5cb85c !default;
+$brand-info:            #5bc0de !default;
+$brand-warning:         #f0ad4e !default;
+$brand-danger:          #d9534f !default;
+
+
+//== Scaffolding
+//
+//## Settings for some of the most global styles.
+
+//** Background color for `<body>`.
+$body-bg:               #fff !default;
+//** Global text color on `<body>`.
+$text-color:            $gray-dark !default;
+
+//** Global textual link color.
+$link-color:            $brand-primary !default;
+//** Link hover color set via `darken()` function.
+$link-hover-color:      darken($link-color, 15%) !default;
+//** Link hover decoration.
+$link-hover-decoration: underline !default;
+
+
+//== Typography
+//
+//## Font, line-height, and color for body text, headings, and more.
+
+$font-family-sans-serif:  "Helvetica Neue", Helvetica, Arial, sans-serif !default;
+$font-family-serif:       Georgia, "Times New Roman", Times, serif !default;
+//** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`.
+$font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace !default;
+$font-family-base:        $font-family-sans-serif !default;
+
+$font-size-base:          14px !default;
+$font-size-large:         ceil(($font-size-base * 1.25)) !default; // ~18px
+$font-size-small:         ceil(($font-size-base * 0.85)) !default; // ~12px
+
+$font-size-h1:            floor(($font-size-base * 2.6)) !default; // ~36px
+$font-size-h2:            floor(($font-size-base * 2.15)) !default; // ~30px
+$font-size-h3:            ceil(($font-size-base * 1.7)) !default; // ~24px
+$font-size-h4:            ceil(($font-size-base * 1.25)) !default; // ~18px
+$font-size-h5:            $font-size-base !default;
+$font-size-h6:            ceil(($font-size-base * 0.85)) !default; // ~12px
+
+//** Unit-less `line-height` for use in components like buttons.
+$line-height-base:        1.428571429 !default; // 20/14
+//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
+$line-height-computed:    floor(($font-size-base * $line-height-base)) !default; // ~20px
+
+//** By default, this inherits from the `<body>`.
+$headings-font-family:    inherit !default;
+$headings-font-weight:    500 !default;
+$headings-line-height:    1.1 !default;
+$headings-color:          inherit !default;
+
+
+//== Components
+//
+//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
+
+$padding-base-vertical:     6px !default;
+$padding-base-horizontal:   12px !default;
+
+$padding-large-vertical:    10px !default;
+$padding-large-horizontal:  16px !default;
+
+$padding-small-vertical:    5px !default;
+$padding-small-horizontal:  10px !default;
+
+$padding-xs-vertical:       1px !default;
+$padding-xs-horizontal:     5px !default;
+
+$line-height-large:         1.3333333 !default; // extra decimals for Win 8.1 Chrome
+$line-height-small:         1.5 !default;
+
+$border-radius-base:        4px !default;
+$border-radius-large:       6px !default;
+$border-radius-small:       3px !default;
+
+//** Global color for active items (e.g., navs or dropdowns).
+$component-active-color:    #fff !default;
+//** Global background color for active items (e.g., navs or dropdowns).
+$component-active-bg:       $brand-primary !default;
+
+//** Width of the `border` for generating carets that indicator dropdowns.
+$caret-width-base:          4px !default;
+//** Carets increase slightly in size for larger components.
+$caret-width-large:         5px !default;
+
+
+//== Buttons
+//
+//## For each of Bootstrap's buttons, define text, background and border color.
+
+$molstar-btn-font-weight:                normal !default;
+
+$molstar-btn-default-color:              #333 !default;
+$molstar-btn-default-bg:                 #fff !default;
+$molstar-btn-default-border:             #ccc !default;
+
+$molstar-btn-primary-color:              #fff !default;
+$molstar-btn-primary-bg:                 $brand-primary !default;
+$molstar-btn-primary-border:             darken($molstar-btn-primary-bg, 5%) !default;
+
+$molstar-btn-success-color:              #fff !default;
+$molstar-btn-success-bg:                 $brand-success !default;
+$molstar-btn-success-border:             darken($molstar-btn-success-bg, 5%) !default;
+
+$molstar-btn-info-color:                 #fff !default;
+$molstar-btn-info-bg:                    $brand-info !default;
+$molstar-btn-info-border:                darken($molstar-btn-info-bg, 5%) !default;
+
+$molstar-btn-warning-color:              #fff !default;
+$molstar-btn-warning-bg:                 $brand-warning !default;
+$molstar-btn-warning-border:             darken($molstar-btn-warning-bg, 5%) !default;
+
+$molstar-btn-danger-color:               #fff !default;
+$molstar-btn-danger-bg:                  $brand-danger !default;
+$molstar-btn-danger-border:              darken($molstar-btn-danger-bg, 5%) !default;
+
+$molstar-btn-link-disabled-color:        $gray-light !default;
+
+// Allows for customizing button radius independently from global border radius
+$molstar-btn-border-radius-base:         $border-radius-base !default;
+$molstar-btn-border-radius-large:        $border-radius-large !default;
+$molstar-btn-border-radius-small:        $border-radius-small !default;
+
+
+//== Media queries breakpoints
+//
+//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
+
+// Extra small screen / phone
+//** Deprecated `$screen-xs` as of v3.0.1
+$screen-xs:                  480px !default;
+//** Deprecated `$screen-xs-min` as of v3.2.0
+$screen-xs-min:              $screen-xs !default;
+//** Deprecated `$screen-phone` as of v3.0.1
+$screen-phone:               $screen-xs-min !default;
+
+// Small screen / tablet
+//** Deprecated `$screen-sm` as of v3.0.1
+$screen-sm:                  768px !default;
+$screen-sm-min:              $screen-sm !default;
+//** Deprecated `$screen-tablet` as of v3.0.1
+$screen-tablet:              $screen-sm-min !default;
+
+// Medium screen / desktop
+//** Deprecated `$screen-md` as of v3.0.1
+$screen-md:                  992px !default;
+$screen-md-min:              $screen-md !default;
+//** Deprecated `$screen-desktop` as of v3.0.1
+$screen-desktop:             $screen-md-min !default;
+
+// Large screen / wide desktop
+//** Deprecated `$screen-lg` as of v3.0.1
+$screen-lg:                  1200px !default;
+$screen-lg-min:              $screen-lg !default;
+//** Deprecated `$screen-lg-desktop` as of v3.0.1
+$screen-lg-desktop:          $screen-lg-min !default;
+
+// So media queries don't overlap when required, provide a maximum
+$screen-xs-max:              ($screen-sm-min - 1) !default;
+$screen-sm-max:              ($screen-md-min - 1) !default;
+$screen-md-max:              ($screen-lg-min - 1) !default;
+
+
+//== Grid system
+//
+//## Define your custom responsive grid.
+
+//** Number of columns in the grid.
+$grid-columns:              12 !default;
+//** Padding between columns. Gets divided in half for the left and right.
+$grid-gutter-width:         30px !default;
+// Navbar collapse
+//** Point at which the navbar becomes uncollapsed.
+$grid-float-breakpoint:     $screen-sm-min !default;
+//** Point at which the navbar begins collapsing.
+$grid-float-breakpoint-max: ($grid-float-breakpoint - 1) !default;
+
+
+//== Forms
+//
+//##
+
+//** `<input>` background color
+$input-bg:                       #fff !default;
+//** `<input disabled>` background color
+$input-bg-disabled:              $gray-lighter !default;
+
+//** Text color for `<input>`s
+$input-color:                    $gray !default;
+//** `<input>` border color
+$input-border:                   #ccc !default;
+
+// TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4
+//** Default `.lm-form-control` border radius
+// This has no effect on `<select>`s in some browsers, due to the limited stylability of `<select>`s in CSS.
+$input-border-radius:            $border-radius-base !default;
+//** Large `.lm-form-control` border radius
+$input-border-radius-large:      $border-radius-large !default;
+//** Small `.lm-form-control` border radius
+$input-border-radius-small:      $border-radius-small !default;
+
+//** Border color for inputs on focus
+$input-border-focus:             #66afe9 !default;
+
+//** Placeholder text color
+$input-color-placeholder:        #999 !default;
+
+//** Default `.lm-form-control` height
+$input-height-base:              ($line-height-computed + ($padding-base-vertical * 2) + 2) !default;
+//** Large `.lm-form-control` height
+$input-height-large:             (ceil($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 2) !default;
+//** Small `.lm-form-control` height
+$input-height-small:             (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 2) !default;
+
+//** `.form-group` margin
+$form-group-margin-bottom:       15px !default;
+
+$legend-color:                   $gray-dark !default;
+$legend-border-color:            #e5e5e5 !default;
+
+//** Background color for textual input addons
+$input-group-addon-bg:           $gray-lighter !default;
+//** Border color for textual input addons
+$input-group-addon-border-color: $input-border !default;
+
+//** Disabled cursor for form controls and buttons.
+$cursor-disabled:                not-allowed !default;
+
+
+//== Thumbnails
+//
+//##
+
+//** Padding around the thumbnail image
+$thumbnail-padding:           4px !default;
+//** Thumbnail background color
+$thumbnail-bg:                $body-bg !default;
+//** Thumbnail border color
+$thumbnail-border:            #ddd !default;
+//** Thumbnail border radius
+$thumbnail-border-radius:     $border-radius-base !default;
+
+//** Custom text color for thumbnail captions
+$thumbnail-caption-color:     $text-color !default;
+//** Padding around the thumbnail caption
+$thumbnail-caption-padding:   9px !default;
+
+
+//== Type
+//
+//##
+
+//** Horizontal offset for forms and lists.
+$component-offset-horizontal: 180px !default;
+//** Text muted color
+$text-muted:                  $gray-light !default;
+//** Abbreviations and acronyms border color
+$abbr-border-color:           $gray-light !default;
+//** Headings small color
+$headings-small-color:        $gray-light !default;
+//** Blockquote small color
+$blockquote-small-color:      $gray-light !default;
+//** Blockquote font size
+$blockquote-font-size:        ($font-size-base * 1.25) !default;
+//** Blockquote border color
+$blockquote-border-color:     $gray-lighter !default;
+//** Page header border color
+$page-header-border-color:    $gray-lighter !default;
+//** Width of horizontal description list titles
+$dl-horizontal-offset:        $component-offset-horizontal !default;
+//** Point at which .dl-horizontal becomes horizontal
+$dl-horizontal-breakpoint:    $grid-float-breakpoint !default;
+//** Horizontal line color.
+$hr-border:                   $gray-lighter !default;
+
+
+//== Form states and alerts
+//
+//## Define colors for form feedback states and, by default, alerts.
+
+$state-success-text:             #3c763d !default;
+$state-success-bg:               #dff0d8 !default;
+$state-success-border:           darken(adjust-hue($state-success-bg, -10), 5%) !default;
+
+$state-info-text:                #31708f !default;
+$state-info-bg:                  #d9edf7 !default;
+$state-info-border:              darken(adjust-hue($state-info-bg, -10), 7%) !default;
+
+$state-warning-text:             #8a6d3b !default;
+$state-warning-bg:               #fcf8e3 !default;
+$state-warning-border:           darken(adjust-hue($state-warning-bg, -10), 5%) !default;
+
+$state-danger-text:              #a94442 !default;
+$state-danger-bg:                #f2dede !default;
+$state-danger-border:            darken(adjust-hue($state-danger-bg, -10), 5%) !default;
+
+
+//== Labels
+//
+//##
+
+//** Default label background color
+$label-default-bg:            $gray-light !default;
+//** Primary label background color
+$label-primary-bg:            $brand-primary !default;
+//** Success label background color
+$label-success-bg:            $brand-success !default;
+//** Info label background color
+$label-info-bg:               $brand-info !default;
+//** Warning label background color
+$label-warning-bg:            $brand-warning !default;
+//** Danger label background color
+$label-danger-bg:             $brand-danger !default;
+
+//** Default label text color
+$label-color:                 #fff !default;
+//** Default text color of a linked label
+$label-link-hover-color:      #fff !default;
+
+
+//== Badges
+//
+//##
+
+$badge-color:                 #fff !default;
+//** Linked badge text color on hover
+$badge-link-hover-color:      #fff !default;
+$badge-bg:                    $gray-light !default;
+
+//** Badge text color in active nav link
+$badge-active-color:          $link-color !default;
+//** Badge background color in active nav link
+$badge-active-bg:             #fff !default;
+
+$badge-font-weight:           bold !default;
+$badge-line-height:           1 !default;
+$badge-border-radius:         10px !default;
\ No newline at end of file
diff --git a/src/mol-app/skin/colors/blue.scss b/src/mol-app/skin/colors/blue.scss
new file mode 100644
index 0000000000000000000000000000000000000000..c31a02bd7acd89bebce6bb407ade29934725f3e5
--- /dev/null
+++ b/src/mol-app/skin/colors/blue.scss
@@ -0,0 +1,24 @@
+$default-background:         #2D3E50;
+$font-color:                 #EDF1F2;
+$hover-font-color:           #3B9AD9;
+$entity-current-font-color:  #FFFFFF;
+$lm-btn-remove-background:      #BF3A31;
+$lm-btn-remove-hover-font-color:#ffffff;
+$lm-btn-commit-on-font-color:   #ffffff;
+$entity-badge-font-color:    #ccd4e0;
+
+// used in LOG
+$log-message:          #0CCA5D;
+$log-info:             #5E3673;
+$log-warning:          #FCC937;
+$log-error:            #FD354B;
+
+$logo-background: rgba(0,0,0,0.75);
+
+@function color-lower-contrast($color, $amount) {
+    @return darken($color, $amount);
+}
+
+@function color-increase-contrast($color, $amount) {
+    @return lighten($color, $amount);
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/colors/dark.scss b/src/mol-app/skin/colors/dark.scss
new file mode 100644
index 0000000000000000000000000000000000000000..6df758473092906b8ceeb171213a18daec8330af
--- /dev/null
+++ b/src/mol-app/skin/colors/dark.scss
@@ -0,0 +1,22 @@
+$default-background:         #111318;
+$font-color:                 #ccd4e0;
+$hover-font-color:           #51A2FB;
+$entity-current-font-color:  #68BEFD;
+$molstar-btn-remove-background:      #DE0A28;
+$molstar-btn-remove-hover-font-color:#F2F4F7;
+$molstar-btn-commit-on-font-color:   #68BEFD;
+$entity-badge-font-color:    #ccd4e0;
+
+// used in LOG
+$log-message:          #0CCA5D;
+$log-info:             #5E3673;
+$log-warning:          #FCC937;
+$log-error:            #FD354B;
+
+@function color-lower-contrast($color, $amount) {
+    @return darken($color, $amount);
+}
+
+@function color-increase-contrast($color, $amount) {
+    @return lighten($color, $amount);
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/colors/light.scss b/src/mol-app/skin/colors/light.scss
new file mode 100644
index 0000000000000000000000000000000000000000..678b744d5ba2a392d60a23262e955e7f1d28bbc9
--- /dev/null
+++ b/src/mol-app/skin/colors/light.scss
@@ -0,0 +1,30 @@
+// this is complement of the dark theme
+
+@function compl($color) {
+    @return rgb(255 - red($color), 255 - green($color), 255 - blue($color));
+}
+
+$default-background:         compl(#111318);
+$font-color:                 compl(#ccd4e0);
+$hover-font-color:           compl(#51A2FB);
+$entity-current-font-color:  compl(#68BEFD);
+$molstar-btn-commit-on-font-color:   compl(#68BEFD);
+$entity-badge-font-color:    lighten(#ccd4e0,10%);
+$molstar-btn-remove-background:      #DE0A28;
+$molstar-btn-remove-hover-font-color:#F2F4F7;
+
+// used in LOG
+$log-message:          #0CCA5D;
+$log-info:             #5E3673;
+$log-warning:          #FCC937;
+$log-error:            #FD354B;
+
+$logo-background: rgba(204,201,193,0.85);
+
+@function color-lower-contrast($color, $amount) {
+    @return lighten($color, $amount);
+}
+
+@function color-increase-contrast($color, $amount) {
+    @return darken($color, $amount);
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/components/controls-base.scss b/src/mol-app/skin/components/controls-base.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e1f17a6837df972004d8b8feb8a88383884950b2
--- /dev/null
+++ b/src/mol-app/skin/components/controls-base.scss
@@ -0,0 +1,144 @@
+.molstar-btn {
+    padding: 0 $control-spacing;
+    line-height: $row-height;
+    border: none;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+}
+
+.molstar-btn, .molstar-btn:active, .molstar-btn-link:focus, .molstar-btn:hover {
+    outline: none !important;
+}
+
+.molstar-btn-icon {
+    height: $row-height;
+    width: $row-height;
+    line-height: $row-height;
+    padding: 0;
+    text-align: center;
+}
+
+.molstar-btn-link {
+    .molstar-icon {
+        font-size: 100%;
+    }
+}
+
+.molstar-btn-link, .molstar-btn-link:active, .molstar-btn-link:focus {
+    color: $molstar-btn-link-font-color;
+    text-decoration: none;
+}
+
+.molstar-btn-link:hover {
+    color: $hover-font-color;
+    text-decoration: none;
+}
+
+.molstar-btn-link-toggle-on {
+    color: $molstar-btn-link-toggle-on-font-color;
+}
+
+.molstar-btn-link-toggle-off, .molstar-btn-link-toggle-off:active, .molstar-btn-link-toggle-off:focus {
+    color: $molstar-btn-link-toggle-off-font-color;
+}
+
+.molstar-btn-link-toggle-off:hover,  .molstar-btn-link-toggle-on:hover {
+    color: $hover-font-color;
+}
+
+@mixin molstar-btn($name, $font, $bg) {
+    .molstar-btn-#{$name}, .molstar-btn-#{$name}:active, .molstar-btn-#{$name}:focus {
+        color: $font;
+        background: $bg;
+    }
+    .molstar-btn-#{$name}:hover {
+        color: $hover-font-color;
+        background: color-lower-contrast($bg, 2.5%);
+    }
+
+    .molstar-btn-#{$name}[disabled], .molstar-btn-#{$name}[disabled]:hover,
+    .molstar-btn-#{$name}[disabled]:active, .molstar-btn-#{$name}[disabled]:focus {
+        color: color-lower-contrast($font, 1%);
+    }
+}
+
+@include molstar-btn('remove', $molstar-btn-remove-font-color, $molstar-btn-remove-background);
+@include molstar-btn('action', $font-color, $molstar-btn-action-background);
+@include molstar-btn('commit-on', $molstar-btn-commit-on-font-color, $molstar-btn-commit-on-background);
+@include molstar-btn('commit-off', $molstar-btn-commit-off-font-color, $molstar-btn-commit-off-background);
+
+.molstar-btn-remove:hover {
+    color: $molstar-btn-remove-hover-font-color;
+}
+.molstar-btn-commit-on:hover {
+	color: $molstar-btn-commit-on-hover-font-color;
+}
+
+.molstar-btn-action {
+    height: $row-height;
+    line-height: $row-height;
+}
+
+.molstar-form-control {
+    width: 100%;
+    background: $molstar-form-control-background;
+    color: $font-color;
+    border: none !important;
+    padding: 0 $control-spacing;
+    line-height: $row-height - 2px;
+    height: $row-height;
+    -webkit-appearance: none;
+    -moz-appearance: none;
+    appearance: none;
+    box-shadow: none !important;
+
+    &:hover {
+        color: $hover-font-color;
+        background-color: color-increase-contrast($molstar-form-control-background, 5%);
+        border: none;
+        outline-offset: -1px;
+        outline: 1px solid color-increase-contrast($molstar-form-control-background, 20%);
+    }
+
+    &:active, &:focus {
+        color: $font-color;
+        background-color: $molstar-form-control-background;
+        border: none;
+        outline-offset: 0;
+        outline: none;
+    }
+}
+
+.molstar-btn-commit {
+    text-align: right;
+    padding-top: 0;
+    padding-bottom: 0;
+    padding-right: $control-spacing;
+    padding-left: 0;
+    line-height: $row-height;
+    border: none;
+    overflow: hidden;
+
+    .molstar-icon {
+        display: block-inline;
+        line-height: $row-height;
+        margin-right: $control-spacing;
+        width: $row-height;
+        text-align: center;
+        float: left;
+    }
+}
+
+select.molstar-form-control {
+    background: none;
+    background-color: $molstar-form-control-background;
+    background-size: 8px 12px;
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAUCAMAAACzvE1FAAAADFBMVEUzMzMzMzMzMzMzMzMKAG/3AAAAA3RSTlMAf4C/aSLHAAAAPElEQVR42q3NMQ4AIAgEQTn//2cLdRKppSGzBYwzVXvznNWs8C58CiussPJj8h6NwgorrKRdTvuV9v16Afn0AYFOB7aYAAAAAElFTkSuQmCC);
+    background-repeat: no-repeat;
+    background-position: right $control-spacing top (($row-height - 12px) / 2);
+}
+
+select.molstar-form-control:-moz-focusring {
+    color: transparent;
+    text-shadow: 0 0 0 $font-color;
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/components/controls.scss b/src/mol-app/skin/components/controls.scss
new file mode 100644
index 0000000000000000000000000000000000000000..2c8aeda0a62cc83464d9dfc128dcf5a1e7f4f1aa
--- /dev/null
+++ b/src/mol-app/skin/components/controls.scss
@@ -0,0 +1,197 @@
+
+.molstar-control-row {
+    position: relative;
+    height: $row-height;
+    background: $default-background;
+    margin-top: 1px;
+
+    > span {
+        line-height: $row-height;
+        display: block;
+        width: $control-label-width + $control-spacing;
+        text-align: right;
+        padding: 0 $control-spacing;
+        color: color-lower-contrast($font-color, 15%);
+
+        @include non-selectable;
+    }
+
+    select, button, input[type=text] {
+        @extend .molstar-form-control;
+    }
+
+    button {
+        @extend .molstar-btn;
+        @extend .molstar-btn-block;
+    }
+
+    > div:nth-child(2) {
+        background: $molstar-form-control-background;
+        position: absolute;
+        left: $control-label-width + $control-spacing;
+        top: 0;
+        right: 0;
+        bottom: 0;
+    }
+}
+
+.molstar-control-group {
+    position: relative;
+}
+
+.molstar-toggle-button {
+    .molstar-icon {
+        display: inline-block;
+        margin-right: 6px;
+    }
+
+    > div > button:hover {
+        border-color: color-increase-contrast($molstar-form-control-background, 5%) !important;
+        border: none;
+        outline-offset: -1px  !important;
+        outline: 1px solid color-increase-contrast($molstar-form-control-background, 20%) !important;
+    }
+}
+
+.molstar-slider {
+    > div {
+        > div:first-child {
+            position: absolute;
+            top: 0;
+            left: 0;
+            bottom: 0;
+            right: 0;
+            width: 100%;
+            padding-right: 50px;
+            display: table;
+
+            > div {
+                height: $row-height;
+                display: table-cell;
+                vertical-align: middle;
+                padding: 0 ($control-spacing + 4px);
+            }
+        }
+        > div:last-child {
+            position: absolute;
+            height: $row-height;
+            right: 0;
+            width: 50px;
+            top: 0;
+            bottom: 0;
+        }
+    }
+
+    input[type=text] {
+        text-align: right;
+    }
+
+    input[type=range] {
+        width: 100%;
+    }
+}
+
+.molstar-toggle-color-picker {
+    button {
+        border: $control-spacing solid $molstar-form-control-background !important;
+        margin: 0;
+        text-align: center;
+        padding-right: $control-spacing;
+        padding-left: $control-spacing;
+
+        &:hover {
+            border-color: color-increase-contrast($molstar-form-control-background, 5%) !important;
+            border: none;
+            outline-offset: -1px  !important;
+            outline: 1px solid color-increase-contrast($molstar-form-control-background, 20%) !important;
+        }
+    }
+
+    .molstar-color-picker {
+        position: absolute;
+        z-index: 100000;
+        background: $default-background;
+        border-top: 1px solid $default-background;
+        padding-bottom: $control-spacing / 2;
+        width: 100%;
+
+        // input[type=text] {
+        //     background: $molstar-form-control-background !important;
+        // }
+    }
+}
+
+.molstar-toggle-color-picker-above {
+    .molstar-color-picker {
+        top: -2 * 32px - 16px - $control-spacing / 2;
+        height: 2 * 32px + 16px + $control-spacing / 2;
+    }
+}
+
+.molstar-toggle-color-picker-below {
+    .molstar-color-picker {
+        top: $row-height;
+        height: 2 * 32px + 16px;
+    }
+}
+
+
+.molstar-control-subgroup {
+    margin-top: 1px;
+
+    .molstar-control-row {
+        margin-left: $control-spacing !important;
+        > span {
+            width: $control-label-width !important;
+        }
+
+        > div:nth-child(2) {
+            left: $control-label-width !important;
+        }
+    }
+}
+
+.molstar-conrol-group-expander {
+    display: block;
+    position: absolute;
+    line-height: $row-height;
+    padding: 0;
+    left: 0;
+    top: 0;
+    width: $control-label-width + $control-spacing;
+    text-align: left;
+
+    .molstar-icon {
+        line-height: $row-height - 3;
+        width: $row-height - 1;
+        text-align: center;
+        display: inline-block;
+        font-size: 100%;
+    }
+}
+
+.molstar-plugin-layout_controls {
+    position: absolute;
+    left: $control-spacing;
+    top: $control-spacing;
+}
+
+.molstar-plugin-layout_controls > button:first-child {
+    margin-right: 6px;
+}
+
+.molstar-empty-control {
+    display: none;
+}
+
+.molstar-control .molstar-btn-block {
+    margin-bottom: 0px;
+    margin-top: 0px;
+}
+
+.molstar-row-text {
+    > div {
+        line-height: $row-height;
+        text-align: center;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/components/entity.scss b/src/mol-app/skin/components/entity.scss
new file mode 100644
index 0000000000000000000000000000000000000000..3ce2e2305532c1d2a498233331d422ad45467acf
--- /dev/null
+++ b/src/mol-app/skin/components/entity.scss
@@ -0,0 +1,225 @@
+
+
+.molstar-entity-tree {
+    overflow: hidden;
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    top: 0;
+    padding-top: $control-spacing;
+    background: $control-background;
+
+    .molstar-entity-tree-children {
+        overflow-x: hidden;
+        overflow-y: auto;
+        position: absolute;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        top: $row-height + $control-spacing + 1;
+        padding: $control-spacing 0;
+    }
+}
+
+.molstar-entity-store-header {
+    height: $row-height + 1;
+    position: relative;
+
+    > span {
+        margin-left: 6px;
+        display: inline-block;
+        line-height: $row-height;
+        font-weight: bold;
+
+        @include non-selectable
+    }
+
+    button {
+        display: block !important;
+        height: $row-height !important;
+        margin: 0 !important;
+        line-height: $row-height !important;
+        border: none !important;
+        position: absolute;
+        top: 0;
+    }
+
+    border-bottom: 1px solid $border-color;
+}
+
+.molstar-entity-store-root {
+    overflow-x: hidden;
+    overflow-y: auto;
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    top: $row-height + 1;
+    right: 0;
+}
+
+.molstar-entity-tree-entry {
+    height: $row-height + 1;
+    position: relative;
+    border-bottom: 1px solid $control-background;
+}
+
+.molstar-entity-tree-entry-current {
+    background: color-lower-contrast($default-background, 4%) !important;
+
+    .molstar-entity-tree-entry-label {
+        color: $entity-current-font-color;
+        font-weight: bold;
+        .molstar-entity-tree-entry-label-tag {
+            font-weight: normal;
+        }
+        &:hover {
+            color: $hover-font-color;
+        }
+    }
+}
+
+.molstar-entity-tree-entry-current-path {
+    background: color-lower-contrast($default-background, 2%) !important;
+    .molstar-entity-tree-entry-label {
+        color: color-lower-contrast($entity-current-font-color, 5%);
+        &:hover {
+            color: $hover-font-color;
+        }
+    }
+}
+
+.molstar-entity-tree-entry button, .molstar-entity-tree-entry > div {
+    display: block !important;
+    height: $row-height !important;
+    margin: 0 !important;
+    line-height: $row-height !important;
+    border: none !important;
+    position: absolute;
+    top: 0;
+}
+
+
+.molstar-entity-tree-entry-toggle-group {
+    width: $row-height;
+    height: $row-height;
+    padding: 0;
+    left: 0;
+}
+
+.molstar-entity-tree-entry-toggle-visible {
+    width: $row-height;
+    right: 0; //$row-height + 6;
+    padding: 0 !important;
+    font-size: 80%;
+}
+
+.molstar-entity-tree-entry-toggle-visible-full, .molstar-entity-tree-entry-toggle-visible-full:focus, .molstar-entity-tree-entry-toggle-visible-full:active {
+    color: $entity-color-fully-visible;
+}
+
+.molstar-entity-tree-entry-toggle-visible-partial, .molstar-entity-tree-entry-toggle-visible-partial:focus, .molstar-entity-tree-entry-toggle-visible-partial:active {
+    color: $entity-color-partialy-visible;
+}
+
+.molstar-entity-tree-entry-toggle-visible-none, .molstar-entity-tree-entry-toggle-visible-none:focus, .molstar-entity-tree-entry-toggle-visible-none:active {
+    //background: transparent !important;
+    color: $entity-color-not-visible;
+}
+
+.molstar-entity-tree-entry-remove {
+    width: $row-height;
+    height: $row-height;
+    right: $row-height;
+    padding: 0 !important;
+    text-align: center;
+    font-size: 80%;
+    color: color-lower-contrast($font-color, 66%)
+}
+
+.molstar-entity-tree-entry-body {
+    position: absolute;
+    left: $row-height;
+    border-radius: 0 0 0 $entity-subtree-offset;
+    right: 0;
+    background: $default-background;
+}
+
+.molstar-entity-tree-entry .molstar-entity-badge {
+    width: $row-height;
+    position: absolute;
+    height: $row-height;
+    left: 0;
+    top: 0;
+    border-radius: 0 $entity-subtree-offset 0 $entity-subtree-offset;
+}
+
+.molstar-entity-tree-entry-label-wrap {
+    right: 2 * $row-height;
+    overflow: hidden;
+    left: $row-height;
+    height: $row-height;
+    position: absolute;
+}
+
+.molstar-entity-tree-entry-label {
+    position: absolute;
+    right: 0;
+    top: 0;
+    left: 0;
+    text-align: left !important;
+    width: 100%;
+    padding: 0 $control-spacing !important;
+}
+
+.molstar-entity-tree-entry-label-tag {
+    color: $entity-tag-color;
+    font-size: 70%;
+    display: inline-block;
+    margin-left: 6px;
+}
+
+
+.molstar-entity-tree-children-wrap {
+    padding-left: $entity-subtree-offset;
+}
+
+.molstar-entity-tree-root {
+    > .molstar-entity-tree-entry {
+        .molstar-entity-badge {
+            border-top-right-radius: 0;
+        }
+        .molstar-entity-tree-entry-label {
+            font-weight: bold;
+        }
+        .molstar-entity-tree-entry-toggle-group {
+            display: none !important;
+        }
+        .molstar-entity-tree-entry-body {
+            left: $row-height - $entity-subtree-offset !important;
+        }
+        background: $default-background;
+        border-bottom: 1px solid $border-color;
+    }
+
+    > .molstar-entity-tree-children-wrap {
+       margin-top: $control-spacing;
+       padding-left: 0 !important;
+    }
+}
+
+.molstar-panel {
+    .molstar-entity-tree-entry-toggle-visible {
+        position: absolute;
+        top: 0;
+        right: 0;
+        height: $row-height;
+        font-size: 100%;
+
+        background: $default-background; //color-increase-contrast($default-background, 4%);
+    }
+
+    // .molstar-entity-tree-entry-toggle-visible-full {
+    //     background: color-increase-contrast($default-background, 8%);
+    // }
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/components/help.scss b/src/mol-app/skin/components/help.scss
new file mode 100644
index 0000000000000000000000000000000000000000..31b400458ef9f0adc6c6792e39e61297b1f904e0
--- /dev/null
+++ b/src/mol-app/skin/components/help.scss
@@ -0,0 +1,28 @@
+
+.molstar-help-row {
+    position: relative;
+    height: $row-height;
+    background: $default-background;
+    margin-top: 1px;
+    display: table;
+    width: 100%;
+
+    > span {
+        width: $control-label-width + $control-spacing;
+        text-align: right;
+        padding: $info-vertical-padding $control-spacing;
+        color: color-lower-contrast($font-color, 15%);
+        display: table-cell;
+        font-weight: bold;
+
+        @include non-selectable;
+    }
+
+    > div {
+        background: $molstar-form-control-background;
+        position: relative;
+        padding: $info-vertical-padding $control-spacing;
+        display: table-cell;
+        color: color-lower-contrast($font-color, 15%);
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/components/jobs.scss b/src/mol-app/skin/components/jobs.scss
new file mode 100644
index 0000000000000000000000000000000000000000..13bae9ef1cc2d685ba76a4e393f2163b0979fd15
--- /dev/null
+++ b/src/mol-app/skin/components/jobs.scss
@@ -0,0 +1,131 @@
+.molstar-job-state {
+
+    line-height: $row-height;
+    //height: $row-height;
+    //position: relative;
+    //margin-top: 1px;
+
+    > span {
+        @include non-selectable;
+        //display: inline-block;
+        //padding: 0 $control-spacing;
+    }
+
+    // > button {
+    //     margin-top: -2px;
+    //     float: left;
+    //     display: block;
+    //     line-height: $row-height;
+    //     height: $row-height;
+    // }
+}
+
+/* overlay */
+
+.molstar-overlay {
+    position: absolute;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    right: 0;
+    z-index: 1000;
+
+    .molstar-overlay-background {
+        position: absolute;
+        top: 0;
+        left: 0;
+        bottom: 0;
+        right: 0;
+        background: transparent;
+        //background: black;
+        //opacity: 0.5;
+    }
+
+    .molstar-overlay-content-wrap {
+        position: absolute;
+        top: 0;
+        left: 0;
+        bottom: 0;
+        right: 0;
+        display: block;
+        width: 100%;
+        height: 100%;
+    }
+
+    .molstar-overlay-content {
+        text-align: center;
+
+        > div {
+
+            padding-top: 2 * $row-height;
+
+            .molstar-job-state {
+                $size: $row-height;
+                text-align: center;
+
+                > div {
+                    height: $size;
+                    margin-top: $control-spacing;
+                    position: relative;
+                    text-align: center;
+                    width: 100%;
+
+                    > div {
+                        height: $size;
+                        line-height: $size;
+                        display: inline-block;
+                        background: $default-background;
+                        padding: 0 ($control-spacing);
+                        font-weight: bold;
+                        @include non-selectable;
+                    }
+
+                    > button {
+                        display: inline-block;
+                        margin-top: -3px;
+                        font-size: 140%;
+                    }
+                }
+            }
+        }
+    }
+}
+
+/* background */
+
+.molstar-background-jobs {
+    position: absolute;
+    left: 0;
+    bottom: 0;
+    z-index: 1000;
+
+    .molstar-job-state {
+        $size: $row-height;
+
+        > div {
+            height: $size;
+            margin-top: 1px;
+            position: relative;
+            width: 100%;
+            background: $default-background;
+
+            > div {
+                height: $size;
+                line-height: $size;
+                display: inline-block;
+                padding: 0 ($control-spacing);
+                @include non-selectable;
+            }
+
+            > button {
+                display: inline-block;
+                margin-top: -3px;
+                font-size: 140%;
+            }
+        }
+    }
+}
+
+// .molstar-background-jobs .molstar-job-state {
+//     color:
+// }
diff --git a/src/mol-app/skin/components/log.scss b/src/mol-app/skin/components/log.scss
new file mode 100644
index 0000000000000000000000000000000000000000..9bc74918c8a11a2958d0843fecf2908d7f3201cd
--- /dev/null
+++ b/src/mol-app/skin/components/log.scss
@@ -0,0 +1,97 @@
+
+.molstar-log-wrap {
+    position: absolute;
+    right: 0;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    overflow: hidden;
+}
+
+.molstar-log {
+    position: absolute;
+    right: -20px;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    overflow-y: scroll;
+    overflow-x: hidden;
+    font-size: 90%;
+    background: $control-background;
+}
+
+.molstar-log {
+    ul {
+        padding: 0;
+        margin: 0;
+    }
+
+    color: $log-font-color;
+
+    li {
+        clear: both;
+        margin: 0;
+        background: $default-background;
+        position: relative;
+
+        &:not(:last-child) {
+            border-bottom: 1px solid $border-color;
+        }
+    }
+
+
+    .molstar-log-entry {
+        margin-left: $control-label-width;
+        background: color-lower-contrast($control-background, 5%);
+        padding: $info-vertical-padding ($control-spacing + 15px) $info-vertical-padding $control-spacing ;
+    }
+
+    .molstar-log-timestamp {
+        padding: ($info-vertical-padding + 1) $control-spacing ($info-vertical-padding - 1) $control-spacing;
+        float: left;
+        text-align: right;
+        width: $control-label-width;
+        color: $log-timestamp-font-color;
+        //vertical-align: baseline;
+        //line-height: $row-height;
+        font-size: 90%;
+    }
+
+    .molstar-log-timestamp small {
+        font-size: 90%;
+    }
+}
+
+// .molstar-log hr {
+//     border-color: $separator-color;
+//     margin: 3px 3px 0 5px;
+// }
+
+.molstar-log .label {
+    margin-top: -3px;
+    font-size: 7pt;
+}
+
+.molstar-log-entry-badge {
+    position: absolute;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    width: 6px;
+}
+
+.molstar-log-entry-message {
+    background: $log-message;
+}
+
+.molstar-log-entry-info {
+    background: $log-info;
+}
+
+.molstar-log-entry-error {
+    background: $log-error;
+}
+
+.molstar-log-entry-warning {
+    background: $log-warning;
+}
diff --git a/src/mol-app/skin/components/misc.scss b/src/mol-app/skin/components/misc.scss
new file mode 100644
index 0000000000000000000000000000000000000000..a19eefd6ad19ccdcadb3c01f71345274e5ef74e5
--- /dev/null
+++ b/src/mol-app/skin/components/misc.scss
@@ -0,0 +1,69 @@
+.molstar-description {
+    padding: $control-spacing;
+    font-size: 85%;
+    background: $default-background;
+    text-align: center;
+    //font-style: italic;
+
+    -webkit-user-select: none; /* Chrome/Safari */
+    -moz-user-select: none; /* Firefox */
+    -ms-user-select: none; /* IE10+ */
+
+    /* Rules below not implemented in browsers yet */
+    -o-user-select: none;
+    user-select: none;
+
+    font-weight: light;
+
+    cursor: default;
+}
+
+.molstar-description:not(:first-child) {
+    border-top: 1px solid $control-background;
+}
+
+.molstar-color-picker input {
+    color: black !important;
+}
+
+.molstar-no-webgl {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    left: 0;
+    top: 0;
+    display: table;
+    text-align: center;
+
+    > div {
+        b {
+            font-size: 120%;
+        }
+        display: table-cell;
+        vertical-align: middle;
+        text-align: center;
+        width: 100%;
+        height: 100%;
+    }
+}
+
+.molstar-loader-molstar-btn-file {
+    position: relative;
+    overflow: hidden;
+}
+
+.molstar-loader-molstar-btn-file input[type=file] {
+    position: absolute;
+    top: 0;
+    right: 0;
+    min-width: 100%;
+    min-height: 100%;
+    font-size: 100px;
+    text-align: right;
+    filter: alpha(opacity=0);
+    opacity: 0;
+    outline: none;
+    background: white;
+    cursor: inherit;
+    display: block;
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/components/panel.scss b/src/mol-app/skin/components/panel.scss
new file mode 100644
index 0000000000000000000000000000000000000000..affbcf9f2c83cf1d952656298871e12d71307563
--- /dev/null
+++ b/src/mol-app/skin/components/panel.scss
@@ -0,0 +1,142 @@
+.molstar-panel-header .molstar-panel-expander {
+    display: block;
+    width: 100%;
+    text-align: left;
+}
+
+.molstar-panel-header {
+
+    //border-bottom-width: 1px;
+    //border-bottom-style: solid;
+    height: $row-height;
+    border-color: $border-color;
+    position: relative;
+
+    //border-radius: $control-spacing 0 0 0;
+
+    .molstar-panel-expander-wrapper {
+
+        position: absolute;
+        top: 0;
+        left: 0;
+        right: 2 * $row-height;
+
+        button {
+            // width: 100%;
+
+            display: block;
+            width: 100%;
+            text-align: left;
+
+            height: $row-height;
+            line-height: $row-height;
+            border: none;
+            font-weight: bold;
+            //color: $panel-header-font-color;
+            padding-left: 0;
+            background: color-lower-contrast($default-background, 4%);
+            //text-align: right!important;
+
+            .molstar-icon {
+                display: inline-block;
+                margin-right: $control-spacing;
+                width: $row-height;
+                text-align: center;
+            }
+
+            &:hover {
+                background: color-lower-contrast($default-background, 4%);
+            }
+        }
+    }
+
+    .molstar-panel-description-standalone {
+        > .molstar-icon {
+            margin-left: $row-height;
+        }
+
+        width: 2 * $row-height;
+    }
+
+    .molstar-panel-description-with-action {
+        width: $row-height;
+        margin-right: $row-height;
+    }
+
+    .molstar-panel-description {
+        color: $font-color;
+        float: right;
+        background: color-lower-contrast($default-background, 4%);
+        //margin-right: $row-height;
+
+        > .molstar-icon {
+            display: block;
+            width: $row-height;
+            height: $row-height;
+            line-height: $row-height;
+            text-align: center;
+            font-size: 70%;
+            cursor: default;
+            background: color-lower-contrast($default-background, 4%);
+            color: color-lower-contrast($font-color, 66%);
+        }
+
+        .molstar-panel-description-content {
+            @include non-selectable;
+
+            color: $font-color;
+            display: none;
+            position: absolute;
+            left: 0;
+            width: 100%;
+            background: color-increase-contrast($molstar-form-control-background, 20%);
+            min-height: $row-height;
+            z-index: 1000000;
+            padding: $info-vertical-padding  $control-spacing $info-vertical-padding ($row-height + $control-spacing);
+            text-align: left;
+            //border-bottom: 1px solid color-lower-contrast($default-background, 4%);
+
+            > .molstar-icon {
+                position: absolute;
+                width: $row-height;
+                height: $row-height;
+                line-height: $row-height;
+                text-align: center;
+                font-size: 80%;
+                cursor: default;
+                top: 0;
+                left: 0;
+            }
+        }
+
+        &:hover {
+            color: $hover-font-color;
+            > .molstar-icon {
+                color: $hover-font-color;
+            }
+            .molstar-panel-description-content {
+                display: block;
+            }
+        }
+    }
+}
+
+.molstar-panel-body {
+    background: $control-background;
+}
+
+.molstar-panel {
+    margin-bottom: $control-spacing;
+}
+
+.molstar-transform-view {
+    padding-top: $control-spacing;
+}
+
+.molstar-expandable-group-color-stripe {
+    position: absolute;
+    left: 0;
+    top: $row-height - 2px;
+    width: $control-label-width + $control-spacing;
+    height: 2px;
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/components/slider.scss b/src/mol-app/skin/components/slider.scss
new file mode 100644
index 0000000000000000000000000000000000000000..2785a3de77b450c00efea05970993147d7083116
--- /dev/null
+++ b/src/mol-app/skin/components/slider.scss
@@ -0,0 +1,164 @@
+@mixin borderBox {
+    box-sizing: border-box;
+    -webkit-tap-highlight-color: rgba(0, 0, 0, 0); //  remove tap highlight color for mobile safari
+
+    * {
+      box-sizing: border-box;
+      -webkit-tap-highlight-color: rgba(0, 0, 0, 0); //  remove tap highlight color for mobile safari
+    }
+  }
+
+  .molstar-slider-base {
+    position: relative;
+    height: 14px;
+    padding: 5px 0;
+    width: 100%;
+    border-radius: $slider-border-radius-base;
+    @include borderBox;
+
+    &-rail {
+      position: absolute;
+      width: 100%;
+      background-color: $border-color;
+      height: 4px;
+      border-radius: 2px;
+    }
+
+    &-track {
+      position: absolute;
+      left: 0;
+      height: 4px;
+      border-radius: $slider-border-radius-base;
+      background-color: tint($font-color, 60%);
+    }
+
+    &-handle {
+      position: absolute;
+      margin-left: -11px;
+      margin-top: -9px;
+      width: 22px;
+      height: 22px;
+      cursor: pointer;
+      border-radius: 50%;
+      background-color: $font-color;
+      border: 4px solid $border-color;
+
+      &:hover {
+        background-color: $hover-font-color;
+      }
+    }
+
+    &-mark {
+      position: absolute;
+      top: 18px;
+      left: 0;
+      width: 100%;
+      font-size: 12px;
+    }
+
+    &-mark-text {
+      position: absolute;
+      display: inline-block;
+      vertical-align: middle;
+      text-align: center;
+      cursor: pointer;
+      color: #999;
+
+      &-active {
+        color: #666;
+      }
+    }
+
+    &-step {
+      position: absolute;
+      width: 100%;
+      height: 4px;
+      background: transparent;
+    }
+
+    &-dot {
+      position: absolute;
+      bottom: -2px;
+      margin-left: -4px;
+      width: 8px;
+      height: 8px;
+      border: 2px solid #e9e9e9;
+      background-color: #fff;
+      cursor: pointer;
+      border-radius: 50%;
+      vertical-align: middle;
+      &:first-child {
+        margin-left: -4px;
+      }
+      &:last-child {
+        margin-left: -4px;
+      }
+      &-active {
+        border-color: tint($font-color, 50%);
+      }
+    }
+
+    &-disabled {
+      background-color: #e9e9e9;
+
+      .molstar-slider-base-track {
+        background-color: $slider-disabledColor;
+      }
+
+      .molstar-slider-base-handle, .molstar-slider-base-dot {
+        border-color: $slider-disabledColor;
+        background-color: #fff;
+        cursor: not-allowed;
+      }
+
+      .molstar-slider-base-mark-text, .molstar-slider-base-dot {
+        cursor: not-allowed!important;
+      }
+    }
+  }
+
+  .molstar-slider-base-vertical {
+    width: 14px;
+    height: 100%;
+    padding: 0 5px;
+
+    .molstar-slider-base {
+      &-rail {
+        height: 100%;
+        width: 4px;
+      }
+
+      &-track {
+        left: 5px;
+        bottom: 0;
+        width: 4px;
+      }
+
+      &-handle {
+        margin-left: -5px;
+        margin-bottom: -7px;
+      }
+
+      &-mark {
+        top: 0;
+        left: 18px;
+        height: 100%;
+      }
+
+      &-step {
+        height: 100%;
+        width: 4px;
+      }
+
+      &-dot {
+        left: 2px;
+        margin-bottom: -4px;
+        &:first-child {
+          margin-bottom: -4px;
+        }
+        &:last-child {
+          margin-bottom: -4px;
+        }
+      }
+    }
+  }
\ No newline at end of file
diff --git a/src/mol-app/skin/components/viewport.scss b/src/mol-app/skin/components/viewport.scss
new file mode 100644
index 0000000000000000000000000000000000000000..3ef5c8a6ad5831d446e578e7ddbd4662d945e3c6
--- /dev/null
+++ b/src/mol-app/skin/components/viewport.scss
@@ -0,0 +1,93 @@
+
+.molstar-viewport {
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    background: black;
+
+    .molstar-btn-link {
+        background: rgba(0,0,0,0.2);
+    }
+
+}
+
+.molstar-viewport-expanded {
+    position: fixed;
+    z-index: 1000;
+}
+
+.molstar-viewport-container {
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    -webkit-user-select: none;
+    -webkit-tap-highlight-color: rgba(0,0,0,0);
+    -webkit-touch-callout: none;
+}
+
+.molstar-viewport-controls {
+    position: absolute;
+    right: $control-spacing;
+    top: $control-spacing;
+}
+
+.molstar-viewport-controls-buttons {
+    text-align: right;
+
+    > button {
+        padding: 0;
+        text-align: center;
+        width: $row-height;
+    }
+
+    > button:last-child {
+        margin-left: $control-spacing;
+    }
+
+    .molstar-btn-link, .molstar-btn-link-toggle-on {
+        color: #eee;
+    }
+
+    .molstar-btn-link-toggle-off {
+        color: $molstar-btn-link-toggle-off-font-color;
+    }
+
+    .molstar-btn-link:hover {
+        color: $hover-font-color;
+    }
+}
+
+.molstar-viewport-controls-scene-options {
+    width: 290px;
+    background: $control-background;
+}
+
+/* highlight */
+
+.molstar-highlight-info {
+
+    color: $highlight-info-font-color;
+    padding: $info-vertical-padding $control-spacing;
+    background: $default-background; //$highlight-info-background;
+
+    position: absolute;
+    top: $control-spacing;
+    left: $control-spacing;
+    text-align: left;
+    min-height: $row-height;
+    max-width: 95%;
+
+    //border-bottom-right-radius: 6px;
+    z-index: 10000;
+    @include non-selectable;
+}
+
+.molstar-highlight-info-additional {
+    font-size: 85%;
+    display: inline-block;
+    color: $highlight-info-additional-font-color;
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/fonts/fontello.eot b/src/mol-app/skin/fonts/fontello.eot
new file mode 100644
index 0000000000000000000000000000000000000000..b522411b265bb3854a7f23579001bfe51e4229cb
Binary files /dev/null and b/src/mol-app/skin/fonts/fontello.eot differ
diff --git a/src/mol-app/skin/fonts/fontello.svg b/src/mol-app/skin/fonts/fontello.svg
new file mode 100644
index 0000000000000000000000000000000000000000..753bf788b40774bbb9a88c2b847d0b335975882a
--- /dev/null
+++ b/src/mol-app/skin/fonts/fontello.svg
@@ -0,0 +1,442 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata>Copyright (C) 2016 by original authors @ fontello.com</metadata>
+<defs>
+<font id="fontello" horiz-adv-x="1000" >
+<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
+<missing-glyph horiz-adv-x="1000" />
+<glyph glyph-name="palette" unicode="&#xe800;" d="M857 622q72-48 101-110t20-104-35-48q-16-4-54 10t-80 10-80-46q-30-46-21-75t34-65 23-50q-2-26-36-63t-126-74-216-37q-186 0-291 101t-95 245q8 118 104 235t216 151q290 84 536-80z m-318-466q30 0 52 22t22 54-22 53-52 21q-32 0-54-21t-22-53 22-54 54-22z" horiz-adv-x="980" />
+
+<glyph glyph-name="search" unicode="&#xe803;" d="M772 78q30-34 6-62l-46-46q-36-32-68 0l-190 190q-74-42-156-42-128 0-223 95t-95 223 90 219 218 91 224-95 96-223q0-88-46-162z m-678 358q0-88 68-156t156-68 151 63 63 153q0 88-68 155t-156 67-151-63-63-151z" horiz-adv-x="789" />
+
+<glyph glyph-name="flashlight" unicode="&#xe804;" d="M807 706q62-62 85-130t-5-92l-134-134q-16-16-62-26t-96-4l-408-408q-18-18-57-6t-75 50q-36 36-49 74t5 56l408 408q-6 50 4 96t26 62l136 136q24 28 92 4t130-86z m-448-408q32-32 80 14 46 46 14 82-14 14-38 10t-44-24-23-43 11-39z m336 298q30-30 68-50t62-25 28-1q2 4-4 27t-26 60-50 67-66 50-59 26-27 2 1-28 25-62 48-66z" horiz-adv-x="902" />
+
+<glyph glyph-name="mail" unicode="&#xe805;" d="M30 586q-32 18-28 40 2 14 26 14l846 0q38 0 20-32-8-14-24-22-14-6-192-102t-182-98q-16-10-46-10-28 0-46 10-4 2-182 98t-192 102z m850-100q20 10 20-10l0-368q0-16-17-32t-33-16l-800 0q-16 0-33 16t-17 32l0 368q0 20 20 10l384-200q18-10 46-10t46 10z" horiz-adv-x="900" />
+
+<glyph glyph-name="heart" unicode="&#xe806;" d="M790 644q70-64 70-156t-70-158l-360-330-360 330q-70 66-70 158t70 156q62 58 151 58t153-58l56-52 58 52q62 58 150 58t152-58z" horiz-adv-x="860" />
+
+<glyph glyph-name="heart-empty" unicode="&#xe807;" d="M790 642q70-64 70-156t-70-156l-360-330-360 330q-70 64-70 156t70 156q64 58 152 58t150-58l58-52 56 52q64 58 152 58t152-58z m-54-260q42 40 42 104 0 66-38 100-38 38-102 38-52 0-104-48l-104-92-106 92q-48 48-102 48-64 0-104-38-38-36-38-100 0-66 44-104l306-286z" horiz-adv-x="860" />
+
+<glyph glyph-name="star" unicode="&#xe808;" d="M440 790l120-336 320 0-262-196 94-348-272 208-272-208 94 348-262 196 320 0z" horiz-adv-x="880" />
+
+<glyph glyph-name="star-empty" unicode="&#xe809;" d="M880 454l-262-196 94-348-272 208-272-208 94 348-262 196 320 0 120 336 120-336 320 0z m-440-238l150-124-62 178 144 114-176-4-56 202-54-202-176 4 142-114-62-178z" horiz-adv-x="880" />
+
+<glyph glyph-name="user" unicode="&#xe80a;" d="M736 128q204-72 204-122l0-106-940 0 0 106q0 50 204 122 94 34 128 69t34 95q0 22-22 49t-32 73q-2 12-9 18t-14 8-14 17-9 43q0 16 5 26t9 12l4 4q-8 50-12 88-4 54 41 112t157 58 158-58 40-112l-12-88q18-8 18-42-2-28-9-43t-14-17-14-8-9-18q-8-48-31-74t-23-48q0-60 35-95t127-69z" horiz-adv-x="940" />
+
+<glyph glyph-name="users" unicode="&#xe80b;" d="M1000-90l-224 0 0 150q0 54-30 81t-154 89q40 30 40 84 0 16-13 33t-19 51q-2 8-14 16t-14 42q0 24 12 30-6 34-8 60-4 38 23 78t95 40 96-40 24-78l-8-60q12-6 12-30-2-34-14-42t-14-16q-6-34-19-51t-13-33q0-42 21-66t77-48q112-46 130-80 6-8 9-61t5-101l0-48z m-488 262q182-78 182-124l0-138-694 0 0 184q0 44 84 78 76 32 104 64t28 88q0 20-19 44t-25 68q-2 10-18 22t-20 56q0 14 3 23t7 13l4 2q-6 46-10 82-4 50 33 103t127 53 127-53 33-103l-10-82q14-8 14-38-4-44-20-56t-18-22q-6-44-25-68t-19-44q0-56 28-88t104-64z" horiz-adv-x="1000" />
+
+<glyph glyph-name="user-add" unicode="&#xe80c;" d="M620 128q180-64 180-122l0-106-800 0 0 202q36 14 82 26 94 34 129 69t35 95q0 22-23 48t-31 74q-2 12-23 25t-25 61q0 16 5 26t9 12l4 4q-8 50-12 88-6 54 40 112t160 58 160-58 42-112l-14-88q18-8 18-42-2-28-9-43t-14-17-14-8-9-18q-10-46-33-73t-23-49q0-60 36-95t130-69z m230 272l150 0 0-100-150 0 0-150-100 0 0 150-150 0 0 100 150 0 0 150 100 0 0-150z" horiz-adv-x="1000" />
+
+<glyph glyph-name="video" unicode="&#xe80d;" d="M980 600l-100 0 0-100 100 0 0-100-100 0 0-100 100 0 0-100-100 0 0-100 100 0 0-60q0-16-12-28t-28-12l-900 0q-16 0-28 12t-12 28l0 60 100 0 0 100-100 0 0 100 100 0 0 100-100 0 0 100 100 0 0 100-100 0 0 60q0 18 12 29t28 11l900 0q16 0 28-11t12-29l0-60z m-600-400l250 150-250 150 0-300z" horiz-adv-x="980" />
+
+<glyph glyph-name="picture" unicode="&#xe80e;" d="M856 518l-100 0-124 150-214-150-180 0q-52 0-90-39t-38-91l0-160-108 296q-10 38 22 52l680 248q36 10 50-24z m106-90q16 0 27-12t11-28l0-472q0-16-11-28t-27-12l-724 0q-16 0-27 12t-11 28l0 472q0 16 11 28t27 12l724 0z m-56-452l0 162-72 160-166-60-130-132-138 170-92-214 0-86 598 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="camera" unicode="&#xe80f;" d="M500 450q64 0 107-44t43-106-44-106-106-44-106 44-44 106 44 106 106 44z m400 150q42 0 71-29t29-71l0-450q0-40-29-70t-71-30l-800 0q-40 0-70 30t-30 70l0 450q0 42 30 71t70 29l120 0q28 0 40 30l30 92q10 28 40 28l340 0q30 0 40-28l30-92q12-30 40-30l120 0z m-400-550q104 0 177 73t73 177-73 177-177 73-177-73-73-177 73-177 177-73z m366 380q14 0 24 11t10 25-10 24-24 10q-36 0-36-34 0-16 11-26t25-10z" horiz-adv-x="1000" />
+
+<glyph glyph-name="layout" unicode="&#xe810;" d="M170 650q80 0 80-80l0-90q0-80-80-80l-90 0q-80 0-80 80l0 90q0 80 80 80l90 0z m350 0q80 0 80-80l0-90q0-80-80-80l-90 0q-80 0-80 80l0 90q0 80 80 80l90 0z m-350-350q80 0 80-80l0-90q0-80-80-80l-90 0q-80 0-80 80l0 90q0 80 80 80l90 0z m350 0q80 0 80-80l0-90q0-80-80-80l-90 0q-80 0-80 80l0 90q0 80 80 80l90 0z" horiz-adv-x="600" />
+
+<glyph glyph-name="menu" unicode="&#xe811;" d="M650 400q22 0 36-15t14-35-15-35-35-15l-600 0q-20 0-35 15t-15 35 14 35 36 15l600 0z m-600 100q-20 0-35 15t-15 35 14 35 36 15l600 0q22 0 36-15t14-35-15-35-35-15l-600 0z m600-300q22 0 36-15t14-35-15-35-35-15l-600 0q-20 0-35 15t-15 35 14 35 36 15l600 0z" horiz-adv-x="700" />
+
+<glyph glyph-name="check" unicode="&#xe812;" d="M249 0q-34 0-56 28l-180 236q-16 24-12 52t26 46 51 14 47-28l118-154 296 474q16 24 43 30t53-8q24-16 30-43t-8-53l-350-560q-20-32-56-32z" horiz-adv-x="667" />
+
+<glyph glyph-name="cancel" unicode="&#xe813;" d="M452 194q18-18 18-43t-18-43q-18-16-43-16t-43 16l-132 152-132-152q-18-16-43-16t-43 16q-16 18-16 43t16 43l138 156-138 158q-16 18-16 43t16 43q18 16 43 16t43-16l132-152 132 152q18 16 43 16t43-16q18-18 18-43t-18-43l-138-158z" horiz-adv-x="470" />
+
+<glyph glyph-name="cancel-circled" unicode="&#xe814;" d="M420 770q174 0 297-123t123-297-123-297-297-123-297 123-123 297 123 297 297 123z m86-420l154 154-86 86-154-152-152 152-88-86 154-154-154-152 88-86 152 152 154-152 86 86z" horiz-adv-x="840" />
+
+<glyph glyph-name="cancel-squared" unicode="&#xe815;" d="M700 750q42 0 71-29t29-71l0-600q0-40-29-70t-71-30l-600 0q-40 0-70 30t-30 70l0 600q0 42 30 71t70 29l600 0z m-146-638l86 86-154 152 154 154-86 86-154-152-152 152-88-86 154-154-154-152 88-86 152 152z" horiz-adv-x="800" />
+
+<glyph glyph-name="plus" unicode="&#xe816;" d="M550 400q30 0 30-50t-30-50l-210 0 0-210q0-30-50-30t-50 30l0 210-210 0q-30 0-30 50t30 50l210 0 0 210q0 30 50 30t50-30l0-210 210 0z" horiz-adv-x="580" />
+
+<glyph glyph-name="plus-circled" unicode="&#xe817;" d="M420 770q174 0 297-123t123-297-123-297-297-123-297 123-123 297 123 297 297 123z m52-470l200 0 0 102-200 0 0 202-102 0 0-202-202 0 0-102 202 0 0-202 102 0 0 202z" horiz-adv-x="840" />
+
+<glyph glyph-name="plus-squared" unicode="&#xe818;" d="M700 750q42 0 71-29t29-71l0-600q0-40-29-70t-71-30l-600 0q-40 0-70 30t-30 70l0 600q0 42 30 71t70 29l600 0z m-50-450l0 100-200 0 0 200-100 0 0-200-200 0 0-100 200 0 0-200 100 0 0 200 200 0z" horiz-adv-x="800" />
+
+<glyph glyph-name="minus" unicode="&#xe819;" d="M550 400q30 0 30-50t-30-50l-520 0q-30 0-30 50t30 50l520 0z" horiz-adv-x="580" />
+
+<glyph glyph-name="minus-circled" unicode="&#xe81a;" d="M420 770q174 0 297-123t123-297-123-297-297-123-297 123-123 297 123 297 297 123z m252-368l-504 0 0-102 504 0 0 102z" horiz-adv-x="840" />
+
+<glyph glyph-name="minus-squared" unicode="&#xe81b;" d="M700 750q42 0 71-29t29-71l0-600q0-40-29-70t-71-30l-600 0q-40 0-70 30t-30 70l0 600q0 42 30 71t70 29l600 0z m-50-450l0 100-500 0 0-100 500 0z" horiz-adv-x="800" />
+
+<glyph glyph-name="help" unicode="&#xe81c;" d="M494 740q86-62 86-184 0-64-42-124-12-20-88-80l-46-30q-40-34-48-60-6-16-8-44 0-14-16-14l-128 0q-16 0-16 12 4 98 28 124 16 22 48 48t56 42l24 14q22 16 34 34 28 44 28 70 0 40-26 78-28 36-92 36-68 0-94-44-28-42-28-92l-166 0q6 162 114 232 70 42 166 42 130 0 214-60z m-216-636q44 0 73-30t27-74q-2-46-32-73t-74-25q-44 0-73 29t-27 75 32 73 74 25z" horiz-adv-x="580" />
+
+<glyph glyph-name="help-circled" unicode="&#xe81d;" d="M454 810q190 2 326-130t140-322q2-190-131-327t-323-141q-190-2-327 131t-139 323q-4 190 130 327t324 139z m-2-740q30 0 49 19t19 47q2 30-17 49t-49 19l-2 0q-28 0-47-18t-21-46q0-30 19-49t47-21l2 0z m166 328q26 34 26 78 0 78-54 116-52 38-134 38-64 0-104-26-68-42-72-146l0-4 110 0 0 4q0 26 16 54 16 24 54 24 40 0 52-20 16-20 16-44 0-18-16-40-8-12-20-20l-6-4q-6-4-16-11t-20-15-21-17-17-17q-14-20-18-78l0-8 108 0 0 4q0 12 4 28 6 20 28 36l28 18q46 34 56 50z" horiz-adv-x="920" />
+
+<glyph glyph-name="info" unicode="&#xe81e;" d="M352 850q48 0 74-27t26-69q0-50-39-88t-95-38q-48 0-74 26t-24 72q0 46 35 85t97 39z m-206-1000q-100 0-54 178l60 254q14 56 0 56-12 0-54-18t-72-38l-26 44q90 78 189 126t151 48q78 0 36-162l-70-266q-16-64 6-64 44 0 118 60l30-40q-84-86-175-132t-139-46z" horiz-adv-x="460" />
+
+<glyph glyph-name="info-circled" unicode="&#xe81f;" d="M454 810q190 2 326-130t140-322q2-190-131-327t-323-141q-190-2-327 131t-139 323q-4 190 130 327t324 139z m52-152q-42 0-65-24t-23-50q-2-28 15-44t49-16q38 0 61 22t23 54q0 58-60 58z m-120-594q30 0 84 26t106 78l-18 24q-48-36-72-36-14 0-4 38l42 160q26 96-22 96-30 0-89-29t-115-75l16-26q52 34 74 34 12 0 0-34l-36-152q-26-104 34-104z" horiz-adv-x="920" />
+
+<glyph glyph-name="back" unicode="&#xe820;" d="M750 540q40 0 70-29t30-71l0-290q0-40-30-70t-70-30l-690 0 0 140 650 0 0 210-500 0 0-110-210 180 210 180 0-110 540 0z" horiz-adv-x="850" />
+
+<glyph glyph-name="home" unicode="&#xe821;" d="M888 336q16-16 11-27t-27-11l-84 0 0-310q0-14-1-21t-8-13-23-6l-204 0 0 310-204 0 0-310-194 0q-28 0-35 10t-7 30l0 310-84 0q-22 0-27 11t11 27l400 402q16 16 38 16t38-16z" horiz-adv-x="900" />
+
+<glyph glyph-name="link" unicode="&#xe822;" d="M294 116q14 14 34 14t36-14q32-34 0-70l-42-40q-56-56-132-56-78 0-134 56t-56 132q0 78 56 134l148 148q70 68 144 77t128-43q16-16 16-36t-16-36q-36-32-70 0-50 48-132-34l-148-146q-26-26-26-64t26-62q26-26 63-26t63 26z m450 574q56-56 56-132 0-78-56-134l-158-158q-74-72-150-72-62 0-112 50-14 14-14 34t14 36q14 14 35 14t35-14q50-48 122 24l158 156q28 28 28 64 0 38-28 62-24 26-56 31t-60-21l-50-50q-16-14-36-14t-34 14q-34 34 0 70l50 50q54 54 127 51t129-61z" horiz-adv-x="800" />
+
+<glyph glyph-name="attach" unicode="&#xe823;" d="M244-140q-102 0-170 72-72 70-74 166t84 190l496 496q80 80 174 54 44-12 79-47t47-79q26-96-54-176l-474-474q-40-40-88-46-48-4-80 28-30 24-27 74t47 92l332 334q24 26 50 0t0-50l-332-332q-44-44-20-70 12-8 24-6 24 4 46 26l474 474q50 50 34 108-16 60-76 76-54 14-108-36l-494-494q-66-76-64-143t52-117q50-48 117-50t141 62l496 494q24 24 50 0 26-22 0-48l-496-496q-82-82-186-82z" horiz-adv-x="939" />
+
+<glyph glyph-name="lock" unicode="&#xe824;" d="M640 476q20 0 40-19t20-41l0-390q0-48-48-66l-60-18q-42-16-96-16l-290 0q-56 0-98 16l-60 18q-48 18-48 66l0 390q0 22 15 41t35 19l100 0 0 70q0 110 51 170t149 60 149-60 51-170l0-70 90 0z m-390 90l0-90 200 0 0 90q0 52-27 81t-73 29-73-29-27-81z" horiz-adv-x="700" />
+
+<glyph glyph-name="lock-open" unicode="&#xe825;" d="M640 450q20 0 40-20t20-40l0-390q0-20-14-39t-34-25l-60-20q-52-16-96-16l-290 0q-46 0-98 16l-60 20q-20 6-34 25t-14 39l0 390q0 22 15 41t35 19l400 0 0 140q0 110-100 110t-100-110l0-40-100 0 0 20q0 110 51 170t149 60q200 0 200-230l0-120 90 0z" horiz-adv-x="700" />
+
+<glyph glyph-name="eye" unicode="&#xe826;" d="M500 630q92 0 177-25t141-62 99-77 63-71 20-45-20-44-63-71-99-78-141-62-177-25-177 25-141 62-99 78-63 71-20 44 20 45 63 71 99 77 141 62 177 25z m0-494q92 0 157 63t65 151q0 90-65 153t-157 63-157-63-65-153q0-88 65-151t157-63z m0 214q8-8 37-2t50 11 25-9q0-44-33-75t-79-31-78 31-32 75q0 46 32 77t78 31q14 0 10-23t-12-47 2-38z" horiz-adv-x="1000" />
+
+<glyph glyph-name="tag" unicode="&#xe827;" d="M944 830q36-106-8-199t-128-157l18-24q16-28 6-54l-48-158q-12-30-36-46l-464-328q-42-30-64 4l-210 304q-12 18-9 39t21 33l464 328q26 18 54 18l158 0q30 0 48-26l28-40q168 130 114 286-10 28 18 40 32 8 38-20z m-216-468q40 32 34 80l-32-16q-8-4-12-4-18 0-28 18-12 30 16 40l24 14q-48 34-92 0-28-18-34-51t14-61q18-26 51-32t59 12z" horiz-adv-x="960" />
+
+<glyph glyph-name="bookmark" unicode="&#xe828;" d="M310 800q22 0 36-15t14-35l0-850-180 180-180-180 0 850q0 50 40 50l270 0z" horiz-adv-x="360" />
+
+<glyph glyph-name="bookmarks" unicode="&#xe829;" d="M500 850q20 0 35-15t15-35l0-850-150 180 0 620q0 20-15 35t-35 15l-100 0q0 50 40 50l210 0z m-250-150q20 0 35-15t15-35l0-800-150 180-150-180 0 800q0 50 40 50l210 0z" horiz-adv-x="550" />
+
+<glyph glyph-name="flag" unicode="&#xe82a;" d="M874 616q14 6 22-1t0-19q-96-138-164-213t-110-90-73-2-60 37-63 40-93-4-139-86l90-352-100 0-184 720 92 34q90 66 152 86t98 3 64-51 62-71 79-62 129-20 198 51z" horiz-adv-x="900" />
+
+<glyph glyph-name="thumbs-up" unicode="&#xe82b;" d="M582 480q2-6 58-13t108-24 52-47q0-72-61-284t-107-212q-144 0-288 42t-144 88l0 342q0 14 15 34t46 45 53 41 62 43 46 31q50 34 104 100t85 104 41 26q48-76 29-137t-59-119-40-60z m-432-4q14 0 0-14-50-50-50-104l0-318q0-50 52-104 10-10-2-10-26 0-55 8t-62 45-33 99l0 242q0 62 33 100t63 47 54 9z" horiz-adv-x="800" />
+
+<glyph glyph-name="thumbs-down" unicode="&#xe82c;" d="M218 218q-2 6-57 13t-108 24-53 47q0 72 62 285t106 213q144 0 288-43t144-89l0-342q0-10-8-24t-25-30-32-29-42-32-41-29-41-28l-33-22q-50-34-104-100t-85-104-41-26q-48 76-29 137t59 119 40 60z m432 4q-12 0 2 14 48 50 48 104l0 318q0 50-52 104-10 10 2 10 26 0 55-8t62-45 33-99l0-242q0-48-18-81t-45-48-48-21-39-6z" horiz-adv-x="800" />
+
+<glyph glyph-name="download" unicode="&#xe82d;" d="M968 198q18-10 27-32t3-40l-28-154q-4-20-22-33t-40-13l-816 0q-22 0-40 13t-22 33l-28 154q-10 48 32 72l158 108 98 0-170-130 178 0q8 0 12-8l40-110 300 0 40 110q8 8 12 8l178 0-170 130 98 0z m-208 322l-260-244-260 244 166 0 0 256 190 0 0-256 164 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="upload" unicode="&#xe82e;" d="M500 776l260-244-164 0 0-256-190 0 0 256-166 0z m468-578q18-10 27-32t3-40l-28-154q-4-20-22-33t-40-13l-816 0q-22 0-40 13t-22 33l-28 154q-10 48 32 72l158 108 98 0-170-130 178 0q8 0 12-8l40-110 300 0 40 110q8 8 12 8l178 0-170 130 98 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="upload-cloud" unicode="&#xe82f;" d="M760 494q100 0 170-68t70-166-70-166-170-68l-190 0 0 190 106 0-176 230-174-230 104 0 0-190-248 0q-74 0-128 52t-54 124q0 74 53 126t129 52q14 0 20-2-2 12-2 38 0 108 78 184t188 76q90 0 160-52t94-134q28 4 40 4z" horiz-adv-x="1000" />
+
+<glyph glyph-name="reply" unicode="&#xe830;" d="M900 10q-86 152-208 197t-330 45l0-218-362 334 362 322 0-192q90 0 168-27t131-70 96-95 69-104 44-95 24-69z" horiz-adv-x="900" />
+
+<glyph glyph-name="reply-all" unicode="&#xe831;" d="M362 556l-212-188 212-196 0-138-362 334 362 322 0-134z m250-58q104 0 182-50t115-122 60-144 27-122l4-50q-86 154-168 198t-220 44l0-218-362 334 362 322 0-192z" horiz-adv-x="1000" />
+
+<glyph glyph-name="forward" unicode="&#xe832;" d="M540 252q-210 0-332-45t-208-197q4 20 13 53t50 117 96 148 156 117 225 53l0 192 360-322-360-334 0 218z" horiz-adv-x="900" />
+
+<glyph glyph-name="quote" unicode="&#xe833;" d="M146 680q146 0 184-146 38-140-40-302-80-168-224-204-32-8-66-8l0 70q112 0 182 108 54 86 26 146-16 36-62 36-60 0-103 44t-43 106 43 106 103 44z m420 0q146 0 184-146 38-140-40-302-80-168-224-204-32-8-66-8l0 70q112 0 182 108 54 86 26 146-16 36-62 36-60 0-103 44t-43 106 43 106 103 44z" horiz-adv-x="762" />
+
+<glyph glyph-name="code" unicode="&#xe834;" d="M380 636q16-14 16-32t-16-30l-246-224 246-226q16-12 16-30t-16-32q-30-30-60 0l-320 288 320 286q30 30 60 0z m302 0l318-286-318-288q-32-30-62 0-32 32 0 62l248 226-248 224q-32 30 0 62 30 30 62 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="export" unicode="&#xe835;" d="M750 60l0 56 100 82 0-188q0-20-15-35t-35-15l-750 0q-20 0-35 15t-15 35l0 550q0 22 14 36t36 14l288 0q-32-24-59-49t-39-39l-10-12-130 0 0-450 650 0z m-82 348q-166 0-242-41t-160-181q0 8 1 22t9 56 22 79 44 83 70 79 107 56 149 23l0 156 332-250-332-260 0 178z" horiz-adv-x="1000" />
+
+<glyph glyph-name="pencil" unicode="&#xe836;" d="M718 680q32-32 47-64t15-48l0-16-252-252-290-288-238-52 50 240 290 288 252 252q54 12 126-60z m-494-640l24 24q-2 44-52 94-22 22-45 35t-35 13l-14 2-22-24-18-80q28-16 46-34 24-24 36-48z" horiz-adv-x="780" />
+
+<glyph glyph-name="feather" unicode="&#xe837;" d="M60-138q-6-20-26-8-18 8-16 34 4 100 50 226-100 154-52 316 10-32 32-78t44-80 32-30q8 4 0 83t-11 166 25 157q22 44 80 94t104 70q-24-46-33-94t-4-78 21-32q12 0 84 120t106 122q46 4 114-29t82-65q12-24 0-79t-40-83q-44-44-146-62t-114-24q-16-10 12-34 54-48 176-20-56-80-136-114t-132-38-54-10q-4-24 49-54t101-14q-30-56-63-84t-54-35-76-11-85-8z" horiz-adv-x="698" />
+
+<glyph glyph-name="print" unicode="&#xe838;" d="M66 526q-26 0-22 22 4 10 12 14 2 0 49 17t93 32 58 15l44 0 0 150 380 0 0-150 46 0q12 0 57-15t92-32 49-17q18-8 12-26-4-10-20-10l-850 0z m860-56q20 0 37-19t17-41l0-174q0-22-17-41t-37-19l-100 0 44-250-760 0 44 250-98 0q-20 0-38 19t-18 41l0 174q0 22 18 41t38 19l870 0z m-716-444l560 0-70 324-420 0z" horiz-adv-x="980" />
+
+<glyph glyph-name="retweet" unicode="&#xe839;" d="M250 190l272 0 128-140-448 0q-42 0-71 30t-29 70l0 302-102 0 176 198 174-198-100 0 0-262z m650 60l100 0-174-200-176 200 102 0 0 260-274 0-128 140 450 0q40 0 70-29t30-71l0-300z" horiz-adv-x="1000" />
+
+<glyph glyph-name="keyboard" unicode="&#xe83a;" d="M930 650q28 0 49-21t21-49l0-460q0-30-21-50t-49-20l-860 0q-28 0-49 20t-21 50l0 460q0 28 21 49t49 21l860 0z m-380-100l0-100 100 0 0 100-100 0z m150-150l-100 0 0-100 100 0 0 100z m-300 150l0-100 100 0 0 100-100 0z m150-150l-100 0 0-100 100 0 0 100z m-300 150l0-100 100 0 0 100-100 0z m150-150l-100 0 0-100 100 0 0 100z m-300 150l0-100 100 0 0 100-100 0z m150-150l-100 0 0-100 100 0 0 100z m-50-250l0 100-100 0 0-100 100 0z m550 0l0 100-500 0 0-100 500 0z m150 0l0 100-100 0 0-100 100 0z m-150 150l100 0 0 100-100 0 0-100z m150 150l0 100-200 0 0-100 200 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="comment" unicode="&#xe83b;" d="M700 700q42 0 71-29t29-71l0-350q0-40-29-70t-71-30l-200 0 0-150-200 150-200 0q-40 0-70 30t-30 70l0 350q0 42 30 71t70 29l600 0z" horiz-adv-x="800" />
+
+<glyph glyph-name="chat" unicode="&#xe83c;" d="M290 240l350 0q2 0 6 2l4 0 0-92q0-40-29-70t-71-30l-250 0-150-150 0 150-50 0q-40 0-70 30t-30 70l0 300q0 42 30 71t70 29l190 0 0-310z m610 560q42 0 71-29t29-71l0-300q0-40-29-70t-71-30l-50 0 0-150-150 150-350 0 0 400q0 42 30 71t70 29l450 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="bell" unicode="&#xe83d;" d="M632 426q16-34 40-52t45-22 44-23 35-55q22-62-74-161t-252-157q-164-58-297-45t-155 75q-20 54 12 111t18 111q-56 192-47 300t113 192q26 22 29 51t29 39q24 8 46-12t56-18q132 2 198-66t160-268z m-186-404q88 32 159 85t100 91 25 50q-8 22-49 33t-124 1-187-48q-102-38-173-87t-94-84-17-53q4-12 50-22t134-4 176 38z m-62 174q8 2 21 7t17 7l2-2q14-40-17-83t-89-63q-96-36-152 14 78 68 218 120z" horiz-adv-x="800" />
+
+<glyph glyph-name="attention" unicode="&#xe83e;" d="M957-24q10-16 0-34-10-16-30-16l-892 0q-18 0-28 16-13 18-2 34l446 782q8 18 30 18t30-18z m-420 50l0 100-110 0 0-100 110 0z m0 174l0 300-110 0 0-300 110 0z" horiz-adv-x="962" />
+
+<glyph glyph-name="alert" unicode="&#xe83f;" d="M885 234q20-16 16-33t-28-23l-78-22q-24-6-40-28t-14-48l4-82q2-24-14-34t-38 0l-86 44q-22 12-47 4t-35-30l-46-88q-12-22-29-23t-33 19l-50 78q-34 48-88 20l-122-70q-22-14-32-6t-2 32l54 164q8 24-4 44t-36 22l-106 12q-24 4-29 18t15 30l86 76q20 16 20 41t-20 41l-86 76q-20 16-16 33t28 23l78 22q24 6 41 28t15 48l-6 82q0 26 15 36t37 0l80-38q24-10 49-2t37 30l46 80q12 22 30 21t30-23l50-86q12-22 35-29t45 7l136 84q22 14 30 6t0-32l-60-170q-10-22 2-41t38-21l114-12q26-2 30-16t-16-30l-86-76q-18-16-18-41t18-41z m-384-92l0 104-100 0 0-104 100 0z m0 160l0 260-100 0 0-260 100 0z" horiz-adv-x="901" />
+
+<glyph glyph-name="vcard" unicode="&#xe840;" d="M900 750q42 0 71-29t29-71l0-600q0-40-29-70t-71-30l-800 0q-40 0-70 30t-30 70l0 600q0 42 30 71t70 29l800 0z m0-700l0 600-800 0 0-600 800 0z m-450 196l0-90-250 0 0 90 250 0z m0 150l0-90-250 0 0 90 250 0z m0 150l0-90-250 0 0 90 250 0z m346-320l4-70-250 0q0 70 6 70 84 22 84 66 0 16-27 56t-27 88q0 110 90 110t90-110q0-48-28-88t-28-56q0-20 21-36t43-22z" horiz-adv-x="1000" />
+
+<glyph glyph-name="address" unicode="&#xe841;" d="M426 800q20 0 20-20l0-860q0-20-20-20l-46 0q-20 0-20 20l0 440-176 0q-16 0-28 6-12 2-26 12l-120 82q-10 6-10 16t10 16l120 82q14 10 26 12 8 4 28 4l176 0 0 190q0 20 20 20l46 0z m564-208q10-6 10-16t-10-16l-118-82q-22-12-26-12-14-6-28-6l-302 0-40 230 342 0q18 0 28-4t26-12z" horiz-adv-x="1000" />
+
+<glyph glyph-name="location" unicode="&#xe842;" d="M250 750q104 0 177-73t73-177q0-106-62-243t-126-223l-62-84q-10 12-27 35t-60 89-76 130-60 147-27 149q0 104 73 177t177 73z m0-388q56 0 96 40t40 96-40 95-96 39-95-39-39-95 39-96 95-40z" horiz-adv-x="500" />
+
+<glyph glyph-name="map" unicode="&#xe843;" d="M984 600q16-10 16-30l0-584q0-20-16-30-8-6-16-6t-18 6l-216 136-216-136q-18-10-34 0l-218 136-216-136q-16-10-34 0-16 10-16 30l0 584q0 20 16 30l234 146q18 10 34 0l216-136 218 136q16 10 32 0z m-750-450l0 506-168-104 0-506z m234-104l0 506-168 104 0-506z m234 104l0 506-170-104 0-506z m232-104l0 506-168 104 0-506z" horiz-adv-x="1000" />
+
+<glyph glyph-name="direction" unicode="&#xe844;" d="M848 768q8-8 11-16t-2-22-10-26-19-39-24-49q-54-112-147-286t-157-292l-66-118-54 380-380 56q442 246 696 368 20 10 48 25t39 20 25 9 23 1 17-11z m-92-96l-304-280 28-234z" horiz-adv-x="860" />
+
+<glyph glyph-name="compass" unicode="&#xe845;" d="M474 830q198 2 340-136t146-336q2-200-136-342t-338-146q-198-2-341 137t-145 337q-4 200 135 342t339 144z m12-858q156 2 266 114t108 270-115 267-269 107q-158-2-267-114t-107-270 114-267 270-107z m-234 154q4 26 12 66t41 128 77 132 125 76 141 42l60 10q-4-26-12-66t-41-128-77-132q-42-42-124-74t-142-42z m180 276q-22-20-22-48t22-50q20-22 49-22t49 22q52 52 88 186-136-36-186-88z" horiz-adv-x="960" />
+
+<glyph glyph-name="cup" unicode="&#xe846;" d="M340 760q152 0 249-41t91-87l-72-594q-2-14-34-36t-97-42-137-20-136 20-97 42-35 36l-72 594q-4 28 36 57t121 50 183 21z m0-216q72 0 137 15t98 33 33 30-33 29-98 32-137 15-137-15-98-32-33-29 33-30 98-33 137-15z" horiz-adv-x="681" />
+
+<glyph glyph-name="trash" unicode="&#xe847;" d="M50 458q122-70 330-70t330 70l-54-486q-2-14-35-36t-100-43-141-21-140 21-100 43-36 36z m488 300q94-18 158-55t64-71l0-10q0-58-112-99t-268-41-268 41-112 99l0 10q0 34 64 71t158 55l42 48q22 26 70 26l92 0q52 0 70-26z m-54-112l84 0q-92 110-104 126-14 16-32 16l-102 0q-22 0-32-16l-106-126 84 0 64 66 82 0z" horiz-adv-x="760" />
+
+<glyph glyph-name="doc" unicode="&#xe848;" d="M600 800q42 0 71-29t29-71l0-700q0-40-29-70t-71-30l-500 0q-40 0-70 30t-30 70l0 700q0 42 30 71t70 29l500 0z m0-800l0 700-500 0 0-700 500 0z" horiz-adv-x="700" />
+
+<glyph glyph-name="docs" unicode="&#xe849;" d="M970 480q38-10 30-46l-150-556q-4-16-18-23t-30-3l-406 110q-16 4-24 18t-4 28l24 92-180-48q-40-10-50 26l-160 602q-10 36 28 48l454 122q16 4 30-3t18-23l66-244z m-888 190l144-542 392 106-144 540z m702-742l132 492-298 82 76-282q10-34-28-46l-196-52-26-102z" horiz-adv-x="1001" />
+
+<glyph glyph-name="doc-landscape" unicode="&#xe84a;" d="M0 600q0 42 30 71t70 29l800 0q42 0 71-29t29-71l0-500q0-40-29-70t-71-30l-800 0q-40 0-70 30t-30 70l0 500z m900 0l-800 0 0-500 800 0 0 500z" horiz-adv-x="1000" />
+
+<glyph glyph-name="doc-text" unicode="&#xe84b;" d="M212 308l0 90 280 0 0-90-280 0z m388 492q42 0 71-29t29-71l0-700q0-40-29-70t-71-30l-500 0q-40 0-70 30t-30 70l0 700q0 42 30 71t70 29l500 0z m0-800l0 700-500 0 0-700 500 0z m-110 592l0-88-280 0 0 88 280 0z m0-392l0-88-280 0 0 88 280 0z" horiz-adv-x="700" />
+
+<glyph glyph-name="doc-text-inv" unicode="&#xe84c;" d="M600 800q42 0 71-29t29-71l0-700q0-40-29-70t-71-30l-500 0q-40 0-70 30t-30 70l0 700q0 42 30 71t70 29l500 0z m-460-208l0-88 420 0 0 88-420 0z m420-480l0 88-420 0 0-88 420 0z m0 196l0 90-418 0 0-90 418 0z" horiz-adv-x="700" />
+
+<glyph glyph-name="newspaper" unicode="&#xe84d;" d="M700 800q42 0 71-29t29-71l0-700q0-40-29-70t-71-30l-600 0q-40 0-70 30t-30 70l0 700q0 42 30 71t70 29l600 0z m0-800l0 700-600 0 0-700 600 0z m-250 250l0-50-250 0 0 50 250 0z m150 200l0-50-200 0 0 50 200 0z m-200 50l0 100 200 0 0-100-200 0z m-50 100l0-200-150 0 0 200 150 0z m-50-250l0-50-100 0 0 50 100 0z m50-50l0 50 250 0 0-50-250 0z m250-150l0-50-400 0 0 50 400 0z m-100 50l0 50 100 0 0-50-100 0z" horiz-adv-x="800" />
+
+<glyph glyph-name="book-open" unicode="&#xe84e;" d="M340 238l0-68-200 80 0 68z m0 208l0-68-200 80 0 68z m538 346q22-12 22-42l0-640q0-34-32-46l-398-160q-8-2-10-2t-5-1-5-1-5 1-5 1l-10 2-398 160q-32 12-32 46l0 640q0 30 22 42 22 16 46 6l382-154 382 154q24 10 46-6z m-478-788l0 560-320 128 0-560z m420 128l0 560-320-128 0-560z m-60 186l0-68-200-80 0 68z m0 208l0-68-200-80 0 68z" horiz-adv-x="900" />
+
+<glyph glyph-name="book" unicode="&#xe84f;" d="M682 594q18-8 18-28l0-562q0-14-12-25t-28-11q-46 0-46 36l0 522q0 12-12 18l-404 216q-32 10-68-10-44-20-56-44l408-228q18-8 18-28l0-550q0-22-18-28-6-4-16-4-14 0-20 4-8 6-202 127t-212 131q-26 18-26 34l-6 524q0 28 14 52 28 46 102 77t116 9z" horiz-adv-x="700" />
+
+<glyph glyph-name="folder" unicode="&#xe850;" d="M954 500q32 0 40-12t6-36l-42-452q-2-24-12-37t-42-13l-806 0q-52 0-56 50l-42 452q-2 24 6 36t40 12l908 0z m-34 110l10-40-846 0 14 132q4 20 20 34t36 14l164 0q52 0 86-34l30-30q32-36 86-36l340 0q20 0 38-12t22-28z" horiz-adv-x="1001" />
+
+<glyph glyph-name="archive" unicode="&#xe851;" d="M840 600l0-50-696 0 0 50q0 22 13 35t25 15l608 0q6 0 14-1t22-14 14-35z m-148 150q6 0 14-1t22-14 14-35l-498 0q0 22 13 35t25 15l410 0z m248-200q34-32 38-46 6-18 0-54l-76-450q-4-22-20-35t-28-15l-710 0q-52 0-60 50-6 26-39 223t-39 227q-10 22-3 44t10 26 21 20l10 10 30 30 0-80 836 0 0 80z m-248-270l0 100-70 0 0-80-260 0 0 80-68 0 0-100q0-50 48-50l300 0q22 0 35 12t13 24z" horiz-adv-x="981" />
+
+<glyph glyph-name="box" unicode="&#xe852;" d="M870 750q12 0 21-9t9-21l0-120-900 0 0 120q0 12 9 21t21 9l840 0z m-820-730l0 530 800 0 0-530q0-30-21-50t-49-20l-660 0q-28 0-49 20t-21 50z m250 430l0-100 300 0 0 100-300 0z" horiz-adv-x="900" />
+
+<glyph glyph-name="rss" unicode="&#xe853;" d="M0 730q314 0 537-223t223-537l-118 0q0 266-188 453t-454 187l0 120z m0-238q218 0 371-153t153-369l-118 0q0 166-119 285t-287 119l0 118z m114-296q46 0 80-33t34-81q0-46-34-79t-80-33-80 33-34 79q0 48 34 81t80 33z" horiz-adv-x="760" />
+
+<glyph glyph-name="phone" unicode="&#xe854;" d="M461 290q162 162 118 206l-8 8q-30 30-41 48t-4 54 49 88q20 24 37 39t35 16 30 1 29-13 24-18 26-25 21-22q48-48-6-194t-204-294q-150-150-295-205t-193-7q-2 2-23 22t-25 25-18 24-13 31 2 30 15 35 38 37q42 34 70 47t54 2 35-18 39-37q44-44 208 120z" horiz-adv-x="800" />
+
+<glyph glyph-name="cog" unicode="&#xe855;" d="M760 350q0-72 80-122-12-40-34-82-70 18-136-44-54-58-34-136-40-20-84-36-46 82-132 82t-132-82q-44 16-84 36 20 80-34 136-54 54-136 34-14 26-34 82 82 52 82 132 0 72-82 124 20 56 34 82 74-18 136 44 54 56 34 136 42 22 84 34 46-80 132-80t132 80q42-12 84-34-20-78 34-136 66-62 136-44 22-42 34-82-80-50-80-124z m-340-182q76 0 129 53t53 129-53 130-129 54-129-54-53-130 53-129 129-53z" horiz-adv-x="840" />
+
+<glyph glyph-name="tools" unicode="&#xe856;" d="M155 506q-8-8-11-22t-3-25-2-11q-2-2-17-15t-19-17q-16-14-28 4l-70 76q-11 12 2 24 2 2 18 14t20 16q6 6 27 6t37 14q14 14 18 38t10 30q2 0 9 7t26 22 41 31q134 90 186 96 122 0 148-2 12 0-8-8-120-52-152-76-80-56-36-114 34-46 38-48 8-8-2-14-2-2-38-35t-38-35q-14-8-18-4-42 48-71 60t-67-12z m286-26l410-476q18-22-2-38l-48-42q-22-14-38 4l-414 472q-8 8 0 20l72 62q12 8 20-2z m554 202q16-104-16-166-50-88-154-62-56 12-100-32l-82-78-68 78 68 70q24 24 31 53t6 65 5 58q12 56 140 112 12 6 18-3t2-15q-12-12-46-80-14-10-12-35t40-53q58-40 96 22 6 12 26 41t22 33q4 10 13 9t11-17z m-858-684l254 248 76-86-246-242q-20-20-38-4l-46 46q-22 18 0 38z" horiz-adv-x="1000" />
+
+<glyph glyph-name="share" unicode="&#xe857;" d="M650 200q62 0 106-43t44-107q0-62-44-106t-106-44-106 44-44 106q0 6 1 14t1 12l-260 156q-42-32-92-32-62 0-106 44t-44 106 44 106 106 44q54 0 92-30l260 156q0 4-1 12t-1 12q0 62 44 106t106 44 106-43 44-107q0-62-44-106t-106-44q-52 0-90 32l-262-156q2-8 2-26 0-16-2-24l262-156q36 30 90 30z" horiz-adv-x="800" />
+
+<glyph glyph-name="shareable" unicode="&#xe858;" d="M340 350q0 68 47 114t113 46 113-46 47-114q0-66-47-113t-113-47-113 47-47 113z m-114 60q-14-60-66-60l-160 0 0 120 118 0q40 124 145 202t237 78q164 0 284-116 16-18 16-43t-16-43q-18-16-43-16t-43 16q-78 82-198 82-100 0-176-62t-98-158z m614-60l160 0 0-120-118 0q-40-124-144-202t-238-78q-164 0-282 118-18 18-18 43t18 41q16 18 41 18t43-18q82-82 198-82 100 0 176 63t98 157q12 60 66 60z" horiz-adv-x="1000" />
+
+<glyph glyph-name="basket" unicode="&#xe859;" d="M150 0q0 40 30 70t70 30q42 0 71-30t29-70q0-42-29-71t-71-29q-40 0-70 29t-30 71z m500 0q0 40 30 70t70 30q42 0 71-30t29-70q0-42-29-71t-71-29q-40 0-70 29t-30 71z m-322 236q-36-10-34-23t44-13l562 0 0-76q0-20-20-20l-654 0q-20 0-20 20l0 76-10 46-98 454-98 0 0 80q0 20 20 20l156 0q20 0 20-20l0-86 704 0 0-274q0-22-18-26z" horiz-adv-x="900" />
+
+<glyph glyph-name="bag" unicode="&#xe85a;" d="M835 668q28-26 24-60l-98-648q-8-30-38-30l-586 0q-28 0-40 30-94 620-96 648-5 34 22 60 6 6 54 43t56 43q18 16 56 16l480 0q38 0 56-16 78-58 110-86z m-406-436q56 0 98 34t63 89 30 89 13 66l-92 0q-38-188-112-188t-112 188l-92 0q46-278 204-278z m-352 368l704 0-110 116-484 0z" horiz-adv-x="859" />
+
+<glyph glyph-name="calendar" unicode="&#xe85b;" d="M800 700q42 0 71-29t29-71l0-600q0-40-29-70t-71-30l-700 0q-40 0-70 30t-30 70l0 600q0 42 30 71t70 29l46 0 0-100 160 0 0 100 290 0 0-100 160 0 0 100 44 0z m0-700l0 400-700 0 0-400 700 0z m-540 800l0-170-70 0 0 170 70 0z m450 0l0-170-70 0 0 170 70 0z" horiz-adv-x="900" />
+
+<glyph glyph-name="login" unicode="&#xe85c;" d="M800 800q42 0 71-29t29-71l0-700q0-40-29-70t-71-30l-450 0q-40 0-69 30t-29 70l0 100 98 0 0-100 450 0 0 700-450 0 0-150-98 0 0 150q0 42 29 71t69 29l450 0z m-350-670l0 120-450 0 0 150 450 0 0 120 200-194z" horiz-adv-x="900" />
+
+<glyph glyph-name="logout" unicode="&#xe85d;" d="M502 0l0 100 98 0 0-100q0-40-29-70t-71-30l-400 0q-40 0-70 30t-30 70l0 700q0 42 30 71t70 29l400 0q42 0 71-29t29-71l0-150-98 0 0 150-402 0 0-700 402 0z m398 326l-198-196 0 120-450 0 0 150 450 0 0 120z" horiz-adv-x="900" />
+
+<glyph glyph-name="mic" unicode="&#xe85e;" d="M620 488q20 0 20-20l0-138q0-92-69-164t-201-84l0-132 130 0q20 0 20-20l0-60q0-20-20-20l-360 0q-20 0-20 20l0 60q0 20 20 20l130 0 0 132q-132 12-201 84t-69 164l0 138q0 20 20 20l30 0q20 0 20-20l0-138q0-66 59-123t191-57 191 57 59 123l0 138q0 20 20 20l30 0z m-300-238q-80 0-115 25t-35 55l0 158 300 0 0-158q0-30-35-55t-115-25z m150 520l0-212-300 0 0 212q0 30 35 55t115 25 115-25 35-55z" horiz-adv-x="640" />
+
+<glyph glyph-name="mute" unicode="&#xe85f;" d="M868 778q16-16 16-36t-16-36l-782-782q-18-14-34-14-18 0-36 14-16 14-16 36t16 36l782 782q34 32 70 0z m-216-386l50 50q74-92 101-172t-7-116q-24-24-75-57t-131-71-161-45-165 23l278 276q44-32 88-54t67-25 33 1q6 10 2 34t-26 68-54 88z m-276 62l-270-270q-40 132 28 283t132 215q34 32 105 11t159-85l-52-50q-58 38-105 53t-57 5q-4-8-2-28t19-58 43-76z" horiz-adv-x="884" />
+
+<glyph glyph-name="sound" unicode="&#xe860;" d="M176 588q42 42 149-5t217-157 157-217 5-149q-28-28-92-67t-156-78-194-29-176 84-84 176 29 194 78 156 67 92z m464-480q8 10-3 49t-49 101-96 118q-56 58-118 96t-101 49-49 3q-8-10 3-49t49-101 94-120q58-56 120-94t101-49 49-3z m6 394q-18 0-34 16-16 14-16 35t16 35l94 96q36 32 72 0 32-36 0-72l-96-94q-16-16-36-16z m-180 124q-18 10-23 30t5 38l54 96q26 44 68 20 18-10 23-30t-5-38l-54-96q-14-26-42-26-14 0-26 6z m438-150q10-18 4-38t-24-30l-96-54q-16-8-24-8-28 0-44 26-10 18-4 38t24 30l96 54q18 10 38 5t30-23z" horiz-adv-x="910" />
+
+<glyph glyph-name="volume" unicode="&#xe861;" d="M896 180q0-34-24-57t-56-23l-780 0q-22 0-31 5t-3 15 24 20l802 452q28 18 48 7t20-45l0-374z" horiz-adv-x="896" />
+
+<glyph glyph-name="clock" unicode="&#xe862;" d="M460 810q190 0 325-135t135-325-135-325-325-135-325 135-135 325 135 325 325 135z m0-820q150 0 255 106t105 254q0 150-105 255t-255 105q-148 0-254-105t-106-255q0-148 106-254t254-106z m36 620l0-244 150-150-50-50-170 170 0 274 70 0z" horiz-adv-x="920" />
+
+<glyph glyph-name="hourglass" unicode="&#xe863;" d="M560 622q0-44-48-96t-97-99-49-77 49-76 97-97 48-97l0-118q0-34-86-73t-194-39-194 39-86 73l0 118q0 46 48 97t97 97 49 76-49 77-97 99-48 96l0 118q0 32 87 71t193 39 193-39 87-71l0-118z m-482 112l-18-14q-4-8 4-14 92-52 216-52 132 0 220 50 14 10-16 30-96 54-202 54-120 0-204-54z m228-384q0 18 4 33t18 33 20 25 31 31 29 28q92 92 92 122l2 50q-100-54-222-54t-222 54l4-50q0-32 90-122 6-6 22-21t23-22l19-19t17-21 11-20 9-23 3-24q0-10-1-19t-6-18-8-16-11-17l-12-15t-15-16-16-15-18-16-17-16q-90-90-90-122l0-66q8 4 66 23t92 43 34 58q0 30 26 30t26-30q0-34 33-58t94-43 67-23l0 66q0 30-92 122-4 4-21 20t-22 21-18 19-18 22-12 20-9 23-2 23z" horiz-adv-x="560" />
+
+<glyph glyph-name="lamp" unicode="&#xe864;" d="M209-110l0 104 282 0 0-104q-70-42-142-40-70-2-140 40z m276 164l-270 0q0 72-36 140t-78 113-74 112-26 139q8 120 94 206t254 86q170 0 255-86t95-206q4-60-16-113t-52-96-65-85-57-96-24-114z m-378 496q-4-4 0-20t2-20 5-19 6-18 8-18 11-19 13-19 14-19 15-21 16-23q88-122 112-212l82 0q24 94 112 212 4 6 25 35t25 36 17 29 16 33 6 28 1 35q-16 196-244 196-226 0-242-196z" horiz-adv-x="700" />
+
+<glyph glyph-name="light-down" unicode="&#xe865;" d="M350 510q68 0 114-47t46-113q0-68-46-114t-114-46q-66 0-113 46t-47 114q0 66 47 113t113 47z m0-264q44 0 73 30t29 74q0 42-29 72t-73 30q-42 0-72-30t-30-72q0-44 30-74t72-30z m-300 144q20 0 35-12t15-28q0-40-50-40t-50 40q0 16 15 28t35 12z m546 204q28-28-8-64-14-14-33-16t-29 10q-12 12-10 31t16 33q36 34 64 6z m54-204q20 0 35-12t15-28q0-40-50-40-48 0-48 40 0 16 14 28t34 12z m-300-290q16 0 28-15t12-35-12-35-28-15-28 15-12 35 12 35 28 15z m-238 62q36 36 64 8t-8-64q-14-14-33-16t-29 8q-30 28 6 64z m-10 430q28 28 64-8 14-14 16-33t-8-29q-30-28-64 6-36 36-8 64z m432-484q-34 36-6 64t64-8q14-14 16-33t-10-29q-30-28-64 6z m-184 492q-16 0-28 15t-12 35 12 35 28 15 28-15 12-35-12-35-28-15z" horiz-adv-x="700" />
+
+<glyph glyph-name="light-up" unicode="&#xe866;" d="M950 390q20 0 35-12t15-28q0-40-50-40l-48 0q-50 0-50 40 0 16 15 28t35 12l48 0z m-450 234q114 0 195-80t81-194q0-116-81-196t-195-80-194 80-80 196q0 114 80 194t194 80z m0-474q82 0 141 58t59 142q0 82-59 141t-141 59-141-59-59-141q0-84 59-142t141-58z m-350 200q0-40-50-40l-50 0q-50 0-50 40 0 16 15 28t35 12l50 0q20 0 35-12t15-28z m350 350q-16 0-28 15t-12 35l0 50q0 20 12 35t28 15 28-15 12-35l0-50q0-20-12-35t-28-15z m0-700q16 0 28-15t12-35l0-50q0-20-12-35t-28-15-28 15-12 35l0 50q0 20 12 35t28 15z m368 660l-34-34q-34-34-64-8-28 28 8 64 4 6 34 36 36 34 64 6t-8-64z m-700-588q14 16 33 18t29-10q12-12 10-31t-16-33l-36-36q-14-14-33-16t-29 10q-30 28 6 64 6 4 36 34z m20 646l36-36q36-36 6-64-10-10-29-8t-33 16q-30 30-36 34-14 14-16 33t10 31q10 12 29 10t33-16z m590-702q-36 36-8 64t64-8l34-34q36-36 8-64t-64 6q-30 30-34 36z" horiz-adv-x="1000" />
+
+<glyph glyph-name="adjust" unicode="&#xe867;" d="M950 390q20 0 35-12t15-28q0-40-50-40l-48 0q-50 0-50 40 0 16 15 28t35 12l48 0z m-450 234q114 0 195-80t81-194q0-116-81-196t-195-80-194 80-80 196q0 114 80 194t194 80z m6-474l0 400q-86 0-146-59t-60-141q0-84 60-142t146-58z m-356 200q0-40-50-40l-50 0q-50 0-50 40 0 16 15 28t35 12l50 0q20 0 35-12t15-28z m350 350q-16 0-28 15t-12 35l0 50q0 20 12 35t28 15 28-15 12-35l0-50q0-20-12-35t-28-15z m0-700q16 0 28-15t12-35l0-50q0-20-12-35t-28-15-28 15-12 35l0 50q0 20 12 35t28 15z m368 660l-34-34q-34-34-64-8-28 28 8 64 4 6 34 36 36 34 64 6t-8-64z m-700-588q14 16 33 18t29-10q12-12 10-31t-16-33l-36-36q-14-14-33-16t-29 10q-30 28 6 64 6 4 36 34z m20 646l36-36q36-36 6-64-10-10-29-8t-33 16q-30 30-36 34-14 14-16 33t10 31q10 12 29 10t33-16z m590-702q-36 36-8 64t64-8l34-34q36-36 8-64t-64 6q-30 30-34 36z" horiz-adv-x="1000" />
+
+<glyph glyph-name="block" unicode="&#xe868;" d="M480 830q200 0 340-140t140-340q0-198-140-339t-340-141q-198 0-339 141t-141 339q0 200 141 340t339 140z m258-220z m-622-260q0-132 82-230l514 514q-100 82-232 82-152 0-258-107t-106-259z m106-258z m258-106q152 0 259 107t107 257q0 130-82 232l-514-514q98-82 230-82z" horiz-adv-x="960" />
+
+<glyph glyph-name="resize-full" unicode="&#xe869;" d="M476 746l316 0 0-316-100 124-146-152-100 100 152 146z m-230-444l100-100-152-146 122-100-316 0 0 316 100-122z" horiz-adv-x="792" />
+
+<glyph glyph-name="resize-small" unicode="&#xe86a;" d="M156 146l-106 100 296 0 0-296-100 106-146-156-100 100z m744 554l-154-144 104-100-294 0 0 294 100-104 144 154z" horiz-adv-x="900" />
+
+<glyph glyph-name="popup" unicode="&#xe86b;" d="M700 750q42 0 71-29t29-71l0-400q0-40-29-70t-71-30l-400 0q-40 0-70 30t-30 70l0 402q0 40 29 69t71 29l400 0z m0-500l0 400-400 0 0-400 400 0z m-600 100l0-300 300 0 0-100-300 0q-40 0-70 30t-30 70l0 300 100 0z" horiz-adv-x="800" />
+
+<glyph glyph-name="publish" unicode="&#xe86c;" d="M900 800q42 0 71-30t29-70l0-600q0-42-29-71t-71-29l-198 0 0 98 200 0 0 462-802 0 0-462 200 0 0-98-200 0q-40 0-70 29t-30 71l0 600q0 40 30 70t70 30l800 0z m-770-168q38 0 38 38 0 16-11 26t-27 10-27-11-11-25q0-16 11-27t27-11z m100 0q38 0 38 38 0 16-11 26t-27 10-27-11-11-25q0-16 11-27t27-11z m672 6l0 62-602 0 0-62 602 0z m-404-198l242-240-150 0 0-300-184 0 0 300-150 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="window" unicode="&#xe86d;" d="M900 750q42 0 71-30t29-70l0-600q0-42-29-71t-71-29l-800 0q-40 0-70 29t-30 71l0 600q0 40 30 70t70 30l800 0z m-670-94q-16 0-27-11t-11-25q0-16 11-27t27-11q38 0 38 38 0 16-11 26t-27 10z m-138-36q0-16 11-27t27-11q38 0 38 38 0 16-11 26t-27 10-27-11-11-25z m810-570l0 460-802 0 0-460 802 0z m0 540l0 60-602 0 0-60 602 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="arrow-combo" unicode="&#xe86e;" d="M230 850l230-364-460 0z m0-1000l-230 366 460 0z" horiz-adv-x="460" />
+
+<glyph glyph-name="down-circled" unicode="&#xe86f;" d="M460 810q190 0 325-135t135-325-135-325-325-135-325 135-135 325 135 325 325 135z m0-820q148 0 254 106t106 254q0 150-106 255t-254 105-254-105-106-255q0-148 106-254t254-106z m90 554l0-206 112 0-202-190-202 190 112 0 0 206 180 0z" horiz-adv-x="920" />
+
+<glyph glyph-name="left-circled" unicode="&#xe870;" d="M920 350q0-190-135-325t-325-135-325 135-135 325q0 192 135 326t325 134 325-134 135-326z m-820 0q0-148 106-254t254-106 254 106 106 254q0 150-106 255t-254 105-254-105-106-255z m552-90l-204 0 0-112-190 202 190 204 0-114 204 0 0-180z" horiz-adv-x="920" />
+
+<glyph glyph-name="right-circled" unicode="&#xe871;" d="M0 350q0 190 135 325t325 135 325-135 135-325-135-325-325-135-325 135-135 325z m820 0q0 150-105 255t-255 105q-148 0-254-105t-106-255q0-148 106-254t254-106q150 0 255 106t105 254z m-552 90l204 0 0 114 190-204-190-202 0 112-204 0 0 180z" horiz-adv-x="920" />
+
+<glyph glyph-name="up-circled" unicode="&#xe872;" d="M460-110q-190 0-325 135t-135 325q0 192 135 326t325 134 325-134 135-326q0-190-135-325t-325-135z m0 820q-148 0-254-105t-106-255q0-148 106-254t254-106q150 0 255 106t105 254q0 150-105 255t-255 105z m-90-552l0 204-112 0 202 192 202-192-112 0 0-204-180 0z" horiz-adv-x="920" />
+
+<glyph glyph-name="down-open" unicode="&#xe873;" d="M564 422l-234-224q-18-18-40-18t-40 18l-234 224q-16 16-16 41t16 41q38 38 78 0l196-188 196 188q40 38 78 0 16-16 16-41t-16-41z" horiz-adv-x="580" />
+
+<glyph glyph-name="left-open" unicode="&#xe874;" d="M242 626q14 16 39 16t41-16q38-36 0-80l-186-196 186-194q38-44 0-80-16-16-40-16t-40 16l-226 236q-16 16-16 38 0 24 16 40 206 214 226 236z" horiz-adv-x="341" />
+
+<glyph glyph-name="right-open" unicode="&#xe875;" d="M98 626l226-236q16-16 16-40 0-22-16-38l-226-236q-16-16-40-16t-40 16q-36 36 0 80l186 194-186 196q-36 44 0 80 16 16 41 16t39-16z" horiz-adv-x="340" />
+
+<glyph glyph-name="up-open" unicode="&#xe876;" d="M564 280q16-16 16-41t-16-41q-38-38-78 0l-196 188-196-188q-40-38-78 0-16 16-16 41t16 41l234 224q16 16 40 16t40-16z" horiz-adv-x="580" />
+
+<glyph glyph-name="down-open-mini" unicode="&#xe877;" d="M405 470q22 26 48 0 26-22 0-48l-196-192q-22-22-48 0l-196 192q-26 26 0 48 24 24 50 0l170-156z" horiz-adv-x="466" />
+
+<glyph glyph-name="left-open-mini" unicode="&#xe878;" d="M252 180q26-26 0-48-26-26-48 0l-192 194q-24 24 0 50l192 194q22 26 48 0 26-22 0-48l-156-172z" horiz-adv-x="265" />
+
+<glyph glyph-name="right-open-mini" unicode="&#xe879;" d="M13 180l158 170-158 172q-26 26 0 48 26 26 48 0l192-194q24-26 0-50l-192-194q-22-26-48 0-26 22 0 48z" horiz-adv-x="265" />
+
+<glyph glyph-name="up-open-mini" unicode="&#xe87a;" d="M62 230q-26-22-50 0-24 24 0 50l196 190q26 26 48 0l196-190q24-26 0-50-24-22-50 0l-170 158z" horiz-adv-x="464" />
+
+<glyph glyph-name="down-open-big" unicode="&#xe87b;" d="M63 570l370-356 372 356q22 26 48 0 26-22 0-48l-396-392q-22-22-48 0l-396 392q-26 26 0 48 24 24 50 0z" horiz-adv-x="866" />
+
+<glyph glyph-name="left-open-big" unicode="&#xe87c;" d="M452-20q26-26 0-48-26-26-48 0l-392 394q-24 24 0 50l392 394q22 26 48 0 26-22 0-48l-358-372z" horiz-adv-x="465" />
+
+<glyph glyph-name="right-open-big" unicode="&#xe87d;" d="M13-20l358 370-358 372q-26 26 0 48 26 26 48 0l392-394q24-26 0-50l-392-394q-22-26-48 0-26 22 0 48z" horiz-adv-x="465" />
+
+<glyph glyph-name="up-open-big" unicode="&#xe87e;" d="M804 130l-372 358-370-358q-26-22-50 0-24 24 0 50l396 390q26 26 48 0l396-390q24-26 0-50-26-22-48 0z" horiz-adv-x="864" />
+
+<glyph glyph-name="down" unicode="&#xe87f;" d="M660 366l-330-380-330 380 192 0 0 350 276 0 0-350 192 0z" horiz-adv-x="660" />
+
+<glyph glyph-name="left" unicode="&#xe880;" d="M378 20l-378 330 378 330 0-190 352 0 0-278-352 0 0-192z" horiz-adv-x="730" />
+
+<glyph glyph-name="right" unicode="&#xe881;" d="M350 680l380-330-380-330 0 192-350 0 0 278 350 0 0 190z" horiz-adv-x="730" />
+
+<glyph glyph-name="up" unicode="&#xe882;" d="M660 336l-192 0 0-350-276 0 0 350-192 0 330 380z" horiz-adv-x="660" />
+
+<glyph glyph-name="down-dir" unicode="&#xe883;" d="M460 550l-230-400-230 400 460 0z" horiz-adv-x="460" />
+
+<glyph glyph-name="left-dir" unicode="&#xe884;" d="M400 580l0-460-400 230z" horiz-adv-x="400" />
+
+<glyph glyph-name="right-dir" unicode="&#xe885;" d="M0 580l400-230-400-230 0 460z" horiz-adv-x="400" />
+
+<glyph glyph-name="up-dir" unicode="&#xe886;" d="M0 150l230 400 230-400-460 0z" horiz-adv-x="460" />
+
+<glyph glyph-name="down-bold" unicode="&#xe887;" d="M760 366l-380-380-380 380 192 0 0 350 376 0 0-350 192 0z" horiz-adv-x="760" />
+
+<glyph glyph-name="left-bold" unicode="&#xe888;" d="M378 730l0-190 352 0 0-378-352 0 0-192-378 380z" horiz-adv-x="730" />
+
+<glyph glyph-name="right-bold" unicode="&#xe889;" d="M350 730l380-380-380-380 0 192-350 0 0 378 350 0 0 190z" horiz-adv-x="730" />
+
+<glyph glyph-name="up-bold" unicode="&#xe88a;" d="M760 336l-192 0 0-350-376 0 0 350-192 0 380 380z" horiz-adv-x="760" />
+
+<glyph glyph-name="down-thin" unicode="&#xe88b;" d="M500 100l-250-240-250 240 162 0 0 740 176 0 0-740 162 0z" horiz-adv-x="500" />
+
+<glyph glyph-name="left-thin" unicode="&#xe88c;" d="M240 100l-240 250 240 250 0-160 740 0 0-178-740 0 0-162z" horiz-adv-x="980" />
+
+<glyph glyph-name="right-thin" unicode="&#xe88d;" d="M742 100l0 162-742 0 0 178 742 0 0 160 238-250z" horiz-adv-x="980" />
+
+<glyph glyph-name="up-thin" unicode="&#xe88e;" d="M500 602l-162 0 0-742-176 0 0 742-162 0 250 238z" horiz-adv-x="500" />
+
+<glyph glyph-name="ccw" unicode="&#xe88f;" d="M532 736q170 0 289-120t119-290-119-290-289-120q-142 0-252 88l70 74q84-60 182-60 126 0 216 90t90 218-90 218-216 90q-124 0-214-87t-92-211l142 0-184-204-184 204 124 0q2 166 122 283t286 117z" horiz-adv-x="940" />
+
+<glyph glyph-name="cw" unicode="&#xe890;" d="M408 760q168 0 287-116t123-282l122 0-184-206-184 206 144 0q-4 124-94 210t-214 86q-126 0-216-90t-90-218q0-126 90-216t216-90q104 0 182 60l70-76q-110-88-252-88-168 0-288 120t-120 290 120 290 288 120z" horiz-adv-x="940" />
+
+<glyph glyph-name="arrows-ccw" unicode="&#xe891;" d="M186 140l116 116 0-292-276 16 88 86q-116 122-114 290t120 288q100 100 240 116l4-102q-100-16-172-88-88-88-90-213t84-217z m332 598l276-16-88-86q116-122 114-290t-120-288q-96-98-240-118l-2 104q98 16 170 88 88 88 90 213t-84 217l-114-116z" horiz-adv-x="820" />
+
+<glyph glyph-name="level-down" unicode="&#xe892;" d="M100 200q-42 0-71 30t-29 70l0 350 140 0 0-310 364 0 0 150 240-220-240-220 0 150-404 0z" horiz-adv-x="744" />
+
+<glyph glyph-name="level-up" unicode="&#xe893;" d="M200 350l0-90-200 160 200 170 0-100 550 0q40 0 70-29t30-71l0-280-140 0 0 240-510 0z" horiz-adv-x="850" />
+
+<glyph glyph-name="shuffle" unicode="&#xe894;" d="M754 516q-54 0-105-32t-80-66-83-104q-48-62-75-94t-78-77-107-66-122-21l-104 0 0 140 104 0q54 0 106 32t81 66 83 104q62 82 101 126t116 88 163 44l36 0 0 120 210-180-210-180 0 100-36 0z m-484-88q-74 78-166 78l-104 0 0 140 104 0q140 0 254-108-14-16-37-45t-27-33q-8-12-24-32z m520-242l0 100 210-180-210-180 0 120-36 0q-140 0-260 116 46 58 72 92 0 2 6 9t8 11q84-88 174-88l36 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="loop" unicode="&#xe895;" d="M800 540q42 0 71-29t29-71l0-290q0-40-29-70t-71-30l-700 0q-40 0-70 30t-30 70l0 290q0 42 30 71t70 29l250 0 0 110 200-180-200-180 0 110-210 0 0-210 620 0 0 210-150 0 0 140 190 0z" horiz-adv-x="900" />
+
+<glyph glyph-name="switch" unicode="&#xe896;" d="M700 592l0-140-500 0 0-90-200 160 200 170 0-100 500 0z m300-420l-200-160 0 90-500 0 0 140 500 0 0 100z" horiz-adv-x="1000" />
+
+<glyph glyph-name="play" unicode="&#xe897;" d="M486 376q14-10 14-26 0-14-14-24l-428-266q-24-16-41-6t-17 40l0 514q0 30 17 40t41-6z" horiz-adv-x="500" />
+
+<glyph glyph-name="stop" unicode="&#xe898;" d="M526 650q74 0 74-64l0-470q0-66-74-66l-450 0q-76 0-76 66l0 470q0 36 18 50t58 14l450 0z" horiz-adv-x="600" />
+
+<glyph glyph-name="pause" unicode="&#xe899;" d="M440 700q90 0 90-64l0-570q0-66-90-66t-90 66l0 570q0 64 90 64z m-350 0q90 0 90-64l0-570q0-66-90-66t-90 66l0 570q0 64 90 64z" horiz-adv-x="530" />
+
+<glyph glyph-name="record" unicode="&#xe89a;" d="M350 700q146 0 248-102t102-248q0-144-102-247t-248-103-248 103-102 247q0 146 102 248t248 102z" horiz-adv-x="700" />
+
+<glyph glyph-name="to-end" unicode="&#xe89b;" d="M412 374q14-10 14-24 0-12-14-22l-362-228q-22-14-36-5t-14 35l0 442q0 26 14 35t36-5z m114 268q74 0 74-58l0-466q0-58-74-58-76 0-76 58l0 466q0 58 76 58z" horiz-adv-x="600" />
+
+<glyph glyph-name="to-start" unicode="&#xe89c;" d="M174 350q0 14 14 24l364 228q20 14 34 5t14-35l0-442q0-26-14-35t-34 5l-364 228q-14 10-14 22z m-174 234q0 58 76 58 74 0 74-58l0-466q0-58-74-58-76 0-76 58l0 466z" horiz-adv-x="600" />
+
+<glyph glyph-name="fast-forward" unicode="&#xe89d;" d="M866 374q14-10 14-24t-14-22l-372-248q-22-14-37-6t-15 36l0 482q0 28 15 36t37-6z m-454 0q14-10 14-24t-14-22l-360-248q-20-14-36-6t-16 36l0 482q0 28 16 36t36-6z" horiz-adv-x="880" />
+
+<glyph glyph-name="fast-backward" unicode="&#xe89e;" d="M0 350q0 14 14 24l374 248q20 14 36 6t16-36l0-482q0-28-16-36t-36 6l-374 248q-14 8-14 22z m454 0q0 14 14 24l360 248q20 14 36 6t16-36l0-482q0-28-16-36t-36 6l-360 248q-14 8-14 22z" horiz-adv-x="880" />
+
+<glyph glyph-name="progress-0" unicode="&#xe89f;" d="M1000 450l0-250q0-42-29-71t-71-29l-800 0q-40 0-70 29t-30 71l0 300q0 40 30 70t70 30l800 0q42 0 71-30t29-70l0-50z m-100-250l0 300-800 0 0-300 800 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="progress-1" unicode="&#xe8a0;" d="M1000 450l0-250q0-42-29-71t-71-29l-800 0q-40 0-70 29t-30 71l0 300q0 40 30 70t70 30l800 0q42 0 71-30t29-70l0-50z m-100-250l0 300-800 0 0-300 800 0z m-750 50l0 198 200 0 0-198-200 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="progress-2" unicode="&#xe8a1;" d="M1000 450l0-250q0-42-29-71t-71-29l-800 0q-40 0-70 29t-30 71l0 300q0 40 30 70t70 30l800 0q42 0 71-30t29-70l0-50z m-100-250l0 300-800 0 0-300 800 0z m-750 50l0 198 200 0 0-198-200 0z m250 0l0 198 200 0 0-198-200 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="progress-3" unicode="&#xe8a2;" d="M1000 450l0-250q0-42-29-71t-71-29l-800 0q-40 0-70 29t-30 71l0 300q0 40 30 70t70 30l800 0q42 0 71-30t29-70l0-50z m-100-250l0 300-800 0 0-300 800 0z m-750 50l0 198 200 0 0-198-200 0z m250 0l0 198 200 0 0-198-200 0z m250 198l200 0 0-198-200 0 0 198z" horiz-adv-x="1000" />
+
+<glyph glyph-name="target" unicode="&#xe8a3;" d="M430 780q178 0 304-126t126-304-126-304-304-126-304 126-126 304 126 304 304 126z m36-778q124 14 212 102t100 212l-192 0 0 70 192 0q-12 124-100 212t-212 102l0-194-70 0 0 194q-124-14-213-102t-101-212l194 0 0-70-194 0q12-124 101-212t213-102l0 194 70 0 0-194z" horiz-adv-x="860" />
+
+<glyph glyph-name="list" unicode="&#xe8a5;" d="M100 200q20 0 35-15t15-35-15-35-35-15l-50 0q-20 0-35 15t-15 35 14 35 36 15l50 0z m0 200q20 0 35-15t15-35-15-35-35-15l-50 0q-20 0-35 15t-15 35 14 35 36 15l50 0z m0 200q20 0 35-15t15-35-15-35-35-15l-50 0q-20 0-35 15t-15 35 14 35 36 15l50 0z m200-100q-20 0-35 15t-15 35 15 35 35 15l350 0q22 0 36-15t14-35-15-35-35-15l-350 0z m350-100q22 0 36-15t14-35-15-35-35-15l-350 0q-20 0-35 15t-15 35 15 35 35 15l350 0z m0-200q22 0 36-15t14-35-15-35-35-15l-350 0q-20 0-35 15t-15 35 15 35 35 15l350 0z" horiz-adv-x="700" />
+
+<glyph glyph-name="list-add" unicode="&#xe8a6;" d="M350 400q22 0 36-15t14-35-15-35-35-15l-300 0q-20 0-35 15t-15 35 14 35 36 15l300 0z m0-200q22 0 36-15t14-35-15-35-35-15l-300 0q-20 0-35 15t-15 35 14 35 36 15l300 0z m620 200q30 0 30-50t-30-50l-170 0 0-170q0-30-50-30t-50 30l0 170-164 0q-30 0-30 50t30 50l164 0 0 170q0 30 50 30t50-30l0-170 170 0z m-620 200q22 0 36-15t14-35-15-35-35-15l-300 0q-20 0-35 15t-15 35 14 35 36 15l300 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="battery" unicode="&#xe8a9;" d="M770 350q0-98 36-157t78-59l66 0q-30-46-64-65t-118-19l-500 0q-130 0-199 94t-69 206q0 110 69 205t199 95l500 0q84 0 118-19t64-65l-66 0q-42 0-78-60t-36-156z m-136-90q10 12-8 26-136 134-178 164-16 10-26 13t-18-5-10-12-8-18l-22-56-148 66q-26 12-34 0-8-14 8-28 136-132 180-162 34-16 42-11t18 31l24 58 146-68q26-12 34 2z m310 192q22 0 39-27t17-71-17-72-39-28l-38 0q-22 0-38 28t-16 72 16 71 38 27l38 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="back-in-time" unicode="&#xe8aa;" d="M532 760q170 0 289-120t119-290-119-290-289-120q-138 0-252 88l70 76q82-60 182-60 126 0 216 90t90 216q0 128-90 218t-216 90q-124 0-213-86t-93-210l142 0-184-206-184 206 124 0q4 166 123 282t285 116z m-36-190l70 0 0-204 130-130-50-50-150 150 0 234z" horiz-adv-x="940" />
+
+<glyph glyph-name="monitor" unicode="&#xe8ab;" d="M900 790q42 0 71-30t29-70l0-550q0-42-29-77t-69-43l-218-44 86-38q50-28-20-28l-500 0q-98 0 32 52l36 14-220 44q-40 8-69 43t-29 77l0 550q0 40 30 70t70 30l800 0z m0-646l0 556-800 0 0-556 800 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="mobile" unicode="&#xe8ac;" d="M480 840q42 0 71-29t29-71l0-780q0-40-29-70t-71-30l-380 0q-40 0-70 30t-30 70l0 780q0 42 30 71t70 29l380 0z m-190-940q30 0 50 15t20 35q0 22-20 36t-50 14q-28 0-49-15t-21-35 21-35 49-15z m210 150l0 660-420 0 0-660 420 0z" horiz-adv-x="580" />
+
+<glyph glyph-name="cd" unicode="&#xe8ae;" d="M460 810q190 0 325-135t135-325-135-325-325-135-325 135-135 325 135 325 325 135z m0-610q62 0 106 44t44 106q0 64-43 107t-107 43q-62 0-106-44t-44-106 44-106 106-44z" horiz-adv-x="920" />
+
+<glyph glyph-name="inbox" unicode="&#xe8af;" d="M967 398q40-42 30-72l-28-154q-4-20-22-33t-40-13l-816 0q-22 0-40 13t-22 33l-28 154q-8 32 32 72 8 10 36 38t68 67 52 51q22 22 52 22l516 0q30 0 52-22 16-16 53-52t67-65 38-39z m-266-32l178 0-102 114-556 0-102-114 178 0q8 0 12-8l40-100 300 0 40 100q4 8 12 8z" horiz-adv-x="999" />
+
+<glyph glyph-name="install" unicode="&#xe8b0;" d="M884 306q24-52 14-96l-34-184q-2-20-19-35t-39-15l-712 0q-22 0-39 15t-19 35l-34 184q-8 50 14 96l158 374q22 46 72 46l104 0-20-204-134 0 254-210 256 210-136 0-18 204 102 0q50 0 74-46z m-68-132q2 22-10 38t-34 16l-644 0q-22 0-34-16t-10-38l14-74q2-22 19-37t37-15l592 0q22 0 39 15t19 37z" horiz-adv-x="901" />
+
+<glyph glyph-name="globe" unicode="&#xe8b1;" d="M480 830q200 0 340-141t140-339q0-200-140-340t-340-140q-198 0-339 140t-141 340q0 198 141 339t339 141z m410-480q0 132-78 239t-202 149q-18-24-16-32 4-38 18-51t30-7l32 12t20 2q22-24 0-47t-45-56-1-77q34-64 96-64 28-2 43-36t17-66q10-80-14-140-22-44 14-76 86 112 86 250z m-466 404q-112-14-199-84t-127-174q6 0 22-2t28-3 26-4 24-8 12-13q4-12-14-45t-18-61q0-30 38-56t38-46q0-28 8-68t8-44q0-12 36-54t52-42q10 0 11 22t-2 54-3 40q0 32 14 74 12 42 59 70t55 46q16 34 9 61t-17 43-34 28-41 17-37 9-22 4q-16 6-42 7t-36-3-27 11-17 29q0 10 15 27t35 37 28 30q8 14 17 21t22 16 27 21q4 4 25 17t27 23z m-72-794q66-20 128-20 128 0 226 68-26 44-118 34-24-2-65-17t-47-17q-74-16-76-16-12-2-26-14t-22-18z" horiz-adv-x="960" />
+
+<glyph glyph-name="cloud" unicode="&#xe8b2;" d="M760 494q100 0 170-68t70-166-70-166-170-68l-578 0q-74 0-128 52t-54 124q0 74 53 126t129 52q2 0 10-1t10-1q-2 12-2 38 0 108 78 184t188 76q90 0 160-52t94-134q28 4 40 4z" horiz-adv-x="1000" />
+
+<glyph glyph-name="cloud-thunder" unicode="&#xe8b3;" d="M760 494q100 0 170-68t70-166-70-166-170-68l-578 0q-74 0-128 52t-54 124q0 74 53 126t129 52q2 0 10-1t10-1q-2 12-2 38 0 108 78 184t188 76q90 0 160-52t94-134q28 4 40 4z m-192-216q14 16 14 30 0 20-30 32l-4 0q-26 14-38 16l50 116q6 0 6 20 0 14-8 18-16 10-34-8-2-2-30-32t-61-66-45-52q-12-18-12-30 0-22 30-30l4-2q8-4 38-16l-52-114-2-8q-2-8-2-14 0-10 8-18 18-10 34 10 100 100 134 148z" horiz-adv-x="1000" />
+
+<glyph glyph-name="flash" unicode="&#xe8b4;" d="M40-100q-4 4 35 94t79 182 38 98-94 45-98 55q-4 12 84 120t180 209 96 97q6-4-74-186t-78-186 95-43 97-57q4-20-174-227t-186-201z" horiz-adv-x="400" />
+
+<glyph glyph-name="moon" unicode="&#xe8b5;" d="M524 238q106 106 125 252t-53 270q52-26 96-72 128-128 128-309t-128-309-310-128-310 128q-40 40-72 94 124-70 271-51t253 125z" horiz-adv-x="820" />
+
+<glyph glyph-name="flight" unicode="&#xe8b6;" d="M268-120l124 400-180 0-112-100-100 0 80 170-80 170 100 0 112-100 180 0-124 400 100 0 224-400 274 0t36-4 46-11 36-21 16-34q0-32-38-49t-74-19l-38-2-258 0-224-400-100 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="paper-plane" unicode="&#xe8b7;" d="M894 720q14 4 22-3t4-19q-2-6-72-310t-74-316q-2-14-14-19t-24 1l-248 134-30 16 22 26q388 420 394 426 4 4-1 9t-9 1l-550-402-112 44-190 76q-12 4-12 12t12 12q8 4 441 157t441 155z m-582-728l0 204 160-82q-130-116-142-128-18-14-18 6z" horiz-adv-x="921" />
+
+<glyph glyph-name="leaf" unicode="&#xe8b8;" d="M236 646q182 106 506 66 168-22 196-50 4-6-2-10-76-40-130-109t-78-132-65-132-93-105q-138-96-382-4-66-76-114-176-12-24-47-7t-25 39q44 100 129 193t176 153 176 106 141 68l54 20q-14 0-41-1t-104-14-148-38-162-84-161-141q-22 242 174 358z" horiz-adv-x="940" />
+
+<glyph glyph-name="lifebuoy" unicode="&#xe8b9;" d="M454 810q190 2 326-130t140-322q2-190-131-327t-323-141q-190-2-327 131t-139 323q-4 190 130 327t324 139z m0-60q-94 0-178-44l62-104q56 28 122 28t122-28l62 104q-88 46-190 44z m-246-522q-28 60-28 122 0 64 28 124l-102 62q-46-88-46-190 2-96 46-180z m258-278q98 4 178 46l-62 104q-60-30-122-30t-122 30l-62-104q86-46 190-46z m-6 180q92 0 156 65t64 155q0 92-64 156t-156 64-156-64-64-156q0-90 64-155t156-65z m252 98l104-62q46 96 44 190 0 96-44 180l-104-62q28-60 28-124 0-62-28-122z" horiz-adv-x="920" />
+
+<glyph glyph-name="mouse" unicode="&#xe8ba;" d="M551 130q28-80-17-157t-139-111q-94-28-175 9t-103 117l-106 384q-20 68 6 134t84 106l-96 186q-14 34 14 48 30 18 48-14l98-192q80 22 154-16t102-116z m-324 274q28 10 40 36t4 54q-10 28-35 41t-53 5q-28-10-40-36t-4-54q10-28 35-41t53-5z" horiz-adv-x="561" />
+
+<glyph glyph-name="briefcase" unicode="&#xe8bb;" d="M456 326l0-100-456 0q8 226 10 292 4 108 100 108l160 0q16 26 37 67t23 45q14 26 23 32t37 6l222 0q26 0 36-7t22-31q18-32 60-112l160 0q96 0 100-108l10-292-454 0 0 100-90 0z m-74 354l-28-54 292 0-28 54q-14 26-42 26l-152 0q-28 0-42-26z m164-604l0 100 430 0q-6-88-10-166-6-84-90-84l-750 0q-90 0-90 84l-10 166 430 0 0-100 90 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="suitcase" unicode="&#xe8bc;" d="M900 650q42 0 71-30t29-70l0-550q0-42-29-71t-71-29l-50 0 0 750 50 0z m-900-100q0 40 30 70t70 30l50 0 0-750-50 0q-40 0-70 29t-30 71l0 550z m670 204l0-104 110 0 0-750-560 0 0 750 110 0 0 104q98 46 170 46t170-46z m-60-104l0 66q-52 24-110 24-54 0-110-24l0-66 220 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="dot" unicode="&#xe8bd;" d="M110 460q46 0 78-32t32-78q0-44-32-77t-78-33-78 33-32 77q0 46 32 78t78 32z" horiz-adv-x="220" />
+
+<glyph glyph-name="dot-2" unicode="&#xe8be;" d="M110 460q46 0 78-32t32-78q0-44-32-77t-78-33-78 32-32 78 32 78 78 32z m350 0q46 0 78-32t32-78q0-44-33-77t-77-33q-46 0-78 32t-32 78 32 78 78 32z" horiz-adv-x="570" />
+
+<glyph glyph-name="dot-3" unicode="&#xe8bf;" d="M110 460q46 0 78-32t32-78q0-44-32-77t-78-33-78 33-32 77q0 46 32 78t78 32z m350 0q46 0 78-32t32-78q0-44-33-77t-77-33-77 33-33 77q0 46 32 78t78 32z m350 0q46 0 78-32t32-78q0-44-32-77t-78-33-78 33-32 77q0 46 32 78t78 32z" horiz-adv-x="920" />
+
+<glyph glyph-name="brush" unicode="&#xe8c0;" d="M118 170q38 34 85 29t87-45q42-40 48-87t-30-83q-86-84-228-102-84-12-80 14 0 4 6 10 52 60 64 145t48 119z m840 646q26-26-148-248t-292-338q-38-38-124-104-8-6-16 8-18 34-48 64-32 32-66 48-16 6-8 16 64 84 104 122 118 116 344 287t254 145z" horiz-adv-x="962" />
+
+<glyph glyph-name="infinity" unicode="&#xe8c2;" d="M796 570q84 0 144-53t60-167q0-112-60-166t-144-54q-78 0-157 40t-139 106q-58-66-137-106t-157-40q-86 0-146 54t-60 166q0 114 60 167t146 53q78 0 157-39t137-105q58 66 138 105t158 39z m-590-352q60 0 127 37t113 95q-46 58-112 95t-128 37q-114 0-114-132t114-132z m590 0q114 0 114 132t-114 132q-62 0-129-37t-111-95q44-58 111-95t129-37z" horiz-adv-x="1000" />
+
+<glyph glyph-name="erase" unicode="&#xe8c3;" d="M902 700q42 0 71-29t29-71l0-500q0-40-29-70t-71-30l-478 0q-38 0-70 28l-340 296q-28 26 0 54l340 296q30 26 70 26l478 0z m-140-550l72 74-128 126 128 128-72 72-128-126-128 126-72-72 128-128-128-126 72-74 128 128z" horiz-adv-x="1002" />
+
+<glyph glyph-name="chart-pie" unicode="&#xe8c4;" d="M368 770l0-368-368 0q18 146 121 249t247 119z m106 0q156-20 261-139t105-279q0-174-123-298t-299-124q-160 0-278 105t-140 263l424 0q20 0 35 14t15 36l0 422z" horiz-adv-x="840" />
+
+<glyph glyph-name="chart-line" unicode="&#xe8c5;" d="M34 284q-42 10-32 56 10 42 54 32l98-24-52-80z m890-12q14 12 33 11t31-15q32-32-2-64l-252-226q-12-12-30-12-14 0-28 10l-286 220-54 14 50 80 36-8q12-4 16-8l264-204z m-490 220l-350-550q-12-22-38-22-12 0-24 8-16 10-20 29t6 33l374 588q8 16 28 20 18 6 36-6l246-156 226 326q10 16 28 19t34-9q38-24 12-62l-252-362q-24-36-62-12z" horiz-adv-x="1003" />
+
+<glyph glyph-name="chart-bar" unicode="&#xe8c6;" d="M750 800q22 0 36-15t14-35l0-850-200 0 0 850q0 50 40 50l110 0z m-300-300q22 0 36-15t14-35l0-550-200 0 0 550q0 50 40 50l110 0z m-300-300q22 0 36-15t14-35l0-250-200 0 0 250q0 50 40 50l110 0z" horiz-adv-x="800" />
+
+<glyph glyph-name="chart-area" unicode="&#xe8c7;" d="M964 732q16 22 16-4l0-768-964 0q-12 0-15 7t5 17l230 288q20 22 40 2l74-66q10-8 21-7t17 11l158 238q16 26 38 4l112-104q20-20 38 4z" horiz-adv-x="980" />
+
+<glyph glyph-name="tape" unicode="&#xe8c8;" d="M770 580q96 0 163-67t67-163q0-94-67-162t-163-68l-540 0q-94 0-162 68t-68 162q0 96 68 163t162 67q96 0 163-67t67-163q0-72-40-130l160 0q-40 64-40 130 0 96 68 163t162 67z m-670-230q0-52 38-91t92-39 92 39 38 91q0 54-38 92t-92 38-92-38-38-92z m670-130q54 0 92 39t38 91q0 54-38 92t-92 38-92-38-38-92q0-52 38-91t92-39z" horiz-adv-x="1000" />
+
+<glyph glyph-name="graduation-cap" unicode="&#xe8c9;" d="M166 238l334-168 276 136q-4-22-8-47t-6-35-11-23-24-23-45-22q-40-18-80-41t-63-34-39-11-40 13-64 37-80 40q-72 32-103 69t-47 109z m810 246q24-14 24-33t-24-33l-78-44-308 102q-22 36-90 36-40 0-67-16t-27-40 27-40 67-16q26 0 36 4l292-68-268-152q-60-32-120 0l-416 234q-24 14-24 33t24 33l416 234q60 32 120 0z m-128-442q18 116 13 182t-19 90l-14 22 70 38q6-8 12-28t17-101-7-197q-4-26-22-30t-35 5-15 19z" horiz-adv-x="1000" />
+
+<glyph glyph-name="language" unicode="&#xe8ca;" d="M988 306q30-82-10-176t-134-160q-10 0-12 2t-16 19-16 19q-2 6 2 10 86 60 117 152t-11 148q-16-38-39-76t-59-80-86-65-106-15q-52 6-84 41t-32 93q0 84 60 148 50 50 114 66l-2 100q-140-24-146-24-6-2-10 4 0 2-5 29t-5 31q-2 2 1 4t7 2l156 28q0 110-2 114 0 8 8 8 46 0 52 2 10 0 10-8l0-104q158 22 164 22 8 4 10-6 0-2 4-23t4-25q4-10-4-12l-176-30 0-102 12 0q86 0 148-36t86-100z m-370-160q28-6 62 6l-4 214q-34-12-60-40-44-44-44-108 0-66 46-72z m122 28q28 24 58 68t45 79 7 41q-36 18-96 18-2 0-6-1t-6-1z m-448 382q10-28 53-165t83-261 40-126q0-4-4-4l-86 0q-6 0-6 4l-50 166-176 0q-48-164-50-166 0-4-6-4l-86 0q-4 0-4 4 10 18 176 552 2 8 10 8l96 0q10 0 10-8z m-130-316l144 0-72 264z" horiz-adv-x="1001" />
+
+<glyph glyph-name="ticket" unicode="&#xe8cb;" d="M216 272l326 326 178-178-326-326z m710 244q14-14 14-36t-14-36l-550-550q-16-16-36-16t-36 16l-76 76q12 20 12 48 0 42-29 72t-71 30q-22 0-50-14l-74 76q-16 16-16 36t16 36l550 550q14 14 36 14t36-14l74-76q-12-22-12-48 0-42 30-71t72-29q26 0 48 12z m-532-502l406 406-258 258-408-406z" horiz-adv-x="940" />
+
+<glyph glyph-name="water" unicode="&#xe8cc;" d="M168 844q10-86 50-155t73-123 33-112q0-66-48-113t-114-47-114 47-48 113q0 58 33 112t73 123 50 155q2 4 7 4t5-4z m616 0q10-86 50-155t73-123 33-112q0-66-48-113t-114-47-114 47-48 113q0 48 21 93t48 78 53 92 34 127q2 4 7 4t5-4z m-320-444q2 4 7 4t5-4q10-86 50-155t73-123 33-112q0-66-48-113t-114-47-114 47-48 113q0 58 33 112t73 123 50 155z" horiz-adv-x="940" />
+
+<glyph glyph-name="droplet" unicode="&#xe8cd;" d="M290 822q14-118 60-219t92-159 82-136 36-160q0-114-83-196t-197-82-197 82-83 196q0 82 36 160t82 136 92 159 60 219q2 8 11 8t9-8z m-42-392q2 4-2 14-6 6-14 6t-12-6l-40-58q-32-46-48-70t-34-75-18-101q0-24 17-41t41-17q58 0 58 68 0 94 42 246 2 6 5 17t5 17z" horiz-adv-x="560" />
+
+<glyph glyph-name="air" unicode="&#xe8ce;" d="M85 534q-16-14-36-12t-34 18q-14 14-12 36t18 36q48 40 79 60t89 40 129 4 159-66 155-53 100 16 89 67q38 30 70-6 32-40-6-72-122-110-234-110-100 0-222 70-68 38-119 52t-93 0-65-29-67-51z m736-110q38 32 70-6 32-40-6-72-40-34-65-53t-72-38-97-19q-96 0-222 70-68 38-119 52t-93 0-65-29-67-51q-14-14-35-12t-35 18q-32 40 6 72 38 34 60 50t69 38 88 23 105-15 134-56q68-38 119-52t93 0 65 29 67 51z m0-256q38 32 70-6 14-14 12-36t-18-36q-40-34-65-53t-72-38-97-19q-96 0-222 70-68 38-119 52t-93 1-66-29-66-52q-14-14-35-12t-35 18q-32 40 6 72 38 34 60 50t69 38 88 23 105-15 134-56q68-38 119-52t93 0 65 29 67 51z" horiz-adv-x="905" />
+
+<glyph glyph-name="credit-card" unicode="&#xe8cf;" d="M900 700q42 0 71-30t29-70l0-500q0-42-29-71t-71-29l-800 0q-40 0-70 29t-30 71l0 500q0 40 30 70t70 30l800 0z m0-600l0 300-800 0 0-300 800 0z m0 450l0 50-800 0 0-50 800 0z m-700-256l30 0 0-30-30 0 0 30z m180-60l30 0 0 30 30 0 0 30 60 0 0-30-30 0 0-30-30 0 0-30-60 0 0 30z m120-30l-30 0 0 30 30 0 0-30z m-150 0l-60 0 0 30 60 0 0-30z m30 60l0-30-30 0 0 60 60 0 0-30-30 0z m-120-30l0-30-60 0 0 30 30 0 0 30 30 0 0 30 60 0 0-30-30 0 0-30-30 0z" horiz-adv-x="1000" />
+
+<glyph glyph-name="floppy" unicode="&#xe8d0;" d="M658 750l142-156 0-544q0-40-29-70t-71-30l-600 0q-40 0-70 30t-30 70l0 600q0 42 30 71t70 29l558 0z m-58-300l0 250-400 0 0-250q0-20 15-35t35-15l300 0q20 0 35 15t15 35z m-50 200l0-200-100 0 0 200 100 0z" horiz-adv-x="800" />
+
+<glyph glyph-name="clipboard" unicode="&#xe8d1;" d="M630 750q28 0 49-21t21-49l0-760q0-30-21-50t-49-20l-560 0q-28 0-49 20t-21 50l0 760q0 28 21 49t49 21l60-150 440 0z m-100-100l-360 0-44 100 108 0 36 100 160 0 36-100 110 0z" horiz-adv-x="700" />
+
+<glyph glyph-name="megaphone" unicode="&#xe8d2;" d="M792 500q58-138 67-258t-39-140q-28-12-61 3t-65 40-99 41-149 8q-28-4-42-19t-6-37q22-56 46-108 4-10 24-22t24-20q14-34-22-46-50-22-102-40-30-10-54 42-32 76-58 132-6 12-34 17t-46 31q-30-10-38-14-34-12-74 12t-54 60q-17 32-5 79t43 61q126 52 213 108t124 103 59 92 25 78 15 59 36 36q48 20 130-70t142-228z m-28-300q8 4 10 38t-11 98-41 128q-28 66-67 123t-67 84-36 23-10-42 10-105 40-133 68-119 68-76 36-19z" horiz-adv-x="860" />
+
+<glyph glyph-name="database" unicode="&#xe8d3;" d="M686 208q14 20 14-2l0-100q0-74-104-135t-246-61q-140 0-245 61t-105 135l0 100q0 8 4 10t10-8q32-52 125-86t211-34 211 34 125 86z m2 254q8 16 12 0l0-116q0-68-102-114t-248-46q-144 0-247 46t-103 114l0 116q0 20 14 0 30-46 124-75t212-29 212 29 126 75z m-338 328q144 0 247-39t103-93l0-64q0-58-103-99t-247-41-247 41-103 99l0 64q0 54 103 93t247 39z" horiz-adv-x="700" />
+
+<glyph glyph-name="drive" unicode="&#xe8d4;" d="M884 304q26-44 14-96l-34-184q-2-20-19-35t-39-15l-712 0q-20 0-38 15t-20 35l-34 184q-8 52 14 96l158 374q22 46 72 46l408 0q50 0 74-46z m-68-132q2 22-10 38t-34 16l-644 0q-22 0-34-16t-10-38l14-74q2-22 19-37t39-15l590 0q22 0 39 15t19 37z" horiz-adv-x="902" />
+
+<glyph glyph-name="bucket" unicode="&#xe8d5;" d="M522 780q174 0 286-49t104-105q-6-38-48-307t-44-281q-2-18-37-44t-107-50-154-24-153 24-106 50-37 44q0 2-4 30 82-6 163 35t139 117q28 0 48 20t20 50q0 28-20 49t-50 21q-28 0-49-21t-21-49q0-20 10-36-48-58-115-89t-131-27q-102 10-157 57t-59 109q-8 122 156 184-18 94-22 138-8 56 104 105t284 49z m-452-470q4-32 37-59t91-39l-32 204q-100-44-96-106z m452 212q82 0 157 18t113 39 38 35-38 35-112 39-158 18q-82 0-156-18t-112-39-38-35 38-35 112-39 156-18z" horiz-adv-x="913" />
+
+<glyph glyph-name="thermometer" unicode="&#xe8d6;" d="M400 356q64-36 102-98t38-138q0-112-79-191t-191-79-191 79-79 191q0 76 38 138t102 98l0 444q0 50 40 50l170 0q20 0 35-15t15-35l0-444z m-130-406q70 0 120 50t50 120q0 56-32 100t-84 60l0 370-100 0 0-368q-54-16-89-61t-35-101q0-70 50-120t120-50z" horiz-adv-x="540" />
+
+<glyph glyph-name="key" unicode="&#xe8d7;" d="M774 612q20-116-28-215t-150-117q-66-12-130-2l-118-194-70-12-104-166q-14-28-46-32l-76-14q-12-4-22 4t-12 22l-16 98q-8 30 12 56l258 386q-24 50-38 120-18 106 53 187t185 101q106 20 195-45t107-177z m-126-76q30 44 21 97t-51 83q-42 32-92 22t-80-54q-8-12-12-23t-1-20 5-16 13-17 18-15 22-16 23-17q6-4 22-16t23-16 19-12 19-8 17 1 18 8 16 19z" horiz-adv-x="780" />
+
+<glyph glyph-name="flow-cascade" unicode="&#xe8d8;" d="M520 120q50 0 85-35t35-85-35-85-85-35q-80 0-110 74l-164 0q-88 0-131 54t-43 118l0 464q-72 34-72 110 0 50 35 85t85 35 85-35 35-85q0-76-72-110l0-114q0-78 78-78l164 0q30 72 110 72 50 0 85-35t35-85-35-85-85-35q-80 0-110 74l-164 0q-42 0-78 16l0-194q0-78 78-78l164 0q30 72 110 72z m0 300q-28 0-49-20t-21-50q0-28 21-48t49-20 49 20 21 48q0 30-21 50t-49 20z m-470 280q0-28 21-48t49-20 49 20 21 48q0 30-21 50t-49 20-49-20-21-50z m470-768q28 0 49 20t21 48q0 30-21 50t-49 20-49-20-21-50q0-28 21-48t49-20z" horiz-adv-x="640" />
+
+<glyph glyph-name="flow-branch" unicode="&#xe8d9;" d="M640 650q0-80-74-110-6-58-28-101t-61-69-68-38-75-26q-42-14-63-22t-47-24-38-40-16-60q70-30 70-110 0-50-35-85t-85-35-85 35-35 85q0 78 72 112l0 378q-72 34-72 110 0 50 35 85t85 35 85-35 35-85q0-76-72-110l0-204q40 30 138 60 58 18 84 29t51 41 29 76q-70 32-70 108 0 50 35 85t85 35 85-35 35-85z m-588 0q0-28 20-48t48-20 49 20 21 48q0 30-21 50t-49 20-48-20-20-50z m68-668q28 0 49 20t21 48q0 30-21 50t-49 20-48-20-20-50q0-28 20-48t48-20z m400 600q28 0 49 20t21 48q0 30-21 50t-49 20-48-20-20-50q0-28 20-48t48-20z" horiz-adv-x="640" />
+
+<glyph glyph-name="flow-tree" unicode="&#xe8da;" d="M868 112q72-34 72-112 0-50-35-85t-85-35-85 35-35 85q0 78 72 112l0 114q0 78-76 78l-100 0q-44 0-78 12l0-204q72-34 72-112 0-50-35-85t-85-35-85 35-35 85q0 78 72 112l0 204q-30-12-76-12l-100 0q-34 0-53-19t-22-33-3-26l0-114q72-34 72-112 0-50-35-85t-85-35-85 35-35 85q0 78 72 112l0 114q0 64 43 118t131 54l100 0q76 0 76 52l0 140q-72 34-72 110 0 50 35 85t85 35 85-35 35-85q0-76-72-110l0-140q0-52 78-52l100 0q86 0 129-54t43-118l0-114z m-678-112q0 30-21 50t-49 20-48-20-20-50q0-28 20-48t48-20 49 20 21 48z m212 700q0-28 20-48t48-20 49 20 21 48q0 30-21 50t-49 20-48-20-20-50z m138-700q0 30-21 50t-49 20-48-20-20-50q0-28 20-48t48-20 49 20 21 48z m280-68q28 0 49 20t21 48q0 30-21 50t-49 20-48-20-20-50q0-28 20-48t48-20z" horiz-adv-x="940" />
+
+<glyph glyph-name="flow-line" unicode="&#xe8db;" d="M168 162q72-34 72-112 0-50-35-85t-85-35-85 35-35 85q0 78 72 112l0 378q-72 34-72 110 0 50 35 85t85 35 85-35 35-85q0-76-72-110l0-378z m-116 488q0-28 20-48t48-20 49 20 21 48q0 30-21 50t-49 20-48-20-20-50z m68-668q28 0 49 20t21 48q0 30-21 50t-49 20-48-20-20-50q0-28 20-48t48-20z" horiz-adv-x="240" />
+
+<glyph glyph-name="flow-parallel" unicode="&#xe8dc;" d="M240 650q0-76-72-110l0-378q72-34 72-112 0-50-35-85t-85-35-85 35-35 85q0 78 72 112l0 378q-72 34-72 110 0 50 35 85t85 35 85-35 35-85z m-50-600q0 30-21 50t-49 20-48-20-20-50q0-28 20-48t48-20 49 20 21 48z m-70 532q28 0 49 20t21 48q0 30-21 50t-49 20-48-20-20-50q0-28 20-48t48-20z m448-420q72-34 72-112 0-50-35-85t-85-35-85 35-35 85q0 78 72 112l0 378q-72 34-72 110 0 50 35 85t85 35 85-35 35-85q0-76-72-110l0-378z m-116 488q0-28 20-48t48-20 49 20 21 48q0 30-21 50t-49 20-48-20-20-50z m68-668q28 0 49 20t21 48q0 30-21 50t-49 20-48-20-20-50q0-28 20-48t48-20z" horiz-adv-x="640" />
+
+<glyph glyph-name="rocket" unicode="&#xe8dd;" d="M543 236q6-50 8-81t-8-59-13-40-35-32-45-26-70-31-85-37q-32-12-45 4t-3 44l40 110-130 132-106-40q-28-12-43 2t-3 46q12 30 31 79t27 65 22 45 25 36 29 20 41 13l52 0t71-6q10 14 29 39t77 85 118 104 145 75 165 19q8 0 14-6 4-4 6-14 10-82-18-168t-76-151-98-118-86-81z m50 296q22-22 54-22t54 22q22 24 22 56t-22 56q-22 22-54 22t-54-22q-22-24-22-56t22-56z" horiz-adv-x="860" />
+
+<glyph glyph-name="gauge" unicode="&#xe8de;" d="M406 178q34 56 214 284t194 220q12-6-96-278t-138-326q-50-86-136-36t-38 136z m94 380q-168 0-284-127t-116-311q0-30 2-46 2-22-12-37t-34-17-36 12-18 34q0 8-1 26t-1 28q0 226 145 382t355 156q72 0 134-18l-70-86q-40 4-64 4z m362-62q138-154 138-376 0-38-2-56-2-20-16-33t-34-13l-4 0q-22 4-35 20t-11 36q2 14 2 46 0 150-80 268 6 14 20 51t22 57z" horiz-adv-x="1000" />
+</font>
+</defs>
+</svg>
\ No newline at end of file
diff --git a/src/mol-app/skin/fonts/fontello.ttf b/src/mol-app/skin/fonts/fontello.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..39a234370e14bbf53e699758840b2ea5de8ccede
Binary files /dev/null and b/src/mol-app/skin/fonts/fontello.ttf differ
diff --git a/src/mol-app/skin/fonts/fontello.woff b/src/mol-app/skin/fonts/fontello.woff
new file mode 100644
index 0000000000000000000000000000000000000000..674807bae0bb1793ab6274a50737b707d9356fd2
Binary files /dev/null and b/src/mol-app/skin/fonts/fontello.woff differ
diff --git a/src/mol-app/skin/fonts/fontello.woff2 b/src/mol-app/skin/fonts/fontello.woff2
new file mode 100644
index 0000000000000000000000000000000000000000..49eb9640642432843920fcacac3346c95157ba26
Binary files /dev/null and b/src/mol-app/skin/fonts/fontello.woff2 differ
diff --git a/src/mol-app/skin/icons.scss b/src/mol-app/skin/icons.scss
new file mode 100644
index 0000000000000000000000000000000000000000..3d1d6cfcbee2d44a08ac532caf8aedc1725326b4
--- /dev/null
+++ b/src/mol-app/skin/icons.scss
@@ -0,0 +1,135 @@
+
+[class^="molstar-icon-"]:before, [class*=" molstar-icon-"]:before {
+    font-family: "fontello";
+    font-style: normal;
+    font-weight: normal;
+    speak: none;
+
+    display: inline-block;
+    text-decoration: inherit;
+    width: 1em;
+    margin-right: .2em;
+    text-align: center;
+    /* opacity: .8; */
+
+    /* For safety - reset parent styles, that can break glyph codes*/
+    font-variant: normal;
+    text-transform: none;
+
+    /* fix buttons height, for twitter bootstrap */
+    line-height: 1em;
+
+    /* Animation center compensation - margins should be symmetric */
+    /* remove if not needed */
+    margin-left: .2em;
+
+    /* you can be more comfortable with increased icons size */
+    /* font-size: 120%; */
+
+    /* Font smoothing. That was taken from TWBS */
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+
+    /* Uncomment for 3D effect */
+    /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
+  }
+
+  .molstar-icon-expand-layout:before {
+      content: "\e84a";
+  }
+
+  .molstar-icon-plus:before {
+      content: "\e816";
+  }
+
+  .molstar-icon-minus:before {
+      content: "\e819";
+  }
+
+  .molstar-icon-reset-scene:before {
+      content: "\e891";
+  }
+
+  .molstar-icon-ok:before {
+      content: "\e812";
+  }
+
+  .molstar-icon-cross:before {
+      content: "\e868";
+  }
+
+  .molstar-icon-off:before {
+      content: "\e813";
+  }
+
+  .molstar-icon-expand:before {
+      content: "\e885";
+  }
+
+  .molstar-icon-collapse:before {
+      content: "\e883";
+  }
+
+  .molstar-icon-visual-visibility:before {
+      content: "\e826";
+  }
+
+  .molstar-icon-abort:before {
+      content: "\e814";
+  }
+
+  .molstar-icon-focus-on-visual:before {
+      content: "\e8a3";
+  }
+
+  .molstar-icon-settings:before  {
+      content: "\e855";
+  }
+
+  .molstar-icon-tools:before {
+      content: "\e856";
+  }
+
+  .molstar-icon-log:before {
+      content: "\e8a5";
+  }
+
+  .molstar-icon-remove:before {
+      content: "\e847";
+  }
+
+  .molstar-icon-help:before {
+      content: '\e81c'
+  }
+
+  .molstar-icon-info:before {
+      content: '\e81e'
+  }
+
+  .molstar-icon-left-open-big:before {
+      content: '\e87c'
+  }
+
+  .molstar-icon-right-open-big:before {
+      content: '\e87d'
+  }
+
+  .molstar-icon-left-open:before {
+      content: '\e874'
+  }
+
+  .molstar-icon-right-open:before {
+      content: '\e875'
+  }
+
+  .molstar-icon-screenshot:before {
+      content: "\e80f";
+  }
+
+  .molstar-icon-help:before {
+      content: "\e81c";
+  }
+
+  .molstar-icon-help-circle:before {
+      content: "\e81d";
+  }
\ No newline at end of file
diff --git a/src/mol-app/skin/layout.scss b/src/mol-app/skin/layout.scss
new file mode 100644
index 0000000000000000000000000000000000000000..ded62b22132c4b64ff0b276f2523b5b9b377b282
--- /dev/null
+++ b/src/mol-app/skin/layout.scss
@@ -0,0 +1,29 @@
+
+@import 'layout/common';
+
+.molstar-layout-standard-outside {
+    position: absolute;
+    @import 'layout/outside';
+}
+
+.molstar-layout-standard-landscape {
+    position: absolute;
+    @import 'layout/landscape';
+}
+
+.molstar-layout-standard-portrait {
+    position: absolute;
+    @import 'layout/portrait';
+}
+
+.molstar-layout-expanded {
+    position: fixed;
+
+    @media (orientation:landscape) {
+        @import 'layout/landscape';
+    };
+
+    @media (orientation:portrait) {
+        @import 'layout/portrait';
+    };
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/layout/common.scss b/src/mol-app/skin/layout/common.scss
new file mode 100644
index 0000000000000000000000000000000000000000..b84b69214b3468b2d165104c9a20c6ff0cf542c3
--- /dev/null
+++ b/src/mol-app/skin/layout/common.scss
@@ -0,0 +1,60 @@
+
+.molstar-layout-expanded, .molstar-layout-standard {
+    left: 0;
+    right: 0;
+    top: 0;
+    bottom: 0;
+}
+
+.molstar-layout-region {
+    overflow: hidden;
+    background: $default-background;
+}
+
+.molstar-layout-static, .molstar-layout-scrollable {
+    position: absolute;
+}
+
+.molstar-layout-scrollable {
+    overflow-y: auto;
+}
+
+.molstar-layout-static {
+    overflow: hidden;
+}
+
+.molstar-layout-main, .molstar-layout-bottom {
+    .molstar-layout-static {
+        left: 0;
+        right: 0;
+        top: 0;
+        bottom: 0;
+    }
+}
+
+.molstar-layout-right {
+
+    .molstar-layout-static {
+        left: 0;
+        right: 0;
+        top: 0;
+        height: $row-height + $control-spacing;
+    }
+
+    .molstar-layout-scrollable {
+        left: 0;
+        right: 0;
+        top: $row-height + $control-spacing + 1;
+        bottom: 0;
+     }
+
+}
+
+.molstar-layout-left {
+    .molstar-layout-static {
+        left: 0;
+        right: 0;
+        bottom: 0;
+        top: 0;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/layout/landscape.scss b/src/mol-app/skin/layout/landscape.scss
new file mode 100644
index 0000000000000000000000000000000000000000..418a7cd4bc0a35f244cc1ce59123527ff679e0d4
--- /dev/null
+++ b/src/mol-app/skin/layout/landscape.scss
@@ -0,0 +1,81 @@
+
+.molstar-layout-main {
+    position: absolute;
+    left: $expanded-left-width;
+    right: $expanded-right-width;
+    bottom: $expanded-bottom-height;
+    top: $expanded-top-height;
+}
+
+.molstar-layout-top {
+    position: absolute;
+    left: $expanded-left-width;
+    right: $expanded-right-width;
+    height: $expanded-top-height;
+    top: 0;
+    border-bottom: 1px solid $border-color;
+}
+
+.molstar-layout-bottom {
+    position: absolute;
+    left: $expanded-left-width;
+    right: $expanded-right-width;
+    height: $expanded-bottom-height;
+    bottom: 0;
+    border-top: 1px solid $border-color;
+}
+
+.molstar-layout-right {
+    position: absolute;
+    width: $expanded-right-width;
+    right: 0;
+    bottom: 0;
+    top: 0;
+    border-left: 1px solid $border-color;
+}
+
+.molstar-layout-left {
+    position: absolute;
+    width: $expanded-left-width;
+    left: 0;
+    bottom: 0;
+    top: 0;
+    border-right: 1px solid $border-color;
+}
+
+.molstar-layout-hide-right {
+    .molstar-layout-right {
+        display: none;
+    }
+    .molstar-layout-main, .molstar-layout-top, .molstar-layout-bottom {
+        right: 0;
+    }
+}
+
+
+.molstar-layout-hide-left {
+    .molstar-layout-left {
+        display: none;
+    }
+    .molstar-layout-main, .molstar-layout-top, .molstar-layout-bottom {
+        left: 0;
+    }
+}
+
+.molstar-layout-hide-bottom {
+    .molstar-layout-bottom {
+        display: none;
+    }
+    .molstar-layout-main {
+        bottom: 0;
+    }
+}
+
+.molstar-layout-hide-top {
+    .molstar-layout-top {
+        display: none;
+    }
+    .molstar-layout-main {
+        top: 0;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/layout/outside.scss b/src/mol-app/skin/layout/outside.scss
new file mode 100644
index 0000000000000000000000000000000000000000..74cac3e423634f96522cec4efa20b69e8a9a56e5
--- /dev/null
+++ b/src/mol-app/skin/layout/outside.scss
@@ -0,0 +1,89 @@
+
+.molstar-layout-main {
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    top: 0;
+}
+
+.molstar-layout-top {
+    position: absolute;
+    right: 0;
+    height: $standard-top-height;
+    top: -$standard-top-height;
+    width: 50%;
+    border-left: 1px solid $border-color;
+    border-bottom: 1px solid $border-color;
+}
+
+.molstar-layout-bottom {
+    position: absolute;
+    left: 0;
+    right: 0;
+    height: $standard-top-height;
+    top: -$standard-top-height;
+    width: 50%;
+    border-bottom: 1px solid $border-color;
+}
+
+.molstar-layout-right {
+    position: absolute;
+    width: 50%;
+    right: 0;
+    bottom: -$standard-bottom-height;
+    height: $standard-bottom-height;
+    border-left: 1px solid $border-color;
+    border-top: 1px solid $border-color;
+}
+
+.molstar-layout-left {
+    position: absolute;
+    width: 50%;
+    left: 0;
+    bottom: 0;
+    bottom: -$standard-bottom-height;
+    height: $standard-bottom-height;
+    border-top: 1px solid $border-color;
+}
+
+/////////////////////////////////////////
+.molstar-layout-hide-right {
+    .molstar-layout-right {
+        display: none;
+    }
+    .molstar-layout-left {
+        width: 100%;
+    }
+}
+
+.molstar-layout-hide-left {
+    .molstar-layout-left {
+        display: none;
+    }
+    .molstar-layout-right {
+        width: 100%;
+        border-left: none;
+    }
+}
+
+///////////////////////////////////
+.molstar-layout-hide-top {
+    .molstar-layout-top {
+        display: none;
+    }
+    .molstar-layout-bottom {
+        width: 100%;
+        border-left: none;
+    }
+}
+
+.molstar-layout-hide-bottom {
+    .molstar-layout-bottom {
+        display: none;
+    }
+    .molstar-layout-top {
+        width: 100%;
+        border-left: none;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/layout/portrait.scss b/src/mol-app/skin/layout/portrait.scss
new file mode 100644
index 0000000000000000000000000000000000000000..efaf39113d637f9e5b18cf67092274c024c07393
--- /dev/null
+++ b/src/mol-app/skin/layout/portrait.scss
@@ -0,0 +1,99 @@
+
+.molstar-layout-main {
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: $expanded-portrait-bottom-height;
+    top: $expanded-portrait-top-height;
+}
+
+.molstar-layout-top {
+    position: absolute;
+    right: 0;
+    height: $expanded-portrait-top-height;
+    top: 0;
+    width: 50%;
+    border-left: 1px solid $border-color;
+    border-bottom: 1px solid $border-color;
+}
+
+.molstar-layout-bottom {
+    position: absolute;
+    left: 0;
+    right: 0;
+    height: $expanded-portrait-top-height;
+    width: 50%;
+    border-bottom: 1px solid $border-color;
+}
+
+.molstar-layout-right {
+    position: absolute;
+    width: 50%;
+    right: 0;
+    bottom: 0;
+    height: $expanded-portrait-bottom-height;
+    border-left: 1px solid $border-color;
+    border-top: 1px solid $border-color;
+}
+
+.molstar-layout-left {
+    position: absolute;
+    width: 50%;
+    left: 0;
+    bottom: 0;
+    height: $expanded-portrait-bottom-height;
+    border-top: 1px solid $border-color;
+}
+
+/////////////////////////////////////////
+.molstar-layout-hide-right {
+    .molstar-layout-right {
+        display: none;
+    }
+    .molstar-layout-left {
+        width: 100%;
+    }
+}
+
+.molstar-layout-hide-left {
+    .molstar-layout-left {
+        display: none;
+    }
+    .molstar-layout-right {
+        width: 100%;
+        border-left: none;
+    }
+}
+
+.molstar-layout-hide-right.molstar-layout-hide-left {
+    .molstar-layout-main {
+        bottom: 0;
+    }
+}
+
+///////////////////////////////////
+.molstar-layout-hide-top {
+    .molstar-layout-top {
+        display: none;
+    }
+    .molstar-layout-bottom {
+        width: 100%;
+        border-left: none;
+    }
+}
+
+.molstar-layout-hide-bottom {
+    .molstar-layout-bottom {
+        display: none;
+    }
+    .molstar-layout-top {
+        width: 100%;
+        border-left: none;
+    }
+}
+
+.molstar-layout-hide-top.molstar-layout-hide-bottom {
+    .molstar-layout-main {
+        top: 0;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/logo.scss b/src/mol-app/skin/logo.scss
new file mode 100644
index 0000000000000000000000000000000000000000..7bd02b7d3a1a4595d15099b6f52aec9d1ea0f422
--- /dev/null
+++ b/src/mol-app/skin/logo.scss
@@ -0,0 +1,47 @@
+.molstar-logo {
+    position: absolute;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    right: 0;
+
+    display: table;
+    width: 100%;
+    height: 100%;
+
+    > div {
+        display: table-cell;
+        vertical-align: middle;
+        text-align: center;
+
+        > div {
+            display: inline-block;
+            position: relative;
+            width: 50%;
+            max-width: 390px;
+            height: 130px;
+
+            > div {
+                position: absolute;
+                top: 0;
+                left: 0;
+                bottom: 0;
+                right: 0;
+            }
+
+            > div:first-child {
+                //border-radius: 8px;
+                background: rgba(0,0,0,0.75)
+            }
+        }
+    }
+}
+
+.molstar-logo-image {
+@include  non-selectable;
+
+background-repeat: no-repeat;
+background-position: center;
+background-size: 90%;
+background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgoAAADKCAIAAABL1P+fAAAAAXNSR0IArs4c6QAAAVlpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KTMInWQAALzZJREFUeNrt3flXG2eaL/D5B+65c3+ZM5Nk7ume7nOSnj7dyXSnE8eOMZlextN27HSWtoPpbJ3Ejh3HSzAk8QrYGINZDBiMWMVuMItYxSKJTQLEjgRIgMQqkMQuhCQgffu+hIzb/RYIkKpKVaXne56THxyo5ZWqPjwq1Vv/8FcIBAKBQAj5BxgCCAQCgQAPEAgEAgEeIBAIBAI8QCAQCAR4gEAgEAjwAIFAIBDgAQKBQCDAAwQCgUCABwgE8te/9oxP7o+I3xvB2xOe8FJ4wi/CEl4IS3z+buLP7iY+F5r8bGjyj0KSfxCS/K8hKc/cQZX6THDKM8GpT9/mP337u/8GpT19a6PSn7r5uDKfCkSV9VRg9lMBqHKerOh6JQw7BHiAQIAH4AECPEAgwAPwAAEeIBDgAXiAAA8QCPAAPECABwgEeAAeIMADBAI8YDx8lltO4vIRBsADBHiAQIAH4AECPEAgwAPwAGEOD221VfcvnyNW7HcVd/X8g426dj7++nne9QuoEm5cTPRfryT/i8kBF5MDL6QEfply82IqqlsX+UFfpn1X6bfXKyN4vbJCfFCVp4S552uga7k7KrkwVnthvO78BKr687qG85OoGs9PSc/rpecNsi8MTV8Ym76Ybv5ipuXsjPzsrPzsXOvZ+fb1Wmg/u9jxuanzc1PXmaWu0+au08s9py3flVX5ma33FKqV3lOr/SfXVCf/33QlvOmBBwiEBB6qctPPHXyJWOf/e70uHnr5S1SH9/i8/vKlIy/7Ht2D6qs3Xvn6D+v1zZt7r7y198rbe6++s+/aH/ddR3Vs343jr/offzXg3X2BXq+iuuW9Xrff248q+H2PsQGFu70AZoOiP+eX/Tm/UOf+x0DeC4N5LwzlP68peF5b8Pxw0c9HBD8bFfxsrPin4yX/PlH6E13ZTybLn5sSPqsXPmuo/PF09Y9QzdT826zoh3PiH8xLnlmofRqVqf5flhr+GdWy9J8ssv9jbfpHVCst/3tV/r/+okuFNz3wAIGwj4fsUB93ewFGxBeBB+ABeIAAD9vwcOcDj3F3aiBME0192S8CD8AD8AABHrbngX/jpPuM/oDgHeABeAAeIMDDjngI+cCjt6nGHYZ+XlvTm/Ui8AA8AA8QjvCQE31H09v9ffX1aJ+s/vUaxkrVM6JSPFmjqNR/q7CTv8d44Pl6ucPQqwVvYzwMFf3aYlRYpr8v68zj6rGhmv2+VjZqrnv1cc2v19rjWuj+9ruyDQYBD8AD8AChiQf0L+RuQWNxJsZD6If7e5s53kDMqIqUmb/EeJjpzSB3LStjfOABeAAeIGzlwWpZCv/s9xgPPD8vq8XM1UFfWzH35f4W40FT9ta3qyTvMvAAPAAPT+Z4uhgbHOqqddQAPJAQpawG4yH0o/1NpVlcfY/qezIVGS9iPCyOkN8wAQ/AA/AAPLCbB5RYn3cxHqI/P8zJBgK1Dr25v8V4GCp5i4p1AQ/AA/AAPLCeB9RAYDzc/cijuYyDDQRqHXoyfoXxsDBMybUW4AF4AB6AB9bzgBJ3yQvjIeYs1xoIm9nQnf4SxoO28mOKVgc8AA/AA/DABR7GBhQYD2F/9qhM49Q8faPSMCIPy0aqbhQHHoAH4AF44AIPKNmhPhgP4Z94LM4auPHWRK1DV9pLGA8j4ovUrRF4AB6AB/s8kHUSp27JwMPfGggiD5xpIEakYUQezAYK55gCHoAH4AF44AgPKDmhPhgPqIwTWrYPtEmv6OS/hPEwVn+D0pUCD8AD8MBSHkzWVeABj2FcS+ThUcQltr8vByp8iDysmKn9+wJ4AB44w0Nh9/BzIQUq/bw78IB2E+0s2mXgAU9FahjGQ+SnByaGWPwWR61DR8rLGA/jTXepXi/wADxwgwd0otzYFyeFYAUPGzZsLMS1QjCRh4VZA5GH/EgWNxDqCh8iD1S3DsAD8MANHh7b4LwQzOfhSRtcLgQTeVhvIPhhGA/3TrK1gZgfbW5PeRnjYbLtAQ2rBh6AB7bzgNngpBAM54Fog2uFYCgPqIGI/vwwxkPqFVZO9N2TdwzjoTf3t2srdNzuBzwAD6zmYVMbnBGCyTxsZYMLhWAoDyhNpVkYD1GnPPrlIna9v6cHRW3JezAe9D2Z9KwdeAAe2MuDHRscFoKxPNi3wVVCMJcHq8Ucc/YwxgPrGoiuh8cwHpQPf0dP6wA8AA/s5WFbGxwTgpk87MQGlwjBXB5QmsuyMB5QqdjTQOh7i+WJezAeaGsdgAfggaU87NAGB4RgIA87t4F+IRjNA2og7p87jPGQ4PO6jQ3z9KEWoT3tIMZDX8Ef6dwG4AF4YB0PdmzwK5E7KQTTeLBjw8m8xq3+F21CMJoHlL4WEcZDzOkDrcJs5r/LJzqykA0YD7MaWlsf4AF4YBcPdmzYOCdu9QM7FIJRPNixAUFo/wfoEYLpPKAkfuOF8cD8BgK1Dm38gxgPvfl/pHkzgAfggUU8bGuD/R/biRDM4WFbG7b9MRqEYAEPqIHAeGB+AzHentWS8ArGw+wQ3VdNgAfggS087NAGJ4VgCA87tMHlQrCAB5Tky14YD4mXXjfNMXRCROuSsZn3CsbDQIUP/VsCPAAPrOBhVzY4IwQTeNiVDa4Vgh08TAwqMR7unzkgzopg5nt9sDaCyINJr6B/S4AH4IH5PDhgg8NCuJwHB2xwoRDs4AElP/ISxkPs554MbCBQ69AUvw/jQe2K1gF4AB6Yz4PDNjgmhGt5cNgGVwnBGh4mhpREHhjYQPRXBhB5cEnrADwADwznwUkbHBDChTw4aYNLhGANDygF9y5hPMR97jmjG2bO231hUil7sA/jYVDk76rtAR6AB8byQIoNuxXCVTyQYgP9QrCJh8U5A5GH4hhf5rzjFcU+RB5sS0bgAQI8UGTDroRwCQ8k2kCzEGziAaUmIxzj4cFZz0kNI979qHWQPtiH8aCpd+WDsoEH4IGBPJBuw86FoJ8H0m2gUwiW8YAaCCIPJfcZ0UD0CC4ReXBh6wA8AA8M5IEiG3YoBM08UGQDbUKwjAcUUWYExkP8F65vIAxqcWPsXoyH0Waea7cKeAAeGMUDpTbsRAg6eUiTD1BnAz1CsI8Hm8Wc4PM6xkN24J9cu1XyDC+Mhzb+Qdom7gYegAfm80CDDdsKsSeqhDYe7EwsSOLOUioE+3hAaRVmYzzwzr820Oayib71KnFD7KsYDxMdWS4fKOABeGAID7TZsO3qXMsDuTZQLQQreUANROKl1zEesgNOuGp7WtK8MB5aU13fOnCGh/mFScOMZr2mNfr/qalp7UatrFqAB4bzQLMNuxKCTh6osIFSIVjJw0YDgfHAO+c52C6mf0tGW7PqY/ZjPIy3ZzFhlFjEw+LihF7fox6o6OzKbJBFiWqDHwnO5ArO5BSdzS48m1l4NqPwXHrBOX7B+dT8Cyn5F5IefZnwyAcVL+9SXK5vXK5fYoF/gSQxX5zUohR3DjRPGEdsq1bgweU8uMSGnQtBGw/U2UCdEGzlATUQ6Te8MR7Svjlqs9L6N/vqilmWcAjjoSPrXYaciZjMg8k0rptoVnTzpQ2B5aUflZV+XFLyiaDkU0HJqYLiU/mCz3bLw/2HX0U//OpezteoInIuh2WjunLv0c2cmlSpom7MMAo8uCQHecJNz1l7okpM1lUX4kQnD1TbsJGTeY2brh29BO7FA4q6VYTxkHjBs7OG1om+R+TZyAaMB+MAUx53yjQeVteWp3SyPkVKXfWp6vL3q8rfF5Z9KCz7iDoeQrKu3Mm8Gpx5NSjz+s2MG5nVfLlKrp/TAw/0/R1gXd1KCPTvLheCBh7osWGrR+k5M8gs5gEl44Y3xkP65SO0NRCrNnMj7xDGQ3umF3PGhyE8rK0tG3TSvs6oukovifCEuMJbVOHtEh5QBaT7+6f7RxZGC1urF8wLwIObC0E1D+y1gfU8oAYC4yHpIn0NxEBDQm30foyH2ZEW4OFxFudUQ70JzZIPG6uONVQdZw4PN9IDrvEDr6bdTKrM6NTQOmGie35zibFCUMoDq21gPQ8oj0I+w3hAtTRP+UTfFpNRErUf40FR7MOowXEhD9OTjf3tgc01b6GSVr/jDA+PSv1qGmKqG2JRVTbEVdbHCevjyuselNXFF9REOsnDZf7Nb/i3/LPCRN2N1hU6LmW77X0PzBSCOh7YbgMXeJjUKIk8NORSPtF3X3UEkYeFSWZNbuMSHmanGhTS0601b8hFb+6KB5ksorMrQz0gnNT3TM8MLVvmdrVenVGLqmugqUUpfiROSiwO2zkPX6Xe8k0NupYZJuqSUo2EO981zUAhKOKBAzZwgQeUkvu+GA/JX1LbQKDWQXzPA+OhR3CJaSNDMw9L8/1DHdc7xUfaRW/shIemRv/+vjzdRIvFMkvF9thWreOGEamiLrsmdSc8XEoJ8km5/U1GRNsQhedTN59ziWlCUMEDN2zgCA+ogSDy0JBHYQPRXRJI5IFprQOdPKytLY/3x/aIf98lPrQtDz0d9yd1MopIsJMxw2h1e1VUYZR9Hi4kB59PvnO3iK+bpeQvDJixlVFCkM4DZ2zgCA8opbF+GA8pPp56LSWXHOcmlKJID4wHtZiJD76mhwfTdGt/4zs9koP2eejrjJrSyVbXll0+LPo5fbGsJCg71A4P55JCziaGVHbKgAduC0EuD1yygTs8mOYMRB6E8ZRM9N2e50vkwWoyMnBYqObh27Xlyf6Yvtrf9Nb+biseWms/GNMIrJZpBo5Pl0ZxNz/GDg+o7hSkzZoWgQeuCkEiDxyzgTs8oNTmRGA8pKIGYpjkBgK1DjUR+zEeVKIIZo4JpTzYzMMj7V/01/5mKx7a694b1xStMaBdsJ9h/VhCZeZWPJxJCPXhR/eMDAEPnBSCLB5Eah3HbOAUD6iBIPJQySO5gWjL8yXywMzWgVIeLAu9Gtkb6rpfb8pDV92fdJoC5sPwZPrGBoPzYjfl4XTC3VO80FplJ/DAPSFYFJpt4BQPKHUPIzEe+L6eY/2k3aem6xNXh3tgPIy2ZjF2QCjiwaSXDNYfGKh/bVMetD1hNus0S49AUZd0Kx5O8sLTaquABxDCTWzgGg82qzn98hGMh4I7pD0pqD7BC+OhkXdolQETd9PJw5JeomnYvykPvfXHF6bb2H4cTs4ZwgSpm/LwSXw432khgAcQghU2cI0HlM6abIyHND9PTScJE33resVVYfsxHkbk2UweDdJ5WDJIhhte2ZQHTec1dn2aZD+CFsmmPHwcH5EqcUoI4AGEYIUNHORho4HAeCClgajnncB4aIg/zOTWgXQezAbJSOPeTXkwDj/i3mEpH+z1TbtH5OGjBxEPZfXAAwjBbRs4yMNGA4HxkO7nqWoqdmaZQ7LsyrseGA/jXcUMHwoSeVgxa8caX9mUh/mpOq4enLpZgw8/isjDBw8im9R9wAMIwWEbuMkDaiByg7wxHnL931hxdKLvVZu5JuowxoMs5QTz315k8bBmM+ia/4vIw6D0TdN0K7cPUd2s0YcfTeQBlVo3ATyAEFy1gZs8oAy2izEeMr56TSFx8DoBah2EoR4YD1P9YuaPA1k8GLpOjjf+CuNBI3vDujTqDgcqEuJiahSRh1NJcRabDXgAIThpA2d5QMkL8sZ4yA046kADgVqH6qjDGA/S5BOsGARSeFgc5k1IXyTysKivdZ82f2LGeCH1PsbDe3GRwcX5wAMIwUkbuMzDUIcY4yHza0caiO6KiIpQD4wHo6aFFYPgPA8riwqd7BdEHtzKhsdCEHnwjrtX3dMFPIAQ3LOByzygCON9MR6yvnnNPL+LO5yXF43lIR4YD+15vmwZAed5mG5/m8iDUR1F7WavWqZnhvrUwtbOLHFjjFAcaudpcSV1CYLahLZekWqkc3p+itINa1b3EXn4JIk3s5tJmejnAZ1xWkcNbCyRWvdcSIH7CLGVDWgQ0FBQNMhuyoNeqyDy0FQQuYvWoTyCyMPchNJNeDCPZeplL2A8THZ+RtHWLi5OKHvza+tvO/kw0dLGrM6B5kXzPBUbya+twnjwio1OFIuYzANXi2NCbGUD1eWmPKBU8nwxHtYbiIUdNRCodSgLPoDx0Mae1sFJHr61GYzy/8R4GGs+tGojeYKp1TXL4EC5WPQ16c+azhMn9w53kbu1lhWrX2YSxsO7sdH9Oh3wAEKwzga35kE/rCDy0FK4owaiKcuPyAOLWgcneTBpQg2yn2M8LOpKyTzVWmYV3fxtnzXtMA8bDxONLwpXaslEYkA3TuThZlEh8ABCsM4Gt+YBRZIRiPGQc8XTMLLNWX5mTFkW7InxoKwMZ9e+O8zDeuvQ9FOMh6nOUyRum2awVFx15slnTVPEw8azpmOLIsYMpH0NN1lSjfFw7P5OGwjgAYRgjg3uzsPSvIHIgzjFz/5vydZbB5wHy6LRTXgwa0KJPFgXeknZKpNpvEV6vbr8/ary92njYeNZ09XtVbYVq/O7MGNaPJ0ch/EQWFQEPIAQ7LLB3XlYP9fnR2A8oDJu3UCg1qHk9gGMB4UwgnU77hgPf/nWPNv8LMbDTL8/KZs0pZPVV58SVXjb56G4/Ms6aXSvWqibUhpnNCurls3tX56fnNb2DEqlXWVFksRteUDFK+ORIkRJWwvGwzv3Y0anZ1zOQ+uoIaZB+WSlyQeKeoafrOPpYu7VrapOdh2ehd0ueCGAh00aCCIPkq0biNqk00QeWNc6OMyDdSKDyMOajYSHak2M1EiEJ8QV3lvxUC26ouwtWFiccHgVWl1/dUtBbH7gVjwEpPsHPwzRz+mdvXCyYj2VFIfxEC8Su5wHCMROWMnDwqxB2VRTnhoW7+vF9z9J+vLbyhMxHnKveurUm9zdNqYQFwcdwHgYkpE/cbda8PZQxSdTHfGmiaYVs4GKUXWMh8WuoxgP00ofEi42qLLrKr224qG5KXx2dpCsHbetWvuGOxOLwzflwX9diFDnhciR1mM8fJCQsLzdNBvAAwR42GkM49pYn3dvv7c/+H2POx94hHzgEfrh/t7mGpJPlFZzbsBRjIeyCG/iT1bf98Z4qI46vGojeeLuOU2NMvOXvVkv9mW/2J/zy/6cX2jK3mICD2uLPfMtP8Z4sC06+8hug07aUHV8Ux6kDYEkwoClXd18Ly+IyMON9IDw/Girc58yzZgWMR7ejompV6mABwjwQE5G1QpkA8YDz8+L9HFRSLIxHvKueY50/90NTWM9YkHQAYwHKloHVdHbGA/q3P9gAg/L2hCMB337H51tDedUjVXHNuVhaLCU6uNh0TyfXZNK5OEaPzClytk3c5SwFOPhbnkF8AABHijkIfQj1ECIyF0RaiDyAo9iPJRF/m1yvRWbuSraG+Oh6h75rcO0SqDIeJGZPCy27cV4ME869ViLtbXlZsmHRB4kVWeoaxqIKW8WEHm4mnZT0t3gzGJl6n6MhzdjYux/vgQ8QIAHZ3mI/vyw1ULyeXmjgXiSh7zrnoPyku//om/ILrrpifEw3F5C7jasrZh7c3/LTB6+XdYstPwQ48HJi9JDvQnS6ncwHuqqT5lM4zQfGGVNxUQeLvNvzpsXnFnsJ0k8jIf6fjXwAAEeKOTh7kcezWVZpI+O4K43xoMg+AjqG1CVhR7BeBDHkT9xt74nsyfjV8zkwTaRjvEw0/2xUx/szKmaa94i8kC/DY+FIPKQJcl3Zpn3hOUYD3F2v78EPECABxJ4iDlLfgOh7RRjPDy68Vp/fc5G64DxMKEk+Zk/qHVQPvwdY3lYVp3FeDCPZTqzAX1tt4g8TOpkrjo2bKtWXhkP4+Eb/q2pOcc7JKlKhfFwip8OPECAB2p5CPuzR3M5+Q1ESZg3xkPxnSOlIUcwHsRx3qSveqo7qzv9JcbyYJL/X4yHVbPW4bWbl8aQDRgPis4Y1x4ehjk9kYd8qeNXyGdMixgPf4i+P71oAh4gwAO1PNw/d3hxluR7Aoa7xBgP+f6ehQGeGA8zYyTPvmczG7rSXmIsD2umHiIPTjVqffFEHiyWWZcfIcWyEoyHr1JvOfMl13PpfIyHOpUaeIAAD9TyEP6JR2VaGOljJE7xs8+DLMuP9JWOSMOYzMOqvgDjYa73gjNrb697D+NBpUhiwhGyYF4g8tAx5PhfA4liEcZDnrwVeIAAD5TzgIr0BsIworTPA/mtw5Kxk/8Sk3mwaoMxHpy58DBn7JCL3sR4sDKgddjIw7pHGA/pkgKHl1bV04XxcL1IADxAgAc6eKhKJ38mbUmK31Y8SDPJbx00In+G87CsOoPxYJt1/JHa2l4exkNfZxRzDhL1+ADGg29qkMNLU+kmMB5OJCQCDxDggQ4eIj89MK3Tkrte44hyKx4WDCSvy6RXdKS8zHAeluRPYzw4c126W3oa40GvkzLqOCHyoNWPObw0jIejMfeBBwjwQD4PPF8vIg/5kZdIHylpTiCRh44y8ifuVlf4sJEHh9e7trbcWvMGxoPVMs2o4yS1MgPjQdrX5vDS/pzIw3jonZgEHiDAA8k8PLzrU8EPw3i4d/LAxBDJ1wPMC0YiD8tkT9xtmlK0p7yM8TAuu8soHr419WA8TMs9Hd/leRXGQ2v9OaYdJ7XdDRgPeY1lDi/tZlEh8AABHujgYWHWQOSh4B75DYRcEPkkD+2l5LcOqnIfIg8rZgPDeZjvPe/wemcmGzEeFK1BTDtOVOMDGA/xQsdvsiHyULfF1BrAAwR4cIoH9O9CfhjGQ9QpDyoaiMc8lIUeWV4guXWYHhS1Je/BeNC18dD/YhQPa/MyEnmY1BZgPIxqBEw7Tkb0oxgPkcXJDi+tsLUV4yFP3gY8QIAHSniwWswxZw9jPKReIX+i766qxA0eVA3kT9zd9fAYxoPy4e/WVsxM42FFx8d4WBp+wG0erCtWjIdLKY63OMADBHigjweU5rIsjAdUKjnZE33bzBuTaqyQPnH3oEieuAfjQd/z/c0EDOdheTzD4fUOdYdjPOhGaxh4qAAPEOCBrTygBuL+ucMYD2nXyJ9FdWNKPipaB4wHRc73rQO3eVC3B2A8LMypgAfgAQI8kMbDegNRnoXxEHP6QHddMbnbsDGhN7nLnOjIQjZgPBj7//YZC/DAJR7atcMYD9cKBcADBHigkAfUQCR+44XxkODzuo3sib7JDWoR2vgHMR568//uqZzAA5d46NfpgAcI8EArDyh9LSKMB1Stwmwmvwbj7VktCa9gPMwOiYAH4IEJPLSOGjhZ7DpTq/Tz9A8R13hASb7shfGQeOl1m5WhDQRqHVpTD2I8KPOOYT8GPAAPVPAQXa8kngLcofxK5OziobB7mAnjxnoe+ltEGA/3zxxoq8xi5qs+WBvRzHsF42F+rBl4AB6AB7CBaUKwngeU/MhLGA+xn3ua5hjXTlqXjE3x+zAe1BWb7BHwADwAD+5sA0OE4AIPE0NKIg/irAimvdiDkggiDya9AngAHoAHsIGBQnCBB5SCe5cwHuIY1kCg1kH2YB/Gg6p8890BHoAH4AFscLkQHOFho4HAeJBkM6iBUBT7EHkwTSmAB+DBhTzsiS49ni5mae2JKuG8DdsKgQaBuhHmCA8oZbwAjIcHZz0nNUomvLoLk0rpg30YD5r6LR+UDTwAD/TwgP6FpWdMlX7+uZACd7DBvhBoENBQ0L89LONhcc5A5KHkvi8TXtoewSUiD7YlI/AAPAAPYAMbhWAZDyiizAiMh/gvXN9AoNahMXYvxoOd1gF4AB6AB7CB4UKwj4eNBgLjoTTWz7WvaEfuSYyHNv5BO60D8AA8AA9gA8OFYB8PKI2FPIwH3vnXBtpErnot9SpxQ+yrGA8THdvctQc8AA/AA9jAZCFYyYPNYk689DrGQ3bACVe9kC1pXhgPrakHH0/cDTwAD8AD2MBGIVjJA0qrMBvjgXfOc7Bd7JLWoT5mP8bDePv2E34AD8AD8AA2MFkItvKw3kD4HsF4yLnpTfOLt7piliUcwniQp/x+29YBeAAegAewgeFCsJUHlLbKLIyHxAuenTW0TvQ9Is9GNmA8GAd2dBUEeAAegAewgclCsJgHlIwb3hgP6ZeP0DbR96rN3Mg7hPHQnum1w18HHoAH4AFsYLIQ7OZB3SrCeEi6SF8DMSzPro3ej/FgVO/0+gfwADwADy63wWRdBSG4yQNKpr83xgM9DYTFZGyIP4zx0Lbj1gF4AB6AB5fbgNZC1jPm0CDTIA3NQrCehxFlM8YDqpaSBKpfp77qCEnUfoyHhcldHIfAA/DgzjwwwQa0LrJ4OJ4uPsgTckwI1vOAUnLfF+Mh+UvPpXkKJ/pGrYP4ngfGQ4/g0q4WAjwAD27LA0NsIJcHtDSOCcEFHiY1SiIPDXkUTvTdWx1B5GFXrQPwADy4LQ/MsYF0HjgmBBd4QCmN9cN4SPGhqoEwTWtFkR4YD7ttHYAH4ME9eWCUDVTwwCUhOMLDegNB4EEYT8lE3+15vkQerCYj8AA8AA/ssoEiHjgjBEd4QKnNicB4SPXx1A8ryN3auQllTcR+jAeVyJEPsoAH4MGteGCgDdTxwA0huMODac5A5KGSR3ID0ZbnS+TBgdYBeAAe3IoHZtpAKQ8cEII7PKDUPYzEeOD7ktlAGDTN1eEeGA+OtQ7AA/DgPjww1gaqeWC7EJziATUQRB5Koz4ja1PrE7wwHhp5h1ZXHLwFD3gAHtyBBybbQAMPrBaCUzygdNZkYzyk+XlqOkmY6FvXK64K24/xMCJ3fAIP4AF4cAcetjpv7okqcbkN9PBAmxCkDzXXeLBZzemXj2A8FNz5EwmtA+8ExkND/GGHWwfgAXhwEx7QaRGdHF3SPWxrA2080CDEVjsL3QPeQGA8pPt5ap1rIEY6SirvemA8DMudmvsPeAAe3OTag0uE2IkNdPJAqRBU2MBNHlADkXHlCMZDYYj3iqPz9K3azDVRhzEeZCkn0L8DD8AD8MBAIXZoA808UCQERTZwkweUwXYxxkPGV68pJA7+sT8kyxaGemA8TPU7ez0DeOASD+3aYeCBIUJsdbpEayduAHU8bHVBnlwhqLOBszyg5AV5YzzkBhx1oIFALUJ11GGMB2nyCee3EHjgEg+Fra0YD6kNUuCBfiHs2IDWTjyJU8eDSK3bamfJEoJSG7jMw1CHGOMh82tHGohBWXZFqAfGw2Q/CV+F4jAPQ93hGA8GndTdeMiTtwEPNAth34ZNT+LU8YCWbGdnnReCahu4zAPKoyBvjIe8wKPm+V3c4by8aCwP8cB4aM8j505shvOwNPzA4fVOagswHkY1AqbZYF2xAg9cEmJbG+jnwf7OOiMEDTZwnAe9VoHxkPXNa00FkTtfQnd5BJGHuQlyDjZG8bA2L8N4mO89z20eRvSjGA+RxcnAA0uF2IkNLuGBCiHosYHjPKBU8nwxHlCZF3bUQKDWoSz4AMZDWx5pkzgxiodvTT0k8mAYq8R4ULQGMe0MpRofwHiIF2Y5vLSIinKMh/LuHuCBHiF2aIOreCBXCNps4D4P+mEFkYeWwh01EN3lkUQeyGodmM/DTMdbjh/58yqMh9b6c0w7PdV2N2A85DWWOby0m0WFGA+9E5PAAw1C7NwGF/JAlhB02sB9HtYbiARfjIecK57z+mH7vzUzpiwL9sR46BQEkrhhjOIBBePB2PRTh9e7traM8dBYdcxqmWbUuSm1MgPjQdTl+PXzL9L5wAP9QuzKBtfy4LwQNNvgFjzMTQ0TeRCn+Nn/LVmWH5EHy6KRxA1jPg+rZq3Dq+6WnsZ40DPsy0vX+IEYD1r9mMNLO3Y/GuNh2WYDHigVYrc2uJwHZ4Sg3wa34GH9XJ8fgfGAyjiitNM6lNw+gPGgEJL88Gqm8bCsOoPxYJttcXjV2l4exkNfZxRzTknq8QEiDw4vbWx6hsjDVj8MPJAihAM2MIEHx4RwiQ3uwsPSvIHIg2TrBkKa6UfkgdzWgYE8WLXBGA/msUyHVz092Yjx0FB1fHVtmSHno2JZCcZDuqTA4aV1DA9jPFwvEgAP1AnhmA0M4WG3QrjKBnfh4bsGIhLjIffq5g3EmEJcHHQA46FfkkD6JjGNh5UJPsbDXO8Fh1e9trZM5GFkqJgJZ6IF88KN9ACMB2l/m8MLzJQ2YjzEisTAA0VCOGwDc3jYuRAutMGNeFixmnMDjmI8lEV4E3+y+r43xkN11GEnZ99jBQ9rph6MB6P8P51Zu7YvHuNBWvMJExoI1DoQebCuWB1e4C1BIcbDVt9qBR6cFMIZGxjFw06EcK0NbsQDikKSjfGQd81zpFv0d61Dj1gQdADjYUiWTcX2MI2H9ffr3/NgkP3cmavTi/MqjIe6Sq9+RYprT0CGOb1/uj/GQ7601JllvhsbjfHQp5sEHkgXYk9UiZNfDGUUD/Z3dqsZ/Wizwb14QA1EXuBRjIeyyL+bXK8q2hvjoeoeJa0DM3kwK/+E8bA86dTHQX1ttzAeJMITRkOXq049tlUrr4xH5GHYie8sqXQTRB7s/Dzw4MxJ08nbypjGw253lk4b3IuHxw3EkzzkXfcclH//pD1VQ3bRTU+Mh0FqWgdm8mAbi8V4mOn+2JkNWJxTEXmorz5lMo275LxT1lQckO6P8ZBUmenMMrOkjRgPNwoFwAM9Quz2lmMG8rDznaXZBrfjATUQgrveGA+C4CMrNjOqstAjGA/iuBPUDT0DeVgz9WA86GUvrNmcOn7U3ZEYD+IKb2mtz+qahX4bbmbcIPIwb15wZrHn0/kYDxVbX3gAHkgUwoEJi5jJw052ln4b3I4HFG2nGOPh0Y3X+utzNloHjIcJpZi6LWEgD+t/77ftxXgwO/f5ktU63Sz5EONBVOEtqTpDZw9R3ixANhB5kHQ3OLPYsRmjV2w0xsPI9AzwQLUQjk1mx1ge/rrddQj6bXBHHlBKwrwxHorvHCkNOYLxII7zpnQzmMnDsjYE48HQ/YmTmzFj7CTyUF3+vrjqjG6imeq3uG3Vml2TGpR5nchDbGmikwvPltZjPHyWlmb/V4AH50+aDk+FzWQettpZV9ngpjwMd4kxHvL9PQsDPDEe9INySjeDmTyszjVhPOhkv7AtKpxt2lTZRB6qyt8Xln3Y2R5nscxSNMjt6uZ7eUHBmVeJPARlhzrzZdaNfJr0AOMhrbEReKBUCGcek8BwHog760Ib3JQHFGHsafs8yLL8qN4GZvKw/vlS11GMh7mBcOc3Rq1M2pQHYdlH5aUfdXenkYtE33BnYnF4SNaVO5lXiTwEPwzVz+mdXIVM3ecddw/jYXrRBDxQJ4STD1ljPg9P7qxrbXBfHgwjSvs8zIxRfkQxlgfrRAbGw4T0RScvUG9EpUjaioey0o9LSj6RyiLHxuUrq45ftdbq+qtbCmLzA8OyL4dlX9mUh+CHIc7bgOKTkYzx4Pfw4ba/BTw4fNL0K5E7+QBOVvCwsbMn8xpda4P78oAiSfHbigdpph8NG8BYHr61GYg8kNJArH+yN1RihwdByaeCklMFxadEtcHdyqJBbb1xRmNfi8lp7bCur61XVCRJjH741b2cr1FF5FzeigdeGW/Bua8qbaRJ3fdeXCTGQ4NKBTwwOWzhgSFxXx6MI8qteKChdWAyDyhmTSjGw3jjr9Zs5ExKODc7UFd9yj4P+YLPHgnO5ArO5BSdzS48m1l4NqPwXKEwoLIhrrI+Lq/iVtKjLxMe+aDi5V2Ky/WNy/W7//CrbXmobq+yOX29YSOXMpMxHj5M3NHEXMAD8AA8MJ0HFGlOIJGHjrIIetbOZB5QA0Hkwagg7UGqq2vLvT2pu+UhveAcv+B8av6FlPwLu+UhtihizDBK1vaj1uGDB5EYDwWtrcAD63hIkw+g87jzRfzGEfDAbh7MC0YiD8tkT9zNRh7WP/3UhGI8jDW+sjxL5re5TKZxeXMY1TzEF4W3qcn8+qxlxXo6OQ7j4c+JvK2e/wM8MJkH6gp4YDcPKHJB5JM8tJdG0LZqhvOAGggiD2PNh75dI3kGKotltqebX1X5Bek8ZAhje4fJn98prrIE2YDxULiz1gF4AB6AB9bwgBqIJ3lYXjDStmqG84CyNByP8TDSuHeqx5eK0Vhdtej1PZ1dGeXCi07ykC9O6hxoXjRT8q2P7hHNRw8iMB7OpfN3vgTgAXgAHtjBA0pXVeIGD6qGbDrXy3we/rJmNsh/jfEw3PDK3OhDSkdmYXFidEzepxbWSqMrJaH2ecgqvy2oTZC0PuoZaNIZtZRu2Kxp8VxKLJGHjuFh4AF4AB44yMOKzbwxqcYKNRN3s5cHFOtsC5EHTcP+pZlW+t+scwuT+mnNRtG/dsuK9WZ+xsfxERgP94S7O7kDD8AD8MAaHlA2puSjeaWs4AFlfiicyINWdsRmHnarM0t8VfEn8eEYD58mPdjhFWnggQm5VdWJhKCnXH5TG/BAWgNBc+vAIh7+smaeajuO8TBYf0Aje8O6NOomp5X0OuFJXjiRh87hXfcxwAMEeGATDy4JW3hYX45Zq2v+L4yHgfrXBqVvuoMQmXWVp3ihRB5ypPUOLA14gAAPwAN3eEAxG8REHtR1v1Y3vsVtITLrhKcT7hJ5CCnOd2yBwAMEeAAeOMXDd0JIiDz01/6mr/Y381N13HuBLCvWkIK0MwmhRB6uP8q0ODo5B/AAAR6AB67xgDIzELkpD721vxvvj11bW+bMq6OZGvdNu3c2MYTIg19mksWJiZuABwjwADxwkAeUaXXkpjz0SA4OtH5p4cQHTdVdsnNJIcgGIg/nUmLHZ5y6dxJ4gAAPwAM3eUCZG324KQ894t93iQ9NaQvY20Zo9WPhgpTzyXc25eFmfobF6QlfgQcI8AA8cJYHlEV97VY8dIqPKKSnpycb2fVyzJsX8hrLfFJuX0gO3pSHW2TYADxAgAfggeM8oNjMw4PSNzfloV30RmvNG13SM0Y2IGFdsYq7G65nhV1KCdqKh/Q6IVmrAx4gwAPwwHEeUL5dWx7turoVD3LRm801b3U2fm6YlK4y8uMm/ZyhUFr6Df/WV6m3fFODNuXBhx/VMtBL4kqBBwjwADxwn4eNGIfz7PCASlr9TrP4z2pl0pJpnAkjb1uxdmsUcSUJV9NuXubftMNDSEGabpbkSXyBBwjwADy4Cw8olqVRTec1Ozw0Vh1rqDpeV+nVVHdxoD93bnaA/gFfMM/3aLvz6nL90/1vpAdc4wfa4eHr9HuVnTIqNgN4gHCWh406/9/rdfHQy1+iOrzH5/WXLx152ffoHlRfvfHK139Yr2/e3Hvlrb1X3t579Z191/647zqqY/tuHH/V//irAe/uC/R6FdUt7/UCHjZ4GMh7YTDvhaH85zUFz2sLnh8u+vmI4Gejgp+NFf90vOTfJ0p/oiv7yWT5c1PCZ/XCZw2VP56u/hGqmZp/mxX9cE78g3nJMwu1T6My1f/LUsM/o1qW/hM9PGxkbqq+T3bSPg8S4QlxhbeowltSdaarPXZosHR2dpC6TVo0zyu1XaL2ytiiiJsZN1AFpPtvy0OKqHBuaYGiTSLy8EJY4vN3E392N/G50ORnQ5N/FJL8g5Dkfw1JeeYOqtRnglOeCU59+jb/6dvf/Tco7elbG5X+1M3HlflUIKqspwKznwrIBh4gwAPwwCweUNbWlqe0BV11f9qWh+ry95981nRD/c3W1nj1QMXYuHx6ZmjZMrfrVmnVojNqJ4wjLUpxXWdFnjjpyWdNB2Ve3wkP8cJsjX6M0iECHiDAA/Dgjjw8RkKnKeiSntk5D1s9a7pSEipuiKlpiKluiEVV2RBXWR8nrI8rr3tQVhdfWhdfUpdg/1nTO+SBV5mlpRgG4AECPAAP7s7D4xgnG/vabjnDgzPPmt4JDzez71a01sybF2gbE+ABAjwAD8DD97Fap0c1Ann9BUbxkCN51KVR0D8awAMEeAAegAc8S6bx8RFRR0uIq3iIKYoskZWoxwesZNz/DDxA3JoHCISizM0ODg2WypvDaqo+p5SH1PIH5c1FCm3Xgnkehh0CAR4gbIrFMjs7OzgwUKHszZfKohpkUY7xwC8NyRcnVbcUNCtrNRP9xnk9jC0EAjxAOJiVVYtxRoPKsFHTGv3/1NS0dqMmp7WmZWgLIBDgAQKBQCDAAwQCgUCABwgEAoEADxAIBAIBHiAQCAQCPEAgEAgEeIBAIBAIR/P/AbAvdi1UGXhFAAAAAElFTkSuQmCC');
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/molstar-blue.scss b/src/mol-app/skin/molstar-blue.scss
new file mode 100644
index 0000000000000000000000000000000000000000..0d870a8c673de9f831209b771d645b4cc511440b
--- /dev/null
+++ b/src/mol-app/skin/molstar-blue.scss
@@ -0,0 +1,2 @@
+@import 'colors/blue';
+@import 'base';
\ No newline at end of file
diff --git a/src/mol-app/skin/molstar-dark.scss b/src/mol-app/skin/molstar-dark.scss
new file mode 100644
index 0000000000000000000000000000000000000000..cdf80ffd913790db09cc67a424f3392c9ec1c01b
--- /dev/null
+++ b/src/mol-app/skin/molstar-dark.scss
@@ -0,0 +1,2 @@
+@import 'colors/dark';
+@import 'base';
\ No newline at end of file
diff --git a/src/mol-app/skin/molstar-light.scss b/src/mol-app/skin/molstar-light.scss
new file mode 100644
index 0000000000000000000000000000000000000000..d45b6bc81bedb18f0c0a6a2efd734bcd3d454670
--- /dev/null
+++ b/src/mol-app/skin/molstar-light.scss
@@ -0,0 +1,2 @@
+@import 'colors/light';
+@import 'base';
\ No newline at end of file
diff --git a/src/mol-app/skin/ui.scss b/src/mol-app/skin/ui.scss
new file mode 100644
index 0000000000000000000000000000000000000000..41e78fc7465ee379df02eb3565bcac082c6c4584
--- /dev/null
+++ b/src/mol-app/skin/ui.scss
@@ -0,0 +1,38 @@
+@mixin non-selectable {
+    -webkit-user-select: none; /* Chrome/Safari */
+    -moz-user-select: none; /* Firefox */
+    -ms-user-select: none; /* IE10+ */
+    /* Rules below not implemented in browsers yet */
+    -o-user-select: none;
+    user-select: none;
+
+    cursor: default;
+}
+
+::-webkit-scrollbar {
+    width: 10px;
+    height:10px;
+}
+
+::-webkit-scrollbar-track {
+    //-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.8);
+    border-radius: 0;
+    background-color: color-lower-contrast($control-background, 4%);
+}
+
+::-webkit-scrollbar-thumb {
+    border-radius: 0;
+    //-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.9);
+    background-color: color-lower-contrast($control-background, 8%);
+}
+
+@import 'components/controls-base';
+@import 'components/controls';
+@import 'components/entity';
+@import 'components/help';
+@import 'components/jobs';
+@import 'components/log';
+@import 'components/misc';
+@import 'components/panel';
+@import 'components/slider';
+@import 'components/viewport';
\ No newline at end of file
diff --git a/src/mol-app/skin/variables.scss b/src/mol-app/skin/variables.scss
new file mode 100644
index 0000000000000000000000000000000000000000..ddbf57aab8c4950b03ff37bcabdcc1295060613c
--- /dev/null
+++ b/src/mol-app/skin/variables.scss
@@ -0,0 +1,78 @@
+
+// measures
+
+$control-label-width:   110px;
+$row-height:            32px;
+$control-spacing:       10px;
+$entity-subtree-offset: 8px;
+$info-vertical-padding: 6px;
+$slider-border-radius-base: 6px;
+
+// layout
+$expanded-top-height:    100px;
+$expanded-bottom-height: 3 * $row-height + 2;
+$expanded-right-width:   290px;
+$expanded-left-width:    290px;
+
+$expanded-portrait-bottom-height: 10 * ($row-height + 1) + 3 * $control-spacing + 1;
+$expanded-portrait-top-height:    2 * $row-height + 1;
+
+$standard-bottom-height: 8 * ($row-height + 1) + 3 * $control-spacing + 1;
+$standard-top-height:    2 * $row-height + 1;
+
+//////////////////////////////////////////////////
+// ENTITY COLORS
+
+
+// entity colors are "somewhat orthogonal" on the RGB cube
+// TypeClass = 'Root' | 'Group' | 'Data' | 'Object' | 'Visual' | 'Selection' | 'Action' | 'Behaviour'
+
+// DO NOT CHANGE THESE!!
+$entity-color-Root:      $default-background;
+$entity-color-Data:      color-lower-contrast(#95a5a6, 15%);
+$entity-color-Selection: color-lower-contrast(#e74c3c, 15%);
+$entity-color-Action:    color-lower-contrast(#34495e, 10%);
+$entity-color-Object:    color-lower-contrast(#2ecc71, 10%);
+$entity-color-Behaviour: color-lower-contrast(#9b59b6, 10%);
+$entity-color-Visual:    color-lower-contrast(#3498db, 5%);
+$entity-color-Group:     color-lower-contrast(#e67e22, 5%);
+
+//////////////////////////////////////////////////
+// COLORS and COMPUTED COLORS
+
+$slider-disabledColor: #ccc;
+
+$control-background: color-increase-contrast($default-background, 6.5%);
+$border-color: color-increase-contrast($default-background, 15%);
+$molstar-form-control-background: color-lower-contrast($default-background, 2.5%);
+
+// buttons
+$molstar-btn-link-font-color: $font-color;
+$molstar-btn-link-toggle-on-font-color: $font-color;
+$molstar-btn-link-toggle-off-font-color: color-lower-contrast($font-color, 33%);
+
+// used for "actions" -- i.e. + in selection
+$molstar-btn-remove-font-color: $font-color;
+
+$molstar-btn-action-background: $molstar-form-control-background;
+
+// update selection etc
+//!! $molstar-btn-commit-on-font-color: $entity-current-font-color;
+$molstar-btn-commit-on-hover-font-color: color-lower-contrast($molstar-btn-commit-on-font-color, 20%); //!!Change
+$molstar-btn-commit-on-background: color-lower-contrast($default-background, 2%);
+$molstar-btn-commit-off-background:  color-lower-contrast($default-background, 4%); //$control-background;
+$molstar-btn-commit-off-font-color: $font-color;
+
+// log
+$log-font-color: color-lower-contrast($font-color, 5%);
+$log-timestamp-font-color: color-lower-contrast($font-color, 20%);
+
+// highlight
+$highlight-info-font-color: $hover-font-color;
+$highlight-info-additional-font-color: color-lower-contrast($hover-font-color, 20%);
+
+// entity state
+$entity-color-fully-visible: $font-color;
+$entity-color-not-visible: color-lower-contrast($font-color, 66%);
+$entity-color-partialy-visible: color-lower-contrast($font-color, 33%);
+$entity-tag-color: color-lower-contrast($font-color, 20%);
\ No newline at end of file
diff --git a/src/mol-app/ui/controls/common.tsx b/src/mol-app/ui/controls/common.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..695273e18cee4b2738f8e563f7aeac75d74ab2e4
--- /dev/null
+++ b/src/mol-app/ui/controls/common.tsx
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import * as React from 'react'
+import { shallowEqual } from 'mol-util'
+
+export type ButtonSize = 'xs' | 'sm' | 'normal' | 'lg'
+
+export type ButtonStyle = 'link' | 'remove' | 'default'
+
+export abstract class Pure<Props> extends React.Component<Props, {}> {
+    shouldComponentUpdate(nextProps: any, nextState: any) {
+        return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState);
+    }
+}
+
+export class Button extends Pure<{
+    onClick: (e: React.MouseEvent<HTMLButtonElement> | React.TouchEvent<HTMLButtonElement>) => void,
+    size?: ButtonSize,
+    style?: ButtonStyle,
+    active?: boolean,
+    activeStyle?: ButtonStyle,
+    icon?: string,
+    activeIcon?: string,
+    disabled?: boolean,
+    disabledStyle?: ButtonStyle,
+    asBlock?: boolean,
+    title?: string,
+    customClass?: string,
+    customStyle?: any
+}> {
+    render() {
+
+        let props = this.props;
+
+        let className = 'molstar-btn';
+        if (props.size && props.size !== 'normal') className += ' molstar-btn-' + props.size;
+        if (props.asBlock) className += ' molstar-btn-block';
+
+        if (props.disabled) className += ' molstar-btn-' + (props.disabledStyle || props.style || 'default');
+        else if (props.active) className += ' molstar-btn-' + (props.activeStyle || props.style || 'default');
+        else className += ' molstar-btn-' + (props.style || 'default');
+
+        if (props.customClass) className += ' ' + props.customClass;
+
+        let icon: any = void 0;
+
+        if (props.icon) {
+            if (props.active && props.activeIcon) icon = <span className={ `molstar-icon molstar-icon-${props.activeIcon}` }></span>
+            else icon = <span className={ `molstar-icon molstar-icon-${props.icon}` }></span>
+        }
+        //onTouchEnd={(e) => { (e.target as HTMLElement).blur() } }
+
+        return <button
+            title={props.title}
+            className={className}
+            style={props.customStyle}
+            disabled={props.disabled}
+            onClick={(e) => { props.onClick.call(null, e); (e.target as HTMLElement).blur() } }
+                >
+            {icon}{props.children}
+        </button>
+    }
+}
+
+export const TextBox = (props: {
+    onChange: (v: string) => void,
+    value?: string,
+    defaultValue?: string,
+    onKeyPress?: (e: React.KeyboardEvent<HTMLInputElement>) => void,
+    onBlur?: (e: React.FormEvent<HTMLInputElement>) => void,
+    placeholder?: string
+}) => <input type='text' className='molstar-form-control' placeholder={props.placeholder} value={props.value} defaultValue={props.defaultValue}
+        onBlur={e => { if (props.onBlur) props.onBlur.call(null, e) } }
+        onChange={e => props.onChange.call(null, (e.target as HTMLInputElement).value)} onKeyPress={props.onKeyPress} />;
+
+export function isEnter(e: React.KeyboardEvent<HTMLInputElement>) {
+    if ((e.keyCode === 13 || e.charCode === 13)) {
+        return true;
+    }
+    return false;
+}
+
+export function TextBoxGroup(props: {
+    value: string,
+    onChange: (v: string) => void,
+    placeholder?:string,
+    label: string,
+    onEnter?: (e: React.KeyboardEvent<HTMLInputElement>) => void
+    title?: string
+}) {
+    return <div className='molstar-control-row molstar-options-group' title={props.title}>
+        <span>{props.label}</span>
+        <div>
+            <TextBox placeholder={props.placeholder} onChange={props.onChange} value={props.value} onKeyPress={(e) => {
+                if (isEnter(e) && props.onEnter) props.onEnter.call(null, e)
+            } }  />
+        </div>
+    </div>;
+}
+
+export const CommitButton = (props: {
+    action: () => void,
+    isOn: boolean,
+    on: string,
+    off?: string,
+    title?: string
+}) => <div style={{ marginTop: '1px' }}><button onClick={e => { props.action(); (e.target as HTMLElement).blur(); }}
+        className={'molstar-btn molstar-btn-block molstar-btn-commit molstar-btn-commit-' + (props.isOn ? 'on' : 'off')}
+        disabled={!props.isOn} title={props.title}>
+        <span className={ `molstar-icon molstar-icon-${props.isOn ? 'ok' : 'cross'}` }></span>
+        {props.isOn ? <b>{props.on}</b> : (props.off ? props.off : props.on) }
+    </button></div> ;
+
+export const Toggle = (props: {
+    onChange: (v: boolean) => void,
+    value: boolean,
+    label: string,
+    title?: string
+}) => <div className='molstar-control-row molstar-toggle-button' title={props.title}>
+        <span>{props.label}</span>
+        <div>
+            <button onClick={e => { props.onChange.call(null, !props.value); (e.target as HTMLElement).blur(); }}>
+                    <span className={ `molstar-icon molstar-icon-${props.value ? 'ok' : 'off'}` }></span> {props.value ? 'On' : 'Off'}
+            </button>
+        </div>
+    </div>
+
+export const ControlGroupExpander = (props: { onChange: (e: boolean) => void, isExpanded: boolean }) =>
+        <Button style='link' title={`${props.isExpanded ? 'Less' : 'More'} options`} onClick={() => props.onChange.call(null, !props.isExpanded) }
+                            icon={props.isExpanded ? 'minus' : 'plus'} customClass='molstar-conrol-group-expander' />
+
+
+export const RowText = (props: {
+    value: any,
+    label: string,
+    title?: string
+}) => <div className='molstar-control-row molstar-row-text' title={props.title}>
+        <span>{props.label}</span>
+        <div>
+            {props.value}
+        </div>
+    </div>
+
+export const HelpBox = (props: {
+    title: string,
+    content: JSX.Element | string
+}) => <div className='molstar-help-row'>
+        <span>{props.title}</span>
+        <div>{props.content}</div>
+    </div>
+
+export function FileInput (props: {
+    accept: string
+    onChange: (v: FileList | null) => void,
+}) {
+    return <input
+        accept={props.accept || '*.*'}
+        type='file'
+        className='molstar-form-control'
+        onChange={e => props.onChange.call(null, e.target.files)}
+    />
+}
\ No newline at end of file
diff --git a/src/mol-app/ui/controls/slider.tsx b/src/mol-app/ui/controls/slider.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ce78033fb17f09fb9fa3077d6436e81525bcbc02
--- /dev/null
+++ b/src/mol-app/ui/controls/slider.tsx
@@ -0,0 +1,814 @@
+/*
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import * as React from 'react'
+import { TextBox, isEnter } from './common'
+
+export class Slider extends React.Component<{
+    label: any,
+    min: number,
+    max: number,
+    value: number,
+    step?: number,
+    title?: string,
+    onChange: (v: number) => void
+}, { value: string }> {
+
+    state = { value: '0' }
+
+    private firedValue = NaN;
+
+    componentWillMount() {
+        this.setState({ value: '' + this.props.value });
+    }
+
+    componentWillReceiveProps(nextProps: any) {
+        this.setState({ value: '' + nextProps.value });
+    }
+
+    private updateValue(s: string) {
+        let v = +s;
+        if (v < this.props.min) { v = this.props.min; s = '' + v; }
+        else if (v > this.props.max) { v = this.props.max; s = '' + v; }
+        this.setState({ value: s })
+    }
+
+    private fire() {
+        let v = +this.state.value;
+        if (isNaN(v)) { v = this.props.value; }
+        if (v !== this.props.value) {
+            if (this.firedValue !== v) {
+                this.firedValue = v;
+                this.props.onChange.call(null, v);
+            }
+        }
+    }
+
+    render() {
+        let step = this.props.step;
+        if (step === void 0) step = 1;
+        return <div className='molstar-control-row molstar-slider' title={this.props.title}>
+            <span>{this.props.label}</span>
+            <div>
+                <div>
+                    <div>
+                        <SliderBase min={this.props.min} max={this.props.max} step={step} value={+this.state.value}
+                            onChange={v => this.setState({ value: '' + v })}
+                            onAfterChange={v => this.fire()} />
+                    </div>
+                </div>
+                <div>
+                    <TextBox value={this.state.value}  onChange={v => this.updateValue(v)} onBlur={() => this.fire()} onKeyPress={e => {
+                        if (isEnter(e)) this.fire();
+                    } } />
+                </div>
+            </div>
+        </div>;
+    }
+}
+
+/**
+ * The following code was adapted from react-components/slider library.
+ *
+ * The MIT License (MIT)
+ * Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+    * The above copyright notice and this permission notice shall be included in
+    * all copies or substantial portions of the Software.
+
+    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+    */
+
+function classNames(_classes: { [name: string]: boolean | number }) {
+    let classes = [];
+    let hasOwn = {}.hasOwnProperty;
+
+    for (let i = 0; i < arguments.length; i++) {
+        let arg = arguments[i];
+        if (!arg) continue;
+
+        let argType = typeof arg;
+
+        if (argType === 'string' || argType === 'number') {
+            classes.push(arg);
+        } else if (Array.isArray(arg)) {
+            classes.push(classNames.apply(null, arg));
+        } else if (argType === 'object') {
+            for (let key in arg) {
+                if (hasOwn.call(arg, key) && arg[key]) {
+                    classes.push(key);
+                }
+            }
+        }
+    }
+
+    return classes.join(' ');
+}
+
+function noop() {
+}
+
+function isNotTouchEvent(e: TouchEvent) {
+    return e.touches.length > 1 || (e.type.toLowerCase() === 'touchend' && e.touches.length > 0);
+}
+
+function getTouchPosition(vertical: boolean, e: TouchEvent) {
+    return vertical ? e.touches[0].clientY : e.touches[0].pageX;
+}
+
+function getMousePosition(vertical: boolean, e: MouseEvent) {
+    return vertical ? e.clientY : e.pageX;
+}
+
+function getHandleCenterPosition(vertical: boolean, handle: HTMLElement) {
+    const coords = handle.getBoundingClientRect();
+    return vertical ?
+        coords.top + (coords.height * 0.5) :
+        coords.left + (coords.width * 0.5);
+}
+
+function pauseEvent(e: Event) {
+    e.stopPropagation();
+    e.preventDefault();
+}
+
+export class Handle extends React.Component<Partial<HandleProps>, {}> {
+    render() {
+        const {
+            className,
+            tipFormatter,
+            vertical,
+            offset,
+            value,
+            index,
+        } = this.props as HandleProps;
+
+        const style = vertical ? { bottom: `${offset}%` } : { left: `${offset}%` };
+        return (
+            <div className={className} style={style} title={tipFormatter(value, index)}
+                />
+        );
+    }
+}
+
+export interface SliderBaseProps {
+    min: number,
+    max: number,
+    step?: number,
+    defaultValue?: number | number[],
+    value?: number | number[],
+    marks?: any,
+    included?: boolean,
+    className?: string,
+    prefixCls?: string,
+    disabled?: boolean,
+    children?: any,
+    onBeforeChange?: (value: number | number[]) => void,
+    onChange?: (value: number | number[]) => void,
+    onAfterChange?: (value: number | number[]) => void,
+    handle?: JSX.Element,
+    tipFormatter?: (value: number, index: number) => any,
+    dots?: boolean,
+    range?: boolean | number,
+    vertical?: boolean,
+    allowCross?: boolean,
+    pushable?: boolean | number,
+}
+
+export interface SliderBaseState {
+    handle: number | null,
+    recent: number,
+    bounds: number[]
+}
+
+export class SliderBase extends React.Component<SliderBaseProps, SliderBaseState> {
+    private sliderElement: HTMLElement | undefined = void 0;
+    private handleElements: (HTMLElement | undefined)[] = [];
+
+    constructor(props: SliderBaseProps) {
+        super(props);
+
+        const { range, min, max } = props;
+        const initialValue = range ? Array.apply(null, Array(+range + 1)).map(() => min) : min;
+        const defaultValue = ('defaultValue' in props ? props.defaultValue : initialValue);
+        const value = (props.value !== undefined ? props.value : defaultValue);
+
+        const bounds = (range ? value : [min, value]).map((v: number) => this.trimAlignValue(v));
+
+        let recent;
+        if (range && bounds[0] === bounds[bounds.length - 1] && bounds[0] === max) {
+            recent = 0;
+        } else {
+            recent = bounds.length - 1;
+        }
+
+        this.state = {
+            handle: null,
+            recent,
+            bounds,
+        };
+    }
+
+    public static defaultProps: SliderBaseProps = {
+        prefixCls: 'molstar-slider-base',
+        className: '',
+        min: 0,
+        max: 100,
+        step: 1,
+        marks: {},
+        handle: <Handle className='' vertical={false} offset={0} tipFormatter={v => v} value={0} index={0} />,
+        onBeforeChange: noop,
+        onChange: noop,
+        onAfterChange: noop,
+        tipFormatter: (value, index) => value,
+        included: true,
+        disabled: false,
+        dots: false,
+        range: false,
+        vertical: false,
+        allowCross: true,
+        pushable: false,
+    };
+
+    private dragOffset = 0;
+    private startPosition = 0;
+    private startValue = 0;
+    private _getPointsCache: any = void 0;
+
+    componentWillReceiveProps(nextProps: SliderBaseProps) {
+        if (!('value' in nextProps || 'min' in nextProps || 'max' in nextProps)) return;
+
+        const { bounds } = this.state;
+        if (nextProps.range) {
+            const value = nextProps.value || bounds;
+            const nextBounds = (value as number[]).map((v: number) => this.trimAlignValue(v, nextProps));
+            if (nextBounds.every((v: number, i: number) => v === bounds[i])) return;
+
+            this.setState({ bounds: nextBounds } as SliderBaseState);
+            if (bounds.some(v => this.isValueOutOfBounds(v, nextProps))) {
+                this.props.onChange!(nextBounds);
+            }
+        } else {
+            const value = nextProps.value !== undefined ? nextProps.value : bounds[1];
+            const nextValue = this.trimAlignValue(value as number, nextProps);
+            if (nextValue === bounds[1] && bounds[0] === nextProps.min) return;
+
+            this.setState({ bounds: [nextProps.min, nextValue] } as SliderBaseState);
+            if (this.isValueOutOfBounds(bounds[1], nextProps)) {
+                this.props.onChange!(nextValue);
+            }
+        }
+    }
+
+    onChange(state: this['state']) {
+        const props = this.props;
+        const isNotControlled = !('value' in props);
+        if (isNotControlled) {
+            this.setState(state);
+        } else if (state.handle !== undefined) {
+            this.setState({ handle: state.handle } as SliderBaseState);
+        }
+
+        const data = { ...this.state, ...(state as any) };
+        const changedValue = props.range ? data.bounds : data.bounds[1];
+        props.onChange!(changedValue);
+    }
+
+    onMouseDown(e: MouseEvent) {
+        if (e.button !== 0) { return; }
+
+        let position = getMousePosition(this.props.vertical!, e);
+        if (!this.isEventFromHandle(e)) {
+            this.dragOffset = 0;
+        } else {
+            const handlePosition = getHandleCenterPosition(this.props.vertical!, e.target as HTMLElement);
+            this.dragOffset = position - handlePosition;
+            position = handlePosition;
+        }
+        this.onStart(position);
+        this.addDocumentEvents('mouse');
+        pauseEvent(e);
+    }
+
+    onMouseMove(e: MouseEvent) {
+        const position = getMousePosition(this.props.vertical!, e);
+        this.onMove(e, position - this.dragOffset);
+    }
+
+    onMove(e: MouseEvent | TouchEvent, position: number) {
+        pauseEvent(e);
+        const props = this.props;
+        const state = this.state;
+
+        let diffPosition = position - this.startPosition;
+        diffPosition = this.props.vertical ? -diffPosition : diffPosition;
+        const diffValue = diffPosition / this.getSliderLength() * (props.max - props.min);
+
+        const value = this.trimAlignValue(this.startValue + diffValue);
+        const oldValue = state.bounds[state.handle!];
+        if (value === oldValue) return;
+
+        const nextBounds = [...state.bounds];
+        nextBounds[state.handle!] = value;
+        let nextHandle = state.handle!;
+        if (props.pushable !== false) {
+            const originalValue = state.bounds[nextHandle];
+            this.pushSurroundingHandles(nextBounds, nextHandle, originalValue);
+        } else if (props.allowCross) {
+            nextBounds.sort((a, b) => a - b);
+            nextHandle = nextBounds.indexOf(value);
+        }
+        this.onChange({
+            handle: nextHandle,
+            bounds: nextBounds,
+        } as SliderBaseState);
+    }
+
+    onStart(position: number) {
+        const props = this.props;
+        props.onBeforeChange!(this.getValue());
+
+        const value = this.calcValueByPos(position);
+        this.startValue = value;
+        this.startPosition = position;
+
+        const state = this.state;
+        const { bounds } = state;
+
+        let valueNeedChanging = 1;
+        if (this.props.range) {
+            let closestBound = 0;
+            for (let i = 1; i < bounds.length - 1; ++i) {
+                if (value > bounds[i]) { closestBound = i; }
+            }
+            if (Math.abs(bounds[closestBound + 1] - value) < Math.abs(bounds[closestBound] - value)) {
+                closestBound = closestBound + 1;
+            }
+            valueNeedChanging = closestBound;
+
+            const isAtTheSamePoint = (bounds[closestBound + 1] === bounds[closestBound]);
+            if (isAtTheSamePoint) {
+                valueNeedChanging = state.recent;
+            }
+
+            if (isAtTheSamePoint && (value !== bounds[closestBound + 1])) {
+                valueNeedChanging = value < bounds[closestBound + 1] ? closestBound : closestBound + 1;
+            }
+        }
+
+        this.setState({
+            handle: valueNeedChanging,
+            recent: valueNeedChanging,
+        } as SliderBaseState);
+
+        const oldValue = state.bounds[valueNeedChanging];
+        if (value === oldValue) return;
+
+        const nextBounds = [...state.bounds];
+        nextBounds[valueNeedChanging] = value;
+        this.onChange({ bounds: nextBounds } as SliderBaseState);
+    }
+
+    onTouchMove(e: TouchEvent) {
+        if (isNotTouchEvent(e)) {
+            this.end('touch');
+            return;
+        }
+
+        const position = getTouchPosition(this.props.vertical!, e);
+        this.onMove(e, position - this.dragOffset);
+    }
+
+    onTouchStart(e: TouchEvent) {
+        if (isNotTouchEvent(e)) return;
+
+        let position = getTouchPosition(this.props.vertical!, e);
+        if (!this.isEventFromHandle(e)) {
+            this.dragOffset = 0;
+        } else {
+            const handlePosition = getHandleCenterPosition(this.props.vertical!, e.target as HTMLElement);
+            this.dragOffset = position - handlePosition;
+            position = handlePosition;
+        }
+        this.onStart(position);
+        this.addDocumentEvents('touch');
+        pauseEvent(e);
+    }
+
+    /**
+     * Returns an array of possible slider points, taking into account both
+     * `marks` and `step`. The result is cached.
+     */
+    getPoints() {
+        const { marks, step, min, max } = this.props;
+        const cache = this._getPointsCache;
+        if (!cache || cache.marks !== marks || cache.step !== step) {
+            const pointsObject = { ...marks };
+            if (step !== null) {
+                for (let point = min; point <= max; point += step!) {
+                    pointsObject[point] = point;
+                }
+            }
+            const points = Object.keys(pointsObject).map(parseFloat);
+            points.sort((a, b) => a - b);
+            this._getPointsCache = { marks, step, points };
+        }
+        return this._getPointsCache.points;
+    }
+
+    getPrecision(step: number) {
+        const stepString = step.toString();
+        let precision = 0;
+        if (stepString.indexOf('.') >= 0) {
+            precision = stepString.length - stepString.indexOf('.') - 1;
+        }
+        return precision;
+    }
+
+    getSliderLength() {
+        const slider = this.sliderElement;
+        if (!slider) {
+            return 0;
+        }
+
+        return this.props.vertical ? slider.clientHeight : slider.clientWidth;
+    }
+
+    getSliderStart() {
+        const slider = this.sliderElement as HTMLElement;
+        const rect = slider.getBoundingClientRect();
+
+        return this.props.vertical ? rect.top : rect.left;
+    }
+
+    getValue(): number {
+        const { bounds } = this.state;
+        return (this.props.range ? bounds : bounds[1]) as number;
+    }
+
+    private eventHandlers = {
+        'touchmove': (e: TouchEvent) => this.onTouchMove(e),
+        'touchend': (e: TouchEvent) => this.end('touch'),
+        'mousemove': (e: MouseEvent) => this.onMouseMove(e),
+        'mouseup': (e: MouseEvent) => this.end('mouse'),
+    }
+
+    addDocumentEvents(type: 'touch' | 'mouse') {
+        if (type === 'touch') {
+            document.addEventListener('touchmove', this.eventHandlers.touchmove);
+            document.addEventListener('touchend', this.eventHandlers.touchend);
+        } else if (type === 'mouse') {
+            document.addEventListener('mousemove', this.eventHandlers.mousemove);
+            document.addEventListener('mouseup', this.eventHandlers.mouseup);
+        }
+    }
+
+    calcOffset(value: number) {
+        const { min, max } = this.props;
+        const ratio = (value - min) / (max - min);
+        return ratio * 100;
+    }
+
+    calcValue(offset: number) {
+        const { vertical, min, max } = this.props;
+        const ratio = Math.abs(offset / this.getSliderLength());
+        const value = vertical ? (1 - ratio) * (max - min) + min : ratio * (max - min) + min;
+        return value;
+    }
+
+    calcValueByPos(position: number) {
+        const pixelOffset = position - this.getSliderStart();
+        const nextValue = this.trimAlignValue(this.calcValue(pixelOffset));
+        return nextValue;
+    }
+
+    end(type: 'mouse' | 'touch') {
+        this.removeEvents(type);
+        this.props.onAfterChange!(this.getValue());
+        this.setState({ handle: null } as SliderBaseState);
+    }
+
+    isEventFromHandle(e: Event) {
+        for (const h of this.handleElements) {
+            if (h === e.target) return true;
+        }
+        return false;
+
+        // return this.state.bounds.some((x, i) => e.target
+
+        // (
+        //     //this.handleElements[i] && e.target === ReactDOM.findDOMNode(this.handleElements[i])
+        // ));
+    }
+
+    isValueOutOfBounds(value: number, props: SliderBaseProps) {
+        return value < props.min || value > props.max;
+    }
+
+    pushHandle(bounds: number[], handle: number, direction: number, amount: number) {
+        const originalValue = bounds[handle];
+        let currentValue = bounds[handle];
+        while (direction * (currentValue - originalValue) < amount) {
+            if (!this.pushHandleOnePoint(bounds, handle, direction)) {
+                // can't push handle enough to create the needed `amount` gap, so we
+                // revert its position to the original value
+                bounds[handle] = originalValue;
+                return false;
+            }
+            currentValue = bounds[handle];
+        }
+        // the handle was pushed enough to create the needed `amount` gap
+        return true;
+    }
+
+    pushHandleOnePoint(bounds: number[], handle: number, direction: number) {
+        const points = this.getPoints();
+        const pointIndex = points.indexOf(bounds[handle]);
+        const nextPointIndex = pointIndex + direction;
+        if (nextPointIndex >= points.length || nextPointIndex < 0) {
+            // reached the minimum or maximum available point, can't push anymore
+            return false;
+        }
+        const nextHandle = handle + direction;
+        const nextValue = points[nextPointIndex];
+        const { pushable: threshold } = this.props;
+        const diffToNext = direction * (bounds[nextHandle] - nextValue);
+        if (!this.pushHandle(bounds, nextHandle, direction, +threshold! - diffToNext)) {
+            // couldn't push next handle, so we won't push this one either
+            return false;
+        }
+        // push the handle
+        bounds[handle] = nextValue;
+        return true;
+    }
+
+    pushSurroundingHandles(bounds: number[], handle: number, originalValue: number) {
+        const { pushable: threshold } = this.props;
+        const value = bounds[handle];
+
+        let direction = 0;
+        if (bounds[handle + 1] - value < threshold!) {
+            direction = +1;
+        } else if (value - bounds[handle - 1] < threshold!) {
+            direction = -1;
+        }
+
+        if (direction === 0) { return; }
+
+        const nextHandle = handle + direction;
+        const diffToNext = direction * (bounds[nextHandle] - value);
+        if (!this.pushHandle(bounds, nextHandle, direction, +threshold! - diffToNext)) {
+            // revert to original value if pushing is impossible
+            bounds[handle] = originalValue;
+        }
+    }
+
+    removeEvents(type: 'touch' | 'mouse') {
+        if (type === 'touch') {
+            document.removeEventListener('touchmove', this.eventHandlers.touchmove);
+            document.removeEventListener('touchend', this.eventHandlers.touchend);
+        } else if (type === 'mouse') {
+            document.removeEventListener('mousemove', this.eventHandlers.mousemove);
+            document.removeEventListener('mouseup', this.eventHandlers.mouseup);
+        }
+    }
+
+    trimAlignValue(v: number, nextProps?: SliderBaseProps) {
+        const { handle, bounds } = (this.state || {}) as this['state'];
+        const { marks, step, min, max, allowCross } = { ...this.props, ...(nextProps || {}) } as SliderBaseProps;
+
+        let val = v;
+        if (val <= min) {
+            val = min;
+        }
+        if (val >= max) {
+            val = max;
+        }
+        /* eslint-disable eqeqeq */
+        if (!allowCross && handle != null && handle > 0 && val <= bounds[handle - 1]) {
+            val = bounds[handle - 1];
+        }
+        if (!allowCross && handle != null && handle < bounds.length - 1 && val >= bounds[handle + 1]) {
+            val = bounds[handle + 1];
+        }
+        /* eslint-enable eqeqeq */
+
+        const points = Object.keys(marks).map(parseFloat);
+        if (step !== null) {
+            const closestStep = (Math.round((val - min) / step!) * step!) + min;
+            points.push(closestStep);
+        }
+
+        const diffs = points.map((point) => Math.abs(val - point));
+        const closestPoint = points[diffs.indexOf(Math.min.apply(Math, diffs))];
+
+        return step !== null ? parseFloat(closestPoint.toFixed(this.getPrecision(step!))) : closestPoint;
+    }
+
+    render() {
+        const {
+            handle,
+            bounds,
+        } = this.state;
+        const {
+            className,
+            prefixCls,
+            disabled,
+            vertical,
+            dots,
+            included,
+            range,
+            step,
+            marks,
+            max, min,
+            tipFormatter,
+            children,
+        } = this.props;
+
+        const customHandle = this.props.handle;
+
+        const offsets = bounds.map(v => this.calcOffset(v));
+
+        const handleClassName = `${prefixCls}-handle`;
+
+        const handlesClassNames = bounds.map((v, i) => classNames({
+            [handleClassName]: true,
+            [`${handleClassName}-${i + 1}`]: true,
+            [`${handleClassName}-lower`]: i === 0,
+            [`${handleClassName}-upper`]: i === bounds.length - 1,
+        }));
+
+        const isNoTip = (step === null) || (tipFormatter === null);
+
+        const commonHandleProps = {
+            prefixCls,
+            noTip: isNoTip,
+            tipFormatter,
+            vertical,
+        };
+
+        this.handleElements = [];
+        const handles = bounds.map((v, i) => React.cloneElement(customHandle!, {
+            ...commonHandleProps,
+            className: handlesClassNames[i],
+            value: v,
+            offset: offsets[i],
+            dragging: handle === i,
+            index: i,
+            key: i,
+            ref: (h: any) => this.handleElements.push(h)  //`handle-${i}`,
+        }));
+        if (!range) { handles.shift(); }
+
+        const isIncluded = included || range;
+
+        const tracks: JSX.Element[] = [];
+        // for (let i = 1; i < bounds.length; ++i) {
+        //     const trackClassName = classNames({
+        //         [`${prefixCls}-track`]: true,
+        //         [`${prefixCls}-track-${i}`]: true,
+        //     });
+        //     tracks.push(
+        //         <Track className={trackClassName} vertical={vertical} included={isIncluded}
+        //             offset={offsets[i - 1]} length={offsets[i] - offsets[i - 1]} key={i}
+        //             />
+        //     );
+        // }
+
+        const sliderClassName = classNames({
+            [prefixCls!]: true,
+            [`${prefixCls}-with-marks`]: Object.keys(marks).length,
+            [`${prefixCls}-disabled`]: disabled!,
+            [`${prefixCls}-vertical`]: this.props.vertical!,
+            [className!]: !!className,
+        });
+
+        return (
+            <div ref={e => this.sliderElement = e!} className={sliderClassName}
+                onTouchStart={disabled ? noop : this.onTouchStart.bind(this)}
+                onMouseDown={disabled ? noop : this.onMouseDown.bind(this)}
+                >
+                <div className={`${prefixCls}-rail`} />
+                {tracks}
+                <Steps prefixCls={prefixCls} vertical={vertical} marks={marks} dots={dots} step={step}
+                    included={isIncluded} lowerBound={bounds[0]}
+                    upperBound={bounds[bounds.length - 1]} max={max} min={min}
+                    />
+                {handles}
+                <Marks className={`${prefixCls}-mark`} vertical={vertical!} marks={marks}
+                    included={isIncluded!} lowerBound={bounds[0]}
+                    upperBound={bounds[bounds.length - 1]} max={max} min={min}
+                    />
+                {children}
+            </div>
+        );
+    }
+}
+
+export interface HandleProps {
+    className: string,
+    vertical: boolean,
+    offset: number,
+    tipFormatter: (v: number, index: number) => any,
+    value: number,
+    index: number,
+}
+
+interface MarksProps {
+    className: string,
+    vertical: boolean,
+    marks: any,
+    included: boolean | number,
+    upperBound: number,
+    lowerBound: number,
+    max: number,
+    min: number
+}
+const Marks = ({ className, vertical, marks, included, upperBound, lowerBound, max, min }: MarksProps) => {
+    const marksKeys = Object.keys(marks);
+    const marksCount = marksKeys.length;
+    const unit = 100 / (marksCount - 1);
+    const markWidth = unit * 0.9;
+
+    const range = max - min;
+    const elements = marksKeys.map(parseFloat).sort((a, b) => a - b).map((point) => {
+        const isActived = (!included && point === upperBound) ||
+            (included && point <= upperBound && point >= lowerBound);
+        const markClassName = classNames({
+            [`${className}-text`]: true,
+            [`${className}-text-active`]: isActived,
+        });
+
+        const bottomStyle = {
+            // height: markWidth + '%',
+            marginBottom: '-50%',
+            bottom: `${(point - min) / range * 100}%`,
+        };
+
+        const leftStyle = {
+            width: `${markWidth}%`,
+            marginLeft: `${-markWidth / 2}%`,
+            left: `${(point - min) / range * 100}%`,
+        };
+
+        const style = vertical ? bottomStyle : leftStyle;
+
+        const markPoint = marks[point];
+        const markPointIsObject = typeof markPoint === 'object' && !React.isValidElement(markPoint);
+        const markLabel = markPointIsObject ? markPoint.label : markPoint;
+        const markStyle = markPointIsObject ? { ...style, ...markPoint.style } : style;
+        return (<span className={markClassName} style={markStyle} key={point}>
+            {markLabel}
+        </span>);
+    });
+
+    return <div className={className}>{elements}</div>;
+};
+
+function calcPoints(vertical: boolean, marks: any, dots: boolean, step: number, min: number, max: number) {
+    const points = Object.keys(marks).map(parseFloat);
+    if (dots) {
+        for (let i = min; i <= max; i = i + step) {
+            if (points.indexOf(i) >= 0) continue;
+            points.push(i);
+        }
+    }
+    return points;
+}
+
+const Steps = ({ prefixCls, vertical, marks, dots, step, included,
+    lowerBound, upperBound, max, min }: any) => {
+    const range = max - min;
+    const elements = calcPoints(vertical, marks, dots, step, min, max).map((point) => {
+        const offset = `${Math.abs(point - min) / range * 100}%`;
+        const style = vertical ? { bottom: offset } : { left: offset };
+
+        const isActived = (!included && point === upperBound) ||
+            (included && point <= upperBound && point >= lowerBound);
+        const pointClassName = classNames({
+            [`${prefixCls}-dot`]: true,
+            [`${prefixCls}-dot-active`]: isActived,
+        });
+
+        return <span className={pointClassName} style={style} key={point} />;
+    });
+
+    return <div className={`${prefixCls}-step`}>{elements}</div>;
+};
\ No newline at end of file
diff --git a/src/mol-app/ui/entity/tree.tsx b/src/mol-app/ui/entity/tree.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1d9afd1d9624bc0e2dacf49166b9ff24e24777a0
--- /dev/null
+++ b/src/mol-app/ui/entity/tree.tsx
@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as React from 'react'
+
+import { View } from '../view';
+import { EntityTreeController } from '../../controller/entity/tree';
+import { Controller } from '../../controller/controller';
+import { AnyEntity, RootEntity } from 'mol-view/state/entity';
+import { AnyTransform, SpacefillUpdate, UrlToData, DataToCif, FileToData, CifToMmcif, MmcifToModel, ModelToStructure, StructureToSpacefill, MmcifFileToSpacefill } from 'mol-view/state/transform';
+
+function getTransforms(entity: AnyEntity): AnyTransform[] {
+    const transforms: AnyTransform[] = []
+    switch (entity.kind) {
+        case 'root':
+            transforms.push(MmcifFileToSpacefill)
+            break;
+        case 'url':
+            transforms.push(UrlToData)
+            break;
+        case 'file':
+            transforms.push(FileToData)
+            break;
+        case 'data':
+            transforms.push(DataToCif)
+            break;
+        case 'cif':
+            transforms.push(CifToMmcif)
+            break;
+        case 'mmcif':
+            transforms.push(MmcifToModel)
+            break;
+        case 'model':
+            transforms.push(ModelToStructure)
+            break;
+        case 'structure':
+            transforms.push(StructureToSpacefill)
+            break;
+        case 'spacefill':
+            transforms.push(SpacefillUpdate)
+            break;
+    }
+    return transforms
+}
+
+export class Entity extends View<Controller<any>, {}, { entity: AnyEntity}> {
+    render() {
+        const entity = this.props.entity
+
+        return <div className='molstar-entity-tree-entry'>
+            <div className='molstar-entity-tree-entry-body'>
+                <div className='molstar-entity-tree-entry-label-wrap'>
+                    <button
+                        className='molstar-entity-tree-entry-label'
+                        onClick={() => {
+                            console.log(entity)
+                            this.controller.context.currentEntity.next(entity)
+                            this.controller.context.currentTransforms.next(getTransforms(entity))
+                        }}
+                    >
+                        <span>{entity.id} - {entity.kind}</span>
+                    </button>
+                </div>
+            </div>
+        </div>;
+    }
+}
+
+export class EntityTree extends View<EntityTreeController, {}, {}> {
+    render() {
+        const entities: JSX.Element[] = []
+        const state = this.controller.state.getValue()
+        if (state) {
+            state.entities.forEach(e => {
+                entities.push(
+                    <div key={e.id}>
+                        <Entity controller={this.controller} entity={e}></Entity>
+                    </div>
+                )
+            })
+        }
+
+        return <div className='molstar-entity-tree'>
+            <div className='molstar-entity-tree-root'>
+                <Entity controller={this.controller} entity={RootEntity}></Entity>
+            </div>
+            <div className='molstar-entity-tree-children'>
+                <div>{entities}</div>
+            </div>
+        </div>;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/ui/layout.tsx b/src/mol-app/ui/layout.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..df5184a84a82e6396e471ce069fb74ba2c310e9d
--- /dev/null
+++ b/src/mol-app/ui/layout.tsx
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import * as React from 'react'
+import { LayoutController, LayoutTarget, LayoutRegion, CollapsedControlsLayout } from '../controller/layout';
+import { View } from './view';
+
+export class Layout extends View<LayoutController, { }, { }> {
+
+    private renderTarget(target: LayoutTarget) {
+        const statics: any[] = [];
+        const scrollable: any[] = [];
+
+        for (let c of target.components) {
+            if (c.isStatic) statics.push(<c.view key={c.key} controller={c.controller} />);
+            else scrollable.push(<c.view key={c.key} controller={c.controller} />);
+        }
+
+        return <div key={target.cssClass} className={'molstar-layout-region molstar-layout-' + target.cssClass}>
+            { statics.length ? <div className='molstar-layout-static'>{statics}</div> : void 0 }
+            { scrollable.length ? <div className='molstar-layout-scrollable'>{scrollable}</div> : void 0 }
+        </div>;
+    }
+
+    private updateTarget(name: string, regionType: LayoutRegion, layout: { regions: any[], layoutClass: string }) {
+        const state = this.controller.latestState;
+        const regionStates = state.regionStates;
+        const region = this.controller.targets[regionType];
+        let show: boolean;
+
+        if (state.hideControls) {
+            show = regionStates !== void 0 && regionStates[regionType] === 'Sticky' && region.components.length > 0;
+        } else if (regionStates && regionStates[regionType] === 'Hidden') {
+            show = false;
+        } else {
+            show = region.components.length > 0;
+        }
+
+        if (show) {
+            layout.regions.push(this.renderTarget(region));
+        } else {
+            layout.layoutClass += ' molstar-layout-hide-' + name;
+        }
+    }
+
+    render() {
+        let layoutClass = '';
+
+        const state = this.controller.latestState;
+        let layoutType: string;
+
+        if (state.isExpanded) {
+            layoutType = 'molstar-layout-expanded';
+        } else {
+            layoutType = 'molstar-layout-standard ';
+            switch (state.collapsedControlsLayout) {
+                case CollapsedControlsLayout.Outside: layoutType += 'molstar-layout-standard-outside'; break;
+                case CollapsedControlsLayout.Landscape: layoutType += 'molstar-layout-standard-landscape'; break;
+                case CollapsedControlsLayout.Portrait: layoutType += 'molstar-layout-standard-portrait'; break;
+                default: layoutType += 'molstar-layout-standard-outside'; break;
+            }
+        }
+
+        const targets = this.controller.targets;
+        const regions = [this.renderTarget(targets[LayoutRegion.Main])];
+
+        const layout = { regions, layoutClass };
+        this.updateTarget('top', LayoutRegion.Top, layout);
+        this.updateTarget('right', LayoutRegion.Right, layout);
+        this.updateTarget('bottom', LayoutRegion.Bottom, layout);
+        this.updateTarget('left', LayoutRegion.Left, layout);
+        layoutClass = layout.layoutClass;
+
+        let root = targets[LayoutRegion.Root].components.map(c => <c.view key={c.key} controller={c.controller} />);
+
+        return <div className='molstar-plugin'>
+            <div className={'molstar-plugin-content ' + layoutType}>
+                <div className={layoutClass}>
+                    {regions}
+                    {root}
+                </div>
+            </div>
+        </div>;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/ui/misc/jobs.tsx b/src/mol-app/ui/misc/jobs.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c8a5fe557d75d6fd473a9367e747205c0b96183a
--- /dev/null
+++ b/src/mol-app/ui/misc/jobs.tsx
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import * as React from 'react'
+import { JobInfo, JobsController } from '../../controller/misc/jobs';
+import { Button } from '../controls/common';
+import { View } from '../view';
+
+class JobState extends React.Component<{ info: JobInfo, isSmall?: boolean }, {}> {
+    render() {
+        const info = this.props.info;
+        return <div className='molstar-task-state'>
+            <div>
+                { info.abort ? <Button onClick={() => info.abort!.call(null) } style='remove'
+                    icon='abort' title='Abort' customClass='molstar-btn-icon'
+                /> : void 0 }
+                <div>
+                    {info.name}: {info.message}
+                </div>
+            </div>
+        </div>;
+    }
+}
+
+export class Overlay extends View<JobsController, {}, {}> {
+    render() {
+        const state = this.controller.latestState;
+
+        if (!state.jobs!.count()) return <div className='molstar-empty-control' />
+
+        const jobs: any[] = [];
+        state.jobs!.forEach((t, k) => jobs.push(<JobState key={k} info={t!} />));
+
+        return <div className='molstar-overlay'>
+            <div className='molstar-overlay-background' />
+            <div className='molstar-overlay-content-wrap'>
+                <div className='molstar-overlay-content'>
+                    <div>
+                        {jobs}
+                    </div>
+                </div>
+            </div>
+        </div>;
+    }
+}
+
+export class BackgroundJobs extends View<JobsController, {}, {}> {
+    render() {
+        const state = this.controller.latestState;
+
+        if (!state.jobs!.count()) return <div className='molstar-empty-control' />
+
+        const jobs: any[] = [];
+        state.jobs!.forEach((t, k) => jobs.push(<JobState key={k} info={t!} isSmall={true} />));
+
+        return <div className='molstar-background-jobs'>
+            {jobs}
+        </div>;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/ui/misc/log.tsx b/src/mol-app/ui/misc/log.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..803d86f70ea25417a5f95a51daebac54561faa51
--- /dev/null
+++ b/src/mol-app/ui/misc/log.tsx
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import * as React from 'react'
+import { View } from '../view';
+import { LogController } from '../../controller/misc/log';
+import { CommonEvents } from '../../event/basic';
+import { formatTime } from 'mol-util';
+import { Logger } from '../../service/logger';
+
+export class Log extends View<LogController, {}, {}> {
+
+    private wrapper: HTMLDivElement | undefined = void 0;
+
+    componentWillMount() {
+        super.componentWillMount();
+        this.subscribe(CommonEvents.LayoutChanged.getStream(this.controller.context), () => this.scrollToBottom());
+    }
+
+    componentDidUpdate() {
+        this.scrollToBottom();
+    }
+
+    private scrollToBottom() {
+        const log = this.wrapper;
+        if (log) log.scrollTop = log.scrollHeight - log.clientHeight - 1;
+    }
+
+    render() {
+        const entries = this.controller.latestState.entries;
+
+        return <div className='molstar-log-wrap'>
+            <div className='molstar-log' ref={log => this.wrapper = log!}>
+                <ul className='molstar-list-unstyled'>
+                    {entries.map((entry, i, arr) => {
+
+                        let label: JSX.Element;
+                        let e = entry!;
+                        switch (e.type) {
+                            case Logger.EntryType.Error:
+                                label = <span className='label label-danger'>Error</span>;
+                                break;
+                            case Logger.EntryType.Warning:
+                                label = <span className='label label-warning'>Warning</span>;
+                                break;
+                            case Logger.EntryType.Info:
+                                label = <span className='label label-info'>Info</span>;
+                                break;
+                            default:
+                                label = <span></span>
+                        }
+
+                        let t = formatTime(e.timestamp);
+                        return <li key={i}>
+                            <div className={'molstar-log-entry-badge molstar-log-entry-' + Logger.EntryType[e.type].toLowerCase()} />
+                            {label}
+                            <div className='molstar-log-timestamp'>{t}</div>
+                            <div className='molstar-log-entry'>{e.message}</div>
+                        </li>;
+                    }) }
+                </ul>
+            </div>
+        </div>;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/ui/transform/file-loader.tsx b/src/mol-app/ui/transform/file-loader.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a88b13f9a5dce132ad90f15923812420459d0b85
--- /dev/null
+++ b/src/mol-app/ui/transform/file-loader.tsx
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as React from 'react'
+import { View } from '../view';
+import { FileInput } from '../controls/common';
+import { TransformListController } from '../../controller/transform/list';
+import { FileEntity } from 'mol-view/state/entity';
+import { MmcifFileToSpacefill } from 'mol-view/state/transform';
+import { StateContext } from 'mol-view/state/context';
+
+export class FileLoader extends View<TransformListController, {}, { ctx: StateContext }> {
+    render() {
+        return <div className='molstar-file-loader'>
+            <FileInput
+                accept='*.cif'
+                onChange={files => {
+                    if (files) {
+                        const fileEntity = FileEntity.ofFile(this.props.ctx, files[0])
+                        MmcifFileToSpacefill.apply(this.props.ctx, fileEntity)
+                    }
+                }}
+            />
+        </div>;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/ui/transform/list.tsx b/src/mol-app/ui/transform/list.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b72e8e6be91a76d34e05e310a8076377c5fcaf87
--- /dev/null
+++ b/src/mol-app/ui/transform/list.tsx
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as React from 'react'
+
+import { View } from '../view';
+import { Controller } from '../../controller/controller';
+import { TransformListController } from '../../controller/transform/list';
+import { AnyTransform } from 'mol-view/state/transform';
+import { Spacefill } from './spacefill';
+import { AnyEntity } from 'mol-view/state/entity';
+import { FileLoader } from './file-loader';
+import { ModelToStructure } from './model';
+
+function getTransformComponent(controller: TransformListController, entity: AnyEntity, transform: AnyTransform) {
+    switch (transform.kind) {
+        case 'file-to-spacefill':
+            return <FileLoader controller={controller} ctx={controller.context.stage.ctx}></FileLoader>
+        case 'model-to-structure':
+            return <ModelToStructure controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></ModelToStructure>
+        case 'spacefill-update':
+            return <Spacefill controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Spacefill>
+    }
+    return <Transform controller={controller} entity={entity} transform={transform}></Transform>
+}
+
+export class Transform extends View<Controller<any>, {}, { transform: AnyTransform, entity: AnyEntity }> {
+    render() {
+        const { transform, entity } = this.props
+
+        return <div className='molstar-transformer-wrapper'>
+            <div className='molstar-panel molstar-control molstar-transformer'>
+                <div className='molstar-panel-header'>
+                    <button
+                        className='molstar-btn molstar-btn-link molstar-panel-expander'
+                        onClick={(e)=> {
+                            console.log(transform, entity)
+                        }}
+                    >
+                        <span>[{transform.kind}] {transform.inputKind} -> {transform.outputKind}</span>
+                    </button>
+                </div>
+            </div>
+        </div>;
+    }
+}
+
+export class TransformList extends View<TransformListController, {}, {}> {
+    render() {
+        const transforms: JSX.Element[] = []
+        const state = this.controller.state.getValue()
+        if (state && state.entity) {
+            const entity = state.entity
+            if (entity) {
+                state.transforms.forEach(t => {
+                    transforms.push(
+                        <div
+                            key={`${t.inputKind}|${t.outputKind}`}
+                            children={getTransformComponent(this.controller, entity, t)}
+                        />
+                    )
+                })
+            }
+        }
+
+        return <div className='molstar-transform-view' children={transforms} />;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/ui/transform/model.tsx b/src/mol-app/ui/transform/model.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ebd4d2edb99c7d9e419005c54799d9284aee5bf8
--- /dev/null
+++ b/src/mol-app/ui/transform/model.tsx
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as React from 'react'
+
+import { View } from '../view';
+import { Controller } from '../../controller/controller';
+import { ModelEntity } from 'mol-view/state/entity';
+import { StructureProps, ModelToStructure as ModelToStructureTransform } from 'mol-view/state/transform'
+import { StateContext } from 'mol-view/state/context';
+
+export class ModelToStructure extends View<Controller<any>, StructureProps, { transform: ModelToStructureTransform, entity: ModelEntity, ctx: StateContext }> {
+    state = {
+        assembly: ''
+    }
+
+    create(state?: Partial<StructureProps>) {
+        const { transform, entity, ctx } = this.props
+        console.log('create structure', transform, entity)
+        const newState = { ...this.state, ...state }
+        this.setState(newState)
+        transform.apply(ctx, entity, newState)
+    }
+
+    render() {
+        const { transform, entity } = this.props
+
+        const assemblyOptions = entity.value[0].symmetry.assemblies.map((value, idx) => {
+            return <option key={value.id} value={value.id}>{value.details}</option>
+        })
+
+        return <div className='molstar-transformer-wrapper'>
+            <div className='molstar-panel molstar-control molstar-transformer molstar-panel-expanded'>
+                <div className='molstar-panel-header'>
+                    <button
+                        className='molstar-btn molstar-btn-link molstar-panel-expander'
+                        onClick={() => this.create()}
+                    >
+                        <span>[{transform.kind}] {transform.inputKind} -> {transform.outputKind}</span>
+                    </button>
+                </div>
+                <div className='molstar-panel-body'>
+                    <div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Details</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.assembly}
+                                    onChange={(e) => this.create({ assembly: e.target.value })}
+                                >
+                                    {assemblyOptions}
+                                </select>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/ui/transform/spacefill.tsx b/src/mol-app/ui/transform/spacefill.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5ee8d295991ba9f2b494138531c63b48be78f76c
--- /dev/null
+++ b/src/mol-app/ui/transform/spacefill.tsx
@@ -0,0 +1,143 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as React from 'react'
+
+import { View } from '../view';
+import { Controller } from '../../controller/controller';
+import { SpacefillEntity } from 'mol-view/state/entity';
+import { SpacefillUpdate } from 'mol-view/state/transform'
+import { StateContext } from 'mol-view/state/context';
+import { ColorTheme } from 'mol-geo/theme';
+import { Color, ColorNames } from 'mol-util/color';
+
+export const ColorThemeInfo = {
+    'atom-index': {},
+    'chain-id': {},
+    'element-symbol': {},
+    'instance-index': {},
+    'uniform': {}
+}
+export type ColorThemeInfo = keyof typeof ColorThemeInfo
+
+interface SpacefillState {
+    doubleSided: boolean
+    detail: number
+    colorTheme: ColorTheme
+    colorValue: Color
+}
+
+export class Spacefill extends View<Controller<any>, SpacefillState, { transform: SpacefillUpdate, entity: SpacefillEntity, ctx: StateContext }> {
+    state = {
+        doubleSided: true,
+        detail: 2,
+        colorTheme: { name: 'element-symbol' } as ColorTheme,
+        colorValue: 0x000000
+    }
+
+    update(state?: Partial<SpacefillState>) {
+        const { transform, entity, ctx } = this.props
+        console.log('update spacefill', transform, entity)
+        const newState = { ...this.state, ...state }
+        this.setState(newState)
+        transform.apply(ctx, entity, newState)
+    }
+
+    render() {
+        const { transform } = this.props
+
+        const sphereDetailOptions = [0, 1, 2, 3].map((value, idx) => {
+            return <option key={value} value={value}>{value.toString()}</option>
+        })
+
+        const colorThemeOptions = Object.keys(ColorThemeInfo).map((name, idx) => {
+            return <option key={name} value={name}>{name}</option>
+        })
+
+        const colorValueOptions = Object.keys(ColorNames).map((name, idx) => {
+            return <option key={name} value={(ColorNames as any)[name]}>{name}</option>
+        })
+
+        return <div className='molstar-transformer-wrapper'>
+            <div className='molstar-panel molstar-control molstar-transformer molstar-panel-expanded'>
+                <div className='molstar-panel-header'>
+                    <button
+                        className='molstar-btn molstar-btn-link molstar-panel-expander'
+                        onClick={() => this.update()}
+                    >
+                        <span>[{transform.kind}] {transform.inputKind} -> {transform.outputKind}</span>
+                    </button>
+                </div>
+                <div className='molstar-panel-body'>
+                    <div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Sphere detail</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.detail}
+                                    onChange={(e) => this.update({ detail: parseInt(e.target.value) })}
+                                >
+                                    {sphereDetailOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Color theme</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.colorTheme.name}
+                                    onChange={(e) => {
+                                        const colorThemeName = e.target.value as ColorThemeInfo
+                                        if (colorThemeName === 'uniform') {
+                                            this.update({
+                                                colorTheme: {
+                                                    name: colorThemeName,
+                                                    value: this.state.colorValue
+                                                }
+                                            })
+                                        } else {
+                                            this.update({
+                                                colorTheme: { name: colorThemeName }
+                                            })
+                                        }
+                                    }}
+                                >
+                                    {colorThemeOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Color value</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.colorValue}
+                                    onChange={(e) => {
+                                        const colorValue = parseInt(e.target.value)
+                                        this.update({
+                                            colorTheme: {
+                                                name: 'uniform',
+                                                value: colorValue
+                                            },
+                                            colorValue
+                                        })
+                                    }}
+                                >
+                                    {colorValueOptions}
+                                </select>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/ui/view.tsx b/src/mol-app/ui/view.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d7374759f3ac87084324a13e580bfa666d82d717
--- /dev/null
+++ b/src/mol-app/ui/view.tsx
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import * as React from 'react'
+import { Observable, Subscription } from 'rxjs';
+import { merge, shallowEqual } from 'mol-util'
+import { Context } from '../context/context';
+import { Controller } from '../controller/controller';
+
+export abstract class PureView<State, Props, ViewState> extends React.Component<{
+    state: State
+    onChange: (s: State) => void
+} & Props, ViewState> {
+
+    protected update(s: State) {
+        let ns = merge<State>(this.props.state, s);
+        if (ns !== this.props.state as any) this.props.onChange(ns);
+    }
+
+    shouldComponentUpdate(nextProps: any, nextState: any) {
+        return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState);
+    }
+}
+
+export abstract class ComponentView<Props> extends React.Component<{ context: Context } & Props, {}> {
+
+    // shouldComponentUpdate(nextProps: any, nextState: any) {
+    //     return !shallowEqual(this.props, nextProps);
+    // }
+
+    private subs: Subscription[] = [];
+    protected subscribe<T>(stream: Observable<T>, obs: (n: T) => void) {
+        let sub = stream.subscribe(obs);
+        this.subs.push(sub);
+        return sub;
+    }
+
+    protected unsubscribe(sub: Subscription) {
+        let idx = this.subs.indexOf(sub);
+        for (let i = idx; i < this.subs.length - 1; i++) {
+            this.subs[i] = this.subs[i + 1];
+        }
+        sub.unsubscribe();
+        this.subs.pop();
+    }
+
+    componentWillUnmount() {
+        for (let s of this.subs) s.unsubscribe();
+        this.subs = [];
+    }
+}
+
+export abstract class ObserverView<P, S> extends React.Component<P, S> {
+    private subs: Subscription[] = [];
+
+    protected subscribe<T>(stream: Observable<T>, obs: (n: T) => void) {
+        let sub = stream.subscribe(obs);
+        this.subs.push(sub);
+        return sub;
+    }
+
+    protected unsubscribe(sub: Subscription) {
+        let idx = this.subs.indexOf(sub);
+        for (let i = idx; i < this.subs.length - 1; i++) {
+            this.subs[i] = this.subs[i + 1];
+        }
+        sub.unsubscribe();
+        this.subs.pop();
+    }
+
+    componentWillUnmount() {
+        for (let s of this.subs) s.unsubscribe();
+        this.subs = [];
+    }
+}
+
+export abstract class View<T extends Controller<any>, State, CustomProps>
+    extends ObserverView<{ controller: T } & CustomProps, State> {
+
+    public get controller(): T {
+        return this.props.controller as any;
+    }
+
+    componentWillMount() {
+        this.subscribe(this.controller.state as any, (s) => {
+            this.forceUpdate()
+        });
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/ui/visualization/viewport.tsx b/src/mol-app/ui/visualization/viewport.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..71b3afdeb2376e5d5dcb9c01cb6f24c8c4d5fbd1
--- /dev/null
+++ b/src/mol-app/ui/visualization/viewport.tsx
@@ -0,0 +1,141 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as React from 'react'
+
+import { ViewportController } from '../../controller/visualization/viewport'
+import { View } from '../view';
+import { HelpBox, Toggle, Button } from '../controls/common'
+import { Slider } from '../controls/slider'
+
+export class ViewportControls extends View<ViewportController, { showSceneOptions?: boolean, showHelp?: boolean }, {}> {
+    state = { showSceneOptions: false, showHelp: false };
+
+    private help() {
+        return <div className='molstar-viewport-controls-scene-options molstar-control'>
+            <HelpBox title='Rotate' content={<div><div>Left button</div><div>One finger touch</div></div>} />
+            <HelpBox title='Zoom' content={<div><div>Right button</div><div>Pinch</div></div>} />
+            <HelpBox title='Move' content={<div><div>Middle button</div><div>Two finger touch</div></div>} />
+            <HelpBox title='Slab' content={<div><div>Mouse wheel</div><div>Three finger touch</div></div>} />
+        </div>
+    }
+
+    render() {
+        let state = this.controller.latestState;
+
+        let options: any;
+
+        let layoutController = this.controller.context.layout;
+        let layoutState = layoutController.latestState;
+        if (this.state.showSceneOptions) {
+            options = <div className='molstar-viewport-controls-scene-options molstar-control'>
+                <Toggle onChange={v => this.controller.setState({ enableFog: v })} value={state.enableFog!} label='Fog' />
+                <Slider label='FOV' min={30} max={90} onChange={v => this.controller.setState({ cameraFOV: v }) } value={state.cameraFOV!} />
+                <Slider label='Camera Speed' min={1} max={10} step={0.01} onChange={v => this.controller.setState({ cameraSpeed: v }) } value={state.cameraSpeed!} />
+            </div>;
+        } else if (this.state.showHelp) {
+            options = this.help();
+        }
+
+        let controlsShown = !layoutState.hideControls;
+        return <div className='molstar-viewport-controls' onMouseLeave={() => this.setState({ showSceneOptions: false, showHelp: false })}>
+            <div className='molstar-viewport-controls-buttons'>
+                <Button
+                    style='link'
+                    active={this.state.showHelp}
+                    customClass={'molstar-btn-link-toggle-' + (this.state.showHelp ? 'on' : 'off')}
+                    icon='help-circle'
+                    onClick={(e) => this.setState({ showHelp: !this.state.showHelp, showSceneOptions: false }) } title='Controls Help' />
+                <Button
+                    style='link'
+                    active={this.state.showSceneOptions}
+                    customClass={'molstar-btn-link-toggle-' + (this.state.showSceneOptions ? 'on' : 'off')}
+                    icon='settings'
+                    onClick={(e) => this.setState({ showSceneOptions: !this.state.showSceneOptions, showHelp: false }) } title='Scene Options' />
+                <Button
+                    style='link'
+                    icon='screenshot'
+                    onClick={(e) => this.controller.context.stage.viewer.downloadScreenshot()}
+                    title='Screenshot' />
+                <Button   onClick={() => { layoutController.update({ hideControls: controlsShown }); this.forceUpdate(); } }
+                    icon='tools' title={controlsShown ? 'Hide Controls' : 'Show Controls'} active={controlsShown }
+                    customClass={'molstar-btn-link-toggle-' + (controlsShown  ? 'on' : 'off')}
+                    style='link' />
+                <Button   onClick={() => layoutController.update({ isExpanded: !layoutState.isExpanded  }) }
+                    icon='expand-layout' title={layoutState.isExpanded ? 'Collapse' : 'Expand'} active={layoutState.isExpanded }
+                    customClass={'molstar-btn-link-toggle-' + (layoutState.isExpanded  ? 'on' : 'off')}
+                    style='link' />
+                <Button
+                    style='link'
+                    icon='reset-scene'
+                    onClick={(e) => this.controller.context.stage.viewer.resetCamera()}
+                    title='Reset camera' />
+            </div>
+            {options}
+        </div>;
+    }
+}
+
+export const Logo = () =>
+    <div className='molstar-logo'>
+        <div>
+            <div>
+                <div />
+                <div className='molstar-logo-image' />
+            </div>
+        </div>
+    </div>
+
+
+export class Viewport extends View<ViewportController, {}, { noWebGl?: boolean, showLogo?: boolean }> {
+    private container: HTMLDivElement | null = null;
+    private canvas: HTMLCanvasElement | null = null;
+    private defaultBg = { r: 1, g: 1, b: 1 }
+    state = { noWebGl: false, showLogo: true };
+
+    componentDidMount() {
+        if (!this.canvas || !this.container || !this.controller.context.initStage(this.canvas, this.container)) {
+            this.setState({ noWebGl: true });
+        }
+        this.controller.context.stage.viewer.reprCount.subscribe(count => {
+            this.setState({
+                showLogo: false
+                // showLogo: count === 0
+            })
+        })
+    }
+
+    componentWillUnmount() {
+        super.componentWillUnmount();
+        this.controller.context.destroy();
+    }
+
+    renderMissing() {
+        return <div className='molstar-no-webgl'>
+            <div>
+                <p><b>WebGL does not seem to be available.</b></p>
+                <p>This can be caused by an outdated browser, graphics card driver issue, or bad weather. Sometimes, just restarting the browser helps.</p>
+                <p>For a list of supported browsers, refer to <a href='http://caniuse.com/#feat=webgl' target='_blank'>http://caniuse.com/#feat=webgl</a>.</p>
+            </div>
+        </div>
+    }
+
+    render() {
+        if (this.state.noWebGl) return this.renderMissing();
+
+        const color = this.controller.latestState.clearColor! || this.defaultBg;
+        return <div className='molstar-viewport' style={{ backgroundColor: `rgb(${255 * color.r}, ${255 * color.g}, ${255 * color.b})` }}>
+            <div ref={elm => this.container = elm} className='molstar-viewport-container'>
+                <canvas ref={elm => this.canvas = elm} className='molstar-viewport-canvas'></canvas>
+            </div>
+            {this.state.showLogo ? <Logo /> : void 0}
+            <ViewportControls controller={this.controller} />
+        </div>;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-io/reader/_spec/cif.spec.ts b/src/mol-io/reader/_spec/cif.spec.ts
index e7d619f280a0d66f6eead466e4b9163c7363b947..a2fb03ed952f69e1c9c93878a7b5904d174f3ddd 100644
--- a/src/mol-io/reader/_spec/cif.spec.ts
+++ b/src/mol-io/reader/_spec/cif.spec.ts
@@ -18,8 +18,8 @@ const strField = TextField({ data: columnData, indices: [3, 4, 4, 5, 5, 6], coun
 const strListField = TextField({ data: columnData, indices: [7, 12], count: 1 }, 1);
 const intListField = TextField({ data: columnData, indices: [14, 19], count: 1 }, 1);
 
-const testBlock = Data.Block(['test'], {
-    test: Data.Category('test', 3, ['int', 'str', 'strList', 'intList'], {
+const testBlock = Data.CifBlock(['test'], {
+    test: Data.CifCategory('test', 3, ['int', 'str', 'strList', 'intList'], {
         int: intField,
         str: strField,
         strList: strListField,
diff --git a/src/mol-io/reader/cif.ts b/src/mol-io/reader/cif.ts
index 440be38553cb525327fd6e376bbd0f29490f4203..54aad885c8c623bb055d221741380dab044c71ca 100644
--- a/src/mol-io/reader/cif.ts
+++ b/src/mol-io/reader/cif.ts
@@ -7,7 +7,7 @@
 
 import parseText from './cif/text/parser'
 import parseBinary from './cif/binary/parser'
-import { Frame } from './cif/data-model'
+import { CifFrame } from './cif/data-model'
 import { toDatabaseCollection, toDatabase } from './cif/schema'
 import { mmCIF_Schema, mmCIF_Database } from './cif/schema/mmcif'
 import { CCD_Schema, CCD_Database } from './cif/schema/ccd'
@@ -22,11 +22,11 @@ export default {
     toDatabaseCollection,
     toDatabase,
     schema: {
-        mmCIF: (frame: Frame) => toDatabase<mmCIF_Schema, mmCIF_Database>(mmCIF_Schema, frame),
-        CCD: (frame: Frame) => toDatabase<CCD_Schema, CCD_Database>(CCD_Schema, frame),
-        BIRD: (frame: Frame) => toDatabase<BIRD_Schema, BIRD_Database>(BIRD_Schema, frame),
-        dic: (frame: Frame) => toDatabase<dic_Schema, dic_Database>(dic_Schema, frame),
-        densityServer: (frame: Frame) => toDatabase<DensityServer_Data_Schema, DensityServer_Data_Database>(DensityServer_Data_Schema, frame)
+        mmCIF: (frame: CifFrame) => toDatabase<mmCIF_Schema, mmCIF_Database>(mmCIF_Schema, frame),
+        CCD: (frame: CifFrame) => toDatabase<CCD_Schema, CCD_Database>(CCD_Schema, frame),
+        BIRD: (frame: CifFrame) => toDatabase<BIRD_Schema, BIRD_Database>(BIRD_Schema, frame),
+        dic: (frame: CifFrame) => toDatabase<dic_Schema, dic_Database>(dic_Schema, frame),
+        densityServer: (frame: CifFrame) => toDatabase<DensityServer_Data_Schema, DensityServer_Data_Database>(DensityServer_Data_Schema, frame)
     }
 }
 
diff --git a/src/mol-io/reader/cif/binary/field.ts b/src/mol-io/reader/cif/binary/field.ts
index dd5758f519c895f28ea88801cc627692cb29cd42..af596d70a9909fd459d077a4c1aa4c21a22b0afd 100644
--- a/src/mol-io/reader/cif/binary/field.ts
+++ b/src/mol-io/reader/cif/binary/field.ts
@@ -10,12 +10,12 @@ import * as Data from '../data-model'
 import { EncodedColumn, decode } from '../../../common/binary-cif'
 import { parseInt as fastParseInt, parseFloat as fastParseFloat } from '../../common/text/number-parser'
 
-export default function Field(column: EncodedColumn): Data.Field {
+export default function Field(column: EncodedColumn): Data.CifField {
     const mask = column.mask ? decode(column.mask) as number[] : void 0;
     const data = decode(column.data);
     const isNumeric = ColumnHelpers.isTypedArray(data);
 
-    const str: Data.Field['str'] = isNumeric
+    const str: Data.CifField['str'] = isNumeric
         ? mask
             ? row => mask[row] === Column.ValueKind.Present ? '' + data[row] : ''
             : row => '' + data[row]
@@ -23,15 +23,15 @@ export default function Field(column: EncodedColumn): Data.Field {
             ? row => mask[row] === Column.ValueKind.Present ? data[row] : ''
             : row => data[row];
 
-    const int: Data.Field['int'] = isNumeric
+    const int: Data.CifField['int'] = isNumeric
         ? row => data[row]
         : row => { const v = data[row]; return fastParseInt(v, 0, v.length); };
 
-    const float: Data.Field['float'] = isNumeric
+    const float: Data.CifField['float'] = isNumeric
         ? row => data[row]
         : row => { const v = data[row]; return fastParseFloat(v, 0, v.length); };
 
-    const valueKind: Data.Field['valueKind'] = mask
+    const valueKind: Data.CifField['valueKind'] = mask
         ? row => mask[row]
         : row => Column.ValueKind.Present;
 
diff --git a/src/mol-io/reader/cif/binary/parser.ts b/src/mol-io/reader/cif/binary/parser.ts
index bd53cd0ad2d569049d6145eb35481bb3187e1fae..8a5f0ea1a8133aed0254577b2791d75952042183 100644
--- a/src/mol-io/reader/cif/binary/parser.ts
+++ b/src/mol-io/reader/cif/binary/parser.ts
@@ -18,7 +18,7 @@ function checkVersions(min: number[], current: number[]) {
     return true;
 }
 
-function Category(data: EncodedCategory): Data.Category {
+function Category(data: EncodedCategory): Data.CifCategory {
     const map = Object.create(null);
     const cache = Object.create(null);
     for (const col of data.columns) map[col.name] = col;
@@ -37,22 +37,22 @@ function Category(data: EncodedCategory): Data.Category {
 }
 
 export default function parse(data: Uint8Array) {
-    return Task.create<Result<Data.File>>('Parse BinaryCIF', async ctx => {
+    return Task.create<Result<Data.CifFile>>('Parse BinaryCIF', async ctx => {
         const minVersion = [0, 3];
 
         try {
             const unpacked = decodeMsgPack(data) as EncodedFile;
             if (!checkVersions(minVersion, unpacked.version.match(/(\d)\.(\d)\.\d/)!.slice(1).map(v => +v))) {
-                return Result.error<Data.File>(`Unsupported format version. Current ${unpacked.version}, required ${minVersion.join('.')}.`);
+                return Result.error<Data.CifFile>(`Unsupported format version. Current ${unpacked.version}, required ${minVersion.join('.')}.`);
             }
-            const file = Data.File(unpacked.dataBlocks.map(block => {
+            const file = Data.CifFile(unpacked.dataBlocks.map(block => {
                 const cats = Object.create(null);
                 for (const cat of block.categories) cats[cat.name.substr(1)] = Category(cat);
-                return Data.Block(block.categories.map(c => c.name.substr(1)), cats, block.header);
+                return Data.CifBlock(block.categories.map(c => c.name.substr(1)), cats, block.header);
             }));
             return Result.success(file);
         } catch (e) {
-            return Result.error<Data.File>('' + e);
+            return Result.error<Data.CifFile>('' + e);
         }
     })
 }
\ No newline at end of file
diff --git a/src/mol-io/reader/cif/data-model.ts b/src/mol-io/reader/cif/data-model.ts
index 4a090d35163ec693de14ff65e0c4e559a6dbaf89..30bc1c43e06cad7e26e412159f7468124f55d60c 100644
--- a/src/mol-io/reader/cif/data-model.ts
+++ b/src/mol-io/reader/cif/data-model.ts
@@ -8,49 +8,49 @@
 import { Column } from 'mol-data/db'
 import { Tensor } from 'mol-math/linear-algebra'
 
-export interface File {
+export interface CifFile {
     readonly name?: string,
-    readonly blocks: ReadonlyArray<Block>
+    readonly blocks: ReadonlyArray<CifBlock>
 }
 
-export function File(blocks: ArrayLike<Block>, name?: string): File {
+export function CifFile(blocks: ArrayLike<CifBlock>, name?: string): CifFile {
     return { name, blocks: blocks as any };
 }
 
-export interface Frame {
+export interface CifFrame {
     readonly header: string,
     // Category names stored separately so that the ordering can be preserved.
     readonly categoryNames: ReadonlyArray<string>,
-    readonly categories: Categories
+    readonly categories: CifCategories
 }
 
-export interface Block extends Frame {
-    readonly saveFrames: Frame[]
+export interface CifBlock extends CifFrame {
+    readonly saveFrames: CifFrame[]
 }
 
-export function Block(categoryNames: string[], categories: Categories, header: string, saveFrames: Frame[] = []): Block {
+export function CifBlock(categoryNames: string[], categories: CifCategories, header: string, saveFrames: CifFrame[] = []): CifBlock {
     return { categoryNames, header, categories, saveFrames };
 }
 
-export function SafeFrame(categoryNames: string[], categories: Categories, header: string): Frame {
+export function CifSafeFrame(categoryNames: string[], categories: CifCategories, header: string): CifFrame {
     return { categoryNames, header, categories };
 }
 
-export type Categories = { readonly [name: string]: Category }
+export type CifCategories = { readonly [name: string]: CifCategory }
 
-export interface Category {
+export interface CifCategory {
     readonly rowCount: number,
     readonly name: string,
     readonly fieldNames: ReadonlyArray<string>,
-    getField(name: string): Field | undefined
+    getField(name: string): CifField | undefined
 }
 
-export function Category(name: string, rowCount: number, fieldNames: string[], fields: { [name: string]: Field }): Category {
+export function CifCategory(name: string, rowCount: number, fieldNames: string[], fields: { [name: string]: CifField }): CifCategory {
     return { rowCount, name, fieldNames: [...fieldNames], getField(name) { return fields[name]; } };
 }
 
-export namespace Category {
-    export function empty(name: string): Category {
+export namespace CifCategory {
+    export function empty(name: string): CifCategory {
         return { rowCount: 0, name, fieldNames: [], getField(name: string) { return void 0; } };
     };
 }
@@ -60,7 +60,7 @@ export namespace Category {
  * Always implement without using "this." in any of the interface functions.
  * This is to ensure that the functions can invoked without having to "bind" them.
  */
-export interface Field {
+export interface CifField {
     readonly '@array': ArrayLike<any> | undefined
     readonly isDefined: boolean,
     readonly rowCount: number,
@@ -78,7 +78,7 @@ export interface Field {
     toFloatArray(params?: Column.ToArrayParams<number>): ReadonlyArray<number>
 }
 
-export function getTensor(category: Category, field: string, space: Tensor.Space, row: number, zeroIndexed: boolean): Tensor.Data {
+export function getTensor(category: CifCategory, field: string, space: Tensor.Space, row: number, zeroIndexed: boolean): Tensor.Data {
     const ret = space.create();
     const offset = zeroIndexed ? 0 : 1;
 
diff --git a/src/mol-io/reader/cif/schema.ts b/src/mol-io/reader/cif/schema.ts
index c7710132f758dc4ec0509b0f2089c9f336289544..8efa52ab41b2a0c2047ad7cb9149751d44fd7610 100644
--- a/src/mol-io/reader/cif/schema.ts
+++ b/src/mol-io/reader/cif/schema.ts
@@ -10,7 +10,7 @@ import { Tensor } from 'mol-math/linear-algebra'
 import { arrayEqual } from 'mol-util'
 import * as Data from './data-model'
 
-export function toDatabaseCollection<Schema extends Database.Schema>(schema: Schema, file: Data.File): DatabaseCollection<Schema> {
+export function toDatabaseCollection<Schema extends Database.Schema>(schema: Schema, file: Data.CifFile): DatabaseCollection<Schema> {
     const dbc: DatabaseCollection<Schema> = {}
     for (const data of file.blocks) {
         dbc[data.header] = toDatabase(schema, data)
@@ -18,15 +18,15 @@ export function toDatabaseCollection<Schema extends Database.Schema>(schema: Sch
     return dbc;
 }
 
-export function toDatabase<Schema extends Database.Schema, Frame extends Database<Schema> = Database<Schema>>(schema: Schema, frame: Data.Frame): Frame {
+export function toDatabase<Schema extends Database.Schema, Frame extends Database<Schema> = Database<Schema>>(schema: Schema, frame: Data.CifFrame): Frame {
     return createDatabase(schema, frame) as Frame;
 }
 
-export function toTable<Schema extends Table.Schema, R extends Table<Schema> = Table<Schema>>(schema: Schema, category: Data.Category): R {
+export function toTable<Schema extends Table.Schema, R extends Table<Schema> = Table<Schema>>(schema: Schema, category: Data.CifCategory): R {
     return new CategoryTable(category, schema, true) as any;
 }
 
-type ColumnCtor = (field: Data.Field, category: Data.Category, key: string) => Column<any>
+type ColumnCtor = (field: Data.CifField, category: Data.CifCategory, key: string) => Column<any>
 
 function getColumnCtor(t: Column.Schema): ColumnCtor {
     switch (t.valueType) {
@@ -38,7 +38,7 @@ function getColumnCtor(t: Column.Schema): ColumnCtor {
     }
 }
 
-function createColumn<T>(schema: Column.Schema, field: Data.Field, value: (row: number) => T, toArray: Column<T>['toArray']): Column<T> {
+function createColumn<T>(schema: Column.Schema, field: Data.CifField, value: (row: number) => T, toArray: Column<T>['toArray']): Column<T> {
     return {
         schema,
         '@array': field['@array'],
@@ -51,7 +51,7 @@ function createColumn<T>(schema: Column.Schema, field: Data.Field, value: (row:
     };
 }
 
-function createListColumn<T extends number|string>(schema: Column.Schema.List<T>, category: Data.Category, key: string): Column<(number|string)[]> {
+function createListColumn<T extends number|string>(schema: Column.Schema.List<T>, category: Data.CifCategory, key: string): Column<(number|string)[]> {
     const separator = schema.separator;
     const itemParse = schema.itemParse;
 
@@ -71,7 +71,7 @@ function createListColumn<T extends number|string>(schema: Column.Schema.List<T>
     };
 }
 
-function createTensorColumn(schema: Column.Schema.Tensor, category: Data.Category, key: string): Column<Tensor.Data> {
+function createTensorColumn(schema: Column.Schema.Tensor, category: Data.CifCategory, key: string): Column<Tensor.Data> {
     const space = schema.space;
     const zeroOffset = category.fieldNames.indexOf(`${key}[0]`) >= 0;
     const fst = zeroOffset ? 0 : 1;
@@ -105,7 +105,7 @@ class CategoryTable implements Table<any> { // tslint:disable-line:class-name
     _schema: any;
     [k: string]: any;
 
-    constructor(category: Data.Category, schema: Table.Schema, public _isDefined: boolean) {
+    constructor(category: Data.CifCategory, schema: Table.Schema, public _isDefined: boolean) {
         const fieldKeys = Object.keys(schema);
         this._rowCount = category.rowCount;
         this._columns = fieldKeys;
@@ -134,7 +134,7 @@ class CategoryTable implements Table<any> { // tslint:disable-line:class-name
     }
 }
 
-function createDatabase(schema: Database.Schema, frame: Data.Frame): Database<any> {
+function createDatabase(schema: Database.Schema, frame: Data.CifFrame): Database<any> {
     const tables = Object.create(null);
     for (const k of Object.keys(schema)) {
         tables[k] = createTable(k, (schema as any)[k], frame);
@@ -142,7 +142,7 @@ function createDatabase(schema: Database.Schema, frame: Data.Frame): Database<an
     return Database.ofTables(frame.header, schema, tables);
 }
 
-function createTable(key: string, schema: Table.Schema, frame: Data.Frame) {
+function createTable(key: string, schema: Table.Schema, frame: Data.CifFrame) {
     const cat = frame.categories[key];
-    return new CategoryTable(cat || Data.Category.empty(key), schema, !!cat);
+    return new CategoryTable(cat || Data.CifCategory.empty(key), schema, !!cat);
 }
\ No newline at end of file
diff --git a/src/mol-io/reader/cif/text/field.ts b/src/mol-io/reader/cif/text/field.ts
index 8b6daa2b88a07aa7adc1d4eedbf8d0c83234b394..32af80a85747eb0c8499d1a76b58689a86b721ed 100644
--- a/src/mol-io/reader/cif/text/field.ts
+++ b/src/mol-io/reader/cif/text/field.ts
@@ -11,24 +11,24 @@ import { Tokens } from '../../common/text/tokenizer'
 import * as Data from '../data-model'
 import { parseInt as fastParseInt, parseFloat as fastParseFloat } from '../../common/text/number-parser'
 
-export default function CifTextField(tokens: Tokens, rowCount: number): Data.Field {
+export default function CifTextField(tokens: Tokens, rowCount: number): Data.CifField {
     const { data, indices } = tokens;
 
-    const str: Data.Field['str'] = row => {
+    const str: Data.CifField['str'] = row => {
         const ret = data.substring(indices[2 * row], indices[2 * row + 1]);
         if (ret === '.' || ret === '?') return '';
         return ret;
     };
 
-    const int: Data.Field['int'] = row => {
+    const int: Data.CifField['int'] = row => {
         return fastParseInt(data, indices[2 * row], indices[2 * row + 1]) || 0;
     };
 
-    const float: Data.Field['float'] = row => {
+    const float: Data.CifField['float'] = row => {
         return fastParseFloat(data, indices[2 * row], indices[2 * row + 1]) || 0;
     };
 
-    const valueKind: Data.Field['valueKind'] = row => {
+    const valueKind: Data.CifField['valueKind'] = row => {
         const s = indices[2 * row];
         if (indices[2 * row + 1] - s !== 1) return Column.ValueKind.Present;
         const v = data.charCodeAt(s);
diff --git a/src/mol-io/reader/cif/text/parser.ts b/src/mol-io/reader/cif/text/parser.ts
index bbd4b39c83edaaa82b95b720511b9fc0baac7cb1..610c7b0f8eb780224f4fb822295887bded4d50c6 100644
--- a/src/mol-io/reader/cif/text/parser.ts
+++ b/src/mol-io/reader/cif/text/parser.ts
@@ -413,7 +413,7 @@ interface CifCategoryResult {
 
 type FrameContext = {
     categoryNames: string[],
-    categories: { [name: string]: Data.Category }
+    categories: { [name: string]: Data.CifCategory }
 }
 
 function FrameContext(): FrameContext {
@@ -451,7 +451,7 @@ function handleSingle(tokenizer: TokenizerState, ctx: FrameContext): CifCategory
     }
 
     const catName = name.substr(1);
-    ctx.categories[catName] = Data.Category(catName, 1, fieldNames, fields);
+    ctx.categories[catName] = Data.CifCategory(catName, 1, fieldNames, fields);
     ctx.categoryNames.push(catName);
 
     return {
@@ -533,7 +533,7 @@ async function handleLoop(tokenizer: TokenizerState, ctx: FrameContext): Promise
     }
 
     const catName = name.substr(1);
-    ctx.categories[catName] = Data.Category(catName, rowCount, fieldNames, fields);
+    ctx.categories[catName] = Data.CifCategory(catName, rowCount, fieldNames, fields);
     ctx.categoryNames.push(catName);
 
     return {
@@ -547,13 +547,13 @@ async function handleLoop(tokenizer: TokenizerState, ctx: FrameContext): Promise
  * Creates an error result.
  */
 function error(line: number, message: string) {
-    return Result.error<Data.File>(message, line);
+    return Result.error<Data.CifFile>(message, line);
 }
 
 /**
  * Creates a data result.
  */
-function result(data: Data.File) {
+function result(data: Data.CifFile) {
     return Result.success(data);
 }
 
@@ -563,7 +563,7 @@ function result(data: Data.File) {
  * @returns CifParserResult wrapper of the result.
  */
 async function parseInternal(data: string, runtimeCtx: RuntimeContext) {
-    const dataBlocks: Data.Block[] = [];
+    const dataBlocks: Data.CifBlock[] = [];
     const tokenizer = createTokenizer(data, runtimeCtx);
     let blockHeader = '';
 
@@ -572,9 +572,9 @@ async function parseInternal(data: string, runtimeCtx: RuntimeContext) {
     let inSaveFrame = false;
 
     // the next three initial values are never used in valid files
-    let saveFrames: Data.Frame[] = [];
+    let saveFrames: Data.CifFrame[] = [];
     let saveCtx = FrameContext();
-    let saveFrame: Data.Frame = Data.SafeFrame(saveCtx.categoryNames, saveCtx.categories, '');
+    let saveFrame: Data.CifFrame = Data.CifSafeFrame(saveCtx.categoryNames, saveCtx.categories, '');
 
     runtimeCtx.update({ message: 'Parsing...', current: 0, max: data.length });
 
@@ -588,7 +588,7 @@ async function parseInternal(data: string, runtimeCtx: RuntimeContext) {
                 return error(tokenizer.lineNumber, 'Unexpected data block inside a save frame.');
             }
             if (blockCtx.categoryNames.length > 0) {
-                dataBlocks.push(Data.Block(blockCtx.categoryNames, blockCtx.categories, blockHeader, saveFrames));
+                dataBlocks.push(Data.CifBlock(blockCtx.categoryNames, blockCtx.categories, blockHeader, saveFrames));
             }
             blockHeader = data.substring(tokenizer.tokenStart + 5, tokenizer.tokenEnd);
             blockCtx = FrameContext();
@@ -609,7 +609,7 @@ async function parseInternal(data: string, runtimeCtx: RuntimeContext) {
                 inSaveFrame = true;
                 const safeHeader = data.substring(tokenizer.tokenStart + 5, tokenizer.tokenEnd);
                 saveCtx = FrameContext();
-                saveFrame = Data.SafeFrame(saveCtx.categoryNames, saveCtx.categories, safeHeader);
+                saveFrame = Data.CifSafeFrame(saveCtx.categoryNames, saveCtx.categories, safeHeader);
             }
             moveNext(tokenizer);
         // Loop
@@ -636,14 +636,14 @@ async function parseInternal(data: string, runtimeCtx: RuntimeContext) {
     }
 
     if (blockCtx.categoryNames.length > 0) {
-        dataBlocks.push(Data.Block(blockCtx.categoryNames, blockCtx.categories, blockHeader, saveFrames));
+        dataBlocks.push(Data.CifBlock(blockCtx.categoryNames, blockCtx.categories, blockHeader, saveFrames));
     }
 
-    return result(Data.File(dataBlocks));
+    return result(Data.CifFile(dataBlocks));
 }
 
 export default function parse(data: string) {
-    return Task.create<Result<Data.File>>('Parse CIF', async ctx => {
+    return Task.create<Result<Data.CifFile>>('Parse CIF', async ctx => {
         return await parseInternal(data, ctx);
     });
 }
\ No newline at end of file
diff --git a/src/mol-io/reader/csv/data-model.ts b/src/mol-io/reader/csv/data-model.ts
index 86f50f077574d8fd4e7fa5b511bdb6773821ccae..401c7aa2d5855cd530131ac22345bc0052823f9f 100644
--- a/src/mol-io/reader/csv/data-model.ts
+++ b/src/mol-io/reader/csv/data-model.ts
@@ -4,32 +4,32 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Field as Column } from '../cif/data-model'
+import { CifField as CsvColumn } from '../cif/data-model'
 
-export { Column }
+export { CsvColumn }
 
-export interface File {
+export interface CsvFile {
     readonly name?: string,
-    readonly table: Table
+    readonly table: CsvTable
 }
 
-export function File(table: Table, name?: string): File {
+export function CsvFile(table: CsvTable, name?: string): CsvFile {
     return { name, table };
 }
 
-export interface Table {
+export interface CsvTable {
     readonly rowCount: number,
     readonly columnNames: ReadonlyArray<string>,
-    getColumn(name: string): Column | undefined
+    getColumn(name: string): CsvColumn | undefined
 }
 
-export function Table(rowCount: number, columnNames: string[], columns: Columns): Table {
+export function CsvTable(rowCount: number, columnNames: string[], columns: CsvColumns): CsvTable {
     return { rowCount, columnNames: [...columnNames], getColumn(name) { return columns[name]; } };
 }
 
-export type Columns = { [name: string]: Column }
+export type CsvColumns = { [name: string]: CsvColumn }
 
-// export namespace Table {
+// export namespace CsvTable {
 //     export function empty(name: string): Table {
 //         return { rowCount: 0, name, fieldNames: [], getColumn(name: string) { return void 0; } };
 //     };
diff --git a/src/mol-io/reader/csv/parser.ts b/src/mol-io/reader/csv/parser.ts
index 66a7a9e1783c3107a9b618cbb0fbbaf0fdd4a6e2..d5bc68535344ff6c9d7aed63445b26e19a9220c7 100644
--- a/src/mol-io/reader/csv/parser.ts
+++ b/src/mol-io/reader/csv/parser.ts
@@ -248,24 +248,24 @@ function init(state: State) {
     }
 }
 
-async function handleRecords(state: State): Promise<Data.Table> {
+async function handleRecords(state: State): Promise<Data.CsvTable> {
     init(state)
     await readRecordsChunks(state)
 
-    const columns: Data.Columns = Object.create(null);
+    const columns: Data.CsvColumns = Object.create(null);
     for (let i = 0; i < state.columnCount; ++i) {
         columns[state.columnNames[i]] = Field(state.tokens[i], state.recordCount);
     }
 
-    return Data.Table(state.recordCount, state.columnNames, columns)
+    return Data.CsvTable(state.recordCount, state.columnNames, columns)
 }
 
-async function parseInternal(data: string, ctx: RuntimeContext, opts: CsvOptions): Promise<Result<Data.File>> {
+async function parseInternal(data: string, ctx: RuntimeContext, opts: CsvOptions): Promise<Result<Data.CsvFile>> {
     const state = State(data, ctx, opts);
 
     ctx.update({ message: 'Parsing...', current: 0, max: data.length });
     const table = await handleRecords(state)
-    const result = Data.File(table)
+    const result = Data.CsvFile(table)
     return Result.success(result);
 }
 
@@ -278,7 +278,7 @@ interface CsvOptions {
 
 export function parse(data: string, opts?: Partial<CsvOptions>) {
     const completeOpts = Object.assign({}, { quote: '"', comment: '#', delimiter: ',', noColumnNames: false }, opts)
-    return Task.create<Result<Data.File>>('Parse CSV', async ctx => {
+    return Task.create<Result<Data.CsvFile>>('Parse CSV', async ctx => {
         return await parseInternal(data, ctx, completeOpts);
     });
 }
diff --git a/src/mol-io/reader/gro/parser.ts b/src/mol-io/reader/gro/parser.ts
index eee90135b4a588a565b25ee1f5354f150d987f3b..afd469111309c7d3ef6626844ddcd431e520a0ff 100644
--- a/src/mol-io/reader/gro/parser.ts
+++ b/src/mol-io/reader/gro/parser.ts
@@ -14,12 +14,12 @@ import { Task, RuntimeContext } from 'mol-task'
 
 interface State {
     tokenizer: Tokenizer,
-    header: Schema.Header,
+    header: Schema.GroHeader,
     numberOfAtoms: number,
     runtimeCtx: RuntimeContext
 }
 
-function createEmptyHeader(): Schema.Header {
+function createEmptyHeader(): Schema.GroHeader {
     return {
         title: '',
         timeInPs: 0,
@@ -88,7 +88,7 @@ function handleNumberOfAtoms(state: State) {
  *     position (in nm, x y z in 3 columns, each 8 positions with 3 decimal places)
  *     velocity (in nm/ps (or km/s), x y z in 3 columns, each 8 positions with 4 decimal places)
  */
-async function handleAtoms(state: State): Promise<Schema.Atoms> {
+async function handleAtoms(state: State): Promise<Schema.GroAtoms> {
     const { tokenizer, numberOfAtoms } = state;
     const lines = await Tokenizer.readLinesAsync(tokenizer, numberOfAtoms, state.runtimeCtx, 100000);
 
@@ -137,11 +137,11 @@ function handleBoxVectors(state: State) {
     state.header.box = [+values[0], +values[1], +values[2]];
 }
 
-async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<Schema.File>> {
+async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<Schema.GroFile>> {
     const tokenizer = Tokenizer(data);
 
     ctx.update({ message: 'Parsing...', current: 0, max: data.length });
-    const structures: Schema.Structure[] = [];
+    const structures: Schema.GroStructure[] = [];
     while (tokenizer.position < data.length) {
         const state = State(tokenizer, ctx);
         handleTitleString(state);
@@ -151,12 +151,12 @@ async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<
         structures.push({ header: state.header, atoms });
     }
 
-    const result: Schema.File = { structures };
+    const result: Schema.GroFile = { structures };
     return Result.success(result);
 }
 
 export function parse(data: string) {
-    return Task.create<Result<Schema.File>>('Parse GRO', async ctx => {
+    return Task.create<Result<Schema.GroFile>>('Parse GRO', async ctx => {
         return await parseInternal(data, ctx);
     });
 }
diff --git a/src/mol-io/reader/gro/schema.d.ts b/src/mol-io/reader/gro/schema.d.ts
index 9a0b1ca550718e16822320091b9500b5cef0597c..ddc84303d6295939188f377c7dde653f0243ba76 100644
--- a/src/mol-io/reader/gro/schema.d.ts
+++ b/src/mol-io/reader/gro/schema.d.ts
@@ -7,7 +7,7 @@
 
 import { Column } from 'mol-data/db'
 
-export interface Header {
+export interface GroHeader {
     title: string,
     timeInPs: number,
     /** number of decimal places */
@@ -16,7 +16,7 @@ export interface Header {
     box: [number, number, number]
 }
 
-export interface Atoms {
+export interface GroAtoms {
     count: number,
     residueNumber: Column<number>,
     residueName: Column<string>,
@@ -30,11 +30,11 @@ export interface Atoms {
     vz: Column<number>
 }
 
-export interface Structure {
-    header: Readonly<Header>,
-    atoms: Readonly<Atoms>
+export interface GroStructure {
+    header: Readonly<GroHeader>,
+    atoms: Readonly<GroAtoms>
 }
 
-export interface File {
-    structures: Structure[]
+export interface GroFile {
+    structures: GroStructure[]
 }
\ No newline at end of file
diff --git a/src/mol-io/reader/mol2/parser.ts b/src/mol-io/reader/mol2/parser.ts
index 32126acb9050dbf33e490f2d9ffed2ca98a5f76c..297e1502618329594b0966427649ba7b014aaa87 100644
--- a/src/mol-io/reader/mol2/parser.ts
+++ b/src/mol-io/reader/mol2/parser.ts
@@ -22,11 +22,11 @@ const { skipWhitespace, eatValue, markLine, getTokenString, readLine } = Tokeniz
 
 interface State {
     tokenizer: Tokenizer,
-    molecule: Schema.Molecule,
+    molecule: Schema.Mol2Molecule,
     runtimeCtx: RuntimeContext
 }
 
-function createEmptyMolecule(): Schema.Molecule {
+function createEmptyMolecule(): Schema.Mol2Molecule {
     return {
         mol_name: '',
         num_atoms: 0,
@@ -93,7 +93,7 @@ function isStatus_bit(aString: String): Boolean {
     return false;
 }
 
-async function handleAtoms(state: State): Promise<Schema.Atoms> {
+async function handleAtoms(state: State): Promise<Schema.Mol2Atoms> {
     const { tokenizer, molecule } = state;
     let hasSubst_id = false;
     let hasSubst_name = false;
@@ -239,7 +239,7 @@ async function handleAtoms(state: State): Promise<Schema.Atoms> {
     return ret;
 }
 
-async function handleBonds(state: State): Promise<Schema.Bonds> {
+async function handleBonds(state: State): Promise<Schema.Mol2Bonds> {
     const { tokenizer, molecule } = state;
     let hasStatus_bit = false;
 
@@ -324,11 +324,11 @@ async function handleBonds(state: State): Promise<Schema.Bonds> {
     return ret;
 }
 
-async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<Schema.File>> {
+async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<Schema.Mol2File>> {
     const tokenizer = Tokenizer(data);
 
     ctx.update({ message: 'Parsing...', current: 0, max: data.length });
-    const structures: Schema.Structure[] = [];
+    const structures: Schema.Mol2Structure[] = [];
     while (tokenizer.position < data.length) {
         const state = State(tokenizer, ctx);
         handleMolecule(state);
@@ -337,12 +337,12 @@ async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<
         structures.push({ molecule: state.molecule, atoms, bonds });
     }
 
-    const result: Schema.File = { structures };
+    const result: Schema.Mol2File = { structures };
     return Result.success(result);
 }
 
 export function parse(data: string) {
-    return Task.create<Result<Schema.File>>('Parse MOL2', async ctx => {
+    return Task.create<Result<Schema.Mol2File>>('Parse MOL2', async ctx => {
         return await parseInternal(data, ctx);
     });
 }
diff --git a/src/mol-io/reader/mol2/schema.d.ts b/src/mol-io/reader/mol2/schema.d.ts
index 374b52d0d8063e5b0995336fb43590b0c5beae14..21205a2673f9675340daa94c7a9d4b9ae3d158ab 100644
--- a/src/mol-io/reader/mol2/schema.d.ts
+++ b/src/mol-io/reader/mol2/schema.d.ts
@@ -14,7 +14,7 @@ import { Column } from 'mol-data/db'
 //
 // note that the format is not a fixed column format but white space separated
 
-export interface Molecule {
+export interface Mol2Molecule {
     mol_name: string
     num_atoms: number
     num_bonds: number
@@ -27,7 +27,7 @@ export interface Molecule {
     mol_comment: string
 }
 
-export interface Atoms {
+export interface Mol2Atoms {
     count: number,
 
     atom_id: Column<number>,
@@ -44,7 +44,7 @@ export interface Atoms {
     status_bit: Column<string>
 }
 
-export interface Bonds {
+export interface Mol2Bonds {
     count: number,
 
     bond_id: Column<number>,
@@ -56,12 +56,12 @@ export interface Bonds {
     status_bits: Column<string>
 }
 
-export interface Structure {
-    molecule: Readonly<Molecule>,
-    atoms: Readonly<Atoms>,
-    bonds: Readonly<Bonds>
+export interface Mol2Structure {
+    molecule: Readonly<Mol2Molecule>,
+    atoms: Readonly<Mol2Atoms>,
+    bonds: Readonly<Mol2Bonds>
 }
 
-export interface File {
-    structures: Structure[]
+export interface Mol2File {
+    structures: Mol2Structure[]
 }
\ No newline at end of file
diff --git a/src/mol-model/structure/model/format.ts b/src/mol-model/structure/model/format.ts
index d463c30d70af897445b2293aca0726a14d53119a..59f96c2a79fd06f442d5a439196d3fe54576a9f6 100644
--- a/src/mol-model/structure/model/format.ts
+++ b/src/mol-model/structure/model/format.ts
@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { File as GroFile } from 'mol-io/reader/gro/schema'
+import { GroFile as GroFile } from 'mol-io/reader/gro/schema'
 import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif'
 
 type Format =
diff --git a/src/mol-model/structure/model/formats/gro.ts b/src/mol-model/structure/model/formats/gro.ts
index cb868ff96ee01927a9fb29f36640b62967e549bc..6e365bb3506446712b08ab3573ea92d8d66fc934 100644
--- a/src/mol-model/structure/model/formats/gro.ts
+++ b/src/mol-model/structure/model/formats/gro.ts
@@ -7,7 +7,7 @@
 import UUID from 'mol-util/uuid'
 import { Column, Table } from 'mol-data/db'
 import { Interval, Segmentation } from 'mol-data/int'
-import { Atoms } from 'mol-io/reader/gro/schema'
+import { GroAtoms } from 'mol-io/reader/gro/schema'
 import Format from '../format'
 import Model from '../model'
 import * as Hierarchy from '../properties/hierarchy'
@@ -24,7 +24,7 @@ import { Entities } from '../properties/common';
 
 type HierarchyOffsets = { residues: ArrayLike<number>, chains: ArrayLike<number> }
 
-function findHierarchyOffsets(atomsData: Atoms, bounds: Interval) {
+function findHierarchyOffsets(atomsData: GroAtoms, bounds: Interval) {
     const start = Interval.start(bounds), end = Interval.end(bounds);
     const residues = [start], chains = [start];
 
@@ -44,7 +44,7 @@ function guessElementSymbol (value: string) {
     return ElementSymbol(guessElement(value));
 }
 
-function createHierarchyData(atomsData: Atoms, offsets: HierarchyOffsets): Hierarchy.Data {
+function createHierarchyData(atomsData: GroAtoms, offsets: HierarchyOffsets): Hierarchy.Data {
     console.log(atomsData.atomName)
     const atoms = Table.ofColumns(Hierarchy.AtomsSchema, {
         type_symbol: Column.ofArray({ array: Column.mapToArray(atomsData.atomName, guessElementSymbol), schema: Column.Schema.Aliased<ElementSymbol>(Column.Schema.str) }),
@@ -77,7 +77,7 @@ function createHierarchyData(atomsData: Atoms, offsets: HierarchyOffsets): Hiera
     return { atoms, residues, chains };
 }
 
-function getConformation(atoms: Atoms): AtomSiteConformation {
+function getConformation(atoms: GroAtoms): AtomSiteConformation {
     return {
         id: UUID.create(),
         atomId: atoms.atomNumber,
diff --git a/src/mol-util/download.ts b/src/mol-util/download.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2e820378ce72dafdb060d158a04fe0a0bdc8eec5
--- /dev/null
+++ b/src/mol-util/download.ts
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+function openUrl (url: string) {
+    const opened = window.open(url, '_blank')
+    if (!opened) {
+        window.location.href = url
+    }
+}
+
+export function download (data: Blob|string, downloadName = 'download') {
+    // using ideas from https://github.com/eligrey/FileSaver.js/blob/master/FileSaver.js
+
+    if (!data) return
+
+    const ua = window.navigator.userAgent
+    const isSafari = /Safari/i.test(ua)
+    const isChromeIos = /CriOS\/[\d]+/.test(ua)
+
+    const a = document.createElement('a')
+
+    function open (str: string) {
+        openUrl(isChromeIos ? str : str.replace(/^data:[^;]*;/, 'data:attachment/file;'))
+    }
+
+    if (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob) {
+        // native saveAs in IE 10+
+        navigator.msSaveOrOpenBlob(data, downloadName)
+    } else if ((isSafari || isChromeIos) && FileReader) {
+        if (data instanceof Blob) {
+            // no downloading of blob urls in Safari
+            const reader = new FileReader()
+            reader.onloadend = () => open(reader.result)
+            reader.readAsDataURL(data)
+        } else {
+            open(data)
+        }
+    } else {
+        let objectUrlCreated = false
+        if (data instanceof Blob) {
+            data = URL.createObjectURL(data)
+            objectUrlCreated = true
+        }
+
+        if ('download' in a) {
+            // download link available
+            a.style.display = 'hidden'
+            document.body.appendChild(a)
+            a.href = data
+            a.download = downloadName
+            a.target = '_blank'
+            a.click()
+            document.body.removeChild(a)
+        } else {
+            openUrl(data)
+        }
+
+        if (objectUrlCreated) {
+            window.URL.revokeObjectURL(data)
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/mol-util/file-info.ts b/src/mol-util/file-info.ts
new file mode 100644
index 0000000000000000000000000000000000000000..193c2091ed8a124ca56bc458183c5c6d67c3b4e5
--- /dev/null
+++ b/src/mol-util/file-info.ts
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+/** A File or Blob object or a URL string */
+export type FileInput = File | Blob | string
+
+// TODO only support compressed files for which uncompression support is available???
+// TODO store globally with decompression plugins?
+const compressedExtList = [ 'gz', 'zip' ]
+
+// TODO store globally with parser plugins?
+const binaryExtList = [ 'bcif', 'ccp4', 'dcd' ]
+
+export interface FileInfo {
+    path: string
+    name: string
+    ext: string
+    base: string
+    dir: string
+    compressed: string | boolean
+    binary: boolean
+    protocol: string
+    query: string
+    src: FileInput
+}
+
+export function getFileInfo (file: FileInput): FileInfo {
+    let path: string
+    let compressed: string|false
+    let protocol = ''
+
+    if (file instanceof File) {
+        path = file.name
+    } else if (file instanceof Blob) {
+        path = ''
+    } else {
+        path = file
+    }
+    const queryIndex = path.lastIndexOf('?')
+    const query = queryIndex !== -1 ? path.substring(queryIndex) : ''
+    path = path.substring(0, queryIndex === -1 ? path.length : queryIndex)
+
+    const name = path.replace(/^.*[\\/]/, '')
+    let base = name.substring(0, name.lastIndexOf('.'))
+
+    const nameSplit = name.split('.')
+    let ext = nameSplit.length > 1 ? (nameSplit.pop() || '').toLowerCase() : ''
+
+    const protocolMatch = path.match(/^(.+):\/\/(.+)$/)
+    if (protocolMatch) {
+        protocol = protocolMatch[ 1 ].toLowerCase()
+        path = protocolMatch[ 2 ] || ''
+    }
+
+    const dir = path.substring(0, path.lastIndexOf('/') + 1)
+
+    if (compressedExtList.includes(ext)) {
+        compressed = ext
+        const n = path.length - ext.length - 1
+        ext = (path.substr(0, n).split('.').pop() || '').toLowerCase()
+        const m = base.length - ext.length - 1
+        base = base.substr(0, m)
+    } else {
+        compressed = false
+    }
+
+    const binary = binaryExtList.includes(ext)
+
+    return { path, name, ext, base, dir, compressed, binary, protocol, query, src: file }
+}
\ No newline at end of file
diff --git a/src/mol-util/id-factory.ts b/src/mol-util/id-factory.ts
index 1f0a939fc28075255d36e854cfd948412731ea69..0de5654ff7194cf31ac15dd50e1d08d190e49cd9 100644
--- a/src/mol-util/id-factory.ts
+++ b/src/mol-util/id-factory.ts
@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-export function idFactory() {
-    let _nextId = 0
+export function idFactory(firstId = 0) {
+    let _nextId = firstId
     return () => _nextId++
 }
\ No newline at end of file
diff --git a/src/mol-util/index.ts b/src/mol-util/index.ts
index 8cb116ef3a21bb50e51bffc14530f58647a1d0a2..94dcad374a72eb65156bd784042ca59f69a04443 100644
--- a/src/mol-util/index.ts
+++ b/src/mol-util/index.ts
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -9,10 +9,16 @@ import BitFlags from './bit-flags'
 import StringBuilder from './string-builder'
 import UUID from './uuid'
 import Mask from './mask'
+import { Progress } from 'mol-task';
 
 export * from './value-cell'
 export { BitFlags, StringBuilder, UUID, Mask }
 
+export function round(n: number, d: number) {
+    let f = Math.pow(10, d)
+    return Math.round(f * n) / f
+}
+
 export function arrayEqual<T>(arr1: T[], arr2: T[]) {
     const length = arr1.length
     if (length !== arr2.length) return false
@@ -69,6 +75,111 @@ export function deepEqual(a: any, b: any) {
     return false
 }
 
+const hasOwnProperty = Object.prototype.hasOwnProperty;
+
+export function shallowEqual<T>(a: T, b: T) {
+    if (!a) {
+        if (!b) return true;
+        return false;
+    }
+    if (!b) return false;
+
+    let keys = Object.keys(a);
+    if (Object.keys(b).length !== keys.length) return false;
+    for (let k of keys) {
+        if (!hasOwnProperty.call(a, k) || (a as any)[k] !== (b as any)[k]) return false;
+    }
+
+    return true;
+}
+
 export function defaults(value: any, defaultValue: any) {
     return value !== undefined ? value : defaultValue
+}
+
+export function extend<S, T, U>(object: S, source: T, guard?: U): S & T & U {
+    let v: any;
+
+    let s = <any>source;
+    let o = <any>object;
+    let g = <any>guard;
+    for (let k of Object.keys(source)) {
+        v = s[k];
+        if (v !== void 0) o[k] = v;
+        else if (guard) o[k] = g[k];
+    }
+
+    if (guard) {
+        for (let k of Object.keys(guard)) {
+            v = o[k];
+            if (v === void 0) o[k] = g[k];
+        }
+    }
+
+    return <any>object;
+}
+
+export function shallowClone<T>(o: T): T {
+    return extend({}, o) as T;
+}
+
+function _assign<T>(target: T): T {
+    for (let s = 1; s < arguments.length; s++) {
+        let from = arguments[s];
+        for (let key of Object.keys(from)) {
+            if (hasOwnProperty.call(from, key)) {
+                (target as any)[key] = from[key];
+            }
+        }
+    }
+    return target;
+}
+
+export declare function _assignType<T>(o: T, ...from: any[]): T;
+export const assign: (<T>(o: T, ...from: any[]) => T) = (Object as any).assign || _assign;
+
+function _shallowMerge1<T>(source: T, update: T) {
+    let changed = false;
+    for (let k of Object.keys(update)) {
+        if (!hasOwnProperty.call(update, k)) continue;
+
+        if ((update as any)[k] !== (source as any)[k]) {
+            changed = true;
+            break;
+        }
+    }
+
+    if (!changed) return source;
+    return assign(shallowClone(source), update);
+}
+
+function _shallowMerge<T>(source: T) {
+    let ret = source;
+
+    for (let s = 1; s < arguments.length; s++) {
+        if (!arguments[s]) continue;
+        ret = _shallowMerge1(source, arguments[s]);
+        if (ret !== source) {
+            for (let i = s + 1; i < arguments.length; i++) {
+                ret = assign(ret, arguments[i]);
+            }
+            break;
+        }
+    }
+    return ret;
+}
+
+export const merge: (<T>(source: T, ...rest: Partial<T>[]) => T)= _shallowMerge;
+
+function padTime(n: number) { return (n < 10 ? '0' : '') + n }
+export function formatTime(d: Date) {
+    const h = d.getHours(), m = d.getMinutes(), s = d.getSeconds();
+    return `${h}:${padTime(m)}:${padTime(s)}`;
+}
+
+export function formatProgress(p: Progress) {
+    const tp = p.root.progress
+    if (tp.isIndeterminate) return tp.message;
+    const x = (100 * tp.current / tp.max).toFixed(2);
+    return `${tp.message} ${x}%`;
 }
\ No newline at end of file
diff --git a/src/mol-util/parse-unit.ts b/src/mol-util/parse-unit.ts
index 4071eec017d1810712f2a0e77438908337f97a36..e0e0652b1ef34104ea50bc7037e621c91aaa739b 100644
--- a/src/mol-util/parse-unit.ts
+++ b/src/mol-util/parse-unit.ts
@@ -11,6 +11,7 @@
 
 const reUnit = /[\d.\-\+]*\s*(.*)/
 
+/** Parsing value of, for example, CSS unit strings */
 export default function parseUnit(str: string, out: [number, string] = [ 0, '' ]) {
     str = String(str)
     const num = parseFloat(str)
diff --git a/src/mol-util/performance-monitor.ts b/src/mol-util/performance-monitor.ts
new file mode 100644
index 0000000000000000000000000000000000000000..614b19a29930bb8eda3424b5e7a5f731acb8493c
--- /dev/null
+++ b/src/mol-util/performance-monitor.ts
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ */
+
+import { now } from 'mol-task/util/now'
+
+export class PerformanceMonitor {
+    private starts = new Map<string, number>();
+    private ends = new Map<string, number>();
+
+    static currentTime() {
+        return now();
+    }
+
+    start(name: string) {
+        this.starts.set(name, now());
+    }
+
+    end(name: string) {
+        this.ends.set(name, now());
+    }
+
+    static format(t: number) {
+        if (isNaN(t)) return 'n/a';
+
+        let h = Math.floor(t / (60 * 60 * 1000)),
+            m = Math.floor(t / (60 * 1000) % 60),
+            s = Math.floor(t / 1000 % 60),
+            ms = Math.floor(t % 1000).toString();
+
+        while (ms.length < 3) ms = '0' + ms;
+
+        if (h > 0) return `${h}h${m}m${s}.${ms}s`;
+        if (m > 0) return `${m}m${s}.${ms}s`;
+        if (s > 0) return `${s}.${ms}s`;
+        return `${t.toFixed(0)}ms`;
+    }
+
+    formatTime(name: string) {
+        return PerformanceMonitor.format(this.time(name));
+    }
+
+    formatTimeSum(...names: string[]) {
+        return PerformanceMonitor.format(this.timeSum(...names));
+    }
+
+    /** Returns the time in milliseconds and removes them from the cache. */
+    time(name: string): number {
+        let start = this.starts.get(name)!, end = this.ends.get(name)!;
+
+        this.starts.delete(name);
+        this.ends.delete(name);
+
+        return end - start;
+    }
+
+    timeSum(...names: string[]) {
+        let t = 0;
+        for (let m of names.map(n => this.ends.get(n)! - this.starts.get(n)!)) t += m;
+        return t;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-util/read.ts b/src/mol-util/read.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b88abd9ebfb5587d9800b7eea9af00d0ce832401
--- /dev/null
+++ b/src/mol-util/read.ts
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+export const readFileAs = (file: File, isBinary = false) => {
+    const fileReader = new FileReader()
+    return new Promise<string | Uint8Array>((resolve, reject) => {
+        fileReader.onerror = () => {
+            fileReader.abort()
+            reject(new DOMException('Error parsing file.'))
+        }
+        fileReader.onload = () => {
+            resolve(isBinary ? new Uint8Array(fileReader.result) : fileReader.result)
+        }
+        if (isBinary) {
+            fileReader.readAsArrayBuffer(file)
+        } else {
+            fileReader.readAsText(file)
+        }
+    })
+}
+
+export function readFileAsText(file: File) {
+    return readFileAs(file, false) as Promise<string>
+}
+
+export function readFileAsBuffer(file: File) {
+    return readFileAs(file, true) as Promise<Uint8Array>
+}
+
+export async function readUrlAs(url: string, isBinary: boolean) {
+    const response = await fetch(url);
+    return isBinary ? new Uint8Array(await response.arrayBuffer()) : await response.text();
+}
+
+export function readUrlAsText(url: string) {
+    return readUrlAs(url, false) as Promise<string>
+}
+
+export function readUrlAsBuffer(url: string) {
+    return readUrlAs(url, true) as Promise<Uint8Array>
+}
\ No newline at end of file
diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fbc3556642efd844813f690223c385097431553d
--- /dev/null
+++ b/src/mol-view/stage.ts
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import Viewer from 'mol-view/viewer'
+import { StateContext } from './state/context';
+import { Progress } from 'mol-task';
+import { MmcifUrlToSpacefill } from './state/transform';
+import { UrlEntity } from './state/entity';
+import { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
+
+// export const ColorTheme = {
+//     'atom-index': {},
+//     'chain-id': {},
+//     'element-symbol': {},
+//     'instance-index': {},
+//     'uniform': {}
+// }
+// export type ColorTheme = keyof typeof ColorTheme
+
+const spacefillProps: SpacefillProps = {
+    doubleSided: true,
+    detail: 2,
+    colorTheme: { name: 'element-symbol' }
+}
+
+export class Stage {
+    viewer: Viewer
+    ctx = new StateContext(Progress.format)
+
+    constructor() {
+
+    }
+
+    async initRenderer (canvas: HTMLCanvasElement, container: HTMLDivElement) {
+        this.viewer = Viewer.create(canvas, container)
+        this.viewer.animate()
+        this.ctx.viewer = this.viewer
+        this.loadPdbid('1crn')
+    }
+
+    async loadPdbid (pdbid: string) {
+        const urlEntity = UrlEntity.ofUrl(this.ctx, `https://files.rcsb.org/download/${pdbid}.cif`)
+        MmcifUrlToSpacefill.apply(this.ctx, urlEntity, spacefillProps)
+    }
+
+    dispose () {
+        // TODO
+    }
+}
\ No newline at end of file
diff --git a/src/mol-view/state/context.ts b/src/mol-view/state/context.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2893a5e37fa78d807888cee2cf4effee7a2efcf6
--- /dev/null
+++ b/src/mol-view/state/context.ts
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { BehaviorSubject } from 'rxjs';
+import { UUID } from 'mol-util'
+import { AnyEntity } from './entity';
+import Viewer from '../viewer';
+import { Progress } from 'mol-task';
+
+// TODO
+export type StateTree = {}
+
+export class StateContext {
+    id = UUID.create()
+    change = new BehaviorSubject(0)
+
+    tree: StateTree = {}
+    entities: Set<AnyEntity> = new Set()
+
+    viewer: Viewer
+
+    constructor(readonly log: (p: Progress) => void) {
+
+    }
+}
diff --git a/src/mol-view/state/entity.ts b/src/mol-view/state/entity.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b77e5b82215d3f1823fd4821b379ca0561feb945
--- /dev/null
+++ b/src/mol-view/state/entity.ts
@@ -0,0 +1,120 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { readFileAs, readUrlAs } from 'mol-util/read'
+import { idFactory } from 'mol-util/id-factory'
+import { StateContext } from './context';
+import { getFileInfo } from 'mol-util/file-info';
+import { CifFile } from 'mol-io/reader/cif';
+import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif';
+import { Model, Structure } from 'mol-model/structure';
+import { StructureRepresentation } from 'mol-geo/representation/structure';
+import { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
+
+const getNextId = idFactory(1)
+
+export interface StateEntity<T, K extends string> {
+    id: number
+    kind: K
+    value: T
+}
+export namespace StateEntity {
+    export function create<T, K extends string>(ctx: StateContext, kind: K, value: T): StateEntity<T, K> {
+        const entity = { id: getNextId(), kind, value }
+        ctx.entities.add(entity)
+        ctx.change.next(ctx.change.getValue() + 1)
+        return entity
+    }
+}
+
+export type AnyEntity = StateEntity<any, any>
+
+export const RootEntity: StateEntity<null, 'root'> = { id: 0, kind: 'root', value: null }
+
+export interface UrlProps {
+    url: string
+    name: string
+    type: string
+    getData: () => Promise<string | Uint8Array>
+}
+
+export type UrlEntity = StateEntity<UrlProps, 'url'>
+export namespace UrlEntity {
+    export function ofUrl(ctx: StateContext, url: string, isBinary?: boolean): UrlEntity {
+        const { name, ext: type, compressed, binary } = getFileInfo(url)
+        return StateEntity.create(ctx, 'url', {
+            url, name, type,
+            getData: () => readUrlAs(url, isBinary || !!compressed || binary)
+        })
+    }
+}
+
+export interface FileProps {
+    name: string
+    type: string
+    getData: () => Promise<string | Uint8Array>
+}
+
+export type FileEntity = StateEntity<FileProps, 'file'>
+export namespace FileEntity {
+    export function ofFile(ctx: StateContext, file: File, isBinary?: boolean): FileEntity {
+        const { name, ext: type, compressed, binary } = getFileInfo(file)
+        return StateEntity.create(ctx, 'file', {
+            name, type,
+            getData: () => readFileAs(file, isBinary || !!compressed || binary)
+        })
+    }
+}
+
+export interface DataProps {
+    type: string
+    data: string | Uint8Array
+}
+
+export type DataEntity = StateEntity<DataProps, 'data'>
+export namespace DataEntity {
+    export function ofData<T>(ctx: StateContext, data: string | Uint8Array, type: string): DataEntity {
+        return StateEntity.create(ctx, 'data', {
+            type,
+            data
+        })
+    }
+}
+
+export type CifEntity = StateEntity<CifFile, 'cif'>
+export namespace CifEntity {
+    export function ofCifFile(ctx: StateContext, file: CifFile): CifEntity {
+        return StateEntity.create(ctx, 'cif', file)
+    }
+}
+
+export type MmcifEntity = StateEntity<mmCIF_Database, 'mmcif'>
+export namespace MmcifEntity {
+    export function ofMmcifDb(ctx: StateContext, db: mmCIF_Database): MmcifEntity {
+        return StateEntity.create(ctx, 'mmcif', db)
+    }
+}
+
+export type ModelEntity = StateEntity<ReadonlyArray<Model>, 'model'>
+export namespace ModelEntity {
+    export function ofModels(ctx: StateContext, models: ReadonlyArray<Model>): ModelEntity {
+        return StateEntity.create(ctx, 'model', models)
+    }
+}
+
+export type StructureEntity = StateEntity<Structure, 'structure'>
+export namespace StructureEntity {
+    export function ofStructure(ctx: StateContext, structure: Structure): StructureEntity {
+        return StateEntity.create(ctx, 'structure', structure)
+    }
+}
+
+export type SpacefillEntity = StateEntity<StructureRepresentation<SpacefillProps>, 'spacefill'>
+export namespace SpacefillEntity {
+    export function ofRepr(ctx: StateContext, repr: StructureRepresentation<SpacefillProps>): SpacefillEntity {
+        return StateEntity.create(ctx, 'spacefill', repr )
+    }
+}
\ No newline at end of file
diff --git a/src/mol-view/state/transform.ts b/src/mol-view/state/transform.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fe02007907f7b1dbbf84790599ec9c25b7e5edaa
--- /dev/null
+++ b/src/mol-view/state/transform.ts
@@ -0,0 +1,152 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import CIF from 'mol-io/reader/cif'
+import { FileEntity, DataEntity, UrlEntity, CifEntity, MmcifEntity, ModelEntity, StructureEntity, SpacefillEntity, AnyEntity } from './entity';
+import { Run } from 'mol-task';
+import { Model, Structure, Symmetry } from 'mol-model/structure';
+
+import { StateContext } from './context';
+import Spacefill, { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
+import { StructureRepresentation } from 'mol-geo/representation/structure';
+
+type transformer<I extends AnyEntity, O extends AnyEntity, P extends {}> = (ctx: StateContext, inputEntity: I, props?: P) => Promise<O>
+
+export interface StateTransform<I extends AnyEntity, O extends AnyEntity, P extends {}> {
+    inputKind: I['kind']
+    outputKind: O['kind']
+    kind: string
+    apply: transformer<I, O, P>
+}
+
+export namespace StateTransform {
+    export function create<I extends AnyEntity, O extends AnyEntity, P extends {}>(inputKind: I['kind'], outputKind: O['kind'], kind: string, transformer: transformer<I, O, P>) {
+        return { inputKind, outputKind, kind, apply: transformer }
+    }
+}
+
+export type AnyTransform = StateTransform<AnyEntity, AnyEntity, {}>
+
+export type UrlToData = StateTransform<UrlEntity, DataEntity, {}>
+export const UrlToData: UrlToData = StateTransform.create('url', 'data', 'url-to-data',
+    async function (ctx: StateContext, urlEntity: UrlEntity) {
+        return DataEntity.ofData(ctx, await urlEntity.value.getData(), urlEntity.value.type)
+    })
+
+export type FileToData = StateTransform<FileEntity, DataEntity, {}>
+export const FileToData: FileToData = StateTransform.create('file', 'data', 'file-to-data',
+    async function (ctx: StateContext, fileEntity: FileEntity) {
+        return DataEntity.ofData(ctx, await fileEntity.value.getData(), fileEntity.value.type)
+    })
+
+export type DataToCif = StateTransform<DataEntity, CifEntity, {}>
+export const DataToCif: DataToCif = StateTransform.create('data', 'cif', 'data-to-cif',
+    async function (ctx: StateContext, dataEntity: DataEntity) {
+        const comp = CIF.parse(dataEntity.value.data)
+        const parsed = await Run(comp, ctx.log)
+        if (parsed.isError) throw parsed
+        return CifEntity.ofCifFile(ctx, parsed.result)
+    })
+
+export type CifToMmcif = StateTransform<CifEntity, MmcifEntity, {}>
+export const CifToMmcif: CifToMmcif = StateTransform.create('cif', 'mmcif', 'cif-to-mmcif',
+    async function (ctx: StateContext, cifEntity: CifEntity) {
+        return MmcifEntity.ofMmcifDb(ctx, CIF.schema.mmCIF(cifEntity.value.blocks[0]))
+    })
+
+export type MmcifToModel = StateTransform<MmcifEntity, ModelEntity, {}>
+export const MmcifToModel: MmcifToModel = StateTransform.create('mmcif', 'model', 'mmcif-to-model',
+    async function (ctx: StateContext, mmcifEntity: MmcifEntity) {
+        return ModelEntity.ofModels(ctx, Model.create({ kind: 'mmCIF', data: mmcifEntity.value }))
+    })
+
+export interface StructureProps {
+    assembly?: string
+}
+
+export type ModelToStructure = StateTransform<ModelEntity, StructureEntity, StructureProps>
+export const ModelToStructure: ModelToStructure = StateTransform.create('model', 'structure', 'model-to-structure',
+    async function (ctx: StateContext, modelEntity: ModelEntity, props: StructureProps = {}) {
+        const model = modelEntity.value[0]
+        const assembly = props.assembly
+        let structure: Structure
+        const assemblies = model.symmetry.assemblies
+        if (assemblies.length) {
+            structure = await Run(Symmetry.buildAssembly(Structure.ofModel(model), assembly || '1'), ctx.log)
+        } else {
+            structure = Structure.ofModel(model)
+        }
+        return StructureEntity.ofStructure(ctx, structure)
+    })
+
+export type StructureToSpacefill = StateTransform<StructureEntity, SpacefillEntity, SpacefillProps>
+export const StructureToSpacefill: StructureToSpacefill = StateTransform.create('structure', 'spacefill', 'structure-to-spacefill',
+    async function (ctx: StateContext, structureEntity: StructureEntity, props: SpacefillProps = {}) {
+        const spacefillRepr = StructureRepresentation(Spacefill)
+        await Run(spacefillRepr.create(structureEntity.value, props), ctx.log)
+        ctx.viewer.add(spacefillRepr)
+        ctx.viewer.requestDraw()
+        console.log(ctx.viewer.stats)
+        return SpacefillEntity.ofRepr(ctx, spacefillRepr)
+    })
+
+export type SpacefillUpdate = StateTransform<SpacefillEntity, SpacefillEntity, SpacefillProps>
+export const SpacefillUpdate: SpacefillUpdate = StateTransform.create('spacefill', 'spacefill', 'spacefill-update',
+    async function (ctx: StateContext, spacefillEntity: SpacefillEntity, props: SpacefillProps = {}) {
+        console.log('fopbar')
+        const spacefillRepr = spacefillEntity.value
+        await Run(spacefillRepr.update(props), ctx.log)
+        ctx.viewer.add(spacefillRepr)
+        ctx.viewer.update()
+        ctx.viewer.requestDraw()
+        console.log(ctx.viewer.stats)
+        return spacefillEntity
+    })
+
+// composed
+
+export type MmcifUrlToModel = StateTransform<UrlEntity, ModelEntity, {}>
+export const MmcifUrlToModel: MmcifUrlToModel = StateTransform.create('url', 'model', 'url-to-model',
+    async function (ctx: StateContext, urlEntity: UrlEntity) {
+        const dataEntity = await UrlToData.apply(ctx, urlEntity)
+        return DataToModel.apply(ctx, dataEntity)
+    })
+
+export type MmcifFileToModel = StateTransform<FileEntity, ModelEntity, {}>
+export const MmcifFileToModel: MmcifFileToModel = StateTransform.create('file', 'model', 'file-to-model',
+    async function (ctx: StateContext, fileEntity: FileEntity) {
+        const dataEntity = await FileToData.apply(ctx, fileEntity)
+        return DataToModel.apply(ctx, dataEntity)
+    })
+
+export type DataToModel = StateTransform<DataEntity, ModelEntity, {}>
+export const DataToModel: DataToModel = StateTransform.create('data', 'model', 'data-to-model',
+    async function getModelFromData(ctx: StateContext, dataEntity: DataEntity) {
+        const cifEntity = await DataToCif.apply(ctx, dataEntity)
+        const mmcifEntity = await CifToMmcif.apply(ctx, cifEntity)
+        return MmcifToModel.apply(ctx, mmcifEntity)
+    })
+
+export type ModelToSpacefill = StateTransform<ModelEntity, SpacefillEntity, SpacefillProps>
+export const ModelToSpacefill: ModelToSpacefill = StateTransform.create('model', 'spacefill', 'model-to-spacefill',
+    async function (ctx: StateContext, modelEntity: ModelEntity, props: SpacefillProps = {}) {
+        const structureEntity = await ModelToStructure.apply(ctx, modelEntity)
+        return StructureToSpacefill.apply(ctx, structureEntity, props)
+    })
+
+export type MmcifUrlToSpacefill = StateTransform<UrlEntity, SpacefillEntity, SpacefillProps>
+export const MmcifUrlToSpacefill: MmcifUrlToSpacefill = StateTransform.create('url', 'spacefill', 'url-to-spacefill',
+    async function (ctx: StateContext, urlEntity: UrlEntity, props: SpacefillProps = {}) {
+        const modelEntity = await MmcifUrlToModel.apply(ctx, urlEntity)
+        return ModelToSpacefill.apply(ctx, modelEntity, props)
+    })
+
+export type MmcifFileToSpacefill = StateTransform<FileEntity, SpacefillEntity, SpacefillProps>
+export const MmcifFileToSpacefill: MmcifFileToSpacefill = StateTransform.create('file', 'spacefill', 'file-to-spacefill',
+    async function (ctx: StateContext, fileEntity: FileEntity, props: SpacefillProps = {}) {
+        const modelEntity = await MmcifFileToModel.apply(ctx, fileEntity)
+        return ModelToSpacefill.apply(ctx, modelEntity, props)
+    })
\ No newline at end of file
diff --git a/src/mol-view/util.ts b/src/mol-view/util.ts
index 294907ad30cdd987b883f7b95848829de48a611e..cf9343d2450dfca85f2045008d054f939240c0eb 100644
--- a/src/mol-view/util.ts
+++ b/src/mol-view/util.ts
@@ -4,6 +4,31 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
+import CIF from 'mol-io/reader/cif'
+import { Run, Progress } from 'mol-task'
+import { VolumeData, parseDensityServerData } from 'mol-model/volume'
+import { DensityServer_Data_Database } from 'mol-io/reader/cif/schema/density-server';
+
+export async function downloadCif(url: string, isBinary: boolean) {
+    const data = await fetch(url);
+    return parseCif(isBinary ? new Uint8Array(await data.arrayBuffer()) : await data.text());
+}
+
+export async function parseCif(data: string|Uint8Array) {
+    const comp = CIF.parse(data)
+    const parsed = await Run(comp, Progress.format);
+    if (parsed.isError) throw parsed;
+    return parsed.result
+}
+
+export type Volume = { source: DensityServer_Data_Database, volume: VolumeData }
+
+export async function getVolumeFromEmdId(emdid: string): Promise<Volume> {
+    const cif = await downloadCif(`https://webchem.ncbr.muni.cz/DensityServer/em/emd-${emdid}/cell?detail=4`, true)
+    const data = CIF.schema.densityServer(cif.blocks[1])
+    return { source: data, volume: await Run(parseDensityServerData(data)) }
+}
+
 export function resizeCanvas (canvas: HTMLCanvasElement, container: Element) {
     let w = window.innerWidth
     let h = window.innerHeight
diff --git a/src/mol-view/viewer.ts b/src/mol-view/viewer.ts
index 4e8d9c26230f39a4407a14e79c137811f6bb9cb2..c4384837d32afff409180b28d77f5267fe8091f4 100644
--- a/src/mol-view/viewer.ts
+++ b/src/mol-view/viewer.ts
@@ -4,6 +4,8 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
+import { BehaviorSubject } from 'rxjs';
+
 import { Vec3, Mat4, EPSILON } from 'mol-math/linear-algebra'
 import InputObserver from 'mol-util/input/input-observer'
 import * as SetUtils from 'mol-util/set'
@@ -29,8 +31,11 @@ interface Viewer {
     draw: (force?: boolean) => void
     requestDraw: () => void
     animate: () => void
+    reprCount: BehaviorSubject<number>
 
     handleResize: () => void
+    resetCamera: () => void
+    downloadScreenshot: () => void
 
     stats: RendererStats
     dispose: () => void
@@ -50,6 +55,7 @@ function getWebGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLCon
 namespace Viewer {
     export function create(canvas: HTMLCanvasElement, container: Element): Viewer {
         const reprMap = new Map<Representation<any>, Set<RenderObject>>()
+        const reprCount = new BehaviorSubject(0)
 
         const input = InputObserver.create(canvas)
         input.resize.subscribe(handleResize)
@@ -124,10 +130,15 @@ namespace Viewer {
                     repr.renderObjects.forEach(o => renderer.add(o))
                 }
                 reprMap.set(repr, newRO)
+                reprCount.next(reprMap.size)
             },
             remove: (repr: Representation<any>) => {
                 const renderObjectSet = reprMap.get(repr)
-                if (renderObjectSet) renderObjectSet.forEach(o => renderer.remove(o))
+                if (renderObjectSet) {
+                    renderObjectSet.forEach(o => renderer.remove(o))
+                    reprMap.delete(repr)
+                    reprCount.next(reprMap.size)
+                }
             },
             update: () => renderer.update(),
             clear: () => {
@@ -140,6 +151,13 @@ namespace Viewer {
             animate,
 
             handleResize,
+            resetCamera: () => {
+                // TODO
+            },
+            downloadScreenshot: () => {
+                // TODO
+            },
+            reprCount,
 
             get stats() {
                 return renderer.stats
diff --git a/tsconfig.json b/tsconfig.json
index 7e025fd23915e407f1caf32627bad388cd7817cd..eedd1592ae0767790c69a6dbeece0b25e46483fd 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -14,6 +14,7 @@
         "outDir": "build/node_modules",
         "baseUrl": "src",
         "paths": {
+            "mol-app": ["./mol-app"],
             "mol-data": ["./mol-data", "./mol-data/index.ts"],
             "mol-geo": ["./mol-geo"],
             "mol-gl": ["./mol-gl"],
diff --git a/web/render-test/index.html b/web/render-test/index.html
deleted file mode 100644
index 2e144b29791e36ed9926e7d0785e4ffae563cc7d..0000000000000000000000000000000000000000
--- a/web/render-test/index.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-    <head>
-        <meta charset="utf-8" />
-        <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=1" />
-        <title>Mol* Render Test</title>
-    </head>
-    <body>
-        <div id="app"></div>
-        <script src="./index.js"></script>
-    </body>
-</html>
\ No newline at end of file
diff --git a/webpack.config.js b/webpack.config.js
index 8ace2768dd73a4664e387ad55298b3f5a184c0ee..0a6bc7e12386ac49423d6771d2b950530348dc67 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,17 +1,34 @@
 const path = require('path');
 const ExtraWatchWebpackPlugin = require('extra-watch-webpack-plugin');
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
 module.exports = {
     module: {
         rules: [
             {
                 loader: 'raw-loader',
                 test: /\.(glsl|frag|vert)$/,
-                include: [ path.resolve(__dirname, "build/node_modules/") ],
+                include: [ path.resolve(__dirname, 'build/node_modules/') ],
             },
             {
                 loader: 'glslify-loader',
                 test: /\.(glsl|frag|vert)$/,
-                include: [ path.resolve(__dirname, "build/node_modules/") ]
+                include: [ path.resolve(__dirname, 'build/node_modules/') ]
+            },
+
+            {
+                loader: 'file-loader',
+                test: /\.(woff2?|ttf|otf|eot|svg|html)$/,
+                include: [ path.resolve(__dirname, 'build/node_modules/') ],
+                options: {
+                    name: '[name].[ext]'
+                }
+            },
+            {
+                test:/\.(s*)css$/,
+                use: ExtractTextPlugin.extract({
+                    fallback:'style-loader',
+                    use:['css-loader', 'resolve-url-loader', 'sass-loader'],
+                })
             }
         ]
     },
@@ -20,8 +37,11 @@ module.exports = {
             files: [
                 './build/node_modules/**/*.vert',
                 './build/node_modules/**/*.frag',
-                './build/node_modules/**/*.glsl'
+                './build/node_modules/**/*.glsl',
+                './build/node_modules/**/*.scss',
+                './build/node_modules/**/*.html'
             ],
         }),
+        new ExtractTextPlugin({ filename:'app.css' }),
     ],
 }
\ No newline at end of file