diff --git a/.angular-cli.json b/.angular-cli.json
index 98a7d8072515d1682376f83583daf20b032da834..9bb6f94d0b5d2c92999b7479144ffea77f1fb7ef 100644
--- a/.angular-cli.json
+++ b/.angular-cli.json
@@ -8,6 +8,7 @@
       "root": "src",
       "outDir": "dist",
       "assets": [
+        "data",
         "assets",
         "favicon.ico"
       ],
@@ -19,9 +20,12 @@
       "testTsconfig": "tsconfig.spec.json",
       "prefix": "app",
       "styles": [
-        "styles.css"
+        "styles.css",
+        "../node_modules/codemirror/lib/codemirror.css",
+        "../node_modules/codemirror/theme/mbo.css"
+      ],
+      "scripts": [
       ],
-      "scripts": [],
       "environmentSource": "environments/environment.ts",
       "environments": {
         "dev": "environments/environment.ts",
diff --git a/.gitignore b/.gitignore
index 54bfd2001e64d03a43570130180635049cd8f0b2..5198bf6a10b6b4d19476a4fd12750b1932cbaab5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
 # See http://help.github.com/ignore-files/ for more about ignoring files.
 
+/src/data/
+
 # compiled output
 /dist
 /tmp
diff --git a/README.md b/README.md
index 45d47d7ef99b86fe1d5663acd16226a12876b7c8..397cf5641fe498f6e60778f6bc8438d61568f10c 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,6 @@
-# RdView
+# RagDoc View
+
+This is a viewer for JastAdd documentation generated by RagDoc Builder.
 
 This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.1.
 
diff --git a/TODO b/TODO
new file mode 100644
index 0000000000000000000000000000000000000000..56b233be1c03ef8cb3015166137cf388d9434b7a
--- /dev/null
+++ b/TODO
@@ -0,0 +1,5 @@
+* add thrown type descriptions
+* add type hierarchy in type details
+* add structured production representation
+* make current member filter more noticeable
+* Add declared-at info for types.
diff --git a/package.json b/package.json
index 7db8573fbc6275ddb5c6a4cc2a0e54d6346ee11c..e19d7864f5a8505a7c03d1f765b600dd99460e0e 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,8 @@
     "@angular/router": "^4.0.0",
     "core-js": "^2.4.1",
     "rxjs": "^5.1.0",
-    "zone.js": "^0.8.4"
+    "zone.js": "^0.8.4",
+    "codemirror": "5.25.2"
   },
   "devDependencies": {
     "@angular/cli": "1.0.1",
diff --git a/src/app/app.component.css b/src/app/app.component.css
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..fec4f109fa18f96b52e953adc1823d55fc6256e4 100644
--- a/src/app/app.component.css
+++ b/src/app/app.component.css
@@ -0,0 +1,118 @@
+nav, article, h1 {
+  font-family: "Roboto",Helvetica,sans-serif;
+}
+.selected {
+  background-color: #BBD8DC !important;
+}
+.types {
+  list-style-type: none;
+  margin: 0;
+  padding: 0;
+  background-color: #607D8B;
+}
+.types li {
+  cursor: pointer;
+  position: relative;
+  left: 0;
+  background-color: #EEE;
+  margin: 1px 0;
+  padding: .3em 0 0 .7em;
+  height: 1.6em;
+}
+.types li:hover {
+  background-color: #CFD8DC;
+}
+.type .text {
+  position: relative;
+  top: -3px;
+}
+.topnav {
+  z-index: 5;
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  box-sizing: border-box;
+  height: 65px;
+  background-color: #CFD8DC;
+  box-shadow: 0 0 10px grey;
+}
+.topnav img {
+  visibility: hidden;
+  display: inline-block;
+  box-sizing: border-box;
+  width: 65px;
+  height: 65px;
+  padding: 15px;
+  position: absolute;
+}
+.topnav h1 {
+  display: inline-block;
+  vertical-align: middle;
+  box-sizing: border-box;
+  line-height: 65px;
+  padding: 0;
+  margin: 0 0 0 1em;
+  pointer-events: none;
+  white-space: nowrap;
+  overflow: hidden;
+}
+.sidenav {
+  width: 16em;
+  overflow-y: auto;
+  overflow-x: hidden;
+  z-index: 4;
+  position: fixed;
+  top: 65px;
+  left: 0;
+  bottom: 0;
+  box-shadow: 0 0 8px #888888;
+  background: white;
+}
+.package {
+  padding: .5em;
+}
+.group {
+  cursor: pointer;
+  padding: .5em .5em .5em 1em;
+  font-weight: bold;
+}
+.group img {
+  float: right;
+}
+article {
+  margin-left: 16em;
+  margin-top: 65px;
+  margin-bottom: 4em;
+  padding-top: 1em;
+  padding-left: 1em;
+}
+.search {
+  padding-left: 1em;
+  padding-top: 1em;
+  padding-right: 1em;
+  padding-bottom: 0.7em;
+}
+.search input {
+  border: 4px solid grey;
+  border-radius: 4px;
+  background: white url('../assets/search_grey_24px.svg') 10px 8px no-repeat;
+  padding: 10px 20px 10px 40px;
+  font-size: 16px;
+  width: 144px;
+}
+
+@media all and (max-width: 1024px) {
+  .hidemenu {
+    visibility: hidden;
+  }
+  .topnav h1 {
+    padding: 0 0 0 64px;
+  }
+  .topnav img {
+    visibility: visible;
+  }
+  article {
+    margin-left: 1em;
+  }
+}
diff --git a/src/app/app.component.html b/src/app/app.component.html
deleted file mode 100644
index b6931b538a2c5989bfedffeef51a11abadd6df58..0000000000000000000000000000000000000000
--- a/src/app/app.component.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<h1>
-  {{title}}
-</h1>
diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts
index c740bcd745ed906aff93b9a3d3bdda3a2a2272b5..7769024464e690a4df3d3f1b09a7a447a09ca9a3 100644
--- a/src/app/app.component.spec.ts
+++ b/src/app/app.component.spec.ts
@@ -1,32 +1,33 @@
-import { TestBed, async } from '@angular/core/testing';
-
 import { AppComponent } from './app.component';
 
-describe('AppComponent', () => {
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { By }           from '@angular/platform-browser';
+import { DebugElement } from '@angular/core';
+
+describe('AppComponent', function () {
+  let de: DebugElement;
+  let comp: AppComponent;
+  let fixture: ComponentFixture<AppComponent>;
+
   beforeEach(async(() => {
     TestBed.configureTestingModule({
-      declarations: [
-        AppComponent
-      ],
-    }).compileComponents();
+      declarations: [ AppComponent ]
+    })
+    .compileComponents();
   }));
 
-  it('should create the app', async(() => {
-    const fixture = TestBed.createComponent(AppComponent);
-    const app = fixture.debugElement.componentInstance;
-    expect(app).toBeTruthy();
-  }));
+  beforeEach(() => {
+    fixture = TestBed.createComponent(AppComponent);
+    comp = fixture.componentInstance;
+    de = fixture.debugElement.query(By.css('h1'));
+  });
 
-  it(`should have as title 'app works!'`, async(() => {
-    const fixture = TestBed.createComponent(AppComponent);
-    const app = fixture.debugElement.componentInstance;
-    expect(app.title).toEqual('app works!');
-  }));
+  it('should create component', () => expect(comp).toBeDefined() );
 
-  it('should render title in a h1 tag', async(() => {
-    const fixture = TestBed.createComponent(AppComponent);
+  it('should have expected <h1> text', () => {
     fixture.detectChanges();
-    const compiled = fixture.debugElement.nativeElement;
-    expect(compiled.querySelector('h1').textContent).toContain('app works!');
-  }));
+    const h1 = de.nativeElement;
+    expect(h1.innerText).toMatch(/angular/i,
+      '<h1> should say something about "Angular"');
+  });
 });
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index ff63e050488720c0766df3a354191d53d1af2ca5..1b084c346fb4749bf924ab352feba13367d20be1 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,10 +1,100 @@
 import { Component } from '@angular/core';
+import { OnInit } from '@angular/core';
+
+import { Package } from './package';
+import { Type } from './type';
+import { PackageService } from './package.service';
+import { SelectionService } from './selection.service';
+import { Member } from './member';
+import { Parameter } from './parameter';
+
+import { ActivatedRoute, Params } from '@angular/router';
+import { Location } from '@angular/common';
 
 @Component({
   selector: 'app-root',
-  templateUrl: './app.component.html',
-  styleUrls: ['./app.component.css']
+  styleUrls: ['./app.component.css'],
+  template: `
+<nav class="topnav">
+  <img src="assets/hamburger_24px.svg" (click)="showMenu = !showMenu"><h1>{{title}}</h1>
+</nav>
+
+<nav class="sidenav" [class.hidemenu]="!showMenu">
+  <div class="search"><input [(ngModel)]="filter" placeholder="Search..."></div>
+
+  <ng-template ngFor let-package [ngForOf]="packages">
+    <div *ngIf="filteredPackage(package)">
+      <div class="package"><b>{{package.name}}</b></div>
+      <ng-template ngFor let-group [ngForOf]="package.groups">
+        <div *ngIf="filteredGroup(group)">
+          <div class="group" (click)="group.hidden = !group.hidden">{{typeKindNames[group.kind]}}<img src="assets/vdots_24px.svg" *ngIf="!group.hidden"><img src="assets/hdots_24px.svg" *ngIf="group.hidden"></div>
+          <ul class="types" [hidden]="group.hidden">
+          <li *ngFor="let type of group.members | nameFilter:filter" class="type"
+            [class.selected]="type.id === selectedType"
+            [routerLink]="['/type', type.id]"
+            (click)="showMenu = false">
+            {{type.name}}
+          </li>
+          </ul>
+        </div>
+      </ng-template>
+    </div>
+  </ng-template>
+</nav>
+
+<article>
+    <router-outlet></router-outlet>
+</article>
+  `,
+  providers: [
+    PackageService,
+    SelectionService,
+  ],
 })
-export class AppComponent {
-  title = 'app works!';
+export class AppComponent implements OnInit {
+  title = 'ExtendJ API Documentation';
+  showMenu = false;
+  packages : Package[];
+  filter = '';
+  selectedType = '';
+
+  private typeKindNames = {
+    'ast-class': 'AST CLASSES',
+    'interface': 'INTERFACES',
+    'class': 'CLASSES',
+  };
+
+  constructor(private packageService: PackageService,
+      private selectionService: SelectionService) {
+    selectionService.selection$.subscribe(id => this.selectedType = id);
+  }
+
+  ngOnInit(): void {
+    this.packageService.getPackages().then(packages => this.packages = packages);
+  }
+
+  declaredAt(member: Member): string {
+    if (member.doc) {
+      return `${member.doc.ragFile}:${member.doc.line}`;
+    } else {
+      return "";
+    }
+  }
+
+  filteredPackage(pkg: Package): boolean {
+    var filter = this.filter.toLowerCase();
+    for (var i = 0; i < pkg.groups.length; i++) {
+      if (this.filteredGroup(pkg.groups[i])) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  filteredGroup(group: any): boolean {
+    var filter = this.filter.toLowerCase();
+    var filtered = group.members.filter(member => member.name.toLowerCase().indexOf(filter) >= 0);
+    return filtered.length > 0;
+  }
 }
+
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 67ae49119baa632d3c92a74364eddb6a32cbdd8e..f65502b76f463b8142d7f8f951321fd4ff8b6cd8 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -1,20 +1,46 @@
+import { NgModule }      from '@angular/core';
 import { BrowserModule } from '@angular/platform-browser';
-import { NgModule } from '@angular/core';
 import { FormsModule } from '@angular/forms';
 import { HttpModule } from '@angular/http';
+import { RouterModule } from '@angular/router';
 
-import { AppComponent } from './app.component';
+import { AppComponent }  from './app.component';
+import { TypeDetailsComponent }  from './type-details.component';
+import { ParametersComponent }  from './parameters.component';
+import { TypeReferenceComponent } from './type-ref.component';
+import { NameFilterPipe } from './name-filter.pipe';
+import { StringFilterPipe } from './string-filter.pipe';
+import { SourceViewComponent } from './source-view/source-view.component';
+import { EditorDirective } from './editor.directive';
+import { DeclaredAtComponent } from './declared-at/declared-at.component';
 
 @NgModule({
-  declarations: [
-    AppComponent
-  ],
-  imports: [
+  imports:      [
     BrowserModule,
     FormsModule,
-    HttpModule
+    HttpModule,
+    RouterModule.forRoot([
+      {
+        path: 'type/:id',
+        component: TypeDetailsComponent
+      },
+      {
+        path: 'source/:filename/:line',
+        component: SourceViewComponent
+      }
+    ]),
+  ],
+  declarations: [
+    AppComponent,
+    TypeDetailsComponent,
+    ParametersComponent,
+    TypeReferenceComponent,
+    NameFilterPipe,
+    StringFilterPipe,
+    SourceViewComponent,
+    EditorDirective,
+    DeclaredAtComponent,
   ],
-  providers: [],
-  bootstrap: [AppComponent]
+  bootstrap:    [ AppComponent ]
 })
 export class AppModule { }
diff --git a/src/app/declared-at/declared-at.component.spec.ts b/src/app/declared-at/declared-at.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..806276db6c38423bbdb6e2916ebc391ddf03cdc9
--- /dev/null
+++ b/src/app/declared-at/declared-at.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DeclaredAtComponent } from './declared-at.component';
+
+describe('DeclaredAtComponent', () => {
+  let component: DeclaredAtComponent;
+  let fixture: ComponentFixture<DeclaredAtComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ DeclaredAtComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(DeclaredAtComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/app/declared-at/declared-at.component.ts b/src/app/declared-at/declared-at.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..12d3c65793db4c07d877f98f706cdac5fc2443cf
--- /dev/null
+++ b/src/app/declared-at/declared-at.component.ts
@@ -0,0 +1,26 @@
+import { Component, OnInit, Input } from '@angular/core';
+
+import {Doc} from '../doc';
+
+@Component({
+  selector: 'declared-at',
+  template: `
+    Declared at <a [routerLink]="['/source', filename, line]">{{filepath}}:{{line}}.</a>
+  `,
+})
+export class DeclaredAtComponent implements OnInit {
+
+  @Input() doc: Doc;
+  filename: string;
+  filepath: string;
+  line: string;
+
+  constructor() { }
+
+  ngOnInit() {
+    this.filepath = this.doc.ragFile;
+    this.filename = this.filepath.replace(/\/|\\/g, '_');
+    this.line = String(this.doc.line);
+  }
+
+}
diff --git a/src/app/doc.ts b/src/app/doc.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d201353965df8dd94cd7693a2c7bebea9538d5b0
--- /dev/null
+++ b/src/app/doc.ts
@@ -0,0 +1,26 @@
+export class Doc {
+  ast: string;
+  ragFile: string;
+  line: number;
+  description: string;
+  apilevel: string;
+  params: string[]
+
+  paramDesc(name: string): string {
+    if (this.params) {
+      for (var i = 0; i < this.params.length; i++) {
+        var param = this.params[i];
+        var index = param.indexOf(' ');
+        if (index >= 0 && param.substring(0, index) === name) {
+          return ' : ' + param.substring(index + 1);
+        }
+      }
+    }
+    return '';
+  }
+
+  static fromJson(json: any): Doc {
+    var obj = Object.create(Doc.prototype);
+    return Object.assign(obj, json);
+  }
+}
diff --git a/src/app/editor.directive.spec.ts b/src/app/editor.directive.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f385018d68622c68f4ee26321093edcd8d6093e3
--- /dev/null
+++ b/src/app/editor.directive.spec.ts
@@ -0,0 +1,8 @@
+import { EditorDirective } from './editor.directive';
+
+describe('EditorDirective', () => {
+  it('should create an instance', () => {
+    const directive = new EditorDirective();
+    expect(directive).toBeTruthy();
+  });
+});
diff --git a/src/app/editor.directive.ts b/src/app/editor.directive.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ee368c749d3d9d45ff4136550b9b8a7147b1e3e2
--- /dev/null
+++ b/src/app/editor.directive.ts
@@ -0,0 +1,35 @@
+import { Directive, ElementRef, Input, OnChanges } from '@angular/core';
+
+import * as CodeMirror from 'codemirror';
+
+import 'codemirror/mode/clike/clike';
+import 'codemirror/addon/selection/active-line';
+
+@Directive({
+  selector: '[appEditor]'
+})
+export class EditorDirective implements OnChanges {
+  editor: any;
+  @Input() sourceText: string;
+  @Input() sourceLine: number;
+
+  constructor(public element: ElementRef) {
+    this.editor = new CodeMirror.fromTextArea(element.nativeElement, {
+      mode: 'text/x-java',
+      theme: 'mbo',
+      lineNumbers: true,
+      styleActiveLine: true,
+      lineWrapping: false,
+    });
+    this.editor.setSize('100%', '100%');
+  }
+
+  ngOnChanges() {
+    this.editor.setValue(this.sourceText || 'no source');
+    if (this.sourceLine) {
+      var line = this.sourceLine - 1;
+      this.editor.scrollIntoView({line: line + 30, ch: 0});
+      this.editor.setCursor({line: line, ch: 0});
+    }
+  }
+}
diff --git a/src/app/inherited-members.ts b/src/app/inherited-members.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bb6cd91fde01964dde582c583bdf3266b339a508
--- /dev/null
+++ b/src/app/inherited-members.ts
@@ -0,0 +1,13 @@
+import {TypeRef} from './type-ref';
+
+export class InheritedMembers {
+  superclass: TypeRef;
+  members: string[];
+
+  static fromJson(json: any): InheritedMembers {
+    return {
+      superclass: TypeRef.fromJson(json.superclass),
+      members: json.members as string[],
+    };
+  }
+}
diff --git a/src/app/member-filter.service.spec.ts b/src/app/member-filter.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..13aa2f57643cf8bd0a8f506c0d04ef3fffbfcd5d
--- /dev/null
+++ b/src/app/member-filter.service.spec.ts
@@ -0,0 +1,15 @@
+import { TestBed, inject } from '@angular/core/testing';
+
+import { MemberFilterService } from './member-filter.service';
+
+describe('MemberFilterService', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [MemberFilterService]
+    });
+  });
+
+  it('should ...', inject([MemberFilterService], (service: MemberFilterService) => {
+    expect(service).toBeTruthy();
+  }));
+});
diff --git a/src/app/member-filter.service.ts b/src/app/member-filter.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0f44d015b97243aa675a2465ec575cadde120de9
--- /dev/null
+++ b/src/app/member-filter.service.ts
@@ -0,0 +1,15 @@
+import { Injectable } from '@angular/core';
+import {Subject} from 'rxjs/Subject';
+
+@Injectable()
+export class MemberFilterService {
+  private filter = new Subject<string>();
+
+  filter$ = this.filter.asObservable();
+
+  constructor() { }
+
+  setFilter(filter: string) {
+    this.filter.next(filter);
+  }
+}
diff --git a/src/app/member.ts b/src/app/member.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6327b45536088600ae88c0b382d15e0d79272886
--- /dev/null
+++ b/src/app/member.ts
@@ -0,0 +1,40 @@
+import {Parameter} from './parameter';
+import {Doc} from './doc';
+import {TypeRef} from './type-ref';
+
+export class Member {
+  name: string;
+  type: TypeRef;
+  doc: Doc;
+  parameters: Parameter[];
+  throws: TypeRef[];
+
+  static fromJson(json: any): Member {
+    var params: Parameter[] = [];
+    var doc: Doc = undefined;
+    if (json.doc) {
+      doc = Doc.fromJson(json.doc);
+    }
+    if (json.params) {
+      params = json.params as Parameter[];
+    }
+    var throws: TypeRef[] = undefined;
+    if (json.throws) {
+      throws = (json.throws as TypeRef[]).map(TypeRef.fromJson);
+    }
+    if (json.type) {
+      return Object.assign({}, json, {
+        type: TypeRef.fromJson(json.type),
+        parameters: params.map(param => Parameter.fromJson(param)),
+        doc: doc,
+        throws: throws,
+      });
+    } else {
+      return Object.assign({}, json, {
+        parameters: params.map(param => Parameter.fromJson(param)),
+        doc: doc,
+        throws: throws,
+      });
+    }
+  }
+}
diff --git a/src/app/name-filter.pipe.ts b/src/app/name-filter.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..45e8434f5e0e8287b347bc28ee23550b4670e66b
--- /dev/null
+++ b/src/app/name-filter.pipe.ts
@@ -0,0 +1,15 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+  name: 'nameFilter'
+})
+
+export class NameFilterPipe implements PipeTransform {
+  transform(items: any[], filter: string): any {
+    if (!items || !filter) {
+      return items;
+    }
+    return items.filter(item => item.name.toLowerCase().indexOf(filter.toLowerCase()) >= 0);
+  }
+}
+
diff --git a/src/app/package-entry.ts b/src/app/package-entry.ts
new file mode 100644
index 0000000000000000000000000000000000000000..716e779dd3ffb1770bf7138264f13f1fd1cd8b0f
--- /dev/null
+++ b/src/app/package-entry.ts
@@ -0,0 +1,11 @@
+import {TypeRef} from './type-ref';
+
+export class PackageEntry {
+  kind: string;
+  name: string;
+  id: string;
+
+  static fromJson(json: any): PackageEntry {
+    return json as PackageEntry;
+  }
+}
diff --git a/src/app/package.service.ts b/src/app/package.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b440d72b6e58c9a20327a959249206e5a6ae5b07
--- /dev/null
+++ b/src/app/package.service.ts
@@ -0,0 +1,20 @@
+import { Injectable } from '@angular/core';
+import { Headers, Http } from '@angular/http';
+
+import 'rxjs/add/operator/toPromise';
+
+import { Package } from './package';
+
+@Injectable()
+export class PackageService {
+  private packageUrl = 'data/packages.json';
+
+  constructor(private http: Http) { }
+
+  getPackages(): Promise<Package[]> {
+    return this.http.get(this.packageUrl)
+      .toPromise()
+      .then(response => (response.json().data as Package[]).map(pkg => Package.fromJson(pkg)))
+      .catch(res => Promise.reject(`Failed to load packages: ${res}`));
+  }
+}
diff --git a/src/app/package.ts b/src/app/package.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dd797eb587e7d37f806a0ee66790aa5a244798cf
--- /dev/null
+++ b/src/app/package.ts
@@ -0,0 +1,12 @@
+import { PackageEntry } from './package-entry';
+
+export class Package {
+  name: string;
+  groups: any[];
+
+  static fromJson(json: any): Package {
+    return json as Package;
+  }
+}
+
+
diff --git a/src/app/parameter.ts b/src/app/parameter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..929502a636eb8ba1a133f70bef2070d687f97652
--- /dev/null
+++ b/src/app/parameter.ts
@@ -0,0 +1,14 @@
+import {TypeRef} from './type-ref';
+
+export class Parameter {
+  type: TypeRef;
+  name: string;
+
+  static fromJson(json: any): Parameter {
+    return {
+      type: TypeRef.fromJson(json.t),
+      name: json.n,
+    };
+  }
+}
+
diff --git a/src/app/parameters.component.ts b/src/app/parameters.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1bd2227ab3e49434587c3e5f29ed07b2be514798
--- /dev/null
+++ b/src/app/parameters.component.ts
@@ -0,0 +1,34 @@
+import { Component, Input, ComponentFactoryResolver } from '@angular/core';
+
+import {Parameter} from './parameter';
+
+@Component({
+  selector: 'parameters',
+  styles: [`
+    .parameters {
+      display: inline;
+      height: 1.2em;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+    .parameter {
+      display: inline;
+    }
+    .sep {
+      display: inline;
+      margin-right: .3em;
+    }
+  `],
+  template: `
+    <div class="parameters">
+      <div *ngFor="let param of params; let isLast=last" class="parameter">
+        <type-ref [type]="param.type"></type-ref> {{param.name}}<div *ngIf="!isLast" class="sep">,</div>
+      </div>
+    </div>
+  `
+})
+export class ParametersComponent {
+  @Input() params : Parameter[];
+
+  constructor(private _componentFactoryResolver: ComponentFactoryResolver) { }
+}
diff --git a/src/app/selection.service.ts b/src/app/selection.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..312d8ccac74817f1f6ea457ca244d155a725899c
--- /dev/null
+++ b/src/app/selection.service.ts
@@ -0,0 +1,13 @@
+import {Injectable} from '@angular/core';
+import {Subject} from 'rxjs/Subject';
+
+@Injectable()
+export class SelectionService {
+  private selection = new Subject<string>();
+
+  selection$ = this.selection.asObservable();
+
+  select(id: string) {
+    this.selection.next(id);
+  }
+}
diff --git a/src/app/source-view/source-view.component.css b/src/app/source-view/source-view.component.css
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/app/source-view/source-view.component.html b/src/app/source-view/source-view.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..6c33c8c90d8191c2391c6dde9032e40dbbad1e2a
--- /dev/null
+++ b/src/app/source-view/source-view.component.html
@@ -0,0 +1,3 @@
+<p>
+  source-view works!
+</p>
diff --git a/src/app/source-view/source-view.component.spec.ts b/src/app/source-view/source-view.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bf324c2aadfbccb6c169f50f9607d13ceb32c431
--- /dev/null
+++ b/src/app/source-view/source-view.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SourceViewComponent } from './source-view.component';
+
+describe('SourceViewComponent', () => {
+  let component: SourceViewComponent;
+  let fixture: ComponentFixture<SourceViewComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ SourceViewComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(SourceViewComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/app/source-view/source-view.component.ts b/src/app/source-view/source-view.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..52af73e6e8931ebf1c04d2abfc88ee42d047024f
--- /dev/null
+++ b/src/app/source-view/source-view.component.ts
@@ -0,0 +1,30 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import { ActivatedRoute, Params } from '@angular/router';
+
+import 'rxjs/add/operator/switchMap';
+
+import {SourceService} from '../source.service';
+
+@Component({
+  selector: 'app-source-viewer',
+  providers: [ SourceService ],
+  template: `<textarea appEditor [sourceText]="source" [sourceLine]="line">{{source}}</textarea>`,
+})
+export class SourceViewComponent implements OnInit {
+  source: string;
+  line = 0;
+
+  constructor(private sourceService: SourceService,
+      private route: ActivatedRoute) { }
+
+  ngOnInit() {
+    this.route.params.switchMap((params: Params) => {
+      this.line = +params['line'];
+      return this.sourceService.getSource(params['filename']);
+    })
+    .subscribe(source => {
+      this.source = source;
+    });
+  }
+
+}
diff --git a/src/app/source.service.spec.ts b/src/app/source.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a7dd4a313f5dfaf63df8259ddf19f9b1eec101e8
--- /dev/null
+++ b/src/app/source.service.spec.ts
@@ -0,0 +1,15 @@
+import { TestBed, inject } from '@angular/core/testing';
+
+import { SourceService } from './source.service';
+
+describe('SourceService', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [SourceService]
+    });
+  });
+
+  it('should ...', inject([SourceService], (service: SourceService) => {
+    expect(service).toBeTruthy();
+  }));
+});
diff --git a/src/app/source.service.ts b/src/app/source.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4bceacee34eebf9c87538da56f1c51ff1ffe3b00
--- /dev/null
+++ b/src/app/source.service.ts
@@ -0,0 +1,17 @@
+import { Injectable } from '@angular/core';
+import { Headers, Http } from '@angular/http';
+
+import 'rxjs/add/operator/toPromise';
+
+@Injectable()
+export class SourceService {
+
+  constructor(private http: Http) { }
+
+  getSource(filename: String): Promise<string> {
+    return this.http.get(`data/${filename}`)
+        .toPromise()
+        .then(response => response.text())
+        .catch(res => Promise.reject(`Failed to load source: ${name}: ${res}.`));
+  }
+}
diff --git a/src/app/string-filter.pipe.spec.ts b/src/app/string-filter.pipe.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4068ad829f0de611a834bdeef733e41f261d4b8f
--- /dev/null
+++ b/src/app/string-filter.pipe.spec.ts
@@ -0,0 +1,8 @@
+import { StringFilterPipe } from './string-filter.pipe';
+
+describe('StringFilterPipe', () => {
+  it('create an instance', () => {
+    const pipe = new StringFilterPipe();
+    expect(pipe).toBeTruthy();
+  });
+});
diff --git a/src/app/string-filter.pipe.ts b/src/app/string-filter.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bd815e6b50a11a5d3ac74a34d44390039826b302
--- /dev/null
+++ b/src/app/string-filter.pipe.ts
@@ -0,0 +1,15 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+  name: 'stringFilter'
+})
+export class StringFilterPipe implements PipeTransform {
+
+  transform(items: any[], filter: string): any {
+    if (!items || !filter) {
+      return items;
+    }
+    return items.filter(item => item.toLowerCase().indexOf(filter.toLowerCase()) >= 0);
+  }
+
+}
diff --git a/src/app/type-details.component.css b/src/app/type-details.component.css
new file mode 100644
index 0000000000000000000000000000000000000000..8787da50a6965cf9f0666409b04b62f8da277d1d
--- /dev/null
+++ b/src/app/type-details.component.css
@@ -0,0 +1,83 @@
+.doc-signature {
+  display: inline-block;
+  height: 1.3em;
+  width: calc(100% - 50px); /* Avoids line wrap in summary. */
+  overflow: hidden;
+  vertical-align: middle;
+}
+.attribute-kind {
+  color: #888;
+}
+.member-type {
+  width: 12em;
+  overflow: hidden;
+  display: inline-block;
+  vertical-align: top;
+}
+.member-name {
+  font-weight: bold;
+}
+.doc-preview {
+  display: inline;
+  float: right;
+  width: 40%;
+  line-height: 1.2em;
+  height: 1.2em;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  vertical-align: top;
+}
+summary {
+  cursor: pointer;
+  padding: .5em 0 .5em 0;
+}
+.member-details {
+  padding-bottom: .5em;
+}
+.members {
+  list-style-type: none;
+  margin: 0;
+  padding: 0;
+}
+.members li {
+  background-color: #EEE;
+  margin: .2em 0;
+  padding: 0 .7em 0 .7em;
+  border-radius: 4px;
+}
+.return {
+  display: inline-block;
+}
+.sep {
+  display: inline;
+  margin-right: .3em;
+}
+.filter input {
+  border: 4px solid grey;
+  border-radius: 4px;
+  background: white url('../assets/search_grey_24px.svg') 10px 8px no-repeat;
+  padding: 10px 20px 10px 40px;
+  font-size: 16px;
+  width: 144px;
+}
+.filter button {
+  border: 4px solid grey;
+  border-radius: 4px;
+  padding: 10px;
+  font-size: 16px;
+  background: lightgrey;
+}
+
+
+@media all and (max-width: 1024px) {
+  .doc-preview {
+    visibility: hidden;
+  }
+}
+@media all and (max-width: 500px) {
+  .member-type {
+    visibility: hidden;
+    width: 0;
+  }
+}
diff --git a/src/app/type-details.component.html b/src/app/type-details.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..93344b769514c6fc14f085e1178c86c8930b4a55
--- /dev/null
+++ b/src/app/type-details.component.html
@@ -0,0 +1,125 @@
+<div *ngIf="type">
+  <h2>{{type.kind}} {{type.name}}</h2>
+  <p *ngIf="type.superclass">extends <type-ref [type]="type.superclass"></type-ref>
+  <ng-container *ngIf="type.superinterfaces">implements <ng-container *ngFor="let iface of type.superinterfaces; let isLast = last"><type-ref [type]="iface"></type-ref><div *ngIf="!isLast" class="sep">, </div></ng-container></ng-container>
+  </p>
+  <div *ngIf="type.doc">
+    <p [innerHTML]="type.doc.description">
+
+    <p *ngIf="type.doc.astdecl">JastAdd production: {{type.doc.astdecl}}
+
+    <p *ngIf="type.doc && type.doc.ragFile"><declared-at [doc]="type.doc"></declared-at>
+  </div>
+  <div class="filter">
+    <input [(ngModel)]="filter" placeholder="Filter members...">
+    <button (click)="clearFilter()" [hidden]="filter === ''">Clear filter</button>
+  </div>
+
+  <div *ngIf="filteredMembers('constr')">
+  <h3>Constructors</h3>
+  <ul class="members">
+    <li *ngFor="let member of type.groups['constr'] | nameFilter:filter">
+      <details>
+      <summary>
+      <div class="doc-signature"><span class="member-name">{{member.name}}</span> ( <parameters [params]="member.parameters"></parameters> )
+        <div class="doc-preview" *ngIf="member.doc" [innerHTML]="member.doc.description"></div></div>
+      </summary>
+
+      <div class="member-details">
+      <p *ngIf="member.doc" [innerHTML]="member.doc.description"></p>
+      <p *ngIf="member.doc && member.doc.ragFile"><declared-at [doc]="member.doc"></declared-at>
+      <p *ngFor="let param of member.parameters; let index = index"><b>Parameter {{index+1}}</b> <type-ref [type]="param.type"></type-ref> <b>{{param.name}}</b><span *ngIf="member.doc" [innerHTML]="paramDesc(member.doc, param.name)"></span></p>
+      <p *ngIf="member.throws">Throws <ng-container *ngFor="let excp of member.throws; let isLast=last"><type-ref [type]="excp"></type-ref><div *ngIf="!isLast" class="sep">, </div></ng-container></p>
+      </div>
+      </details>
+    </li>
+  </ul>
+  </div>
+
+  <div *ngIf="filteredMembers('attr')">
+  <h3>Attributes</h3>
+  <ul class="members">
+    <li *ngFor="let member of type.groups['attr'] | nameFilter:filter">
+      <details>
+      <summary>
+      <div class="doc-signature"><div class="member-type"><span *ngIf="member.doc" class="attribute-kind">{{member.doc.attribute}}</span> <type-ref [type]="member.type"></type-ref></div> <span class="member-name">{{member.name}}</span> ( <parameters [params]="member.parameters"></parameters> )
+        <div class="doc-preview" *ngIf="member.doc" [innerHTML]="member.doc.description"></div></div>
+      </summary>
+
+      <div class="member-details">
+      <p *ngIf="member.doc" [innerHTML]="member.doc.description"></p>
+      <p *ngIf="member.doc && member.doc.ragFile"><declared-at [doc]="member.doc"></declared-at>
+    <p *ngFor="let param of member.parameters; let index = index"><b>Parameter {{index+1}}</b> <type-ref [type]="param.type"></type-ref> <b>{{param.name}}</b><span *ngIf="member.doc" [innerHTML]="paramDesc(member.doc, param.name)"></span></p>
+      <p><b>Returns</b> <type-ref [type]="member.type"></type-ref><ng-container *ngIf="member.doc && member.doc.return"> : <span class="return" *ngIf="member.doc" [innerHTML]="member.doc.return"></span></ng-container>
+      </div>
+      </details>
+    </li>
+  </ul>
+  </div>
+
+  <div *ngIf="type.inherited_attributes">
+    <ng-container *ngFor="let inherited of type.inherited_attributes">
+      <ng-container *ngIf="inheritedMembers(inherited)">
+      <h3>Attributes inherited from <type-ref [type]="inherited.superclass"></type-ref></h3>
+      <ng-container *ngFor="let member of inherited.members | stringFilter:filter; let isLast = last"><type-ref [type]="inherited.superclass" [name]="member" [filter]="member"></type-ref><ng-container *ngIf="!isLast">, </ng-container></ng-container>
+      </ng-container>
+    </ng-container>
+  </div>
+
+  <div *ngIf="filteredMembers('field')">
+  <h3>Fields</h3>
+  <ul class="members">
+    <li *ngFor="let member of type.groups['field'] | nameFilter:filter">
+      <details>
+      <summary>
+      <div class="doc-signature"><div class="member-type"><type-ref [type]="member.type"></type-ref></div> <span class="member-name">{{member.name}}</span>
+        <div class="doc-preview" *ngIf="member.doc" [innerHTML]="member.doc.description"></div></div>
+      </summary>
+
+      <p *ngIf="member.doc" [innerHTML]="member.doc.description"></p>
+      <p *ngIf="member.doc && member.doc.ragFile"><declared-at [doc]="member.doc"></declared-at>
+      </details>
+    </li>
+  </ul>
+  </div>
+
+  <div *ngIf="type.inherited_fields">
+    <ng-container *ngFor="let inherited of type.inherited_fields">
+      <ng-container *ngIf="inheritedMembers(inherited)">
+      <h3>Fields inherited from <type-ref [type]="inherited.superclass"></type-ref></h3>
+      <ng-container *ngFor="let member of inherited.members | stringFilter:filter; let isLast = last"><type-ref [type]="inherited.superclass" [name]="member" [filter]="member"></type-ref><ng-container *ngIf="!isLast">, </ng-container></ng-container>
+      </ng-container>
+    </ng-container>
+  </div>
+
+  <div *ngIf="filteredMembers('method')">
+  <h3>Methods</h3>
+  <ul class="members">
+    <li *ngFor="let member of type.groups['method'] | nameFilter:filter">
+      <details>
+      <summary>
+      <div class="doc-signature"><div class="member-type"><type-ref [type]="member.type"></type-ref></div> <span class="member-name">{{member.name}}</span> ( <parameters [params]="member.parameters"></parameters> )
+        <div class="doc-preview" *ngIf="member.doc" [innerHTML]="member.doc.description"></div></div>
+      </summary>
+
+      <div class="member-details">
+      <p *ngIf="member.doc" [innerHTML]="member.doc.description"></p>
+      <p *ngIf="member.doc && member.doc.ragFile"><declared-at [doc]="member.doc"></declared-at>
+      <p *ngFor="let param of member.parameters; let index = index"><b>Parameter {{index+1}}</b> <type-ref [type]="param.type"></type-ref> <b>{{param.name}}</b><span *ngIf="member.doc" [innerHTML]="paramDesc(member.doc, param.name)"></span></p>
+      <p><b>Returns</b> <type-ref [type]="member.type"></type-ref><ng-container *ngIf="member.doc && member.doc.return"> : <span class="return" *ngIf="member.doc" [innerHTML]="member.doc.return"></span></ng-container>
+      <p *ngIf="member.throws">Throws <ng-container *ngFor="let excp of member.throws; let isLast=last"><type-ref [type]="excp"></type-ref><div *ngIf="!isLast" class="sep">, </div></ng-container></p>
+      </div>
+      </details>
+    </li>
+  </ul>
+  </div>
+
+  <div *ngIf="type.inherited_methods">
+    <ng-container *ngFor="let inherited of type.inherited_methods">
+      <ng-container *ngIf="inheritedMembers(inherited)">
+      <h3>Methods inherited from <type-ref [type]="inherited.superclass"></type-ref></h3>
+      <ng-container *ngFor="let member of inherited.members | stringFilter:filter; let isLast = last"><type-ref [type]="inherited.superclass" [name]="member" [filter]="member"></type-ref><ng-container *ngIf="!isLast">, </ng-container></ng-container>
+      </ng-container>
+    </ng-container>
+  </div>
+</div>
diff --git a/src/app/type-details.component.ts b/src/app/type-details.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3d54cd6e1a221c37995b2ab3da865d1010fc234e
--- /dev/null
+++ b/src/app/type-details.component.ts
@@ -0,0 +1,81 @@
+import { Component } from '@angular/core';
+import { OnInit } from '@angular/core';
+
+import { Package } from './package';
+import { Type } from './type';
+import { TypeService } from './type.service';
+import { MemberFilterService } from './member-filter.service';
+import { SelectionService } from './selection.service';
+import { Member } from './member';
+import { InheritedMembers } from './inherited-members';
+import {Doc} from './doc';
+import { Parameter } from './parameter';
+
+import { ActivatedRoute, Params } from '@angular/router';
+import { Location } from '@angular/common';
+
+import 'rxjs/add/operator/switchMap';
+
+@Component({
+  selector: 'type-details',
+  styleUrls: ['./type-details.component.css'],
+  templateUrl: './type-details.component.html',
+  providers: [
+    TypeService,
+    MemberFilterService,
+  ],
+})
+export class TypeDetailsComponent  implements OnInit {
+  type: Type = Object.create(Type.prototype);
+  filter: string = '';
+
+  constructor(private typeService: TypeService,
+      private memberFilterService: MemberFilterService,
+      private route: ActivatedRoute,
+      private location: Location,
+      private selectionService: SelectionService) {
+    memberFilterService.filter$.subscribe(filter => this.filter = filter);
+  }
+
+  ngOnInit() {
+    this.route.params.switchMap((params: Params) => {
+      return this.typeService.getType(params['id'])
+    })
+    .subscribe(type => {
+      this.selectionService.select(type.id);
+      this.type = type;
+    });
+  }
+
+  declaredAt(doc: Doc): string {
+    return `${doc.ragFile}:${doc.line}`;
+  }
+
+  paramDesc(doc: Doc, name: string): string {
+    if (doc) {
+      return doc.paramDesc(name);
+    }
+    return '';
+  }
+
+  filteredMembers(kind: string): boolean {
+    if (this.type.groups && this.type.groups[kind]) {
+      var filter = this.filter.toLowerCase();
+      var filtered = this.type.groups[kind].filter(item =>
+          item.name.toLowerCase().indexOf(filter) >= 0)
+      return filtered.length > 0;
+    }
+    return false;
+  }
+
+  inheritedMembers(inherited: InheritedMembers): boolean {
+    var filter = this.filter.toLowerCase();
+    var filtered = inherited.members.filter(item => item.toLowerCase().indexOf(filter) >= 0)
+    return filtered.length > 0;
+  }
+
+  clearFilter() {
+    this.filter = '';
+  }
+}
+
diff --git a/src/app/type-ref.component.ts b/src/app/type-ref.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..34ad060356fde6bd41ba39e923267ed8bebecfa7
--- /dev/null
+++ b/src/app/type-ref.component.ts
@@ -0,0 +1,48 @@
+import { Component, Input, ComponentFactoryResolver } from '@angular/core';
+
+import {Type} from './type';
+import { MemberFilterService } from './member-filter.service';
+
+@Component({
+  selector: 'type-ref',
+  styles: [`
+    .sep {
+      display: inline;
+      margin-right: .3em;
+    }
+    .usertype {
+      color: #8c1339;
+      text-decoration: none;
+    }
+    .non-usertype {
+      color: #444;
+    }
+  `],
+  template: `<a *ngIf="type.id; else elseBlock" class="usertype" [routerLink]="['/type', type.id]" (click)="onClick()">{{getName()}}</a><!--
+  --><ng-template #elseBlock><span class="non-usertype">{{getName()}}</span></ng-template><!--
+    --><ng-container *ngIf="type.args && !name"><!--
+      -->&lt;<ng-container *ngFor="let arg of type.args; let isLast=last"><type-ref [type]="arg"></type-ref><div *ngIf="!isLast" class="sep">,</div></ng-container>&gt;<!--
+    --></ng-container>`
+})
+export class TypeReferenceComponent {
+  @Input() type : Type;
+  @Input() name : string;
+  @Input() filter : string;
+
+  constructor(private _componentFactoryResolver: ComponentFactoryResolver,
+    private memberFilterService: MemberFilterService) { }
+
+  getName(): string {
+    if (this.name) {
+      return this.name;
+    } else {
+      return this.type.name;
+    }
+  }
+
+  onClick() {
+    if (this.filter) {
+      this.memberFilterService.setFilter(this.filter);
+    }
+  }
+}
diff --git a/src/app/type-ref.ts b/src/app/type-ref.ts
new file mode 100644
index 0000000000000000000000000000000000000000..154d4a64ff7e34379e9fc0e8d5c30d41acaf54a6
--- /dev/null
+++ b/src/app/type-ref.ts
@@ -0,0 +1,54 @@
+export class TypeRef {
+  name: string;
+  id: string;
+  args: TypeRef[];
+
+  static fromJson(json: any): TypeRef {
+    var args: TypeRef[] = undefined;
+    if (json.a) {
+      args = (json.a as TypeRef[]).map(TypeRef.fromJson);
+    }
+    if (json.u) {
+      // User type.
+      if (json.i) {
+        return {
+          name: json.u,
+          id: TypeRef.typeId(json.u, json.i),
+          args: args,
+        };
+      } else {
+        return {
+          name: json.u,
+          id: TypeRef.simpleName(json.u),
+          args: args,
+        };
+      }
+    } else {
+      // Library or built-in type.
+      return {
+        name: json.n,
+        id: undefined,
+        args: args,
+      };
+    }
+  }
+
+  // Remove array and type args.
+  static simpleName(name: string): string {
+    return TypeRef.strip('<', TypeRef.strip('[', name));
+  }
+
+  static strip(tok: string, name: string): string {
+    const index = name.indexOf(tok);
+    if (index < 0) {
+      return name;
+    } else {
+      var simple = name.substring(0, index);
+      return simple;
+    }
+  }
+
+  static typeId(name: string, idPattern: string): string {
+    return idPattern.replace('%', TypeRef.simpleName(name));
+  }
+}
diff --git a/src/app/type.service.ts b/src/app/type.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cb9eebfad0af5bb88476e1aed731ddc81d3ccdb5
--- /dev/null
+++ b/src/app/type.service.ts
@@ -0,0 +1,20 @@
+import { Injectable } from '@angular/core';
+import { Headers, Http } from '@angular/http';
+
+import 'rxjs/add/operator/toPromise';
+
+import { Type } from './type';
+
+@Injectable()
+export class TypeService {
+
+  constructor(private http: Http) { }
+
+  getType(id: string): Promise<Type> {
+    const typeUrl = `data/${id}.json`;
+    return this.http.get(typeUrl)
+        .toPromise()
+        .then(response => Type.fromJson(response.json().data))
+        .catch(res => Promise.reject(`Failed to load type: ${id}: ${res}.`));
+  }
+}
diff --git a/src/app/type.ts b/src/app/type.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6ad43f65b6de3d7297ec76724b95e667c0522f79
--- /dev/null
+++ b/src/app/type.ts
@@ -0,0 +1,69 @@
+import {Doc} from './doc';
+import {Member} from './member';
+import {TypeRef} from './type-ref';
+import {InheritedMembers} from './inherited-members';
+
+export class Type {
+  kind: string;
+  name: string;
+  pkg: string;
+  mods: string[];
+  id: string;
+  doc: Doc;
+  groups: { [id: string] : Member[]; };
+  args: TypeRef[]; // Type arguments.
+  superclass: TypeRef;
+  superinterfaces: TypeRef[];
+  inherited_methods: InheritedMembers[];
+  inherited_attributes: InheritedMembers[];
+  inherited_fields: InheritedMembers[];
+
+  static fromJson(json: any): Type {
+    var groups = {};
+    if (json.groups) {
+      for (var i = 0; i < json.groups.length; i++) {
+        var group = json.groups[i];
+        if (group.members) {
+          groups[group.kind] = (group.members as Member[]).map(member => Member.fromJson(member));
+        }
+      }
+    }
+    var superclass: TypeRef = undefined;
+    if (json.superclass) {
+      superclass = TypeRef.fromJson(json.superclass);
+    }
+    var superinterfaces: TypeRef[] = undefined;
+    if (json.superinterfaces) {
+      superinterfaces = (json.superinterfaces as TypeRef[]).map(TypeRef.fromJson);
+    }
+    var inherited_methods: InheritedMembers[] = undefined;
+    if (json.inherited_methods) {
+      inherited_methods = (json.inherited_methods as any[]).map(inherited =>
+          InheritedMembers.fromJson(inherited));
+    }
+    var inherited_attributes: InheritedMembers[] = undefined;
+    if (json.inherited_attributes) {
+      inherited_attributes = (json.inherited_attributes as any[]).map(inherited =>
+          InheritedMembers.fromJson(inherited));
+    }
+    var inherited_fields: InheritedMembers[] = undefined;
+    if (json.inherited_fields) {
+      inherited_fields = (json.inherited_fields as any[]).map(inherited =>
+          InheritedMembers.fromJson(inherited));
+    }
+    var args: TypeRef[] = undefined;
+    if (json.args) {
+      args = (json.args as any[]).map(arg => TypeRef.fromJson(arg));
+    }
+    return Object.assign({}, json, {
+      groups: groups,
+      id: TypeRef.typeId(json.name, json.id),
+      superclass: superclass,
+      superinterfaces: superinterfaces,
+      inherited_methods: inherited_methods,
+      inherited_attributes: inherited_attributes,
+      inherited_fields: inherited_fields,
+    });
+  }
+}
+
diff --git a/src/assets/arrow_down_24px.svg b/src/assets/arrow_down_24px.svg
new file mode 100644
index 0000000000000000000000000000000000000000..5630615a1c5da4b29080bb09e059dab0e62e22a0
--- /dev/null
+++ b/src/assets/arrow_down_24px.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="24"
+   height="24"
+   viewBox="0 0 24 24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="arrow_down_24px.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="31.678384"
+     inkscape:cx="5.4015737"
+     inkscape:cy="12.166913"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-page="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1017"
+     inkscape:window-x="-8"
+     inkscape:window-y="-8"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 2.2910156,9.7011719 c -1.2697658,0 -2.30078123,1.0290281 -2.30078123,2.2988281 0,0.7108 0.32929283,1.337966 0.83593751,1.759766 l 7.92773432,7.929687 c 1.6731978,1.673198 4.3295148,1.76227 6.4648438,0 l 7.927734,-7.929687 C 23.653129,13.337966 23.982422,12.7108 23.982422,12 c 0,-1.2698 -1.031015,-2.2988281 -2.300781,-2.2988281 -0.672306,0 -1.271002,0.2938062 -1.691407,0.7539061 l -7.156209,7.154463 c -0.876709,1.039344 -0.910342,0.784861 -1.796652,-0.101233 L 3.9824219,10.455078 C 3.5620177,9.9949781 2.9633216,9.7011719 2.2910156,9.7011719 Z"
+       transform="translate(0,1028.3622)"
+       id="path4195"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ssccccssccccs" />
+  </g>
+</svg>
diff --git a/src/assets/hamburger_24px.svg b/src/assets/hamburger_24px.svg
new file mode 100644
index 0000000000000000000000000000000000000000..19f1b5de1f502688f1be04e30e71ecc73c38f4e7
--- /dev/null
+++ b/src/assets/hamburger_24px.svg
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="24"
+   height="24"
+   viewBox="0 0 24 24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="hamburger_24px.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="22.627417"
+     inkscape:cx="2.1195307"
+     inkscape:cy="9.0194555"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:snap-page="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1017"
+     inkscape:window-x="-8"
+     inkscape:window-y="-8"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3621)">
+    <rect
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="rect4136"
+       width="24"
+       height="4.6875"
+       x="0"
+       y="1028.3621" />
+    <rect
+       y="1038.0182"
+       x="0"
+       height="4.6875"
+       width="24"
+       id="rect4163"
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+    <rect
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="rect4165"
+       width="24"
+       height="4.6880002"
+       x="0"
+       y="1047.6741" />
+  </g>
+</svg>
diff --git a/src/assets/hdots_24px.svg b/src/assets/hdots_24px.svg
new file mode 100644
index 0000000000000000000000000000000000000000..90e6351ceec13e206c51d469a4d0cd0f9f7363a0
--- /dev/null
+++ b/src/assets/hdots_24px.svg
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="24"
+   height="24"
+   viewBox="0 0 24 24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="hdots_24px.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="22.4"
+     inkscape:cx="3.1793573"
+     inkscape:cy="10.235898"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-page="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1017"
+     inkscape:window-x="-8"
+     inkscape:window-y="-8"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <g
+       id="g4164"
+       transform="matrix(0,1,-1,0,1052.3981,1027.706)"
+       style="fill:#666666">
+      <circle
+         r="2.2991071"
+         cy="1030.6882"
+         cx="12.656249"
+         id="path4136"
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+      <circle
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="circle4160"
+         cx="12.656249"
+         cy="1040.3981"
+         r="2.2991071" />
+      <circle
+         r="2.2991071"
+         cy="1050.1079"
+         cx="12.656249"
+         id="circle4162"
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+    </g>
+  </g>
+</svg>
diff --git a/src/assets/search_grey_24px.svg b/src/assets/search_grey_24px.svg
new file mode 100644
index 0000000000000000000000000000000000000000..468bbf80290b1de6f7d1d9f22c3a7098e5882601
--- /dev/null
+++ b/src/assets/search_grey_24px.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="24"
+   height="24"
+   viewBox="0 0 24 24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="search_grey_24px.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="22.627417"
+     inkscape:cx="-4.9515371"
+     inkscape:cy="9.0194555"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:snap-page="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1017"
+     inkscape:window-x="-8"
+     inkscape:window-y="-8"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3621)">
+    <path
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;filter-blend-mode:normal;filter-gaussianBlur-deviation:0"
+       d="m 8.5185547,1028.9784 a 7.6455922,7.6455922 0 0 0 -7.64648437,7.6464 7.6455922,7.6455922 0 0 0 7.64648437,7.6465 7.6455922,7.6455922 0 0 0 3.7832033,-1.0078 l 8.482422,8.4824 2.34375,-2.3437 -8.371094,-8.3711 a 7.6455922,7.6455922 0 0 0 1.408203,-4.4063 7.6455922,7.6455922 0 0 0 -7.6464843,-7.6464 z m 0,2.5859 a 5.0602331,5.0602331 0 0 1 5.0605473,5.0605 5.0602331,5.0602331 0 0 1 -5.0605473,5.0606 5.0602331,5.0602331 0 0 1 -5.0605469,-5.0606 5.0602331,5.0602331 0 0 1 5.0605469,-5.0605 z"
+       id="path4155"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/src/assets/search_white_24px.svg b/src/assets/search_white_24px.svg
new file mode 100644
index 0000000000000000000000000000000000000000..14c170fc7c80ea4ec20d3c1030c44a5bb8e47c60
--- /dev/null
+++ b/src/assets/search_white_24px.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="24"
+   height="24"
+   viewBox="0 0 24 24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="search_white_24px.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="22.627417"
+     inkscape:cx="2.1195307"
+     inkscape:cy="9.0194555"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:snap-page="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1017"
+     inkscape:window-x="-8"
+     inkscape:window-y="-8"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3621)">
+    <path
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 8.5185547,1028.9784 a 7.6455922,7.6455922 0 0 0 -7.64648437,7.6464 7.6455922,7.6455922 0 0 0 7.64648437,7.6465 7.6455922,7.6455922 0 0 0 3.7832033,-1.0078 l 8.482422,8.4824 2.34375,-2.3437 -8.371094,-8.3711 a 7.6455922,7.6455922 0 0 0 1.408203,-4.4063 7.6455922,7.6455922 0 0 0 -7.6464843,-7.6464 z m 0,2.5859 a 5.0602331,5.0602331 0 0 1 5.0605473,5.0605 5.0602331,5.0602331 0 0 1 -5.0605473,5.0606 5.0602331,5.0602331 0 0 1 -5.0605469,-5.0606 5.0602331,5.0602331 0 0 1 5.0605469,-5.0605 z"
+       id="path4155"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/src/assets/vdots_24px.svg b/src/assets/vdots_24px.svg
new file mode 100644
index 0000000000000000000000000000000000000000..0d0f1257c07803ee887f0e6e5d4fd02568f3cf8f
--- /dev/null
+++ b/src/assets/vdots_24px.svg
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="24"
+   height="24"
+   viewBox="0 0 24 24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="vdots_24px.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="22.4"
+     inkscape:cx="3.1793573"
+     inkscape:cy="10.235898"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-page="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1017"
+     inkscape:window-x="-8"
+     inkscape:window-y="-8"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <g
+       id="g4164"
+       transform="translate(-0.65624905,-0.03587129)"
+       style="fill:#666666">
+      <circle
+         r="2.2991071"
+         cy="1030.6882"
+         cx="12.656249"
+         id="path4136"
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+      <circle
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="circle4160"
+         cx="12.656249"
+         cy="1040.3981"
+         r="2.2991071" />
+      <circle
+         r="2.2991071"
+         cy="1050.1079"
+         cx="12.656249"
+         id="circle4162"
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+    </g>
+  </g>
+</svg>
diff --git a/src/favicon.ico b/src/favicon.ico
index 8081c7ceaf2be08bf59010158c586170d9d2d517..49ef308e4ec2f538edbac743b32ea687fa81da4a 100644
Binary files a/src/favicon.ico and b/src/favicon.ico differ
diff --git a/src/index.html b/src/index.html
index 78ba4dc0e10b708868d761cfe679601405da8519..f20bd8bc57170d8c5f9bf18b6be3210cb287f5ff 100644
--- a/src/index.html
+++ b/src/index.html
@@ -2,8 +2,9 @@
 <html>
 <head>
   <meta charset="utf-8">
-  <title>RdView</title>
+  <title>JastAdd API Docs</title>
   <base href="/">
+  <link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
 
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <link rel="icon" type="image/x-icon" href="favicon.ico">
diff --git a/src/styles.css b/src/styles.css
index 90d4ee0072ce3fc41812f8af910219f9eea3c3de..55e5ebc43d3c13175756a642b2ae1891fd5faa12 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -1 +1,7 @@
 /* You can add global styles to this file, and also import other style files */
+a:hover {
+  background: #ff4;
+}
+::selection {
+  background: #ff4;
+}