UI: Switch to SCSS

Adds some initial scss support to the UI build.

Change-Id: I10c1c5b9a79be1c122cf169599dc62a17a8bd465
diff --git a/ui/BUILD.gn b/ui/BUILD.gn
index a42e9bb..7ebca4c 100644
--- a/ui/BUILD.gn
+++ b/ui/BUILD.gn
@@ -28,10 +28,10 @@
   deps = [
     ":assets_dist",
     ":controller_bundle_dist",
-    ":css_dist",
     ":engine_bundle_dist",
     ":frontend_bundle_dist",
     ":index_dist",
+    ":scss",
     ":test_scripts",
     ":wasm_dist",
   ]
@@ -247,6 +247,33 @@
 }
 
 # +----------------------------------------------------------------------------+
+# | Build css.                                                                 |
+# +----------------------------------------------------------------------------+
+
+# Build css.
+node_bin("scss") {
+  deps = [
+    ":dist_symlink",
+  ]
+  main_css = "src/assets/perfetto.scss"
+  inputs = [
+    main_css,
+    "src/assets/sidebar.scss",
+    "src/assets/topbar.scss",
+  ]
+  outputs = [
+    "$ui_dir/perfetto.css",
+  ]
+
+  node_cmd = "node-sass"
+  args = [
+    "--quiet",
+    rebase_path(main_css, root_build_dir),
+    rebase_path(outputs[0], root_build_dir),
+  ]
+}
+
+# +----------------------------------------------------------------------------+
 # | Copy rules: create the final output directory.                             |
 # +----------------------------------------------------------------------------+
 copy("index_dist") {
@@ -258,14 +285,6 @@
   ]
 }
 
-copy("css_dist") {
-  sources = [
-    "perfetto.css",
-  ]
-  outputs = [
-    "$ui_dir/perfetto.css",
-  ]
-}
 copy("assets_dist") {
   sources = [
     "src/assets",
diff --git a/ui/index.html b/ui/index.html
index 0217eb1..a1cf24a 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -2,16 +2,16 @@
 <html lang="en-us">
 <head>
   <title>Perfetto UI</title>
+  <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" />
   <link href="perfetto.css" rel="stylesheet">
+  <link rel="icon" type="image/png" href="/assets/logo.png">
   <link rel="preload" href="controller_bundle.js" as="script">
   <link rel="preload" href="engine_bundle.js" as="script">
   <link rel="preload" href="trace_processor.wasm" as="fetch">
   <link href='https://fonts.googleapis.com/css?family=Raleway:100,400|Google+Sans:100,400,500|Inconsolata|Material+Icons' rel='stylesheet' type='text/css'>
 </head>
 <body>
-  <main>
-    <div class="full-page-loading-screen"></div>
-  </main>
-  <script src="frontend_bundle.js"></script>
+  <div class="full-page-loading-screen"></div>
 </body>
 </html>
+<script src="frontend_bundle.js"></script>
diff --git a/ui/package-lock.json b/ui/package-lock.json
index 92e835d..ad10e77 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -127,6 +127,12 @@
       "integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=",
       "dev": true
     },
+    "abbrev": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+      "dev": true
+    },
     "accepts": {
       "version": "1.3.5",
       "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
@@ -267,6 +273,22 @@
         "default-require-extensions": "^1.0.0"
       }
     },
+    "aproba": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+      "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
+      "dev": true
+    },
+    "are-we-there-yet": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
+      "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
+      "dev": true,
+      "requires": {
+        "delegates": "^1.0.0",
+        "readable-stream": "^2.0.6"
+      }
+    },
     "argparse": {
       "version": "1.0.10",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -300,6 +322,12 @@
       "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
       "dev": true
     },
+    "array-find-index": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
+      "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
+      "dev": true
+    },
     "array-unique": {
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
@@ -363,6 +391,12 @@
       "integrity": "sha1-dhfBkXQB/Yykooqtzj266Yr+tDI=",
       "dev": true
     },
+    "async-foreach": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
+      "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=",
+      "dev": true
+    },
     "async-limiter": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
@@ -759,6 +793,24 @@
       "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=",
       "dev": true
     },
+    "block-stream": {
+      "version": "0.0.9",
+      "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
+      "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
+      "dev": true,
+      "requires": {
+        "inherits": "~2.0.0"
+      }
+    },
+    "boom": {
+      "version": "2.10.1",
+      "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
+      "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
+      "dev": true,
+      "requires": {
+        "hoek": "2.x.x"
+      }
+    },
     "brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -1051,6 +1103,24 @@
       "dev": true,
       "optional": true
     },
+    "camelcase-keys": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+      "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
+      "dev": true,
+      "requires": {
+        "camelcase": "^2.0.0",
+        "map-obj": "^1.0.0"
+      },
+      "dependencies": {
+        "camelcase": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+          "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
+          "dev": true
+        }
+      }
+    },
     "capture-exit": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-1.2.0.tgz",
@@ -1305,6 +1375,12 @@
         "moment": "*"
       }
     },
+    "console-control-strings": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+      "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
+      "dev": true
+    },
     "cookie": {
       "version": "0.3.1",
       "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
@@ -1340,6 +1416,15 @@
         "which": "^1.2.9"
       }
     },
+    "cryptiles": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
+      "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
+      "dev": true,
+      "requires": {
+        "boom": "2.x.x"
+      }
+    },
     "cssom": {
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz",
@@ -1355,6 +1440,15 @@
         "cssom": "0.3.x"
       }
     },
+    "currently-unhandled": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
+      "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
+      "dev": true,
+      "requires": {
+        "array-find-index": "^1.0.1"
+      }
+    },
     "dashdash": {
       "version": "1.14.1",
       "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -1474,6 +1568,12 @@
       "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
       "dev": true
     },
+    "delegates": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+      "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
+      "dev": true
+    },
     "depd": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -2763,18 +2863,61 @@
         }
       }
     },
+    "fstream": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
+      "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "inherits": "~2.0.0",
+        "mkdirp": ">=0.5 0",
+        "rimraf": "2"
+      }
+    },
     "function-bind": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
       "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
       "dev": true
     },
+    "gauge": {
+      "version": "2.7.4",
+      "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+      "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+      "dev": true,
+      "requires": {
+        "aproba": "^1.0.3",
+        "console-control-strings": "^1.0.0",
+        "has-unicode": "^2.0.0",
+        "object-assign": "^4.1.0",
+        "signal-exit": "^3.0.0",
+        "string-width": "^1.0.1",
+        "strip-ansi": "^3.0.1",
+        "wide-align": "^1.1.0"
+      }
+    },
+    "gaze": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz",
+      "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==",
+      "dev": true,
+      "requires": {
+        "globule": "^1.0.0"
+      }
+    },
     "get-caller-file": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
       "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=",
       "dev": true
     },
+    "get-stdin": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+      "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
+      "dev": true
+    },
     "get-stream": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
@@ -2835,6 +2978,17 @@
       "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==",
       "dev": true
     },
+    "globule": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz",
+      "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==",
+      "dev": true,
+      "requires": {
+        "glob": "~7.1.1",
+        "lodash": "~4.17.10",
+        "minimatch": "~3.0.2"
+      }
+    },
     "graceful-fs": {
       "version": "4.1.11",
       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
@@ -2939,6 +3093,12 @@
       "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
       "dev": true
     },
+    "has-unicode": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+      "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
+      "dev": true
+    },
     "has-value": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
@@ -2971,6 +3131,24 @@
         }
       }
     },
+    "hawk": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
+      "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
+      "dev": true,
+      "requires": {
+        "boom": "2.x.x",
+        "cryptiles": "2.x.x",
+        "hoek": "2.x.x",
+        "sntp": "1.x.x"
+      }
+    },
+    "hoek": {
+      "version": "2.16.3",
+      "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
+      "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=",
+      "dev": true
+    },
     "home-or-tmp": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz",
@@ -3086,6 +3264,21 @@
       "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
       "dev": true
     },
+    "in-publish": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz",
+      "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=",
+      "dev": true
+    },
+    "indent-string": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+      "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
+      "dev": true,
+      "requires": {
+        "repeating": "^2.0.0"
+      }
+    },
     "indexof": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
@@ -3921,6 +4114,12 @@
         "merge-stream": "^1.0.1"
       }
     },
+    "js-base64": {
+      "version": "2.4.8",
+      "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.8.tgz",
+      "integrity": "sha512-hm2nYpDrwoO/OzBhdcqs/XGT6XjSuSSCVEpia+Kl2J6x4CYt5hISlVL/AYU1khoDXv0AQVgxtdJySb9gjAn56Q==",
+      "dev": true
+    },
     "js-tokens": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
@@ -4004,6 +4203,15 @@
       "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=",
       "dev": true
     },
+    "json-stable-stringify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
+      "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
+      "dev": true,
+      "requires": {
+        "jsonify": "~0.0.0"
+      }
+    },
     "json-stringify-safe": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
@@ -4025,6 +4233,12 @@
         "graceful-fs": "^4.1.6"
       }
     },
+    "jsonify": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
+      "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
+      "dev": true
+    },
     "jsprim": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
@@ -4246,12 +4460,30 @@
       "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==",
       "dev": true
     },
+    "lodash.assign": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
+      "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=",
+      "dev": true
+    },
+    "lodash.clonedeep": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+      "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
+      "dev": true
+    },
     "lodash.isfinite": {
       "version": "3.3.2",
       "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz",
       "integrity": "sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=",
       "dev": true
     },
+    "lodash.mergewith": {
+      "version": "4.6.1",
+      "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
+      "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==",
+      "dev": true
+    },
     "lodash.sortby": {
       "version": "4.7.0",
       "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
@@ -4278,6 +4510,16 @@
         "js-tokens": "^3.0.0"
       }
     },
+    "loud-rejection": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
+      "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
+      "dev": true,
+      "requires": {
+        "currently-unhandled": "^0.4.1",
+        "signal-exit": "^3.0.0"
+      }
+    },
     "lru-cache": {
       "version": "4.1.3",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz",
@@ -4312,6 +4554,12 @@
       "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
       "dev": true
     },
+    "map-obj": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+      "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
+      "dev": true
+    },
     "map-visit": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
@@ -4336,6 +4584,24 @@
         "mimic-fn": "^1.0.0"
       }
     },
+    "meow": {
+      "version": "3.7.0",
+      "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+      "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
+      "dev": true,
+      "requires": {
+        "camelcase-keys": "^2.0.0",
+        "decamelize": "^1.1.2",
+        "loud-rejection": "^1.0.0",
+        "map-obj": "^1.0.1",
+        "minimist": "^1.1.3",
+        "normalize-package-data": "^2.3.4",
+        "object-assign": "^4.0.1",
+        "read-pkg-up": "^1.0.1",
+        "redent": "^1.0.0",
+        "trim-newlines": "^1.0.0"
+      }
+    },
     "merge": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz",
@@ -4519,8 +4785,7 @@
       "version": "2.10.0",
       "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
       "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==",
-      "dev": true,
-      "optional": true
+      "dev": true
     },
     "nanomatch": {
       "version": "1.2.9",
@@ -4562,6 +4827,136 @@
       "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=",
       "dev": true
     },
+    "node-gyp": {
+      "version": "3.7.0",
+      "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.7.0.tgz",
+      "integrity": "sha512-qDQE/Ft9xXP6zphwx4sD0t+VhwV7yFaloMpfbL2QnnDZcyaiakWlLdtFGGQfTAwpFHdpbRhRxVhIHN1OKAjgbg==",
+      "dev": true,
+      "requires": {
+        "fstream": "^1.0.0",
+        "glob": "^7.0.3",
+        "graceful-fs": "^4.1.2",
+        "mkdirp": "^0.5.0",
+        "nopt": "2 || 3",
+        "npmlog": "0 || 1 || 2 || 3 || 4",
+        "osenv": "0",
+        "request": ">=2.9.0 <2.82.0",
+        "rimraf": "2",
+        "semver": "~5.3.0",
+        "tar": "^2.0.0",
+        "which": "1"
+      },
+      "dependencies": {
+        "ajv": {
+          "version": "4.11.8",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
+          "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=",
+          "dev": true,
+          "requires": {
+            "co": "^4.6.0",
+            "json-stable-stringify": "^1.0.1"
+          }
+        },
+        "assert-plus": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
+          "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
+          "dev": true
+        },
+        "aws-sign2": {
+          "version": "0.6.0",
+          "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
+          "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
+          "dev": true
+        },
+        "form-data": {
+          "version": "2.1.4",
+          "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
+          "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=",
+          "dev": true,
+          "requires": {
+            "asynckit": "^0.4.0",
+            "combined-stream": "^1.0.5",
+            "mime-types": "^2.1.12"
+          }
+        },
+        "har-schema": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",
+          "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=",
+          "dev": true
+        },
+        "har-validator": {
+          "version": "4.2.1",
+          "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz",
+          "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=",
+          "dev": true,
+          "requires": {
+            "ajv": "^4.9.1",
+            "har-schema": "^1.0.5"
+          }
+        },
+        "http-signature": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
+          "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
+          "dev": true,
+          "requires": {
+            "assert-plus": "^0.2.0",
+            "jsprim": "^1.2.2",
+            "sshpk": "^1.7.0"
+          }
+        },
+        "performance-now": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz",
+          "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=",
+          "dev": true
+        },
+        "qs": {
+          "version": "6.4.0",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz",
+          "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=",
+          "dev": true
+        },
+        "request": {
+          "version": "2.81.0",
+          "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz",
+          "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=",
+          "dev": true,
+          "requires": {
+            "aws-sign2": "~0.6.0",
+            "aws4": "^1.2.1",
+            "caseless": "~0.12.0",
+            "combined-stream": "~1.0.5",
+            "extend": "~3.0.0",
+            "forever-agent": "~0.6.1",
+            "form-data": "~2.1.1",
+            "har-validator": "~4.2.1",
+            "hawk": "~3.1.3",
+            "http-signature": "~1.1.0",
+            "is-typedarray": "~1.0.0",
+            "isstream": "~0.1.2",
+            "json-stringify-safe": "~5.0.1",
+            "mime-types": "~2.1.7",
+            "oauth-sign": "~0.8.1",
+            "performance-now": "^0.2.0",
+            "qs": "~6.4.0",
+            "safe-buffer": "^5.0.1",
+            "stringstream": "~0.0.4",
+            "tough-cookie": "~2.3.0",
+            "tunnel-agent": "^0.6.0",
+            "uuid": "^3.0.0"
+          }
+        },
+        "semver": {
+          "version": "5.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+          "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
+          "dev": true
+        }
+      }
+    },
     "node-int64": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@@ -4580,6 +4975,79 @@
         "which": "^1.3.0"
       }
     },
+    "node-sass": {
+      "version": "4.9.2",
+      "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.2.tgz",
+      "integrity": "sha512-LdxoJLZutx0aQXHtWIYwJKMj+9pTjneTcLWJgzf2XbGu0q5pRNqW5QvFCEdm3mc5rJOdru/mzln5d0EZLacf6g==",
+      "dev": true,
+      "requires": {
+        "async-foreach": "^0.1.3",
+        "chalk": "^1.1.1",
+        "cross-spawn": "^3.0.0",
+        "gaze": "^1.0.0",
+        "get-stdin": "^4.0.1",
+        "glob": "^7.0.3",
+        "in-publish": "^2.0.0",
+        "lodash.assign": "^4.2.0",
+        "lodash.clonedeep": "^4.3.2",
+        "lodash.mergewith": "^4.6.0",
+        "meow": "^3.7.0",
+        "mkdirp": "^0.5.1",
+        "nan": "^2.10.0",
+        "node-gyp": "^3.3.1",
+        "npmlog": "^4.0.0",
+        "request": "2.87.0",
+        "sass-graph": "^2.2.4",
+        "stdout-stream": "^1.4.0",
+        "true-case-path": "^1.0.2"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+          "dev": true
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^2.2.1",
+            "escape-string-regexp": "^1.0.2",
+            "has-ansi": "^2.0.0",
+            "strip-ansi": "^3.0.0",
+            "supports-color": "^2.0.0"
+          }
+        },
+        "cross-spawn": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
+          "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
+          "dev": true,
+          "requires": {
+            "lru-cache": "^4.0.1",
+            "which": "^1.2.9"
+          }
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+          "dev": true
+        }
+      }
+    },
+    "nopt": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+      "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
+      "dev": true,
+      "requires": {
+        "abbrev": "1"
+      }
+    },
     "normalize-package-data": {
       "version": "2.4.0",
       "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
@@ -4610,6 +5078,18 @@
         "path-key": "^2.0.0"
       }
     },
+    "npmlog": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+      "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+      "dev": true,
+      "requires": {
+        "are-we-there-yet": "~1.1.2",
+        "console-control-strings": "~1.1.0",
+        "gauge": "~2.7.3",
+        "set-blocking": "~2.0.0"
+      }
+    },
     "number-is-nan": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
@@ -4809,6 +5289,16 @@
       "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
       "dev": true
     },
+    "osenv": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
+      "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
+      "dev": true,
+      "requires": {
+        "os-homedir": "^1.0.0",
+        "os-tmpdir": "^1.0.0"
+      }
+    },
     "p-finally": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
@@ -5269,6 +5759,16 @@
         "util.promisify": "^1.0.0"
       }
     },
+    "redent": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
+      "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
+      "dev": true,
+      "requires": {
+        "indent-string": "^2.1.0",
+        "strip-indent": "^1.0.1"
+      }
+    },
     "regenerator-runtime": {
       "version": "0.11.1",
       "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
@@ -5602,12 +6102,109 @@
         }
       }
     },
+    "sass-graph": {
+      "version": "2.2.4",
+      "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz",
+      "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=",
+      "dev": true,
+      "requires": {
+        "glob": "^7.0.0",
+        "lodash": "^4.0.0",
+        "scss-tokenizer": "^0.2.3",
+        "yargs": "^7.0.0"
+      },
+      "dependencies": {
+        "camelcase": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
+          "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
+          "dev": true
+        },
+        "cliui": {
+          "version": "3.2.0",
+          "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
+          "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
+          "dev": true,
+          "requires": {
+            "string-width": "^1.0.1",
+            "strip-ansi": "^3.0.1",
+            "wrap-ansi": "^2.0.0"
+          }
+        },
+        "os-locale": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
+          "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
+          "dev": true,
+          "requires": {
+            "lcid": "^1.0.0"
+          }
+        },
+        "which-module": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
+          "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=",
+          "dev": true
+        },
+        "yargs": {
+          "version": "7.1.0",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz",
+          "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=",
+          "dev": true,
+          "requires": {
+            "camelcase": "^3.0.0",
+            "cliui": "^3.2.0",
+            "decamelize": "^1.1.1",
+            "get-caller-file": "^1.0.1",
+            "os-locale": "^1.4.0",
+            "read-pkg-up": "^1.0.1",
+            "require-directory": "^2.1.1",
+            "require-main-filename": "^1.0.1",
+            "set-blocking": "^2.0.0",
+            "string-width": "^1.0.2",
+            "which-module": "^1.0.0",
+            "y18n": "^3.2.1",
+            "yargs-parser": "^5.0.0"
+          }
+        },
+        "yargs-parser": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz",
+          "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=",
+          "dev": true,
+          "requires": {
+            "camelcase": "^3.0.0"
+          }
+        }
+      }
+    },
     "sax": {
       "version": "1.2.4",
       "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
       "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
       "dev": true
     },
+    "scss-tokenizer": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
+      "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=",
+      "dev": true,
+      "requires": {
+        "js-base64": "^2.1.8",
+        "source-map": "^0.4.2"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.4.4",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
+          "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
+          "dev": true,
+          "requires": {
+            "amdefine": ">=0.0.4"
+          }
+        }
+      }
+    },
     "semver": {
       "version": "5.5.0",
       "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
@@ -5886,6 +6483,15 @@
         "kind-of": "^3.2.0"
       }
     },
+    "sntp": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
+      "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
+      "dev": true,
+      "requires": {
+        "hoek": "2.x.x"
+      }
+    },
     "socket.io": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.0.4.tgz",
@@ -6103,6 +6709,15 @@
       "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=",
       "dev": true
     },
+    "stdout-stream": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.0.tgz",
+      "integrity": "sha1-osfIWH5U2UJ+qe2zrD8s1SLfN4s=",
+      "dev": true,
+      "requires": {
+        "readable-stream": "^2.0.1"
+      }
+    },
     "stealthy-require": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
@@ -6146,6 +6761,28 @@
         }
       }
     },
+    "string-width": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+      "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+      "dev": true,
+      "requires": {
+        "code-point-at": "^1.0.0",
+        "is-fullwidth-code-point": "^1.0.0",
+        "strip-ansi": "^3.0.0"
+      },
+      "dependencies": {
+        "is-fullwidth-code-point": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+          "dev": true,
+          "requires": {
+            "number-is-nan": "^1.0.0"
+          }
+        }
+      }
+    },
     "string_decoder": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -6155,6 +6792,21 @@
         "safe-buffer": "~5.1.0"
       }
     },
+    "stringstream": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz",
+      "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==",
+      "dev": true
+    },
+    "strip-ansi": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+      "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+      "dev": true,
+      "requires": {
+        "ansi-regex": "^2.0.0"
+      }
+    },
     "strip-bom": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
@@ -6170,6 +6822,15 @@
       "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
       "dev": true
     },
+    "strip-indent": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
+      "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
+      "dev": true,
+      "requires": {
+        "get-stdin": "^4.0.1"
+      }
+    },
     "supports-color": {
       "version": "5.4.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
@@ -6185,6 +6846,17 @@
       "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=",
       "dev": true
     },
+    "tar": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
+      "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
+      "dev": true,
+      "requires": {
+        "block-stream": "*",
+        "fstream": "^1.0.2",
+        "inherits": "2"
+      }
+    },
     "test-exclude": {
       "version": "4.2.1",
       "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.1.tgz",
@@ -6354,12 +7026,42 @@
         }
       }
     },
+    "trim-newlines": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
+      "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
+      "dev": true
+    },
     "trim-right": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
       "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
       "dev": true
     },
+    "true-case-path": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.2.tgz",
+      "integrity": "sha1-fskRMJJHZsf1c74wIMNPj9/QDWI=",
+      "dev": true,
+      "requires": {
+        "glob": "^6.0.4"
+      },
+      "dependencies": {
+        "glob": {
+          "version": "6.0.4",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
+          "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
+          "dev": true,
+          "requires": {
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "2 || 3",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        }
+      }
+    },
     "tslib": {
       "version": "1.9.3",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
@@ -6427,9 +7129,9 @@
       "dev": true
     },
     "typescript": {
-      "version": "2.8.4",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.4.tgz",
-      "integrity": "sha512-IIU5cN1mR5J3z9jjdESJbnxikTrEz3lzAw/D0Tf45jHpBp55nY31UkUvmVHoffCfKHTqJs3fCLPDxknQTTFegQ==",
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.1.tgz",
+      "integrity": "sha512-zQIMOmC+372pC/CCVLqnQ0zSBiY7HHodU7mpQdjiZddek4GMj31I3dUJ7gAs9o65X7mnRma6OokOkc6f9jjfBg==",
       "dev": true
     },
     "ua-parser-js": {
@@ -6725,6 +7427,15 @@
       "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
       "dev": true
     },
+    "wide-align": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+      "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+      "dev": true,
+      "requires": {
+        "string-width": "^1.0.2 || 2"
+      }
+    },
     "window-size": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
diff --git a/ui/package.json b/ui/package.json
index 669c67b..ebac0d3 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -20,6 +20,7 @@
     "@types/puppeteer": "^1.3.4",
     "jest": "^23.1.0",
     "lite-server": "^2.3.0",
+    "node-sass": "^4.9.2",
     "puppeteer": "^1.5.0",
     "rollup": "^0.59.4",
     "rollup-plugin-commonjs": "^9.1.3",
diff --git a/ui/src/assets/logo-3d.png b/ui/src/assets/logo-3d.png
new file mode 100644
index 0000000..60626a9
--- /dev/null
+++ b/ui/src/assets/logo-3d.png
Binary files differ
diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss
new file mode 100644
index 0000000..1c478ce
--- /dev/null
+++ b/ui/src/assets/perfetto.scss
@@ -0,0 +1,262 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+:root {
+    --sidebar-width: 256px;
+    --topbar-height: 48px;
+}
+
+@mixin transition($time:0.1s) {
+    transition: opacity $time ease, background-color $time ease, max-height $time ease, max-width $time ease, border-radius $time ease;
+}
+
+@mixin material-icon($content) {
+    direction: ltr;
+    display: inline-block;
+    font-family: 'Material Icons';
+    font-size: 24px;
+    font-style: normal;
+    font-weight: normal;
+    letter-spacing: normal;
+    line-height: 1;
+    text-transform: none;
+    white-space: nowrap;
+    word-wrap: normal;
+    -webkit-font-feature-settings: 'liga';
+    -webkit-font-smoothing: antialiased;
+    content: $content;
+}
+
+* {
+    box-sizing: border-box;
+    overflow: hidden;
+    -webkit-tap-highlight-color: none;
+    touch-action: none;
+}
+
+html {
+    font-family: Roboto, verdana, sans-serif;
+    height: 100%;
+    width: 100%;
+}
+
+html,
+body {
+    height: 100%;
+    width: 100%;
+    padding: 0;
+    margin: 0;
+    user-select: none;
+}
+
+h1,
+h2,
+h3 {
+    font-family: initial;
+    font-size: initial;
+    font-weight: initial;
+    padding: 0;
+    margin: 0;
+}
+
+table {
+    user-select: text;
+}
+
+body {
+    display: grid;
+    grid-template-areas: "sidebar topbar" "sidebar page";
+    grid-template-rows: var(--topbar-height) auto;
+    grid-template-columns: var(--sidebar-width) auto;
+    color: #121212;
+}
+
+.full-page-loading-screen {
+    position: absolute;
+    background: #3e4a5a;
+    width: 100%;
+    height: 100%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    flex-direction: row;
+    background-image: url('assets/logo.png');
+    background-attachment: fixed;
+    background-repeat: no-repeat;
+    background-position: center;
+}
+
+.page {
+    grid-area: page;
+    position: relative;
+}
+
+@import 'sidebar';
+@import 'topbar';
+.home-page {
+    text-align: center;
+    padding-top: 20vh;
+}
+
+.home-page .logo {
+    width: 250px;
+}
+
+.home-page-title {
+    font-size: 60px;
+    margin: 25px;
+    text-align: center;
+    font-family: 'Raleway', sans-serif;
+    font-weight: 100;
+    color: #333;
+}
+
+.query-table {
+    width: 100%;
+    border-collapse: collapse;
+    font-size: 14px;
+    border: 0;
+    thead td {
+        background-color: hsl(214, 22%, 90%);
+        color: #262f3b;
+        text-align: center;
+        padding: 1px 3px;
+        border-style: solid;
+        border-color: #fff;
+        border-right-width: 1px;
+        border-left-width: 1px;
+    }
+    tbody tr {
+        @include transition();
+        background-color: hsl(214, 22%, 100%);
+        font-family: 'Inconsolata', monospace;
+        &:nth-child(even) {
+            background-color: hsl(214, 22%, 95%);
+        }
+        td:first-child {
+            padding-left: 5px;
+        }
+        td:last-child {
+            padding-right: 5px;
+        }
+        &:hover {
+            background-color: hsl(214, 22%, 90%);
+        }
+    }
+}
+
+.page header {
+    height: 25px;
+    line-height: 25px;
+    background-color: hsl(213, 22%, 82%);
+    color: hsl(213, 22%, 20%);
+    font-family: 'Google sans';
+    font-size: 15px;
+    font-weight: 400;
+    padding: 0 10px;
+    vertical-align: middle;
+    border-color: hsl(213, 22%, 75%);
+    border-style: solid;
+    border-top-width: 1px;
+    border-bottom-width: 1px;
+    .code {
+        font-family: 'Inconsolata', mononspace;
+        font-size: 12px;
+        margin-left: 10px;
+        color: hsl(213, 22%, 40%);
+    }
+}
+
+.track {
+    border: 1px solid hsl(213, 22%, 82%);
+    position: absolute;
+    left: 0;
+    width: 100%;
+    .track-shell {
+        background: #fff;
+        padding: 20px;
+        border-right: 1px solid hsl(213, 22%, 82%);
+        height: 100%;
+        z-index: 100;
+        position: relative;
+        h1 {
+            margin: 0;
+            font-size: 16px;
+            font-family: 'Google Sans';
+            color: hsl(213, 22%, 30%)
+        }
+    }
+    .track-content {
+        height: 100%;
+        position: absolute;
+        top: 0;
+    }
+}
+
+.scrollable-container {
+    width: 100%;
+    height: 100%;
+    overflow-y: auto;
+    overflow-x: hidden;
+    will-change: transform;
+    position: relative;
+}
+
+/** Time Axis **/
+
+.axis {
+    overflow: hidden;
+    height: 41px;
+    position: relative;
+    .mark-label {
+        position: relative;
+        left: -100px;
+        width: 200px;
+        text-align: center;
+        cursor: default;
+    }
+    .tick {
+        height: 20px;
+        width: 1px;
+        background: #666;
+    }
+}
+
+/** Overview Timeline **/
+
+.overview-timeline {
+    width: 100%;
+    overflow: hidden;
+    height: 120px;
+    position: relative;
+}
+
+.brush-rect {
+    background: rgba(210, 210, 210, 0.7);
+    position: absolute;
+    pointer-events: none;
+    top: 0;
+    height: 100%;
+}
+
+.brush-handle {
+    position: absolute;
+    border-radius: 3px;
+    border: 1px solid #999;
+    cursor: pointer;
+    background: #fff;
+    top: 25px;
+    width: 14px;
+    height: 30px;
+    pointer-events: auto;
+}
\ No newline at end of file
diff --git a/ui/src/assets/sidebar.scss b/ui/src/assets/sidebar.scss
new file mode 100644
index 0000000..d933a9b
--- /dev/null
+++ b/ui/src/assets/sidebar.scss
@@ -0,0 +1,118 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+.sidebar {
+    grid-area: sidebar;
+    z-index: 2;
+    background-color: #262f3c;
+    overflow-y: auto;
+    >* {
+        border-bottom: 1px solid #404854;
+    }
+    input[type=file] { display:none; }
+    >header {
+        font-family: 'Raleway';
+        height: var(--topbar-height);
+        line-height: var(--topbar-height);
+        vertical-align: middle;
+        padding: 0 20px;
+        color: #fff;
+        font-weight: 400;
+        font-size: 24px;
+        letter-spacing: 0.5px;
+        &:before {
+            width: 32px;
+            height: 32px;
+            display: inline-block;
+            content: '';
+            vertical-align: middle;
+            background-image: url('/assets/logo.png');
+            background-size: contain;
+            background-repeat: no-repeat;
+            margin-right: 15px;
+        }
+    }
+    >section {
+        @include transition();
+        padding: 20px 0;
+        max-height: 80px;
+        overflow: hidden;
+        >h1,
+        >h2 {
+            font-family: 'Raleway';
+            letter-spacing: 0.25px;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+            margin: 0 24px;
+        }
+        >h1 {
+            color: #fff;
+            font-size: 15px;
+            font-weight: 500;
+        }
+        >h2 {
+            @include transition();
+            color: rgba(255, 255, 255, 0.5);
+            font-size: 12px;
+            margin-top: 8px;
+            font-weight: 400;
+        }
+        &:hover {
+            background-color: #373f4b;
+        }
+        &.expanded {
+            background-color: #19212b;
+            max-height: 250px;
+            >h2 {
+                opacity: 0;
+            }
+            .section-content {
+                opacity: 1;
+            }
+        }
+    }
+    .section-content {
+        @include transition();
+        opacity: 0;
+        color: #b4b7ba;
+        a {
+            color: #b4b7ba;
+        }
+        ul {
+            list-style-type: none;
+            margin: 0;
+            padding: 0;
+        }
+        li {
+            @include transition();
+            a {
+                line-height: 24px;
+                font-size: 14px;
+                font-weight: 400;
+                font-family: 'Raleway';
+                letter-spacing: 0.5px;
+                padding: 5px 24px;
+                text-decoration: none;
+                display: block;
+            }
+            .material-icons {
+                vertical-align: middle;
+                margin-right: 10px;
+            }
+            &:hover {
+                background-color: #373f4b;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/src/assets/topbar.scss b/ui/src/assets/topbar.scss
new file mode 100644
index 0000000..75c130c
--- /dev/null
+++ b/ui/src/assets/topbar.scss
@@ -0,0 +1,171 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+@mixin omnibox-width() {
+    width: 90%;
+    max-width: 600px;
+}
+
+.topbar {
+    grid-area: topbar;
+    position: relative;
+    z-index: 1;
+    overflow: visible;
+    background-color: hsl(215, 1%, 95%);
+    box-shadow: 0px -3px 14px 2px #bbb;
+    line-height: var(--topbar-height);
+    .omnibox {
+        @include omnibox-width();
+        @include transition(0.25s);
+        display: grid;
+        grid-template-areas: "icon input";
+        grid-template-columns: 34px auto;
+        border-radius: 20px;
+        background-color: #fcfcfc;
+        border: 0;
+        margin: 6px auto 0 auto;
+        height: 36px;
+        line-height: 36px;
+        &:before {
+            @include material-icon('search');
+            vertical-align: middle;
+            font-size: 32px;
+            margin: 5px;
+            color: #aaa;
+            grid-area: icon;
+        }
+        input {
+            grid-area: input;
+            border: 0;
+            padding: 0 10px;
+            font-size: 18px;
+            font-family: 'Google Sans';
+            color: #666;
+            background-color: transparent;
+            &:focus {
+                outline: none;
+            }
+            &::placeholder {
+                color: #aaa;
+                font-family: 'Raleway';
+                font-weight: 100;
+            }
+        }
+        &.command-mode {
+            // background-color: #354252;
+            background-color: #111;
+            border-radius: 0;
+            width: 100%;
+            max-width: 100%;
+            margin-top: 0;
+            border-left: 1px solid #404854;
+            height: var(--topbar-height);
+            input {
+                color: #9ddc67;
+                font-family: 'Inconsolata', monospace;
+                padding-left: 0;
+            }
+            &:before {
+                content: 'attach_money';
+                color: #9ddc67;
+                font-size: 26px;
+                padding-top: 5px;
+            }
+        }
+    }
+    .omnibox-results {
+        @include transition(0.25s);
+        @include omnibox-width();
+        position: absolute;
+        z-index: 1;
+        max-height: 250px;
+        top: calc(var(--topbar-height) + 2px);
+        left: 0;
+        right: 0;
+        margin: auto;
+        background-color: hsla(213, 0%, 95%, 0.99);
+        box-shadow: 0 3px 9px #ccc;
+        border-radius: 0 0 5px 5px;
+        >* {
+            height: 24px;
+            line-height: 24px;
+            padding: 0 10px;
+            font-family: 'Raleway', sans-serif;
+            font-size: 14px;
+            vertical-align: middle;
+            color: #666;
+            border-bottom: 1px solid #eee;
+            &:hover {
+                background-color: hsl(213, 20%, 92%);
+            }
+            &.selected {
+                background-color: hsl(213, 40%, 90%);
+            }
+        }
+    }
+    .progress {
+        position: absolute;
+        bottom: 0;
+        height: 1px;
+        width: 100%;
+        &:before {
+            content: '';
+            position: absolute;
+            background-color: hsl(219, 50%, 50%);
+            top: 0;
+            left: 0;
+            bottom: 0;
+            will-change: left, right;
+            animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
+        }
+        &:after {
+            content: '';
+            position: absolute;
+            background-color: hsl(219, 50%, 50%);
+            top: 0;
+            left: 0;
+            bottom: 0;
+            will-change: left, right;
+            animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
+            animation-delay: 1.15s;
+        }
+    }
+    @keyframes indeterminate {
+        0% {
+            left: -35%;
+            right: 100%;
+        }
+        60% {
+            left: 100%;
+            right: -90%;
+        }
+        100% {
+            left: 100%;
+            right: -90%;
+        }
+    }
+    @keyframes indeterminate-short {
+        0% {
+            left: -35%;
+            right: 100%;
+        }
+        60% {
+            left: 100%;
+            right: -90%;
+        }
+        100% {
+            left: 100%;
+            right: -90%;
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 630e8f1..de1e71c 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -14,7 +14,7 @@
 
 export interface Action { type: string; }
 
-export function openTrace(url: string) {
+export function openTraceFromUrl(url: string) {
   return {
     type: 'OPEN_TRACE',
     url,
diff --git a/ui/src/frontend/home_page.ts b/ui/src/frontend/home_page.ts
index 6a975a4..5c15395 100644
--- a/ui/src/frontend/home_page.ts
+++ b/ui/src/frontend/home_page.ts
@@ -14,62 +14,14 @@
 
 import * as m from 'mithril';
 
-import {
-  Action,
-  navigate,
-  openTrace,
-  openTraceFromFile
-} from '../common/actions';
-import {EngineConfig} from '../common/state';
-import {globals} from './globals';
-import {quietDispatch} from './mithril_helpers';
 import {createPage} from './pages';
 
-const EXAMPLE_TRACE_URL =
-    'https://storage.googleapis.com/perfetto-misc/example_trace';
-
-function extractFile(e: Event): File|null {
-  if (!(e.target instanceof HTMLInputElement)) {
-    throw new Error('Not input element');
-  }
-  if (!e.target.files) return null;
-  return e.target.files.item(0);
-}
-
-function loadTraceFromFile(e: Event): Action|null {
-  const file = extractFile(e);
-  if (!file) return null;
-  return openTraceFromFile(file);
-}
-
-function renderEngine(engine: EngineConfig) {
-  return m(
-      '.home-page-traces-item',
-      m('button',
-        {
-          onclick: quietDispatch(navigate(`/query/${engine.id}`)),
-        },
-        `Query trace ${engine.id}`));
-}
-
 export const HomePage = createPage({
   view() {
-    const engines = Object.values(globals.state.engines);
     return m(
-        '#page.home-page',
+        '.page.home-page',
         m('.home-page-title', 'Perfetto'),
-        m('.home-page-controls',
-          m('label.file-input',
-            m('input[type=file]', {
-              onchange: quietDispatch(loadTraceFromFile),
-            }),
-            'Load trace'),
-          ' or ',
-          m('button',
-            {
-              onclick: quietDispatch(openTrace(EXAMPLE_TRACE_URL)),
-            },
-            'Open example trace')),
-        m('.home-page-traces', engines.map(engine => renderEngine(engine))));
+        m('img.logo[src=/assets/logo-3d.png]'),
+      );
   },
 });
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index 9029717..89b3da9 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -26,7 +26,6 @@
 import {ControllerProxy} from './controller_proxy';
 import {globals} from './globals';
 import {HomePage} from './home_page';
-import {QueryPage} from './query_page';
 import {ViewerPage} from './viewer_page';
 
 function createController(): ControllerProxy {
@@ -81,26 +80,9 @@
   globals.trackDataStore = new Map<string, {}>();
   warmupWasmEngineWorker();
 
-  const root = document.querySelector('main');
-  if (!root) {
-    console.error('root element not found.');
-    return;
-  }
-
-  m.route(root, '/', {
+  m.route(document.body, '/', {
     '/': HomePage,
     '/viewer': ViewerPage,
-    '/query/:engineId': {
-      onmatch(args) {
-        if (globals.state.engines[args.engineId]) {
-          return QueryPage;
-        }
-        // We only hit this case if the user reloads/navigates
-        // while on the query page.
-        m.route.set('/');
-        return undefined;
-      }
-    },
   });
 
   // tslint:disable-next-line no-any
diff --git a/ui/src/frontend/pages.ts b/ui/src/frontend/pages.ts
index 08deb1b..cdcc1c1 100644
--- a/ui/src/frontend/pages.ts
+++ b/ui/src/frontend/pages.ts
@@ -14,6 +14,7 @@
 
 import * as m from 'mithril';
 import {Sidebar} from './sidebar';
+import {Topbar} from './topbar';
 
 /**
  * Wrap component with common UI elements (nav bar etc).
@@ -23,7 +24,8 @@
     view() {
       return [
         m(Sidebar),
-        m('.page-content', m(component)),
+        m(Topbar),
+        m(component),
       ];
     },
   };
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index 8d584da..6f082b5 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -14,112 +14,112 @@
 
 import * as m from 'mithril';
 
-export const Sidebar: m.Component<{}, {expanded: boolean[]}> = {
-  oninit() {
-    this.expanded = [true, false, false, false];
+import {navigate, openTraceFromFile, openTraceFromUrl} from '../common/actions';
+
+import {globals} from './globals';
+import {quietHandler} from './mithril_helpers';
+
+const EXAMPLE_TRACE_URL =
+    'https://storage.googleapis.com/perfetto-misc/example_trace_30s';
+
+const SECTIONS = [
+  {
+    title: 'Traces',
+    summary: 'Open or record a trace',
+    expanded: true,
+    items: [
+      {t: 'Open trace file', a: popupFileSelectionDialog, i: 'folder_open'},
+      {t: 'Open example trace', a: handleOpenTraceURL, i: 'description'},
+      {t: 'Record new trace', a: navigateHome, i: 'fiber_smart_record'},
+      {t: 'Share current trace', a: navigateHome, i: 'share'},
+    ],
   },
+  {
+    title: 'Workspaces',
+    summary: 'Custom and pre-arranged views',
+    items: [
+      {t: 'Big Picture', a: navigateHome, i: 'art_track'},
+      {t: 'Apps and process', a: navigateHome, i: 'apps'},
+      {t: 'Storage and I/O', a: navigateHome, i: 'storage'},
+      {t: 'Add custom...', a: navigateHome, i: 'library_add'},
+    ],
+  },
+  {
+    title: 'Tracks and views',
+    summary: 'Add new tracks to the workspace',
+    items: [
+      {t: 'User interactions', a: navigateHome, i: 'touch_app'},
+      {t: 'Device info', a: navigateHome, i: 'perm_device_information'},
+      {t: 'Scheduler trace', a: navigateHome, i: 'blur_linear'},
+      {t: 'Process list', a: navigateHome, i: 'equalizer'},
+      {t: 'Battery and power', a: navigateHome, i: 'battery_alert'},
+    ],
+  },
+  {
+    title: 'Metrics and auditors',
+    summary: 'Add new tracks to the workspace',
+    items: [
+      {t: 'CPU Usage breakdown', a: navigateHome, i: 'table_chart'},
+      {t: 'Memory breakdown', a: navigateHome, i: 'memory'},
+    ],
+  },
+];
+
+function popupFileSelectionDialog(e: Event) {
+  e.preventDefault();
+  (document.querySelector('input[type=file]')! as HTMLInputElement).click();
+}
+
+function handleOpenTraceURL(e: Event) {
+  e.preventDefault();
+  globals.dispatch(openTraceFromUrl(EXAMPLE_TRACE_URL));
+}
+
+function onInputElementFileSelectionChanged(e: Event) {
+  if (!(e.target instanceof HTMLInputElement)) {
+    throw new Error('Not an input element');
+  }
+  if (!e.target.files) return;
+  globals.dispatch(openTraceFromFile(e.target.files[0]));
+}
+
+function stopClickPropagation(e: Event) {
+  e.stopImmediatePropagation();
+}
+
+function navigateHome(_: Event) {
+  globals.dispatch(navigate('/'));
+}
+
+
+export const Sidebar: m.Component<{}, {}> = {
   view() {
+    const vdomSections = [];
+    for (const section of SECTIONS) {
+      const vdomItems = [];
+      for (const item of section.items) {
+        vdomItems.push(
+            m('li',
+              m(`a[href=#]`,
+                {onclick: quietHandler(item.a)},
+                m('i.material-icons', item.i),
+                item.t)));
+      }
+      vdomSections.push(
+          m(`section${section.expanded ? '.expanded' : ''}`,
+            {onclick: () => section.expanded = !section.expanded},
+            m('h1', section.title),
+            m('h2', section.summary),
+            m('.section-content',
+              // Prevent that the clicks on the bottom part of the expanded
+              // sections propagate up to and trigger their compression.
+              m('ul', {onclick: stopClickPropagation}, vdomItems))));
+    }
     return m(
         'nav.sidebar',
         m('header', 'Perfetto'),
-        m(`section${this.expanded[0] ? '.expanded' : ''}`,
-          {onclick: () => this.expanded[0] = !this.expanded[0]},
-          m('h1', 'Traces'),
-          m('h2', 'Open or record a trace'),
-          m('.section-content',
-            m('ul',
-              m('li',
-                m('a[href=/]',
-                  {oncreate: m.route.link},
-                  m('i.material-icons', 'folder_open'),
-                  'Open Trace File')),
-              m('li',
-                m('a[href=/viewer]',
-                  {oncreate: m.route.link},
-                  m('i.material-icons', 'art_track'),
-                  'View Trace')),
-              m('li',
-                m('a[href=/]',
-                  {oncreate: m.route.link},
-                  m('i.material-icons', 'fiber_smart_record'),
-                  'Record new trace')),
-              m('li',
-                m('a[href=/]',
-                  {oncreate: m.route.link},
-                  m('i.material-icons', 'share'),
-                  'Share current trace'))))),
-        m(`section${this.expanded[1] ? '.expanded' : ''}`,
-          {onclick: () => this.expanded[1] = !this.expanded[1]},
-          m('h1', 'Workspaces'),
-          m('h2', 'Custom and pre-arranged views'),
-          m('.section-content',
-            m('ul',
-              m('li',
-                m('a[href=/]',
-                  {oncreate: m.route.link},
-                  m('i.material-icons', 'art_track'),
-                  'Big Picture')),
-              m('li',
-                m('a[href=/]',
-                  {oncreate: m.route.link},
-                  m('i.material-icons', 'apps'),
-                  'Apps and process')),
-              m('li',
-                m('a[href=/]',
-                  {oncreate: m.route.link},
-                  m('i.material-icons', 'storage'),
-                  'Storage and I/O')),
-              m('li',
-                m('a[href=/]',
-                  {oncreate: m.route.link},
-                  m('i.material-icons', 'library_add'),
-                  'Add custom...'))))),
-        m(`section${this.expanded[2] ? '.expanded' : ''}`,
-          {onclick: () => this.expanded[2] = !this.expanded[2]},
-          m('h1', 'Tracks and Views'),
-          m('h2', 'Add new tracks to the workspace'),
-          m('.section-content',
-            m('ul',
-              m('li',
-                m('a[href=/]',
-                  {oncreate: m.route.link},
-                  m('i.material-icons', 'touch_app'),
-                  'User interactions')),
-              m('li',
-                m('a[href=/]',
-                  {oncreate: m.route.link},
-                  m('i.material-icons', 'perm_device_information'),
-                  'Device info')),
-              m('li',
-                m('a[href=/]',
-                  {oncreate: m.route.link},
-                  m('i.material-icons', 'blur_linear'),
-                  'Scheduler trace')),
-              m('li',
-                m('a[href=/]',
-                  {oncreate: m.route.link},
-                  m('i.material-icons', 'equalizer'),
-                  'Process list')),
-              m('li',
-                m('a[href=/]',
-                  {oncreate: m.route.link},
-                  m('i.material-icons', 'battery_alert'),
-                  'Battern & power'))))),
-        m(`section${this.expanded[3] ? '.expanded' : ''}`,
-          {onclick: () => this.expanded[3] = !this.expanded[3]},
-          m('h1', 'Metrics and Auditors'),
-          m('h2', 'Summary analysis of the trace'),
-          m('.section-content',
-            m('ul',
-              m('li',
-                m('a[href=/]',
-                  {oncreate: m.route.link},
-                  m('i.material-icons', 'table_chart'),
-                  'CPU usage breakdown')),
-              m('li',
-                m('a[href=/]',
-                  {oncreate: m.route.link},
-                  m('i.material-icons', 'memory'),
-                  'Memory breakdown'))))));
-  }
+        m('input[type=file]',
+          {onchange: quietHandler(onInputElementFileSelectionChanged)}),
+        ...vdomSections);
+  },
 };
\ No newline at end of file
diff --git a/ui/src/frontend/topbar.ts b/ui/src/frontend/topbar.ts
new file mode 100644
index 0000000..f8b5294
--- /dev/null
+++ b/ui/src/frontend/topbar.ts
@@ -0,0 +1,105 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import * as m from 'mithril';
+import { globals } from './globals';
+import { quietDispatch } from './mithril_helpers';
+import { navigate } from '../common/actions';
+
+let selResult = 0;
+let numResults = 0;
+let mode: 'search'|'command' = 'search';
+
+function clearOmniboxResults() {
+  // TODO(primiano): Implement in next CLs.
+}
+
+function onKeyDown(e: Event) {
+  e.stopPropagation();
+  const key = (e as KeyboardEvent).key;
+
+  // Avoid that the global 'a', 'd', 'w', 's' handler sees these keystrokes.
+  // TODO: this seems a bug in the pan_and_zoom_handler.ts.
+  if (key === 'ArrowUp' || key === 'ArrowDown') {
+    e.preventDefault();
+    return;
+  }
+  const txt =
+      (e.target as HTMLElement).querySelector('input') as HTMLInputElement;
+  if (key === ':' && txt.value === '') {
+    mode = 'command';
+    m.redraw();
+    e.preventDefault();
+    return;
+  }
+  if (key === 'Escape' && mode === 'command') {
+    txt.value = '';
+    mode = 'search';
+    m.redraw();
+    return;
+  }
+  if (key === 'Backspace' && txt.value.length === 0 && mode === 'command') {
+    mode = 'search';
+    m.redraw();
+    return;
+  }
+  // TODO(primiano): add query handling here.
+}
+
+function onKeyUp(e: Event) {
+  e.stopPropagation();
+  const key = (e as KeyboardEvent).key;
+  const txt = e.target as HTMLInputElement;
+  if (key === 'ArrowUp' || key === 'ArrowDown') {
+    selResult += (key === 'ArrowUp') ? -1 : 1;
+    selResult = Math.max(selResult, 0);
+    selResult = Math.min(selResult, numResults - 1);
+    e.preventDefault();
+    m.redraw();
+    return;
+  }
+  if (txt.value.length <= 0 || key === 'Escape') {
+    clearOmniboxResults();
+    m.redraw();
+    return;
+  }
+  // TODO(primiano): add query handling here.
+}
+
+
+const Omnibox: m.Component = {
+  oncreate(vnode) {
+    const txt = vnode.dom.querySelector('input') as HTMLInputElement;
+    txt.addEventListener('blur', clearOmniboxResults);
+    txt.addEventListener('keydown', onKeyDown);
+    txt.addEventListener('keyup', onKeyUp);
+  },
+  view() {
+    // TODO(primiano): handle query results here.
+    const placeholder = {
+      search: 'Search or type : to enter command mode',
+      command: 'e.g., select * from sched left join thread using(utid) limit 10'
+    };
+    const commandMode = mode === 'command';
+    return m(
+        `.omnibox${commandMode ? '.command-mode' : ''}`,
+        m(`input[type=text][placeholder=${placeholder[mode]}]`));
+  },
+};
+
+export const Topbar: m.Component = {
+  view() {
+    return m('.topbar', m(Omnibox));
+  },
+};
\ No newline at end of file