Add gosrc

Move github.com/garyburd/gosrc to this repo.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..65d761b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2013 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.markdown b/README.markdown
new file mode 100644
index 0000000..2b2b86e
--- /dev/null
+++ b/README.markdown
@@ -0,0 +1,120 @@
+This project is the source for http://godoc.org/
+
+[![GoDoc](https://godoc.org/github.com/garyburd/gddo?status.png)](http://godoc.org/github.com/garyburd/gddo)
+
+The code in this project is designed to be used by godoc.org. Send mail to
+info@godoc.org if you want to discuss other uses of the code.
+
+Feedback
+--------
+
+Send ideas and questions to info@godoc.org. Request features and report bugs
+using the [GitHub Issue
+Tracker](https://github.com/garyburd/gopkgdoc/issues/new). 
+
+
+Contributions
+-------------
+Contributions to this project are welcome, though please send mail before
+starting work on anything major. Contributors retain their copyright, so we
+need you to fill out a short form before we can accept your contribution:
+https://developers.google.com/open-source/cla/individual
+
+Development Environment Setup
+-----------------------------
+
+- Install and run [Redis 2.8.x](http://redis.io/download). The redis.conf file included in the Redis distribution is suitable for development.
+- Install Go 1.2.
+- Install and run the server:
+
+        $ go get github.com/garyburd/gddo/gddo-server
+        $ gddo-server
+
+- Go to http://localhost:8080/ in your browser
+- Enter an import path to have the server retrieve & display a package's documentation
+
+Optional:
+
+- Create the file gddo-server/config.go using the template in [gddo-server/config.go.template](gddo-server/config.go.template).
+
+API
+---
+
+The GoDoc API is comprised of these endpoints:
+
+**api.godoc.org/search?q=`Query`**—Returns search results for Query, in JSON format.
+
+```json
+{
+	"results": [
+		{
+			"path": "import/path/one",
+			"synopsis": "Package synopsis is here, if present."
+		},
+		{
+			"path": "import/path/two",
+			"synopsis": "Package synopsis is here, if present."
+		}
+	]
+}
+```
+
+**api.godoc.org/packages**—Returns all indexed packages, in JSON format.
+
+```json
+{
+	"results": [
+		{
+			"path": "import/path/one"
+		},
+		{
+			"path": "import/path/two"
+		},
+		{
+			"path": "import/path/three"
+		}
+	]
+}
+```
+
+**api.godoc.org/importers/`ImportPath`**—Returns packages that import ImportPath, in JSON format. Not recursive, direct imports only.
+
+```json
+{
+	"results": [
+		{
+			"path": "import/path/one",
+			"synopsis": "Package synopsis is here, if present."
+		},
+		{
+			"path": "import/path/two",
+			"synopsis": "Package synopsis is here, if present."
+		}
+	]
+}
+```
+
+**api.godoc.org/imports/`ImportPath`**—Returns packages that ImportPath imports, in JSON format. Not recursive, direct imports only.
+
+```json
+{
+	"imports": [
+		{
+			"path": "import/path/one",
+			"synopsis": "Package synopsis is here, if present."
+		},
+		{
+			"path": "import/path/two",
+			"synopsis": "Package synopsis is here, if present."
+		}
+	],
+	"testImports": [
+		{
+			"path": "import/path/three",
+			"synopsis": "Package synopsis is here, if present."
+		}
+	]
+}
+```
+
+A plain text interface is documented at <http://godoc.org/-/about>.
diff --git a/database/database.go b/database/database.go
new file mode 100644
index 0000000..411b2ba
--- /dev/null
+++ b/database/database.go
@@ -0,0 +1,1067 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+// Redis keys and types:
+//
+// maxPackageId string: next id to assign
+// ids hash maps import path to package id
+// pkg:<id> hash
+//      terms: space separated search terms
+//      path: import path
+//      synopsis: synopsis
+//      gob: snappy compressed gob encoded doc.Package
+//      score: document search score
+//      etag:
+//      kind: p=package, c=command, d=directory with no go files
+// index:<term> set: package ids for given search term
+// index:import:<path> set: packages with import path
+// index:project:<root> set: packages in project with root
+// block set: packages to block
+// popular zset: package id, score
+// popular:0 string: scaled base time for popular scores
+// nextCrawl zset: package id, Unix time for next crawl
+// newCrawl set: new paths to crawl
+// badCrawl set: paths that returned error when crawling.
+
+// Package database manages storage for GoPkgDoc.
+package database
+
+import (
+	"bytes"
+	"encoding/gob"
+	"errors"
+	"flag"
+	"fmt"
+	"log"
+	"math"
+	"net/url"
+	"os"
+	"path"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+
+	"code.google.com/p/snappy-go/snappy"
+	"github.com/garyburd/gddo/doc"
+	"github.com/garyburd/gosrc"
+	"github.com/garyburd/redigo/redis"
+)
+
+type Database struct {
+	Pool interface {
+		Get() redis.Conn
+	}
+}
+
+type Package struct {
+	Path     string `json:"path"`
+	Synopsis string `json:"synopsis,omitempty"`
+}
+
+type byPath []Package
+
+func (p byPath) Len() int           { return len(p) }
+func (p byPath) Less(i, j int) bool { return p[i].Path < p[j].Path }
+func (p byPath) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+
+var (
+	redisServer      = flag.String("db-server", "redis://127.0.0.1:6379", "URI of Redis server.")
+	redisIdleTimeout = flag.Duration("db-idle-timeout", 250*time.Second, "Close Redis connections after remaining idle for this duration.")
+	redisLog         = flag.Bool("db-log", false, "Log database commands")
+)
+
+func dialDb() (c redis.Conn, err error) {
+	u, err := url.Parse(*redisServer)
+	if err != nil {
+		return nil, err
+	}
+
+	defer func() {
+		if err != nil && c != nil {
+			c.Close()
+		}
+	}()
+
+	c, err = redis.Dial("tcp", u.Host)
+	if err != nil {
+		return
+	}
+
+	if *redisLog {
+		l := log.New(os.Stderr, "", log.LstdFlags)
+		c = redis.NewLoggingConn(c, l, "")
+	}
+
+	if u.User != nil {
+		if pw, ok := u.User.Password(); ok {
+			if _, err = c.Do("AUTH", pw); err != nil {
+				return
+			}
+		}
+	}
+	return
+}
+
+// New creates a database configured from command line flags.
+func New() (*Database, error) {
+	pool := &redis.Pool{
+		Dial:        dialDb,
+		MaxIdle:     10,
+		IdleTimeout: *redisIdleTimeout,
+	}
+
+	if c := pool.Get(); c.Err() != nil {
+		return nil, c.Err()
+	} else {
+		c.Close()
+	}
+
+	return &Database{Pool: pool}, nil
+}
+
+// Exists returns true if package with import path exists in the database.
+func (db *Database) Exists(path string) (bool, error) {
+	c := db.Pool.Get()
+	defer c.Close()
+	return redis.Bool(c.Do("HEXISTS", "ids", path))
+}
+
+var putScript = redis.NewScript(0, `
+    local path = ARGV[1]
+    local synopsis = ARGV[2]
+    local score = ARGV[3]
+    local gob = ARGV[4]
+    local terms = ARGV[5]
+    local etag = ARGV[6]
+    local kind = ARGV[7]
+    local nextCrawl = ARGV[8]
+
+    local id = redis.call('HGET', 'ids', path)
+    if not id then
+        id = redis.call('INCR', 'maxPackageId')
+        redis.call('HSET', 'ids', path, id)
+    end
+
+    if etag ~= '' and etag == redis.call('HGET', 'pkg:' .. id, 'clone') then
+        terms = ''
+        score = 0
+    end
+
+    local update = {}
+    for term in string.gmatch(redis.call('HGET', 'pkg:' .. id, 'terms') or '', '([^ ]+)') do
+        update[term] = 1
+    end
+
+    for term in string.gmatch(terms, '([^ ]+)') do
+        update[term] = (update[term] or 0) + 2
+    end
+
+    for term, x in pairs(update) do
+        if x == 1 then
+            redis.call('SREM', 'index:' .. term, id)
+        elseif x == 2 then 
+            redis.call('SADD', 'index:' .. term, id)
+        end
+    end
+
+    redis.call('SREM', 'badCrawl', path)
+    redis.call('SREM', 'newCrawl', path)
+
+    if nextCrawl ~= '0' then
+        redis.call('ZADD', 'nextCrawl', nextCrawl, id)
+        redis.call('HSET', 'pkg:' .. id, 'crawl', nextCrawl)
+    end
+
+    return redis.call('HMSET', 'pkg:' .. id, 'path', path, 'synopsis', synopsis, 'score', score, 'gob', gob, 'terms', terms, 'etag', etag, 'kind', kind)
+`)
+
+var addCrawlScript = redis.NewScript(0, `
+    for i=1,#ARGV do
+        local pkg = ARGV[i]
+        if redis.call('HEXISTS', 'ids',  pkg) == 0  and redis.call('SISMEMBER', 'badCrawl', pkg) == 0 then
+            redis.call('SADD', 'newCrawl', pkg)
+        end
+    end
+`)
+
+func (db *Database) AddNewCrawl(importPath string) error {
+	if !gosrc.IsValidRemotePath(importPath) {
+		return errors.New("bad path")
+	}
+	c := db.Pool.Get()
+	defer c.Close()
+	_, err := addCrawlScript.Do(c, importPath)
+	return err
+}
+
+// Put adds the package documentation to the database.
+func (db *Database) Put(pdoc *doc.Package, nextCrawl time.Time, hide bool) error {
+	c := db.Pool.Get()
+	defer c.Close()
+
+	score := 0.0
+	if !hide {
+		score = documentScore(pdoc)
+	}
+	terms := documentTerms(pdoc, score)
+
+	var gobBuf bytes.Buffer
+	if err := gob.NewEncoder(&gobBuf).Encode(pdoc); err != nil {
+		return err
+	}
+
+	// Truncate large documents.
+	if gobBuf.Len() > 200000 {
+		pdocNew := *pdoc
+		pdoc = &pdocNew
+		pdoc.Truncated = true
+		pdoc.Vars = nil
+		pdoc.Funcs = nil
+		pdoc.Types = nil
+		pdoc.Consts = nil
+		pdoc.Examples = nil
+		gobBuf.Reset()
+		if err := gob.NewEncoder(&gobBuf).Encode(pdoc); err != nil {
+			return err
+		}
+	}
+
+	gobBytes, err := snappy.Encode(nil, gobBuf.Bytes())
+	if err != nil {
+		return err
+	}
+
+	kind := "p"
+	switch {
+	case pdoc.Name == "":
+		kind = "d"
+	case pdoc.IsCmd:
+		kind = "c"
+	}
+
+	t := int64(0)
+	if !nextCrawl.IsZero() {
+		t = nextCrawl.Unix()
+	}
+
+	_, err = putScript.Do(c, pdoc.ImportPath, pdoc.Synopsis, score, gobBytes, strings.Join(terms, " "), pdoc.Etag, kind, t)
+	if err != nil {
+		return err
+	}
+
+	if nextCrawl.IsZero() {
+		// Skip crawling related packages if this is not a full save.
+		return nil
+	}
+
+	paths := make(map[string]bool)
+	for _, p := range pdoc.Imports {
+		if gosrc.IsValidRemotePath(p) {
+			paths[p] = true
+		}
+	}
+	for _, p := range pdoc.TestImports {
+		if gosrc.IsValidRemotePath(p) {
+			paths[p] = true
+		}
+	}
+	for _, p := range pdoc.XTestImports {
+		if gosrc.IsValidRemotePath(p) {
+			paths[p] = true
+		}
+	}
+	if pdoc.ImportPath != pdoc.ProjectRoot && pdoc.ProjectRoot != "" {
+		paths[pdoc.ProjectRoot] = true
+	}
+	for _, p := range pdoc.Subdirectories {
+		paths[pdoc.ImportPath+"/"+p] = true
+	}
+
+	args := make([]interface{}, 0, len(paths))
+	for p := range paths {
+		args = append(args, p)
+	}
+	_, err = addCrawlScript.Do(c, args...)
+	return err
+}
+
+var setNextCrawlEtagScript = redis.NewScript(0, `
+    local root = ARGV[1]
+    local etag = ARGV[2]
+    local nextCrawl = ARGV[3]
+
+    local pkgs = redis.call('SORT', 'index:project:' .. root, 'GET', '#',  'GET', 'pkg:*->etag')
+
+    for i=1,#pkgs,2 do
+        if pkgs[i+1] == etag then
+            redis.call('ZADD', 'nextCrawl', nextCrawl, pkgs[i])
+            redis.call('HSET', 'pkg:' .. pkgs[i], 'crawl', nextCrawl)
+        end
+    end
+`)
+
+// SetNextCrawlEtag sets the next crawl time for all packages in the project with the given etag.
+func (db *Database) SetNextCrawlEtag(projectRoot string, etag string, t time.Time) error {
+	c := db.Pool.Get()
+	defer c.Close()
+	_, err := setNextCrawlEtagScript.Do(c, normalizeProjectRoot(projectRoot), etag, t.Unix())
+	return err
+}
+
+// bumpCrawlScript sets the crawl time to now. To avoid continuously crawling
+// frequently updated repositories, the crawl is scheduled in the future.
+var bumpCrawlScript = redis.NewScript(0, `
+    local root = ARGV[1]
+    local now = tonumber(ARGV[2])
+    local nextCrawl = now + 7200
+    local pkgs = redis.call('SORT', 'index:project:' .. root, 'GET', '#')
+
+    for i=1,#pkgs do
+        local v = redis.call('HMGET', 'pkg:' .. pkgs[i], 'crawl', 'kind')
+        local t = tonumber(v[1] or 0)
+        if t == 0 or now < t then
+            redis.call('HSET', 'pkg:' .. pkgs[i], 'crawl', now)
+        end
+        local nextCrawl = now + 86400
+        if v[2] == 'p' then
+            nextCrawl = now + 7200
+        end
+        t = tonumber(redis.call('ZSCORE', 'nextCrawl', pkgs[i]) or 0)
+        if t == 0 or nextCrawl < t then
+            redis.call('ZADD', 'nextCrawl', nextCrawl, pkgs[i])
+        end
+    end
+`)
+
+func (db *Database) BumpCrawl(projectRoot string) error {
+	c := db.Pool.Get()
+	defer c.Close()
+	_, err := bumpCrawlScript.Do(c, normalizeProjectRoot(projectRoot), time.Now().Unix())
+	return err
+}
+
+// getDocScript gets the package documentation and update time for the
+// specified path. If path is "-", then the oldest document is returned.
+var getDocScript = redis.NewScript(0, `
+    local path = ARGV[1]
+
+    local id
+    if path == '-' then
+        local r = redis.call('ZRANGE', 'nextCrawl', 0, 0)
+        if not r or #r == 0 then
+            return false
+        end
+        id = r[1]
+    else
+        id = redis.call('HGET', 'ids', path)
+        if not id then
+            return false
+        end
+    end
+
+    local gob = redis.call('HGET', 'pkg:' .. id, 'gob')
+    if not gob then
+        return false
+    end
+
+    local nextCrawl = redis.call('HGET', 'pkg:' .. id, 'crawl')
+    if not nextCrawl then 
+        nextCrawl = redis.call('ZSCORE', 'nextCrawl', id)
+        if not nextCrawl then
+            nextCrawl = 0
+        end
+    end
+    
+    return {gob, nextCrawl}
+`)
+
+func (db *Database) getDoc(c redis.Conn, path string) (*doc.Package, time.Time, error) {
+	r, err := redis.Values(getDocScript.Do(c, path))
+	if err == redis.ErrNil {
+		return nil, time.Time{}, nil
+	} else if err != nil {
+		return nil, time.Time{}, err
+	}
+
+	var p []byte
+	var t int64
+
+	if _, err := redis.Scan(r, &p, &t); err != nil {
+		return nil, time.Time{}, err
+	}
+
+	p, err = snappy.Decode(nil, p)
+	if err != nil {
+		return nil, time.Time{}, err
+	}
+
+	var pdoc doc.Package
+	if err := gob.NewDecoder(bytes.NewReader(p)).Decode(&pdoc); err != nil {
+		return nil, time.Time{}, err
+	}
+
+	nextCrawl := pdoc.Updated
+	if t != 0 {
+		nextCrawl = time.Unix(t, 0).UTC()
+	}
+
+	return &pdoc, nextCrawl, err
+}
+
+var getSubdirsScript = redis.NewScript(0, `
+    local reply
+    for i = 1,#ARGV do
+        reply = redis.call('SORT', 'index:project:' .. ARGV[i], 'ALPHA', 'BY', 'pkg:*->path', 'GET', 'pkg:*->path', 'GET', 'pkg:*->synopsis', 'GET', 'pkg:*->kind')
+        if #reply > 0 then
+            break
+        end
+    end
+    return reply
+`)
+
+func (db *Database) getSubdirs(c redis.Conn, path string, pdoc *doc.Package) ([]Package, error) {
+	var reply interface{}
+	var err error
+
+	switch {
+	case isStandardPackage(path):
+		reply, err = getSubdirsScript.Do(c, "go")
+	case pdoc != nil:
+		reply, err = getSubdirsScript.Do(c, pdoc.ProjectRoot)
+	default:
+		var roots []interface{}
+		projectRoot := path
+		for i := 0; i < 5; i++ {
+			roots = append(roots, projectRoot)
+			if j := strings.LastIndex(projectRoot, "/"); j < 0 {
+				break
+			} else {
+				projectRoot = projectRoot[:j]
+			}
+		}
+		reply, err = getSubdirsScript.Do(c, roots...)
+	}
+
+	values, err := redis.Values(reply, err)
+	if err != nil {
+		return nil, err
+	}
+
+	var subdirs []Package
+	prefix := path + "/"
+
+	for len(values) > 0 {
+		var pkg Package
+		var kind string
+		values, err = redis.Scan(values, &pkg.Path, &pkg.Synopsis, &kind)
+		if err != nil {
+			return nil, err
+		}
+		if (kind == "p" || kind == "c") && strings.HasPrefix(pkg.Path, prefix) {
+			subdirs = append(subdirs, pkg)
+		}
+	}
+
+	return subdirs, err
+}
+
+// Get gets the package documenation and sub-directories for the the given
+// import path.
+func (db *Database) Get(path string) (*doc.Package, []Package, time.Time, error) {
+	c := db.Pool.Get()
+	defer c.Close()
+
+	pdoc, nextCrawl, err := db.getDoc(c, path)
+	if err != nil {
+		return nil, nil, time.Time{}, err
+	}
+
+	if pdoc != nil {
+		// fixup for speclal "-" path.
+		path = pdoc.ImportPath
+	}
+
+	subdirs, err := db.getSubdirs(c, path, pdoc)
+	if err != nil {
+		return nil, nil, time.Time{}, err
+	}
+	return pdoc, subdirs, nextCrawl, nil
+}
+
+func (db *Database) GetDoc(path string) (*doc.Package, time.Time, error) {
+	c := db.Pool.Get()
+	defer c.Close()
+	return db.getDoc(c, path)
+}
+
+var deleteScript = redis.NewScript(0, `
+    local path = ARGV[1]
+
+    local id = redis.call('HGET', 'ids', path)
+    if not id then
+        return false
+    end
+
+    for term in string.gmatch(redis.call('HGET', 'pkg:' .. id, 'terms') or '', '([^ ]+)') do
+        redis.call('SREM', 'index:' .. term, id)
+    end
+
+    redis.call('ZREM', 'nextCrawl', id)
+    redis.call('SREM', 'newCrawl', path)
+    redis.call('ZREM', 'popular', id)
+    redis.call('DEL', 'pkg:' .. id)
+    return redis.call('HDEL', 'ids', path)
+`)
+
+// Delete deletes the documenation for the given import path.
+func (db *Database) Delete(path string) error {
+	c := db.Pool.Get()
+	defer c.Close()
+	_, err := deleteScript.Do(c, path)
+	return err
+}
+
+func packages(reply interface{}, all bool) ([]Package, error) {
+	values, err := redis.Values(reply, nil)
+	if err != nil {
+		return nil, err
+	}
+	result := make([]Package, 0, len(values)/3)
+	for len(values) > 0 {
+		var pkg Package
+		var kind string
+		values, err = redis.Scan(values, &pkg.Path, &pkg.Synopsis, &kind)
+		if err != nil {
+			return nil, err
+		}
+		if !all && kind == "d" {
+			continue
+		}
+		if pkg.Path == "C" {
+			pkg.Synopsis = "Package C is a \"pseudo-package\" used to access the C namespace from a cgo source file."
+		}
+		result = append(result, pkg)
+	}
+	return result, nil
+}
+
+func (db *Database) getPackages(key string, all bool) ([]Package, error) {
+	c := db.Pool.Get()
+	defer c.Close()
+	reply, err := c.Do("SORT", key, "ALPHA", "BY", "pkg:*->path", "GET", "pkg:*->path", "GET", "pkg:*->synopsis", "GET", "pkg:*->kind")
+	if err != nil {
+		return nil, err
+	}
+	return packages(reply, all)
+}
+
+func (db *Database) GoIndex() ([]Package, error) {
+	return db.getPackages("index:project:go", false)
+}
+
+func (db *Database) GoSubrepoIndex() ([]Package, error) {
+	return db.getPackages("index:project:subrepo", false)
+}
+
+func (db *Database) Index() ([]Package, error) {
+	return db.getPackages("index:all:", false)
+}
+
+func (db *Database) Project(projectRoot string) ([]Package, error) {
+	return db.getPackages("index:project:"+normalizeProjectRoot(projectRoot), true)
+}
+
+func (db *Database) AllPackages() ([]Package, error) {
+	c := db.Pool.Get()
+	defer c.Close()
+	values, err := redis.Values(c.Do("SORT", "nextCrawl", "DESC", "BY", "pkg:*->score", "GET", "pkg:*->path", "GET", "pkg:*->kind"))
+	if err != nil {
+		return nil, err
+	}
+	result := make([]Package, 0, len(values)/2)
+	for len(values) > 0 {
+		var pkg Package
+		var kind string
+		values, err = redis.Scan(values, &pkg.Path, &kind)
+		if err != nil {
+			return nil, err
+		}
+		if kind == "d" {
+			continue
+		}
+		result = append(result, pkg)
+	}
+	return result, nil
+}
+
+var packagesScript = redis.NewScript(0, `
+    local result = {}
+    for i = 1,#ARGV do
+        local path = ARGV[i]
+        local synopsis = ''
+        local kind = 'u'
+        local id = redis.call('HGET', 'ids',  path)
+        if id then
+            synopsis = redis.call('HGET', 'pkg:' .. id, 'synopsis')
+            kind = redis.call('HGET', 'pkg:' .. id, 'kind')
+        end
+        result[#result+1] = path
+        result[#result+1] = synopsis
+        result[#result+1] = kind
+    end
+    return result
+`)
+
+func (db *Database) Packages(paths []string) ([]Package, error) {
+	var args []interface{}
+	for _, p := range paths {
+		args = append(args, p)
+	}
+	c := db.Pool.Get()
+	defer c.Close()
+	reply, err := packagesScript.Do(c, args...)
+	if err != nil {
+		return nil, err
+	}
+	pkgs, err := packages(reply, false)
+	sort.Sort(byPath(pkgs))
+	return pkgs, err
+}
+
+func (db *Database) ImporterCount(path string) (int, error) {
+	c := db.Pool.Get()
+	defer c.Close()
+	return redis.Int(c.Do("SCARD", "index:import:"+path))
+}
+
+func (db *Database) Importers(path string) ([]Package, error) {
+	return db.getPackages("index:import:"+path, false)
+}
+
+func (db *Database) Block(root string) error {
+	c := db.Pool.Get()
+	defer c.Close()
+	if _, err := c.Do("SADD", "block", root); err != nil {
+		return err
+	}
+	keys, err := redis.Strings(c.Do("HKEYS", "ids"))
+	if err != nil {
+		return err
+	}
+	for _, key := range keys {
+		if key == root || strings.HasPrefix(key, root) && key[len(root)] == '/' {
+			if _, err := deleteScript.Do(c, key); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+var isBlockedScript = redis.NewScript(0, `
+    local path = ''
+    for s in string.gmatch(ARGV[1], '[^/]+') do
+        path = path .. s
+        if redis.call('SISMEMBER', 'block', path) == 1 then
+            return 1
+        end
+        path = path .. '/'
+    end
+    return  0
+`)
+
+func (db *Database) IsBlocked(path string) (bool, error) {
+	c := db.Pool.Get()
+	defer c.Close()
+	return redis.Bool(isBlockedScript.Do(c, path))
+}
+
+type queryResult struct {
+	Path     string
+	Synopsis string
+	Score    float64
+}
+
+type byScore []*queryResult
+
+func (p byScore) Len() int           { return len(p) }
+func (p byScore) Less(i, j int) bool { return p[j].Score < p[i].Score }
+func (p byScore) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+
+func (db *Database) Query(q string) ([]Package, error) {
+	terms := parseQuery(q)
+	if len(terms) == 0 {
+		return nil, nil
+	}
+	c := db.Pool.Get()
+	defer c.Close()
+	n, err := redis.Int(c.Do("INCR", "maxQueryId"))
+	if err != nil {
+		return nil, err
+	}
+	id := "tmp:query-" + strconv.Itoa(n)
+
+	args := []interface{}{id}
+	for _, term := range terms {
+		args = append(args, "index:"+term)
+	}
+	c.Send("SINTERSTORE", args...)
+	c.Send("SORT", id, "DESC", "BY", "nosort", "GET", "pkg:*->path", "GET", "pkg:*->synopsis", "GET", "pkg:*->score")
+	c.Send("DEL", id)
+	c.Flush()
+	c.Receive()                              // SINTERSTORE
+	values, err := redis.Values(c.Receive()) // SORT
+	if err != nil {
+		return nil, err
+	}
+	c.Receive() // DEL
+
+	var queryResults []*queryResult
+	if err := redis.ScanSlice(values, &queryResults, "Path", "Synopsis", "Score"); err != nil {
+		return nil, err
+	}
+
+	for _, qr := range queryResults {
+		c.Send("SCARD", "index:import:"+qr.Path)
+	}
+	c.Flush()
+
+	for _, qr := range queryResults {
+		importCount, err := redis.Int(c.Receive())
+		if err != nil {
+			return nil, err
+		}
+
+		qr.Score *= math.Log(float64(10 + importCount))
+
+		if isStandardPackage(qr.Path) {
+			if strings.HasSuffix(qr.Path, q) {
+				// Big bump for exact match on standard package name.
+				qr.Score *= 10000
+			} else {
+				qr.Score *= 1.2
+			}
+		}
+
+		if q == path.Base(qr.Path) {
+			qr.Score *= 1.1
+		}
+	}
+
+	sort.Sort(byScore(queryResults))
+
+	pkgs := make([]Package, len(queryResults))
+	for i, qr := range queryResults {
+		pkgs[i].Path = qr.Path
+		pkgs[i].Synopsis = qr.Synopsis
+	}
+
+	return pkgs, nil
+}
+
+type PackageInfo struct {
+	PDoc  *doc.Package
+	Score float64
+	Kind  string
+	Size  int
+}
+
+// Do executes function f for each document in the database.
+func (db *Database) Do(f func(*PackageInfo) error) error {
+	c := db.Pool.Get()
+	defer c.Close()
+	cursor := 0
+	c.Send("SCAN", cursor, "MATCH", "pkg:*")
+	c.Flush()
+	for {
+		// Recieve previous SCAN.
+		values, err := redis.Values(c.Receive())
+		if err != nil {
+			return err
+		}
+		var keys [][]byte
+		if _, err := redis.Scan(values, &cursor, &keys); err != nil {
+			return err
+		}
+		if cursor == 0 {
+			break
+		}
+		for _, key := range keys {
+			c.Send("HMGET", key, "gob", "score", "kind", "path", "terms", "synopis")
+		}
+		c.Send("SCAN", cursor, "MATCH", "pkg:*")
+		c.Flush()
+		for _ = range keys {
+			values, err := redis.Values(c.Receive())
+			if err != nil {
+				return err
+			}
+
+			var (
+				pi       PackageInfo
+				p        []byte
+				path     string
+				terms    string
+				synopsis string
+			)
+
+			if _, err := redis.Scan(values, &p, &pi.Score, &pi.Kind, &path, &terms, &synopsis); err != nil {
+				return err
+			}
+
+			if p == nil {
+				continue
+			}
+
+			pi.Size = len(path) + len(p) + len(terms) + len(synopsis)
+
+			p, err = snappy.Decode(nil, p)
+			if err != nil {
+				return fmt.Errorf("snappy decoding %s: %v", path, err)
+			}
+
+			if err := gob.NewDecoder(bytes.NewReader(p)).Decode(&pi.PDoc); err != nil {
+				return fmt.Errorf("gob decoding %s: %v", path, err)
+			}
+			if err := f(&pi); err != nil {
+				return fmt.Errorf("func %s: %v", path, err)
+			}
+		}
+	}
+	return nil
+}
+
+var importGraphScript = redis.NewScript(0, `
+    local path = ARGV[1]
+
+    local id = redis.call('HGET', 'ids', path)
+    if not id then
+        return false
+    end
+
+    return redis.call('HMGET', 'pkg:' .. id, 'synopsis', 'terms')
+`)
+
+func (db *Database) ImportGraph(pdoc *doc.Package, hideStdDeps bool) ([]Package, [][2]int, error) {
+
+	// This breadth-first traversal of the package's dependencies uses the
+	// Redis pipeline as queue. Links to packages with invalid import paths are
+	// only included for the root package.
+
+	c := db.Pool.Get()
+	defer c.Close()
+	if err := importGraphScript.Load(c); err != nil {
+		return nil, nil, err
+	}
+
+	nodes := []Package{{Path: pdoc.ImportPath, Synopsis: pdoc.Synopsis}}
+	edges := [][2]int{}
+	index := map[string]int{pdoc.ImportPath: 0}
+
+	for _, path := range pdoc.Imports {
+		j := len(nodes)
+		index[path] = j
+		edges = append(edges, [2]int{0, j})
+		nodes = append(nodes, Package{Path: path})
+		importGraphScript.Send(c, path)
+	}
+
+	for i := 1; i < len(nodes); i++ {
+		c.Flush()
+		r, err := redis.Values(c.Receive())
+		if err == redis.ErrNil {
+			continue
+		} else if err != nil {
+			return nil, nil, err
+		}
+		var synopsis, terms string
+		if _, err := redis.Scan(r, &synopsis, &terms); err != nil {
+			return nil, nil, err
+		}
+		nodes[i].Synopsis = synopsis
+		if hideStdDeps && isStandardPackage(nodes[i].Path) {
+			continue
+		}
+		for _, term := range strings.Fields(terms) {
+			if strings.HasPrefix(term, "import:") {
+				path := term[len("import:"):]
+				j, ok := index[path]
+				if !ok {
+					j = len(nodes)
+					index[path] = j
+					nodes = append(nodes, Package{Path: path})
+					importGraphScript.Send(c, path)
+				}
+				edges = append(edges, [2]int{i, j})
+			}
+		}
+	}
+	return nodes, edges, nil
+}
+
+func (db *Database) PutGob(key string, value interface{}) error {
+	var buf bytes.Buffer
+	if err := gob.NewEncoder(&buf).Encode(value); err != nil {
+		return err
+	}
+	c := db.Pool.Get()
+	defer c.Close()
+	_, err := c.Do("SET", "gob:"+key, buf.Bytes())
+	return err
+}
+
+func (db *Database) GetGob(key string, value interface{}) error {
+	c := db.Pool.Get()
+	defer c.Close()
+	p, err := redis.Bytes(c.Do("GET", "gob:"+key))
+	if err == redis.ErrNil {
+		return nil
+	} else if err != nil {
+		return err
+	}
+	return gob.NewDecoder(bytes.NewReader(p)).Decode(value)
+}
+
+var incrementPopularScoreScript = redis.NewScript(0, `
+    local path = ARGV[1]
+    local n = ARGV[2]
+    local t = ARGV[3]
+
+    local id = redis.call('HGET', 'ids', path)
+    if not id then
+        return
+    end
+
+    local t0 = redis.call('GET', 'popular:0') or '0'
+    local f = math.exp(tonumber(t) - tonumber(t0))
+    redis.call('ZINCRBY', 'popular', tonumber(n) * f, id)
+    if f > 10 then
+        redis.call('SET', 'popular:0', t)
+        redis.call('ZUNIONSTORE', 'popular', 1, 'popular', 'WEIGHTS', 1.0 / f)
+        redis.call('ZREMRANGEBYSCORE', 'popular', '-inf', 0.05)
+    end
+`)
+
+const popularHalfLife = time.Hour * 24 * 7
+
+func (db *Database) incrementPopularScoreInternal(path string, delta float64, t time.Time) error {
+	// nt = n0 * math.Exp(-lambda * t)
+	// lambda = math.Ln2 / thalf
+	c := db.Pool.Get()
+	defer c.Close()
+	const lambda = math.Ln2 / float64(popularHalfLife)
+	scaledTime := lambda * float64(t.Sub(time.Unix(1257894000, 0)))
+	_, err := incrementPopularScoreScript.Do(c, path, delta, scaledTime)
+	return err
+}
+
+func (db *Database) IncrementPopularScore(path string) error {
+	return db.incrementPopularScoreInternal(path, 1, time.Now())
+}
+
+var popularScript = redis.NewScript(0, `
+    local stop = ARGV[1]
+    local ids = redis.call('ZREVRANGE', 'popular', '0', stop)
+    local result = {}
+    for i=1,#ids do
+        local values = redis.call('HMGET', 'pkg:' .. ids[i], 'path', 'synopsis', 'kind')
+        result[#result+1] = values[1]
+        result[#result+1] = values[2]
+        result[#result+1] = values[3]
+    end
+    return result
+`)
+
+func (db *Database) Popular(count int) ([]Package, error) {
+	c := db.Pool.Get()
+	defer c.Close()
+	reply, err := popularScript.Do(c, count-1)
+	if err != nil {
+		return nil, err
+	}
+	pkgs, err := packages(reply, false)
+	return pkgs, err
+}
+
+var popularWithScoreScript = redis.NewScript(0, `
+    local ids = redis.call('ZREVRANGE', 'popular', '0', -1, 'WITHSCORES')
+    local result = {}
+    for i=1,#ids,2 do
+        result[#result+1] = redis.call('HGET', 'pkg:' .. ids[i], 'path')
+        result[#result+1] = ids[i+1]
+        result[#result+1] = 'p'
+    end
+    return result
+`)
+
+func (db *Database) PopularWithScores() ([]Package, error) {
+	c := db.Pool.Get()
+	defer c.Close()
+	reply, err := popularWithScoreScript.Do(c)
+	if err != nil {
+		return nil, err
+	}
+	pkgs, err := packages(reply, false)
+	return pkgs, err
+}
+
+func (db *Database) PopNewCrawl() (string, bool, error) {
+	c := db.Pool.Get()
+	defer c.Close()
+
+	var subdirs []Package
+
+	path, err := redis.String(c.Do("SPOP", "newCrawl"))
+	switch {
+	case err == redis.ErrNil:
+		err = nil
+		path = ""
+	case err == nil:
+		subdirs, err = db.getSubdirs(c, path, nil)
+	}
+	return path, len(subdirs) > 0, err
+}
+
+func (db *Database) AddBadCrawl(path string) error {
+	c := db.Pool.Get()
+	defer c.Close()
+	_, err := c.Do("SADD", "badCrawl", path)
+	return err
+}
+
+var incrementCounterScript = redis.NewScript(0, `
+    local key = 'counter:' .. ARGV[1]
+    local n = tonumber(ARGV[2])
+    local t = tonumber(ARGV[3])
+    local exp = tonumber(ARGV[4])
+
+    local counter = redis.call('GET', key)
+    if counter then
+        counter = cjson.decode(counter)
+        n = n + counter.n * math.exp(counter.t - t)
+    end
+
+    redis.call('SET', key, cjson.encode({n = n; t = t}))
+    redis.call('EXPIRE', key, exp)
+    return tostring(n)
+`)
+
+const counterHalflife = time.Hour
+
+func (db *Database) incrementCounterInternal(key string, delta float64, t time.Time) (float64, error) {
+	// nt = n0 * math.Exp(-lambda * t)
+	// lambda = math.Ln2 / thalf
+	c := db.Pool.Get()
+	defer c.Close()
+	const lambda = math.Ln2 / float64(counterHalflife)
+	scaledTime := lambda * float64(t.Sub(time.Unix(1257894000, 0)))
+	return redis.Float64(incrementCounterScript.Do(c, key, delta, scaledTime, (4*counterHalflife)/time.Second))
+}
+
+func (db *Database) IncrementCounter(key string, delta float64) (float64, error) {
+	return db.incrementCounterInternal(key, delta, time.Now())
+}
diff --git a/database/database_test.go b/database/database_test.go
new file mode 100644
index 0000000..f37a61f
--- /dev/null
+++ b/database/database_test.go
@@ -0,0 +1,259 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package database
+
+import (
+	"math"
+	"reflect"
+	"strconv"
+	"testing"
+	"time"
+
+	"github.com/garyburd/gddo/doc"
+	"github.com/garyburd/redigo/redis"
+)
+
+func newDB(t *testing.T) *Database {
+	p := redis.NewPool(func() (redis.Conn, error) {
+		c, err := redis.DialTimeout("tcp", ":6379", 0, 1*time.Second, 1*time.Second)
+		if err != nil {
+			return nil, err
+		}
+		_, err = c.Do("SELECT", "9")
+		if err != nil {
+			c.Close()
+			return nil, err
+		}
+		return c, nil
+	}, 1)
+
+	c := p.Get()
+	defer c.Close()
+	n, err := redis.Int(c.Do("DBSIZE"))
+	if n != 0 || err != nil {
+		t.Fatalf("DBSIZE returned %d, %v", n, err)
+	}
+	return &Database{Pool: p}
+}
+
+func closeDB(db *Database) {
+	c := db.Pool.Get()
+	c.Do("FLUSHDB")
+	c.Close()
+}
+
+func TestPutGet(t *testing.T) {
+	var nextCrawl = time.Unix(time.Now().Add(time.Hour).Unix(), 0).UTC()
+
+	db := newDB(t)
+	defer closeDB(db)
+	pdoc := &doc.Package{
+		ImportPath:  "github.com/user/repo/foo/bar",
+		Name:        "bar",
+		Synopsis:    "hello",
+		ProjectRoot: "github.com/user/repo",
+		ProjectName: "foo",
+		Updated:     time.Now().Add(-time.Hour),
+		Imports:     []string{"C", "errors", "github.com/user/repo/foo/bar"}, // self import for testing convenience.
+	}
+	if err := db.Put(pdoc, nextCrawl, false); err != nil {
+		t.Errorf("db.Put() returned error %v", err)
+	}
+	if err := db.Put(pdoc, time.Time{}, false); err != nil {
+		t.Errorf("second db.Put() returned error %v", err)
+	}
+
+	actualPdoc, actualSubdirs, actualCrawl, err := db.Get("github.com/user/repo/foo/bar")
+	if err != nil {
+		t.Fatalf("db.Get(.../foo/bar) returned %v", err)
+	}
+	if len(actualSubdirs) != 0 {
+		t.Errorf("db.Get(.../foo/bar) returned subdirs %v, want none", actualSubdirs)
+	}
+	if !reflect.DeepEqual(actualPdoc, pdoc) {
+		t.Errorf("db.Get(.../foo/bar) returned doc %v, want %v", actualPdoc, pdoc)
+	}
+	if !nextCrawl.Equal(actualCrawl) {
+		t.Errorf("db.Get(.../foo/bar) returned crawl %v, want %v", actualCrawl, nextCrawl)
+	}
+
+	before := time.Now().Unix()
+	if err := db.BumpCrawl(pdoc.ProjectRoot); err != nil {
+		t.Errorf("db.BumpCrawl() returned %v", err)
+	}
+	after := time.Now().Unix()
+
+	_, _, actualCrawl, _ = db.Get("github.com/user/repo/foo/bar")
+	if actualCrawl.Unix() < before || after < actualCrawl.Unix() {
+		t.Errorf("actualCrawl=%v, expect value between %v and %v", actualCrawl.Unix(), before, after)
+	}
+
+	// Popular
+
+	if err := db.IncrementPopularScore(pdoc.ImportPath); err != nil {
+		t.Errorf("db.IncrementPopularScore() returned %v", err)
+	}
+
+	// Get "-"
+
+	actualPdoc, _, _, err = db.Get("-")
+	if err != nil {
+		t.Fatalf("db.Get(-) returned %v", err)
+	}
+	if !reflect.DeepEqual(actualPdoc, pdoc) {
+		t.Errorf("db.Get(-) returned doc %v, want %v", actualPdoc, pdoc)
+	}
+
+	actualPdoc, actualSubdirs, _, err = db.Get("github.com/user/repo/foo")
+	if err != nil {
+		t.Fatalf("db.Get(.../foo) returned %v", err)
+	}
+	if actualPdoc != nil {
+		t.Errorf("db.Get(.../foo) returned doc %v, want %v", actualPdoc, nil)
+	}
+	expectedSubdirs := []Package{{Path: "github.com/user/repo/foo/bar", Synopsis: "hello"}}
+	if !reflect.DeepEqual(actualSubdirs, expectedSubdirs) {
+		t.Errorf("db.Get(.../foo) returned subdirs %v, want %v", actualSubdirs, expectedSubdirs)
+	}
+	actualImporters, err := db.Importers("github.com/user/repo/foo/bar")
+	if err != nil {
+		t.Fatalf("db.Importers() retunred error %v", err)
+	}
+	expectedImporters := []Package{{"github.com/user/repo/foo/bar", "hello"}}
+	if !reflect.DeepEqual(actualImporters, expectedImporters) {
+		t.Errorf("db.Importers() = %v, want %v", actualImporters, expectedImporters)
+	}
+	actualImports, err := db.Packages(pdoc.Imports)
+	if err != nil {
+		t.Fatalf("db.Imports() retunred error %v", err)
+	}
+	for i := range actualImports {
+		if actualImports[i].Path == "C" {
+			actualImports[i].Synopsis = ""
+		}
+	}
+	expectedImports := []Package{{"C", ""}, {"errors", ""}, {"github.com/user/repo/foo/bar", "hello"}}
+	if !reflect.DeepEqual(actualImports, expectedImports) {
+		t.Errorf("db.Imports() = %v, want %v", actualImports, expectedImports)
+	}
+	importerCount, _ := db.ImporterCount("github.com/user/repo/foo/bar")
+	if importerCount != 1 {
+		t.Errorf("db.ImporterCount() = %d, want %d", importerCount, 1)
+	}
+	if err := db.Delete("github.com/user/repo/foo/bar"); err != nil {
+		t.Errorf("db.Delete() returned error %v", err)
+	}
+
+	db.Query("bar")
+
+	if err := db.Put(pdoc, time.Time{}, false); err != nil {
+		t.Errorf("db.Put() returned error %v", err)
+	}
+
+	if err := db.Block("github.com/user/repo"); err != nil {
+		t.Errorf("db.Block() returned error %v", err)
+	}
+
+	blocked, err := db.IsBlocked("github.com/user/repo/foo/bar")
+	if !blocked || err != nil {
+		t.Errorf("db.IsBlocked(github.com/user/repo/foo/bar) returned %v, %v, want true, nil", blocked, err)
+	}
+
+	blocked, err = db.IsBlocked("github.com/foo/bar")
+	if blocked || err != nil {
+		t.Errorf("db.IsBlocked(github.com/foo/bar) returned %v, %v, want false, nil", blocked, err)
+	}
+
+	c := db.Pool.Get()
+	defer c.Close()
+	c.Send("DEL", "maxQueryId")
+	c.Send("DEL", "maxPackageId")
+	c.Send("DEL", "block")
+	c.Send("DEL", "popular:0")
+	c.Send("DEL", "newCrawl")
+	keys, err := redis.Values(c.Do("HKEYS", "ids"))
+	for _, key := range keys {
+		t.Errorf("unexpected id %s", key)
+	}
+	keys, err = redis.Values(c.Do("KEYS", "*"))
+	for _, key := range keys {
+		t.Errorf("unexpected key %s", key)
+	}
+}
+
+const epsilon = 0.000001
+
+func TestPopular(t *testing.T) {
+	db := newDB(t)
+	defer closeDB(db)
+	c := db.Pool.Get()
+	defer c.Close()
+
+	// Add scores for packages. On each iteration, add half-life to time and
+	// divide the score by two. All packages should have the same score.
+
+	now := time.Now()
+	score := float64(4048)
+	for id := 12; id >= 0; id-- {
+		path := "github.com/user/repo/p" + strconv.Itoa(id)
+		c.Do("HSET", "ids", path, id)
+		err := db.incrementPopularScoreInternal(path, score, now)
+		if err != nil {
+			t.Fatal(err)
+		}
+		now = now.Add(popularHalfLife)
+		score /= 2
+	}
+
+	values, _ := redis.Values(c.Do("ZRANGE", "popular", "0", "100000", "WITHSCORES"))
+	if len(values) != 26 {
+		t.Fatalf("Expected 26 values, got %d", len(values))
+	}
+
+	// Check for equal scores.
+	score, err := redis.Float64(values[1], nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for i := 3; i < len(values); i += 2 {
+		s, _ := redis.Float64(values[i], nil)
+		if math.Abs(score-s)/score > epsilon {
+			t.Errorf("Bad score, score[1]=%g, score[%d]=%g", score, i, s)
+		}
+	}
+}
+
+func TestCounter(t *testing.T) {
+	db := newDB(t)
+	defer closeDB(db)
+
+	const key = "127.0.0.1"
+
+	now := time.Now()
+	n, err := db.incrementCounterInternal(key, 1, now)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if math.Abs(n-1.0) > epsilon {
+		t.Errorf("1: got n=%g, want 1", n)
+	}
+	n, err = db.incrementCounterInternal(key, 1, now)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if math.Abs(n-2.0)/2.0 > epsilon {
+		t.Errorf("2: got n=%g, want 2", n)
+	}
+	now = now.Add(counterHalflife)
+	n, err = db.incrementCounterInternal(key, 1, now)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if math.Abs(n-2.0)/2.0 > epsilon {
+		t.Errorf("3: got n=%g, want 2", n)
+	}
+}
diff --git a/database/index.go b/database/index.go
new file mode 100644
index 0000000..acfe9eb
--- /dev/null
+++ b/database/index.go
@@ -0,0 +1,155 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package database
+
+import (
+	"path"
+	"regexp"
+	"strings"
+	"unicode"
+
+	"github.com/garyburd/gddo/doc"
+	"github.com/garyburd/gosrc"
+)
+
+func isStandardPackage(path string) bool {
+	return strings.Index(path, ".") < 0
+}
+
+func isTermSep(r rune) bool {
+	return unicode.IsSpace(r) || unicode.IsPunct(r) || unicode.IsSymbol(r)
+}
+
+func normalizeProjectRoot(projectRoot string) string {
+	if projectRoot == "" {
+		return "go"
+	}
+	return projectRoot
+}
+
+var synonyms = map[string]string{
+	"redis":    "redisdb", // append db to avoid stemming to 'red'
+	"rand":     "random",
+	"postgres": "postgresql",
+	"mongo":    "mongodb",
+}
+
+func term(s string) string {
+	s = strings.ToLower(s)
+	if x, ok := synonyms[s]; ok {
+		s = x
+	}
+	return stem(s)
+}
+
+var httpPat = regexp.MustCompile(`https?://\S+`)
+
+func documentTerms(pdoc *doc.Package, score float64) []string {
+
+	terms := make(map[string]bool)
+
+	// Project root
+
+	projectRoot := normalizeProjectRoot(pdoc.ProjectRoot)
+	terms["project:"+projectRoot] = true
+
+	if strings.HasPrefix(pdoc.ImportPath, "code.google.com/p/go.") {
+		terms["project:subrepo"] = true
+	}
+
+	// Imports
+
+	for _, path := range pdoc.Imports {
+		if gosrc.IsValidPath(path) {
+			terms["import:"+path] = true
+		}
+	}
+
+	if score > 0 {
+
+		if isStandardPackage(pdoc.ImportPath) {
+			for _, term := range parseQuery(pdoc.ImportPath) {
+				terms[term] = true
+			}
+		} else {
+			terms["all:"] = true
+			for _, term := range parseQuery(pdoc.ProjectName) {
+				terms[term] = true
+			}
+			for _, term := range parseQuery(pdoc.Name) {
+				terms[term] = true
+			}
+		}
+
+		// Synopsis
+
+		synopsis := httpPat.ReplaceAllLiteralString(pdoc.Synopsis, "")
+		for i, s := range strings.FieldsFunc(synopsis, isTermSep) {
+			s = strings.ToLower(s)
+			if !stopWord[s] && (i > 3 || s != "package") {
+				terms[term(s)] = true
+			}
+		}
+	}
+
+	result := make([]string, 0, len(terms))
+	for term := range terms {
+		result = append(result, term)
+	}
+	return result
+}
+
+func documentScore(pdoc *doc.Package) float64 {
+	if pdoc.Name == "" ||
+		pdoc.IsCmd ||
+		len(pdoc.Errors) > 0 ||
+		strings.HasSuffix(pdoc.ImportPath, ".go") ||
+		strings.HasPrefix(pdoc.ImportPath, "gist.github.com/") {
+		return 0
+	}
+
+	for _, p := range pdoc.Imports {
+		if strings.HasSuffix(p, ".go") {
+			return 0
+		}
+	}
+
+	if !pdoc.Truncated &&
+		len(pdoc.Consts) == 0 &&
+		len(pdoc.Vars) == 0 &&
+		len(pdoc.Funcs) == 0 &&
+		len(pdoc.Types) == 0 &&
+		len(pdoc.Examples) == 0 {
+		return 0
+	}
+
+	r := 1.0
+	if pdoc.Doc == "" || pdoc.Synopsis == "" {
+		r *= 0.95
+	}
+	if path.Base(pdoc.ImportPath) != pdoc.Name {
+		r *= 0.9
+	}
+	for i := 0; i < strings.Count(pdoc.ImportPath[len(pdoc.ProjectRoot):], "/"); i++ {
+		r *= 0.99
+	}
+	if strings.Index(pdoc.ImportPath[len(pdoc.ProjectRoot):], "/src/") > 0 {
+		r *= 0.95
+	}
+	return r
+}
+
+func parseQuery(q string) []string {
+	var terms []string
+	q = strings.ToLower(q)
+	for _, s := range strings.FieldsFunc(q, isTermSep) {
+		if !stopWord[s] {
+			terms = append(terms, term(s))
+		}
+	}
+	return terms
+}
diff --git a/database/index_test.go b/database/index_test.go
new file mode 100644
index 0000000..353ed6d
--- /dev/null
+++ b/database/index_test.go
@@ -0,0 +1,100 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package database
+
+import (
+	"github.com/garyburd/gddo/doc"
+	"reflect"
+	"sort"
+	"testing"
+)
+
+var indexTests = []struct {
+	pdoc  *doc.Package
+	terms []string
+}{
+	{&doc.Package{
+		ImportPath:  "strconv",
+		ProjectRoot: "",
+		ProjectName: "Go",
+		Name:        "strconv",
+		Synopsis:    "Package strconv implements conversions to and from string representations of basic data types.",
+		Doc:         "Package strconv implements conversions to and from string representations\nof basic data types.",
+		Imports:     []string{"errors", "math", "unicode/utf8"},
+		Funcs:       []*doc.Func{{}},
+	},
+		[]string{
+			"bas",
+			"convert",
+			"dat",
+			"import:errors",
+			"import:math",
+			"import:unicode/utf8",
+			"project:go",
+			"repres",
+			"strconv",
+			"string",
+			"typ"},
+	},
+	{&doc.Package{
+		ImportPath:  "github.com/user/repo/dir",
+		ProjectRoot: "github.com/user/repo",
+		ProjectName: "go-oauth",
+		ProjectURL:  "https://github.com/user/repo/",
+		Name:        "dir",
+		Synopsis:    "Package dir implements a subset of the OAuth client interface as defined in RFC 5849.",
+		Doc: "Package oauth implements a subset of the OAuth client interface as defined in RFC 5849.\n\n" +
+			"This package assumes that the application writes request URL paths to the\nnetwork using " +
+			"the encoding implemented by the net/url URL RequestURI method.\n" +
+			"The HTTP client in the standard net/http package uses this encoding.",
+		IsCmd: false,
+		Imports: []string{
+			"bytes",
+			"crypto/hmac",
+			"crypto/sha1",
+			"encoding/base64",
+			"encoding/binary",
+			"errors",
+			"fmt",
+			"io",
+			"io/ioutil",
+			"net/http",
+			"net/url",
+			"regexp",
+			"sort",
+			"strconv",
+			"strings",
+			"sync",
+			"time",
+		},
+		TestImports: []string{"bytes", "net/url", "testing"},
+		Funcs:       []*doc.Func{{}},
+	},
+		[]string{
+			"all:",
+			"5849", "cly", "defin", "dir", "go",
+			"import:bytes", "import:crypto/hmac", "import:crypto/sha1",
+			"import:encoding/base64", "import:encoding/binary", "import:errors",
+			"import:fmt", "import:io", "import:io/ioutil", "import:net/http",
+			"import:net/url", "import:regexp", "import:sort", "import:strconv",
+			"import:strings", "import:sync", "import:time", "interfac",
+			"oau", "project:github.com/user/repo", "rfc", "subset",
+		},
+	},
+}
+
+func TestDocTerms(t *testing.T) {
+	for _, tt := range indexTests {
+		score := documentScore(tt.pdoc)
+		terms := documentTerms(tt.pdoc, score)
+		sort.Strings(terms)
+		sort.Strings(tt.terms)
+		if !reflect.DeepEqual(terms, tt.terms) {
+			t.Errorf("documentTerms(%s)=%#v, want %#v", tt.pdoc.ImportPath, terms, tt.terms)
+		}
+	}
+}
diff --git a/database/stem.go b/database/stem.go
new file mode 100644
index 0000000..534ae68
--- /dev/null
+++ b/database/stem.go
@@ -0,0 +1,123 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+// This file implements the Paice/Husk stemming algorithm.
+// http://www.comp.lancs.ac.uk/computing/research/stemming/Links/paice.htm
+
+package database
+
+import (
+	"bytes"
+	"regexp"
+	"strconv"
+)
+
+const stemRuleText = `
+ai*2. a*1. 
+bb1. 
+city3s. ci2> cn1t> 
+dd1. dei3y> deec2ss. dee1. de2> dooh4> 
+e1> 
+feil1v. fi2> 
+gni3> gai3y. ga2> gg1. 
+ht*2. hsiug5ct. hsi3> 
+i*1. i1y> 
+ji1d. juf1s. ju1d. jo1d. jeh1r. jrev1t. jsim2t. jn1d. j1s. 
+lbaifi6. lbai4y. lba3> lbi3. lib2l> lc1. lufi4y. luf3> lu2. lai3> lau3> la2> ll1. 
+mui3. mu*2. msi3> mm1. 
+nois4j> noix4ct. noi3> nai3> na2> nee0. ne2> nn1. 
+pihs4> pp1. 
+re2> rae0. ra2. ro2> ru2> rr1. rt1> rei3y> 
+sei3y> sis2. si2> ssen4> ss0. suo3> su*2. s*1> s0. 
+tacilp4y. ta2> tnem4> tne3> tna3> tpir2b. tpro2b. tcud1. tpmus2. tpec2iv. tulo2v. tsis0. tsi3> tt1. 
+uqi3. ugo1. 
+vis3j> vie0. vi2> 
+ylb1> yli3y> ylp0. yl2> ygo1. yhp1. ymo1. ypo1. yti3> yte3> ytl2. yrtsi5. yra3> yro3> yfi3. ycn2t> yca3> 
+zi2> zy1s. 
+`
+
+type stemRule struct {
+	text   string
+	suffix []byte
+	intact bool
+	remove int
+	append []byte
+	more   bool
+}
+
+func parseStemRules() map[byte][]*stemRule {
+
+	rules := make(map[byte][]*stemRule)
+	for _, m := range regexp.MustCompile(`(?m)(?:^| )([a-zA-Z]*)(\*?)([0-9])([a-zA-z]*)([.>])`).FindAllStringSubmatch(stemRuleText, -1) {
+
+		suffix := []byte(m[1])
+		for i := 0; i < len(suffix)/2; i++ {
+			j := len(suffix) - 1 - i
+			suffix[i], suffix[j] = suffix[j], suffix[i]
+		}
+
+		remove, _ := strconv.Atoi(m[3])
+		r := &stemRule{
+			text:   m[0],
+			suffix: suffix,
+			intact: m[2] == "*",
+			remove: remove,
+			append: []byte(m[4]),
+			more:   m[5] == ">",
+		}
+		c := suffix[len(suffix)-1]
+		rules[c] = append(rules[c], r)
+	}
+	return rules
+}
+
+var stemRules = parseStemRules()
+
+func firstVowel(offset int, p []byte) int {
+	for i, b := range p {
+		switch b {
+		case 'a', 'e', 'i', 'o', 'u':
+			return offset + i
+		case 'y':
+			if offset+i > 0 {
+				return offset + i
+			}
+		}
+	}
+	return -1
+}
+
+func acceptableStem(a, b []byte) bool {
+	i := firstVowel(0, a)
+	if i < 0 {
+		i = firstVowel(len(a), b)
+	}
+	l := len(a) + len(b)
+	if i == 0 {
+		return l > 1
+	}
+	return i >= 0 && l > 2
+}
+
+func stem(s string) string {
+	stem := bytes.ToLower([]byte(s))
+	intact := true
+	run := acceptableStem(stem, []byte{})
+	for run {
+		run = false
+		for _, rule := range stemRules[stem[len(stem)-1]] {
+			if bytes.HasSuffix(stem, rule.suffix) &&
+				(intact || !rule.intact) &&
+				acceptableStem(stem[:len(stem)-rule.remove], rule.append) {
+				stem = append(stem[:len(stem)-rule.remove], rule.append...)
+				intact = false
+				run = rule.more
+				break
+			}
+		}
+	}
+	return string(stem)
+}
diff --git a/database/stem_test.go b/database/stem_test.go
new file mode 100644
index 0000000..c3bc05f
--- /dev/null
+++ b/database/stem_test.go
@@ -0,0 +1,31 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package database
+
+import (
+	"testing"
+)
+
+var stemTests = []struct {
+	s, expected string
+}{
+	{"html", "html"},
+	{"strings", "string"},
+	{"ballroom", "ballroom"},
+	{"mechanicalization", "mech"},
+	{"pragmaticality", "pragm"},
+	{"rationalistically", "rat"},
+}
+
+func TestStem(t *testing.T) {
+	for _, tt := range stemTests {
+		actual := stem(tt.s)
+		if actual != tt.expected {
+			t.Errorf("stem(%q) = %q, want %q", tt.s, actual, tt.expected)
+		}
+	}
+}
diff --git a/database/stop.go b/database/stop.go
new file mode 100644
index 0000000..3b23e34
--- /dev/null
+++ b/database/stop.go
@@ -0,0 +1,143 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package database
+
+import (
+	"strings"
+)
+
+var stopWord = createStopWordMap()
+
+func createStopWordMap() map[string]bool {
+	m := make(map[string]bool)
+	for _, s := range strings.Fields(stopText) {
+		m[s] = true
+	}
+	return m
+}
+
+const stopText = `
+a
+about
+after
+all
+also
+am
+an
+and
+another
+any
+are
+as
+at
+b
+be
+because
+been
+before
+being
+between
+both
+but
+by
+c
+came
+can
+come
+could
+d
+did
+do
+e
+each
+f
+for
+from
+g
+get
+got
+h
+had
+has
+have
+he
+her
+here
+him
+himself
+his
+how
+i
+if
+implement
+implements
+in
+into
+is
+it
+j
+k
+l
+like
+m
+make
+many
+me
+might
+more
+most
+much
+must
+my
+n
+never
+now
+o
+of
+on
+only
+or
+other
+our
+out
+over
+p
+q
+r
+s
+said
+same
+see
+should
+since
+some
+still
+such
+t
+take
+than
+that
+the
+their
+them
+then
+there
+these
+they
+this
+those
+through
+to
+too
+u
+under
+v
+w
+x
+y
+z
+`
diff --git a/doc/builder.go b/doc/builder.go
new file mode 100644
index 0000000..f772f54
--- /dev/null
+++ b/doc/builder.go
@@ -0,0 +1,590 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package doc
+
+import (
+	"bytes"
+	"errors"
+	"go/ast"
+	"go/build"
+	"go/doc"
+	"go/format"
+	"go/parser"
+	"go/token"
+	"regexp"
+	"sort"
+	"strings"
+	"time"
+	"unicode"
+	"unicode/utf8"
+
+	"github.com/garyburd/gosrc"
+)
+
+func startsWithUppercase(s string) bool {
+	r, _ := utf8.DecodeRuneInString(s)
+	return unicode.IsUpper(r)
+}
+
+var badSynopsisPrefixes = []string{
+	"Autogenerated by Thrift Compiler",
+	"Automatically generated ",
+	"Auto-generated by ",
+	"Copyright ",
+	"COPYRIGHT ",
+	`THE SOFTWARE IS PROVIDED "AS IS"`,
+	"TODO: ",
+	"vim:",
+}
+
+// synopsis extracts the first sentence from s. All runs of whitespace are
+// replaced by a single space.
+func synopsis(s string) string {
+
+	parts := strings.SplitN(s, "\n\n", 2)
+	s = parts[0]
+
+	var buf []byte
+	const (
+		other = iota
+		period
+		space
+	)
+	last := space
+Loop:
+	for i := 0; i < len(s); i++ {
+		b := s[i]
+		switch b {
+		case ' ', '\t', '\r', '\n':
+			switch last {
+			case period:
+				break Loop
+			case other:
+				buf = append(buf, ' ')
+				last = space
+			}
+		case '.':
+			last = period
+			buf = append(buf, b)
+		default:
+			last = other
+			buf = append(buf, b)
+		}
+	}
+
+	// Ensure that synopsis fits an App Engine datastore text property.
+	const m = 400
+	if len(buf) > m {
+		buf = buf[:m]
+		if i := bytes.LastIndex(buf, []byte{' '}); i >= 0 {
+			buf = buf[:i]
+		}
+		buf = append(buf, " ..."...)
+	}
+
+	s = string(buf)
+
+	r, n := utf8.DecodeRuneInString(s)
+	if n < 0 || unicode.IsPunct(r) || unicode.IsSymbol(r) {
+		// ignore Markdown headings, editor settings, Go build constraints, and * in poorly formatted block comments.
+		s = ""
+	} else {
+		for _, prefix := range badSynopsisPrefixes {
+			if strings.HasPrefix(s, prefix) {
+				s = ""
+				break
+			}
+		}
+	}
+
+	return s
+}
+
+var referencesPats = []*regexp.Regexp{
+	regexp.MustCompile(`"([-a-zA-Z0-9~+_./]+)"`), // quoted path
+	regexp.MustCompile(`https://drone\.io/([-a-zA-Z0-9~+_./]+)/status\.png`),
+	regexp.MustCompile(`\b(?:` + strings.Join([]string{
+		`go\s+get\s+`,
+		`goinstall\s+`,
+		regexp.QuoteMeta("http://godoc.org/"),
+		regexp.QuoteMeta("http://gopkgdoc.appspot.com/pkg/"),
+		regexp.QuoteMeta("http://go.pkgdoc.org/"),
+		regexp.QuoteMeta("http://gowalker.org/"),
+	}, "|") + `)([-a-zA-Z0-9~+_./]+)`),
+}
+
+// addReferences adds packages referenced in plain text s.
+func addReferences(references map[string]bool, s []byte) {
+	for _, pat := range referencesPats {
+		for _, m := range pat.FindAllSubmatch(s, -1) {
+			p := string(m[1])
+			if gosrc.IsValidRemotePath(p) {
+				references[p] = true
+			}
+		}
+	}
+}
+
+type byFuncName []*doc.Func
+
+func (s byFuncName) Len() int           { return len(s) }
+func (s byFuncName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
+func (s byFuncName) Less(i, j int) bool { return s[i].Name < s[j].Name }
+
+func removeAssociations(dpkg *doc.Package) {
+	for _, t := range dpkg.Types {
+		dpkg.Funcs = append(dpkg.Funcs, t.Funcs...)
+		t.Funcs = nil
+	}
+	sort.Sort(byFuncName(dpkg.Funcs))
+}
+
+// builder holds the state used when building the documentation.
+type builder struct {
+	srcs     map[string]*source
+	fset     *token.FileSet
+	examples []*doc.Example
+	buf      []byte // scratch space for printNode method.
+}
+
+type Value struct {
+	Decl Code
+	Pos  Pos
+	Doc  string
+}
+
+func (b *builder) values(vdocs []*doc.Value) []*Value {
+	var result []*Value
+	for _, d := range vdocs {
+		result = append(result, &Value{
+			Decl: b.printDecl(d.Decl),
+			Pos:  b.position(d.Decl),
+			Doc:  d.Doc,
+		})
+	}
+	return result
+}
+
+type Note struct {
+	Pos  Pos
+	UID  string
+	Body string
+}
+
+type posNode token.Pos
+
+func (p posNode) Pos() token.Pos { return token.Pos(p) }
+func (p posNode) End() token.Pos { return token.Pos(p) }
+
+func (b *builder) notes(gnotes map[string][]*doc.Note) map[string][]*Note {
+	if len(gnotes) == 0 {
+		return nil
+	}
+	notes := make(map[string][]*Note)
+	for tag, gvalues := range gnotes {
+		values := make([]*Note, len(gvalues))
+		for i := range gvalues {
+			values[i] = &Note{
+				Pos:  b.position(posNode(gvalues[i].Pos)),
+				UID:  gvalues[i].UID,
+				Body: strings.TrimSpace(gvalues[i].Body),
+			}
+		}
+		notes[tag] = values
+	}
+	return notes
+}
+
+type Example struct {
+	Name   string
+	Doc    string
+	Code   Code
+	Play   string
+	Output string
+}
+
+var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*output:`)
+
+func (b *builder) getExamples(name string) []*Example {
+	var docs []*Example
+	for _, e := range b.examples {
+		if !strings.HasPrefix(e.Name, name) {
+			continue
+		}
+		n := e.Name[len(name):]
+		if n != "" {
+			if i := strings.LastIndex(n, "_"); i != 0 {
+				continue
+			}
+			n = n[1:]
+			if startsWithUppercase(n) {
+				continue
+			}
+			n = strings.Title(n)
+		}
+
+		code, output := b.printExample(e)
+
+		play := ""
+		if e.Play != nil {
+			b.buf = b.buf[:0]
+			if err := format.Node(sliceWriter{&b.buf}, b.fset, e.Play); err != nil {
+				play = err.Error()
+			} else {
+				play = string(b.buf)
+			}
+		}
+
+		docs = append(docs, &Example{
+			Name:   n,
+			Doc:    e.Doc,
+			Code:   code,
+			Output: output,
+			Play:   play})
+	}
+	return docs
+}
+
+type Func struct {
+	Decl     Code
+	Pos      Pos
+	Doc      string
+	Name     string
+	Recv     string
+	Examples []*Example
+}
+
+func (b *builder) funcs(fdocs []*doc.Func) []*Func {
+	var result []*Func
+	for _, d := range fdocs {
+		var exampleName string
+		switch {
+		case d.Recv == "":
+			exampleName = d.Name
+		case d.Recv[0] == '*':
+			exampleName = d.Recv[1:] + "_" + d.Name
+		default:
+			exampleName = d.Recv + "_" + d.Name
+		}
+		result = append(result, &Func{
+			Decl:     b.printDecl(d.Decl),
+			Pos:      b.position(d.Decl),
+			Doc:      d.Doc,
+			Name:     d.Name,
+			Recv:     d.Recv,
+			Examples: b.getExamples(exampleName),
+		})
+	}
+	return result
+}
+
+type Type struct {
+	Doc      string
+	Name     string
+	Decl     Code
+	Pos      Pos
+	Consts   []*Value
+	Vars     []*Value
+	Funcs    []*Func
+	Methods  []*Func
+	Examples []*Example
+}
+
+func (b *builder) types(tdocs []*doc.Type) []*Type {
+	var result []*Type
+	for _, d := range tdocs {
+		result = append(result, &Type{
+			Doc:      d.Doc,
+			Name:     d.Name,
+			Decl:     b.printDecl(d.Decl),
+			Pos:      b.position(d.Decl),
+			Consts:   b.values(d.Consts),
+			Vars:     b.values(d.Vars),
+			Funcs:    b.funcs(d.Funcs),
+			Methods:  b.funcs(d.Methods),
+			Examples: b.getExamples(d.Name),
+		})
+	}
+	return result
+}
+
+var packageNamePats = []*regexp.Regexp{
+	// Strip suffix and prefix separated by illegal id runes "." and "-".
+	regexp.MustCompile(`/([^-./]+)[-.](?:go|git)$`),
+	regexp.MustCompile(`/(?:go)[-.]([^-./]+)$`),
+
+	regexp.MustCompile(`^code\.google\.com/p/google-api-go-client/([^/]+)/v[^/]+$`),
+	regexp.MustCompile(`^code\.google\.com/p/biogo\.([^/]+)$`),
+
+	// It's also common for the last element of the path to contain an
+	// extra "go" prefix, but not always. TODO: examine unresolved ids to
+	// detect when trimming the "go" prefix is appropriate.
+
+	// Last component of path.
+	regexp.MustCompile(`([^/]+)$`),
+}
+
+func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) {
+	pkg := imports[path]
+	if pkg != nil {
+		return pkg, nil
+	}
+
+	// Guess the package name without importing it.
+	for _, pat := range packageNamePats {
+		m := pat.FindStringSubmatch(path)
+		if m != nil {
+			pkg = ast.NewObj(ast.Pkg, m[1])
+			pkg.Data = ast.NewScope(nil)
+			imports[path] = pkg
+			return pkg, nil
+		}
+	}
+
+	return nil, errors.New("package not found")
+}
+
+type File struct {
+	Name string
+	URL  string
+}
+
+type Pos struct {
+	Line int32  // 0 if not valid.
+	N    uint16 // number of lines - 1
+	File int16  // index in Package.Files
+}
+
+type source struct {
+	name      string
+	browseURL string
+	data      []byte
+	index     int
+}
+
+// PackageVersion is modified when previously stored packages are invalid.
+const PackageVersion = "6"
+
+type Package struct {
+	// The import path for this package.
+	ImportPath string
+
+	// Import path prefix for all packages in the project.
+	ProjectRoot string
+
+	// Name of the project.
+	ProjectName string
+
+	// Project home page.
+	ProjectURL string
+
+	// Errors found when fetching or parsing this package.
+	Errors []string
+
+	// Packages referenced in README files.
+	References []string
+
+	// Version control system: git, hg, bzr, ...
+	VCS string
+
+	// The time this object was created.
+	Updated time.Time
+
+	// Cache validation tag. This tag is not necessarily an HTTP entity tag.
+	// The tag is "" if there is no meaningful cache validation for the VCS.
+	Etag string
+
+	// Subdirectories, possibly containing Go code.
+	Subdirectories []string
+
+	// Package name or "" if no package for this import path. The proceeding
+	// fields are set even if a package is not found for the import path.
+	Name string
+
+	// Synopsis and full documentation for the package.
+	Synopsis string
+	Doc      string
+
+	// Format this package as a command.
+	IsCmd bool
+
+	// True if package documentation is incomplete.
+	Truncated bool
+
+	// Environment
+	GOOS, GOARCH string
+
+	// Top-level declarations.
+	Consts []*Value
+	Funcs  []*Func
+	Types  []*Type
+	Vars   []*Value
+
+	// Package examples
+	Examples []*Example
+
+	Notes map[string][]*Note
+
+	// Source.
+	LineFmt   string
+	BrowseURL string
+	Files     []*File
+	TestFiles []*File
+
+	// Source size in bytes.
+	SourceSize     int
+	TestSourceSize int
+
+	// Imports
+	Imports      []string
+	TestImports  []string
+	XTestImports []string
+}
+
+var goEnvs = []struct{ GOOS, GOARCH string }{
+	{"linux", "amd64"},
+	{"darwin", "amd64"},
+	{"windows", "amd64"},
+}
+
+func newPackage(dir *gosrc.Directory) (*Package, error) {
+
+	pkg := &Package{
+		Updated:        time.Now().UTC(),
+		LineFmt:        dir.LineFmt,
+		ImportPath:     dir.ImportPath,
+		ProjectRoot:    dir.ProjectRoot,
+		ProjectName:    dir.ProjectName,
+		ProjectURL:     dir.ProjectURL,
+		BrowseURL:      dir.BrowseURL,
+		Etag:           PackageVersion + "-" + dir.Etag,
+		VCS:            dir.VCS,
+		Subdirectories: dir.Subdirectories,
+	}
+
+	var b builder
+	b.srcs = make(map[string]*source)
+	references := make(map[string]bool)
+	for _, file := range dir.Files {
+		if strings.HasSuffix(file.Name, ".go") {
+			gosrc.OverwriteLineComments(file.Data)
+			b.srcs[file.Name] = &source{name: file.Name, browseURL: file.BrowseURL, data: file.Data}
+		} else {
+			addReferences(references, file.Data)
+		}
+	}
+
+	for r := range references {
+		pkg.References = append(pkg.References, r)
+	}
+
+	if len(b.srcs) == 0 {
+		return pkg, nil
+	}
+
+	b.fset = token.NewFileSet()
+
+	// Find the package and associated files.
+
+	ctxt := build.Context{
+		GOOS:        "linux",
+		GOARCH:      "amd64",
+		CgoEnabled:  true,
+		ReleaseTags: build.Default.ReleaseTags,
+		BuildTags:   build.Default.BuildTags,
+		Compiler:    "gc",
+	}
+
+	var err error
+	var bpkg *build.Package
+
+	for _, env := range goEnvs {
+		ctxt.GOOS = env.GOOS
+		ctxt.GOARCH = env.GOARCH
+		bpkg, err = dir.Import(&ctxt, 0)
+		if _, ok := err.(*build.NoGoError); !ok {
+			break
+		}
+	}
+	if err != nil {
+		if _, ok := err.(*build.NoGoError); !ok {
+			pkg.Errors = append(pkg.Errors, err.Error())
+		}
+		return pkg, nil
+	}
+
+	// Parse the Go files
+
+	files := make(map[string]*ast.File)
+	names := append(bpkg.GoFiles, bpkg.CgoFiles...)
+	sort.Strings(names)
+	pkg.Files = make([]*File, len(names))
+	for i, name := range names {
+		file, err := parser.ParseFile(b.fset, name, b.srcs[name].data, parser.ParseComments)
+		if err != nil {
+			pkg.Errors = append(pkg.Errors, err.Error())
+		} else {
+			files[name] = file
+		}
+		src := b.srcs[name]
+		src.index = i
+		pkg.Files[i] = &File{Name: name, URL: src.browseURL}
+		pkg.SourceSize += len(src.data)
+	}
+
+	apkg, _ := ast.NewPackage(b.fset, files, simpleImporter, nil)
+
+	// Find examples in the test files.
+
+	names = append(bpkg.TestGoFiles, bpkg.XTestGoFiles...)
+	sort.Strings(names)
+	pkg.TestFiles = make([]*File, len(names))
+	for i, name := range names {
+		file, err := parser.ParseFile(b.fset, name, b.srcs[name].data, parser.ParseComments)
+		if err != nil {
+			pkg.Errors = append(pkg.Errors, err.Error())
+		} else {
+			b.examples = append(b.examples, doc.Examples(file)...)
+		}
+		pkg.TestFiles[i] = &File{Name: name, URL: b.srcs[name].browseURL}
+		pkg.TestSourceSize += len(b.srcs[name].data)
+	}
+
+	b.vetPackage(pkg, apkg)
+
+	mode := doc.Mode(0)
+	if pkg.ImportPath == "builtin" {
+		mode |= doc.AllDecls
+	}
+
+	dpkg := doc.New(apkg, pkg.ImportPath, mode)
+
+	if pkg.ImportPath == "builtin" {
+		removeAssociations(dpkg)
+	}
+
+	pkg.Name = dpkg.Name
+	pkg.Doc = strings.TrimRight(dpkg.Doc, " \t\n\r")
+	pkg.Synopsis = synopsis(pkg.Doc)
+
+	pkg.Examples = b.getExamples("")
+	pkg.IsCmd = bpkg.IsCommand()
+	pkg.GOOS = ctxt.GOOS
+	pkg.GOARCH = ctxt.GOARCH
+
+	pkg.Consts = b.values(dpkg.Consts)
+	pkg.Funcs = b.funcs(dpkg.Funcs)
+	pkg.Types = b.types(dpkg.Types)
+	pkg.Vars = b.values(dpkg.Vars)
+	pkg.Notes = b.notes(dpkg.Notes)
+
+	pkg.Imports = bpkg.Imports
+	pkg.TestImports = bpkg.TestImports
+	pkg.XTestImports = bpkg.XTestImports
+
+	return pkg, nil
+}
diff --git a/doc/builder_test.go b/doc/builder_test.go
new file mode 100644
index 0000000..1606160
--- /dev/null
+++ b/doc/builder_test.go
@@ -0,0 +1,93 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package doc
+
+import (
+	"go/ast"
+	"testing"
+)
+
+var badSynopsis = []string{
+	"+build !release",
+	"COPYRIGHT Jimmy Bob",
+	"### Markdown heading",
+	"-*- indent-tabs-mode: nil -*-",
+	"vim:set ts=2 sw=2 et ai ft=go:",
+}
+
+func TestBadSynopsis(t *testing.T) {
+	for _, s := range badSynopsis {
+		if synopsis(s) != "" {
+			t.Errorf(`synopsis(%q) did not return ""`, s)
+		}
+	}
+}
+
+const readme = `
+    $ go get github.com/user/repo/pkg1
+    [foo](http://gopkgdoc.appspot.com/pkg/github.com/user/repo/pkg2)
+    [foo](http://go.pkgdoc.org/github.com/user/repo/pkg3)
+    [foo](http://godoc.org/github.com/user/repo/pkg4)
+    <http://go.pkgdoc.org/github.com/user/repo/pkg5>
+    [foo](http://godoc.org/github.com/user/repo/pkg6#Export)
+    http://gowalker.org/github.com/user/repo/pkg7
+    Build Status: [![Build Status](https://drone.io/github.com/user/repo1/status.png)](https://drone.io/github.com/user/repo1/latest)
+    'go get example.org/package1' will install package1.
+    (http://go.pkgdoc.org/example.org/package2 "Package2's documentation on GoPkgDoc").
+    import "example.org/package3"
+`
+
+var expectedReferences = []string{
+	"github.com/user/repo/pkg1",
+	"github.com/user/repo/pkg2",
+	"github.com/user/repo/pkg3",
+	"github.com/user/repo/pkg4",
+	"github.com/user/repo/pkg5",
+	"github.com/user/repo/pkg6",
+	"github.com/user/repo/pkg7",
+	"github.com/user/repo1",
+	"example.org/package1",
+	"example.org/package2",
+	"example.org/package3",
+}
+
+func TestReferences(t *testing.T) {
+	references := make(map[string]bool)
+	addReferences(references, []byte(readme))
+	for _, r := range expectedReferences {
+		if !references[r] {
+			t.Errorf("missing %s", r)
+		}
+		delete(references, r)
+	}
+	for r := range references {
+		t.Errorf("extra %s", r)
+	}
+}
+
+var simpleImporterTests = []string{
+	"code.google.com/p/biogo.foobar",
+	"code.google.com/p/google-api-go-client/foobar/v3",
+	"git.gitorious.org/go-pkg/foobar.git",
+	"github.com/quux/go-foobar",
+	"github.com/quux/go.foobar",
+	"github.com/quux/foobar.go",
+	"github.com/quux/foobar-go",
+	"github.com/quux/foobar",
+	"foobar",
+	"quux/foobar",
+}
+
+func TestSimpleImporter(t *testing.T) {
+	for _, path := range simpleImporterTests {
+		m := make(map[string]*ast.Object)
+		obj, _ := simpleImporter(m, path)
+		if obj.Name != "foobar" {
+			t.Errorf("simpleImporter(%q) = %q, want %q", path, obj.Name, "foobar")
+		}
+	}
+}
diff --git a/doc/code.go b/doc/code.go
new file mode 100644
index 0000000..01b0ab2
--- /dev/null
+++ b/doc/code.go
@@ -0,0 +1,317 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package doc
+
+import (
+	"bytes"
+	"go/ast"
+	"go/doc"
+	"go/printer"
+	"go/scanner"
+	"go/token"
+	"math"
+	"strconv"
+)
+
+const (
+	notPredeclared = iota
+	predeclaredType
+	predeclaredConstant
+	predeclaredFunction
+)
+
+// predeclared represents the set of all predeclared identifiers.
+var predeclared = map[string]int{
+	"bool":       predeclaredType,
+	"byte":       predeclaredType,
+	"complex128": predeclaredType,
+	"complex64":  predeclaredType,
+	"error":      predeclaredType,
+	"float32":    predeclaredType,
+	"float64":    predeclaredType,
+	"int16":      predeclaredType,
+	"int32":      predeclaredType,
+	"int64":      predeclaredType,
+	"int8":       predeclaredType,
+	"int":        predeclaredType,
+	"rune":       predeclaredType,
+	"string":     predeclaredType,
+	"uint16":     predeclaredType,
+	"uint32":     predeclaredType,
+	"uint64":     predeclaredType,
+	"uint8":      predeclaredType,
+	"uint":       predeclaredType,
+	"uintptr":    predeclaredType,
+
+	"true":  predeclaredConstant,
+	"false": predeclaredConstant,
+	"iota":  predeclaredConstant,
+	"nil":   predeclaredConstant,
+
+	"append":  predeclaredFunction,
+	"cap":     predeclaredFunction,
+	"close":   predeclaredFunction,
+	"complex": predeclaredFunction,
+	"copy":    predeclaredFunction,
+	"delete":  predeclaredFunction,
+	"imag":    predeclaredFunction,
+	"len":     predeclaredFunction,
+	"make":    predeclaredFunction,
+	"new":     predeclaredFunction,
+	"panic":   predeclaredFunction,
+	"print":   predeclaredFunction,
+	"println": predeclaredFunction,
+	"real":    predeclaredFunction,
+	"recover": predeclaredFunction,
+}
+
+type AnnotationKind int16
+
+const (
+	// Link to export in package specifed by Paths[PathIndex] with fragment
+	// Text[strings.LastIndex(Text[Pos:End], ".")+1:End].
+	LinkAnnotation AnnotationKind = iota
+
+	// Anchor with name specified by Text[Pos:End] or typeName + "." +
+	// Text[Pos:End] for type declarations.
+	AnchorAnnotation
+
+	// Comment.
+	CommentAnnotation
+
+	// Link to package specified by Paths[PathIndex].
+	PackageLinkAnnotation
+
+	// Link to builtin entity with name Text[Pos:End].
+	BuiltinAnnotation
+)
+
+type Annotation struct {
+	Pos, End  int32
+	Kind      AnnotationKind
+	PathIndex int16
+}
+
+type Code struct {
+	Text        string
+	Annotations []Annotation
+	Paths       []string
+}
+
+// annotationVisitor collects annotations.
+type annotationVisitor struct {
+	annotations []Annotation
+	paths       []string
+	pathIndex   map[string]int
+}
+
+func (v *annotationVisitor) add(kind AnnotationKind, importPath string) {
+	pathIndex := -1
+	if importPath != "" {
+		var ok bool
+		pathIndex, ok = v.pathIndex[importPath]
+		if !ok {
+			pathIndex = len(v.paths)
+			v.paths = append(v.paths, importPath)
+			v.pathIndex[importPath] = pathIndex
+		}
+	}
+	v.annotations = append(v.annotations, Annotation{Kind: kind, PathIndex: int16(pathIndex)})
+}
+
+func (v *annotationVisitor) ignoreName() {
+	v.add(-1, "")
+}
+
+func (v *annotationVisitor) Visit(n ast.Node) ast.Visitor {
+	switch n := n.(type) {
+	case *ast.TypeSpec:
+		v.ignoreName()
+		switch n := n.Type.(type) {
+		case *ast.InterfaceType:
+			for _, f := range n.Methods.List {
+				for _ = range f.Names {
+					v.add(AnchorAnnotation, "")
+				}
+				ast.Walk(v, f.Type)
+			}
+		case *ast.StructType:
+			for _, f := range n.Fields.List {
+				for _ = range f.Names {
+					v.add(AnchorAnnotation, "")
+				}
+				ast.Walk(v, f.Type)
+			}
+		default:
+			ast.Walk(v, n)
+		}
+	case *ast.FuncDecl:
+		if n.Recv != nil {
+			ast.Walk(v, n.Recv)
+		}
+		v.ignoreName()
+		ast.Walk(v, n.Type)
+	case *ast.Field:
+		for _ = range n.Names {
+			v.ignoreName()
+		}
+		ast.Walk(v, n.Type)
+	case *ast.ValueSpec:
+		for _ = range n.Names {
+			v.add(AnchorAnnotation, "")
+		}
+		if n.Type != nil {
+			ast.Walk(v, n.Type)
+		}
+		for _, x := range n.Values {
+			ast.Walk(v, x)
+		}
+	case *ast.Ident:
+		switch {
+		case n.Obj == nil && predeclared[n.Name] != notPredeclared:
+			v.add(BuiltinAnnotation, "")
+		case n.Obj != nil && ast.IsExported(n.Name):
+			v.add(LinkAnnotation, "")
+		default:
+			v.ignoreName()
+		}
+	case *ast.SelectorExpr:
+		if x, _ := n.X.(*ast.Ident); x != nil {
+			if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
+				if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
+					if path, err := strconv.Unquote(spec.Path.Value); err == nil {
+						v.add(PackageLinkAnnotation, path)
+						if path == "C" {
+							v.ignoreName()
+						} else {
+							v.add(LinkAnnotation, path)
+						}
+						return nil
+					}
+				}
+			}
+		}
+		ast.Walk(v, n.X)
+		v.ignoreName()
+	default:
+		return v
+	}
+	return nil
+}
+
+func (b *builder) printDecl(decl ast.Decl) (d Code) {
+	v := &annotationVisitor{pathIndex: make(map[string]int)}
+	ast.Walk(v, decl)
+	b.buf = b.buf[:0]
+	err := (&printer.Config{Mode: printer.UseSpaces, Tabwidth: 4}).Fprint(sliceWriter{&b.buf}, b.fset, decl)
+	if err != nil {
+		return Code{Text: err.Error()}
+	}
+
+	var annotations []Annotation
+	var s scanner.Scanner
+	fset := token.NewFileSet()
+	file := fset.AddFile("", fset.Base(), len(b.buf))
+	s.Init(file, b.buf, nil, scanner.ScanComments)
+loop:
+	for {
+		pos, tok, lit := s.Scan()
+		switch tok {
+		case token.EOF:
+			break loop
+		case token.COMMENT:
+			p := file.Offset(pos)
+			e := p + len(lit)
+			annotations = append(annotations, Annotation{Kind: CommentAnnotation, Pos: int32(p), End: int32(e)})
+		case token.IDENT:
+			if len(v.annotations) == 0 {
+				// Oops!
+				break loop
+			}
+			annotation := v.annotations[0]
+			v.annotations = v.annotations[1:]
+			if annotation.Kind == -1 {
+				continue
+			}
+			p := file.Offset(pos)
+			e := p + len(lit)
+			annotation.Pos = int32(p)
+			annotation.End = int32(e)
+			annotations = append(annotations, annotation)
+		}
+	}
+	return Code{Text: string(b.buf), Annotations: annotations, Paths: v.paths}
+}
+
+func (b *builder) position(n ast.Node) Pos {
+	var position Pos
+	pos := b.fset.Position(n.Pos())
+	src := b.srcs[pos.Filename]
+	if src != nil {
+		position.File = int16(src.index)
+		position.Line = int32(pos.Line)
+		end := b.fset.Position(n.End())
+		if src == b.srcs[end.Filename] {
+			n := end.Line - pos.Line
+			if n >= 0 && n <= math.MaxUint16 {
+				position.N = uint16(n)
+			}
+		}
+	}
+	return position
+}
+
+func (b *builder) printExample(e *doc.Example) (code Code, output string) {
+	output = e.Output
+
+	b.buf = b.buf[:0]
+	err := (&printer.Config{Mode: printer.UseSpaces, Tabwidth: 4}).Fprint(
+		sliceWriter{&b.buf},
+		b.fset,
+		&printer.CommentedNode{
+			Node:     e.Code,
+			Comments: e.Comments,
+		})
+	if err != nil {
+		return Code{Text: err.Error()}, output
+	}
+
+	// additional formatting if this is a function body
+	if i := len(b.buf); i >= 2 && b.buf[0] == '{' && b.buf[i-1] == '}' {
+		// remove surrounding braces
+		b.buf = b.buf[1 : i-1]
+		// unindent
+		b.buf = bytes.Replace(b.buf, []byte("\n    "), []byte("\n"), -1)
+		// remove output comment
+		if j := exampleOutputRx.FindIndex(b.buf); j != nil {
+			b.buf = bytes.TrimSpace(b.buf[:j[0]])
+		}
+	} else {
+		// drop output, as the output comment will appear in the code
+		output = ""
+	}
+
+	var annotations []Annotation
+	var s scanner.Scanner
+	fset := token.NewFileSet()
+	file := fset.AddFile("", fset.Base(), len(b.buf))
+	s.Init(file, b.buf, nil, scanner.ScanComments)
+scanLoop:
+	for {
+		pos, tok, lit := s.Scan()
+		switch tok {
+		case token.EOF:
+			break scanLoop
+		case token.COMMENT:
+			p := file.Offset(pos)
+			e := p + len(lit)
+			annotations = append(annotations, Annotation{Kind: CommentAnnotation, Pos: int32(p), End: int32(e)})
+		}
+	}
+
+	return Code{Text: string(b.buf), Annotations: annotations}, output
+}
diff --git a/doc/get.go b/doc/get.go
new file mode 100644
index 0000000..2fae014
--- /dev/null
+++ b/doc/get.go
@@ -0,0 +1,55 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+// Package doc fetches Go package documentation from version control services.
+package doc
+
+import (
+	"github.com/garyburd/gosrc"
+	"go/doc"
+	"net/http"
+	"strings"
+)
+
+func Get(client *http.Client, importPath string, etag string) (*Package, error) {
+
+	const versionPrefix = PackageVersion + "-"
+
+	if strings.HasPrefix(etag, versionPrefix) {
+		etag = etag[len(versionPrefix):]
+	} else {
+		etag = ""
+	}
+
+	dir, err := gosrc.Get(client, importPath, etag)
+	if err != nil {
+		return nil, err
+	}
+
+	pdoc, err := newPackage(dir)
+	if err != nil {
+		return pdoc, err
+	}
+
+	if pdoc.Synopsis == "" &&
+		pdoc.Doc == "" &&
+		!pdoc.IsCmd &&
+		pdoc.Name != "" &&
+		dir.ImportPath == dir.ProjectRoot &&
+		len(pdoc.Errors) == 0 {
+		project, err := gosrc.GetProject(client, dir.ResolvedPath)
+		switch {
+		case err == nil:
+			pdoc.Synopsis = doc.Synopsis(project.Description)
+		case gosrc.IsNotFound(err):
+			// ok
+		default:
+			return nil, err
+		}
+	}
+
+	return pdoc, nil
+}
diff --git a/doc/goprint.go b/doc/goprint.go
new file mode 100644
index 0000000..192ee70
--- /dev/null
+++ b/doc/goprint.go
@@ -0,0 +1,69 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+// +build ignore
+
+// Command astprint prints the AST for a file.
+//
+// Usage: go run asprint.go fname
+package main
+
+import (
+	"flag"
+	"go/ast"
+	"go/build"
+	"go/doc"
+	"go/parser"
+	"go/token"
+	"io/ioutil"
+	"log"
+	"path/filepath"
+	"strings"
+
+	"github.com/davecgh/go-spew/spew"
+)
+
+func importer(imports map[string]*ast.Object, path string) (*ast.Object, error) {
+	pkg := imports[path]
+	if pkg == nil {
+		name := path[strings.LastIndex(path, "/")+1:]
+		pkg = ast.NewObj(ast.Pkg, name)
+		pkg.Data = ast.NewScope(nil) // required by ast.NewPackage for dot-import
+		imports[path] = pkg
+	}
+	return pkg, nil
+}
+
+func main() {
+	flag.Parse()
+	if len(flag.Args()) != 1 {
+		log.Fatal("Usage: go run goprint.go path")
+	}
+	bpkg, err := build.Default.Import(flag.Args()[0], ".", 0)
+	if err != nil {
+		log.Fatal(err)
+	}
+	fset := token.NewFileSet()
+	files := make(map[string]*ast.File)
+	for _, fname := range bpkg.GoFiles {
+		p, err := ioutil.ReadFile(filepath.Join(bpkg.SrcRoot, bpkg.ImportPath, fname))
+		if err != nil {
+			log.Fatal(err)
+		}
+		file, err := parser.ParseFile(fset, fname, p, parser.ParseComments)
+		if err != nil {
+			log.Fatal(err)
+		}
+		files[fname] = file
+	}
+	c := spew.NewDefaultConfig()
+	c.DisableMethods = true
+	apkg, _ := ast.NewPackage(fset, files, importer, nil)
+	c.Dump(apkg)
+	ast.Print(fset, apkg)
+	dpkg := doc.New(apkg, bpkg.ImportPath, 0)
+	c.Dump(dpkg)
+}
diff --git a/doc/print.go b/doc/print.go
new file mode 100644
index 0000000..9833332
--- /dev/null
+++ b/doc/print.go
@@ -0,0 +1,50 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+// +build ignore
+
+// Command print fetches and prints package documentation.
+//
+// Usage: go run print.go importPath
+package main
+
+import (
+	"flag"
+	"log"
+	"net/http"
+	"os"
+
+	"github.com/davecgh/go-spew/spew"
+	"github.com/garyburd/gddo/doc"
+	"github.com/garyburd/gosrc"
+)
+
+var (
+	etag  = flag.String("etag", "", "Etag")
+	local = flag.Bool("local", false, "Get package from local directory.")
+)
+
+func main() {
+	flag.Parse()
+	if len(flag.Args()) != 1 {
+		log.Fatal("Usage: go run print.go importPath")
+	}
+	path := flag.Args()[0]
+
+	var (
+		pdoc *doc.Package
+		err  error
+	)
+	if *local {
+		gosrc.SetLocalDevMode(os.Getenv("GOPATH"))
+	}
+	pdoc, err = doc.Get(http.DefaultClient, path, *etag)
+	//}
+	if err != nil {
+		log.Fatal(err)
+	}
+	spew.Dump(pdoc)
+}
diff --git a/doc/util.go b/doc/util.go
new file mode 100644
index 0000000..e9a665e
--- /dev/null
+++ b/doc/util.go
@@ -0,0 +1,14 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package doc
+
+type sliceWriter struct{ p *[]byte }
+
+func (w sliceWriter) Write(p []byte) (int, error) {
+	*w.p = append(*w.p, p...)
+	return len(p), nil
+}
diff --git a/doc/vet.go b/doc/vet.go
new file mode 100644
index 0000000..a4111de
--- /dev/null
+++ b/doc/vet.go
@@ -0,0 +1,81 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package doc
+
+import (
+	"fmt"
+	"go/ast"
+	"go/token"
+	"strconv"
+	"strings"
+
+	"github.com/garyburd/gosrc"
+)
+
+// This list of deprecated exports is used to find code that has not been
+// updated for Go 1.
+var deprecatedExports = map[string][]string{
+	`"bytes"`:         {"Add"},
+	`"crypto/aes"`:    {"Cipher"},
+	`"crypto/hmac"`:   {"NewSHA1", "NewSHA256"},
+	`"crypto/rand"`:   {"Seed"},
+	`"encoding/json"`: {"MarshalForHTML"},
+	`"encoding/xml"`:  {"Marshaler", "NewParser", "Parser"},
+	`"html"`:          {"NewTokenizer", "Parse"},
+	`"image"`:         {"Color", "NRGBAColor", "RGBAColor"},
+	`"io"`:            {"Copyn"},
+	`"log"`:           {"Exitf"},
+	`"math"`:          {"Fabs", "Fmax", "Fmod"},
+	`"os"`:            {"Envs", "Error", "Getenverror", "NewError", "Time", "UnixSignal", "Wait"},
+	`"reflect"`:       {"MapValue", "Typeof"},
+	`"runtime"`:       {"UpdateMemStats"},
+	`"strconv"`:       {"Atob", "Atof32", "Atof64", "AtofN", "Atoi64", "Atoui", "Atoui64", "Btoui64", "Ftoa64", "Itoa64", "Uitoa", "Uitoa64"},
+	`"time"`:          {"LocalTime", "Nanoseconds", "NanosecondsToLocalTime", "Seconds", "SecondsToLocalTime", "SecondsToUTC"},
+	`"unicode/utf8"`:  {"NewString"},
+}
+
+type vetVisitor struct {
+	errors map[string]token.Pos
+}
+
+func (v *vetVisitor) Visit(n ast.Node) ast.Visitor {
+	if sel, ok := n.(*ast.SelectorExpr); ok {
+		if x, _ := sel.X.(*ast.Ident); x != nil {
+			if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
+				if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
+					for _, name := range deprecatedExports[spec.Path.Value] {
+						if name == sel.Sel.Name {
+							v.errors[fmt.Sprintf("%s.%s not found", spec.Path.Value, sel.Sel.Name)] = n.Pos()
+							return nil
+						}
+					}
+				}
+			}
+		}
+	}
+	return v
+}
+
+func (b *builder) vetPackage(pkg *Package, apkg *ast.Package) {
+	errors := make(map[string]token.Pos)
+	for _, file := range apkg.Files {
+		for _, is := range file.Imports {
+			importPath, _ := strconv.Unquote(is.Path.Value)
+			if !gosrc.IsValidPath(importPath) &&
+				!strings.HasPrefix(importPath, "exp/") &&
+				!strings.HasPrefix(importPath, "appengine") {
+				errors[fmt.Sprintf("Unrecognized import path %q", importPath)] = is.Pos()
+			}
+		}
+		v := vetVisitor{errors: errors}
+		ast.Walk(&v, file)
+	}
+	for message, pos := range errors {
+		pkg.Errors = append(pkg.Errors,
+			fmt.Sprintf("%s (%s)", message, b.fset.Position(pos)))
+	}
+}
diff --git a/gddo-admin/block.go b/gddo-admin/block.go
new file mode 100644
index 0000000..73d9307
--- /dev/null
+++ b/gddo-admin/block.go
@@ -0,0 +1,33 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package main
+
+import (
+	"github.com/garyburd/gddo/database"
+	"log"
+	"os"
+)
+
+var blockCommand = &command{
+	name:  "block",
+	run:   block,
+	usage: "block path",
+}
+
+func block(c *command) {
+	if len(c.flag.Args()) != 1 {
+		c.printUsage()
+		os.Exit(1)
+	}
+	db, err := database.New()
+	if err != nil {
+		log.Fatal(err)
+	}
+	if err := db.Block(c.flag.Args()[0]); err != nil {
+		log.Fatal(err)
+	}
+}
diff --git a/gddo-admin/crawl.go b/gddo-admin/crawl.go
new file mode 100644
index 0000000..25543c4
--- /dev/null
+++ b/gddo-admin/crawl.go
@@ -0,0 +1,65 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"strings"
+
+	"github.com/garyburd/gddo/database"
+	"github.com/garyburd/redigo/redis"
+)
+
+var crawlCommand = &command{
+	name:  "crawl",
+	run:   crawl,
+	usage: "crawl [new]",
+}
+
+func crawl(c *command) {
+	if len(c.flag.Args()) > 1 {
+		c.printUsage()
+		os.Exit(1)
+	}
+	db, err := database.New()
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	if len(c.flag.Args()) == 1 {
+		p, err := ioutil.ReadFile(c.flag.Args()[0])
+		if err != nil {
+			log.Fatal(err)
+		}
+		for _, p := range strings.Fields(string(p)) {
+			db.AddNewCrawl(p)
+		}
+	}
+
+	conn := db.Pool.Get()
+	defer conn.Close()
+	paths, err := redis.Strings(conn.Do("SMEMBERS", "newCrawl"))
+	if err != nil {
+		log.Fatal(err)
+	}
+	fmt.Println("NEW")
+	for _, path := range paths {
+		fmt.Println(path)
+	}
+
+	paths, err = redis.Strings(conn.Do("SMEMBERS", "badCrawl"))
+	if err != nil {
+		log.Fatal(err)
+	}
+	fmt.Println("BAD")
+	for _, path := range paths {
+		fmt.Println(path)
+	}
+}
diff --git a/gddo-admin/dangle.go b/gddo-admin/dangle.go
new file mode 100644
index 0000000..c8011d7
--- /dev/null
+++ b/gddo-admin/dangle.go
@@ -0,0 +1,59 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package main
+
+import (
+	"fmt"
+	"log"
+	"os"
+
+	"github.com/garyburd/gddo/database"
+	"github.com/garyburd/gosrc"
+)
+
+var dangleCommand = &command{
+	name:  "dangle",
+	run:   dangle,
+	usage: "dangle",
+}
+
+func dangle(c *command) {
+	if len(c.flag.Args()) != 0 {
+		c.printUsage()
+		os.Exit(1)
+	}
+	db, err := database.New()
+	if err != nil {
+		log.Fatal(err)
+	}
+	m := make(map[string]int)
+	err = db.Do(func(pi *database.PackageInfo) error {
+		m[pi.PDoc.ImportPath] |= 1
+		for _, p := range pi.PDoc.Imports {
+			if gosrc.IsValidPath(p) {
+				m[p] |= 2
+			}
+		}
+		for _, p := range pi.PDoc.TestImports {
+			if gosrc.IsValidPath(p) {
+				m[p] |= 2
+			}
+		}
+		for _, p := range pi.PDoc.XTestImports {
+			if gosrc.IsValidPath(p) {
+				m[p] |= 2
+			}
+		}
+		return nil
+	})
+
+	for p, v := range m {
+		if v == 2 {
+			fmt.Println(p)
+		}
+	}
+}
diff --git a/gddo-admin/delete.go b/gddo-admin/delete.go
new file mode 100644
index 0000000..93f9a08
--- /dev/null
+++ b/gddo-admin/delete.go
@@ -0,0 +1,34 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package main
+
+import (
+	"log"
+	"os"
+
+	"github.com/garyburd/gddo/database"
+)
+
+var deleteCommand = &command{
+	name:  "delete",
+	run:   del,
+	usage: "delete path",
+}
+
+func del(c *command) {
+	if len(c.flag.Args()) != 1 {
+		c.printUsage()
+		os.Exit(1)
+	}
+	db, err := database.New()
+	if err != nil {
+		log.Fatal(err)
+	}
+	if err := db.Delete(c.flag.Args()[0]); err != nil {
+		log.Fatal(err)
+	}
+}
diff --git a/gddo-admin/main.go b/gddo-admin/main.go
new file mode 100644
index 0000000..223c94c
--- /dev/null
+++ b/gddo-admin/main.go
@@ -0,0 +1,70 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+// Command gddo-admin is the GoDoc.org command line administration tool.
+package main
+
+import (
+	"flag"
+	"fmt"
+	"os"
+	"strings"
+)
+
+type command struct {
+	name  string
+	run   func(c *command)
+	flag  flag.FlagSet
+	usage string
+}
+
+func (c *command) printUsage() {
+	fmt.Fprintf(os.Stderr, "%s %s\n", os.Args[0], c.usage)
+	c.flag.PrintDefaults()
+}
+
+var commands = []*command{
+	blockCommand,
+	reindexCommand,
+	deleteCommand,
+	popularCommand,
+	dangleCommand,
+	crawlCommand,
+	statsCommand,
+}
+
+func printUsage() {
+	var n []string
+	for _, c := range commands {
+		n = append(n, c.name)
+	}
+	fmt.Fprintf(os.Stderr, "%s %s\n", os.Args[0], strings.Join(n, "|"))
+	flag.PrintDefaults()
+	for _, c := range commands {
+		c.printUsage()
+	}
+}
+
+func main() {
+	flag.Usage = printUsage
+	flag.Parse()
+	args := flag.Args()
+	if len(args) >= 1 {
+		for _, c := range commands {
+			if args[0] == c.name {
+				c.flag.Usage = func() {
+					c.printUsage()
+					os.Exit(2)
+				}
+				c.flag.Parse(args[1:])
+				c.run(c)
+				return
+			}
+		}
+	}
+	printUsage()
+	os.Exit(2)
+}
diff --git a/gddo-admin/popular.go b/gddo-admin/popular.go
new file mode 100644
index 0000000..5c82d36
--- /dev/null
+++ b/gddo-admin/popular.go
@@ -0,0 +1,44 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package main
+
+import (
+	"fmt"
+	"log"
+	"os"
+
+	"github.com/garyburd/gddo/database"
+)
+
+var (
+	popularCommand = &command{
+		name:  "popular",
+		usage: "popular",
+	}
+)
+
+func init() {
+	popularCommand.run = popular
+}
+
+func popular(c *command) {
+	if len(c.flag.Args()) != 0 {
+		c.printUsage()
+		os.Exit(1)
+	}
+	db, err := database.New()
+	if err != nil {
+		log.Fatal(err)
+	}
+	pkgs, err := db.PopularWithScores()
+	if err != nil {
+		log.Fatal(err)
+	}
+	for _, pkg := range pkgs {
+		fmt.Println(pkg.Path, pkg.Synopsis)
+	}
+}
diff --git a/gddo-admin/reindex.go b/gddo-admin/reindex.go
new file mode 100644
index 0000000..8187a1a
--- /dev/null
+++ b/gddo-admin/reindex.go
@@ -0,0 +1,68 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package main
+
+import (
+	"log"
+	"os"
+	"time"
+
+	"github.com/garyburd/gddo/database"
+	"github.com/garyburd/gddo/doc"
+)
+
+var reindexCommand = &command{
+	name:  "reindex",
+	run:   reindex,
+	usage: "reindex",
+}
+
+func fix(pdoc *doc.Package) {
+	/*
+	   	for _, v := range pdoc.Consts {
+	   	}
+	   	for _, v := range pdoc.Vars {
+	   	}
+	   	for _, v := range pdoc.Funcs {
+	   	}
+	   	for _, t := range pdoc.Types {
+	   		for _, v := range t.Consts {
+	   		}
+	   		for _, v := range t.Vars {
+	   		}
+	   		for _, v := range t.Funcs {
+	   		}
+	   		for _, v := range t.Methods {
+	   		}
+	   	}
+	       for _, notes := range pdoc.Notes {
+	           for _, v := range notes {
+	           }
+	       }
+	*/
+}
+
+func reindex(c *command) {
+	if len(c.flag.Args()) != 0 {
+		c.printUsage()
+		os.Exit(1)
+	}
+	db, err := database.New()
+	if err != nil {
+		log.Fatal(err)
+	}
+	var n int
+	err = db.Do(func(pi *database.PackageInfo) error {
+		n += 1
+		fix(pi.PDoc)
+		return db.Put(pi.PDoc, time.Time{}, false)
+	})
+	if err != nil {
+		log.Fatal(err)
+	}
+	log.Printf("Updated %d documents", n)
+}
diff --git a/gddo-admin/stats.go b/gddo-admin/stats.go
new file mode 100644
index 0000000..b73d12a
--- /dev/null
+++ b/gddo-admin/stats.go
@@ -0,0 +1,67 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package main
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"sort"
+
+	"github.com/garyburd/gddo/database"
+)
+
+var statsCommand = &command{
+	name:  "stats",
+	run:   stats,
+	usage: "stats",
+}
+
+type itemSize struct {
+	path string
+	size int
+}
+
+type bySizeDesc []itemSize
+
+func (p bySizeDesc) Len() int           { return len(p) }
+func (p bySizeDesc) Less(i, j int) bool { return p[i].size > p[j].size }
+func (p bySizeDesc) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+
+func stats(c *command) {
+	if len(c.flag.Args()) != 0 {
+		c.printUsage()
+		os.Exit(1)
+	}
+	db, err := database.New()
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	var packageSizes []itemSize
+	projectSizes := make(map[string]int)
+	err = db.Do(func(pi *database.PackageInfo) error {
+		packageSizes = append(packageSizes, itemSize{pi.PDoc.ImportPath, pi.Size})
+		projectSizes[pi.PDoc.ProjectRoot] += pi.Size
+		return nil
+	})
+
+	var sizes []itemSize
+	for path, size := range projectSizes {
+		sizes = append(sizes, itemSize{path, size})
+	}
+	sort.Sort(bySizeDesc(sizes))
+	for _, size := range sizes {
+		fmt.Printf("%6d %s\n", size.size, size.path)
+	}
+
+	sort.Sort(bySizeDesc(packageSizes))
+	for _, size := range packageSizes {
+		fmt.Printf("%6d %s\n", size.size, size.path)
+	}
+
+}
diff --git a/gddo-server/assets/BingSiteAuth.xml b/gddo-server/assets/BingSiteAuth.xml
new file mode 100644
index 0000000..0737e3e
--- /dev/null
+++ b/gddo-server/assets/BingSiteAuth.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<users>
+	<user>6F3E495D5591D0B1308072CA245E8849</user>
+</users>
\ No newline at end of file
diff --git a/gddo-server/assets/apiRobots.txt b/gddo-server/assets/apiRobots.txt
new file mode 100644
index 0000000..a4751e2
--- /dev/null
+++ b/gddo-server/assets/apiRobots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: *
diff --git a/gddo-server/assets/favicon.ico b/gddo-server/assets/favicon.ico
new file mode 100755
index 0000000..ee16b8e
--- /dev/null
+++ b/gddo-server/assets/favicon.ico
Binary files differ
diff --git a/gddo-server/assets/google3d2f3cd4cc2bb44b.html b/gddo-server/assets/google3d2f3cd4cc2bb44b.html
new file mode 100644
index 0000000..a1d57ce
--- /dev/null
+++ b/gddo-server/assets/google3d2f3cd4cc2bb44b.html
@@ -0,0 +1 @@
+google-site-verification: google3d2f3cd4cc2bb44b.html
\ No newline at end of file
diff --git a/gddo-server/assets/humans.txt b/gddo-server/assets/humans.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gddo-server/assets/humans.txt
diff --git a/gddo-server/assets/robots.txt b/gddo-server/assets/robots.txt
new file mode 100644
index 0000000..cd2d540
--- /dev/null
+++ b/gddo-server/assets/robots.txt
@@ -0,0 +1,10 @@
+User-agent: *
+Disallow: /*?imports
+Disallow: /*?importers
+Disallow: /*?import-graph*
+Disallow: /*?gosrc*
+Disallow: /*?file*
+Disallow: /*?tools
+
+user-agent: AhrefsBot
+disallow: /
diff --git a/gddo-server/assets/sidebar.css b/gddo-server/assets/sidebar.css
new file mode 100644
index 0000000..d5be4f2
--- /dev/null
+++ b/gddo-server/assets/sidebar.css
@@ -0,0 +1,82 @@
+.container { max-width: 970px; }
+
+.section-header {
+    padding-bottom: 4px;
+    margin: 20px 0 10px;
+    border-bottom: 1px solid #eeeeee;
+}
+
+/* Sidebar navigation (copied from bootstrap docs.css) */
+/* First level of nav */
+.gddo-sidebar {
+    margin-top: 5px;
+    margin-bottom: 30px;
+    padding-bottom: 10px;
+    text-shadow: 0 1px 0 #fff;
+    border-radius: 5px;
+}
+
+/* By default it's not affixed in mobile views, so undo that */
+.gddo-sidebar .nav.affix {
+    position: static;
+}
+
+.gddo-sidebar .nav {
+    overflow: auto;
+    height: 95%;
+}
+
+/* All levels of nav */
+.gddo-sidebar .nav > li > a {
+    display: block;
+    color: #716b7a;
+    padding: 5px 0px;
+}
+.gddo-sidebar .nav > li > a:hover,
+.gddo-sidebar .nav > li > a:focus {
+    text-decoration: none;
+    background-color: #e5e3e9;
+}
+.gddo-sidebar .nav > .active > a,
+.gddo-sidebar .nav > .active:hover > a,
+.gddo-sidebar .nav > .active:focus > a {
+    font-weight: bold;
+    color: #563d7c;
+    background-color: transparent;
+}
+
+/* Nav: second level (shown on .active) */
+.gddo-sidebar .nav .nav {
+    display: none; /* Hide by default, but at >768px, show it */
+    margin-bottom: 8px;
+}
+.gddo-sidebar .nav .nav > li > a {
+    padding-top:    3px;
+    padding-bottom: 3px;
+    padding-left: 15px;
+    font-size: 90%;
+}
+
+/* Show and affix the side nav when space allows it */
+@media screen and (min-width: 992px) {
+    .gddo-sidebar .nav > .active > ul {
+        display: block;
+    }
+    /* Widen the fixed sidebar */
+    .gddo-sidebar .nav.affix,
+    .gddo-sidebar .nav.affix-bottom {
+        width: 213px;
+    }
+    .gddo-sidebar .nav.affix {
+        position: fixed; /* Undo the static from mobile first approach */
+        top: 10px;
+    }
+    .gddo-sidebar .nav.affix-bottom {
+        position: absolute; /* Undo the static from mobile first approach */
+    }
+    .gddo-sidebar .nav.affix-bottom .bs-sidenav,
+    .gddo-sidebar .nav.affix .bs-sidenav {
+        margin-top: 0;
+        margin-bottom: 0;
+    }
+}
diff --git a/gddo-server/assets/site.css b/gddo-server/assets/site.css
new file mode 100644
index 0000000..603412e
--- /dev/null
+++ b/gddo-server/assets/site.css
@@ -0,0 +1,92 @@
+html { background-color: whitesmoke; }
+body { background-color: white; }
+h4 { margin-top: 20px; }
+.container { max-width: 728px; }
+
+#x-projnav {
+    min-height: 20px;
+    margin-bottom: 20px;
+    background-color: #eee;
+    padding: 9px;
+    border-radius: 3px;
+}
+
+#x-footer {
+    padding-top: 14px;
+    padding-bottom: 15px;
+    margin-top: 5px;
+    background-color: #eee;
+    border-top-style: solid;
+    border-top-width: 1px;
+
+}
+
+#x-pkginfo {
+    margin-top: 25px;
+    border-top: 1px solid #ccc;
+    padding-top: 20px;
+    margin-bottom: 15px;
+}
+
+code {
+    background-color: inherit;
+    border: none;
+    color: inherit;
+    padding: 0;
+}
+
+pre {
+    overflow: auto;
+    white-space: pre;
+    word-break: normal;
+    word-wrap: normal;
+}
+
+.funcdecl {
+  white-space: pre-wrap;
+  word-break: break-all;
+  word-wrap: break-word;
+}
+
+pre .com {
+  color: rgb(147, 161, 161);
+}
+
+a, .navbar-default .navbar-brand {
+    color: #375eab;
+}
+
+.navbar-default, #x-footer {
+    background-color: hsl(209, 51%, 92%);
+    border-color: hsl(209, 51%, 88%);
+}
+
+.navbar-default .navbar-nav > .active > a,
+.navbar-default .navbar-nav > .active > a:hover,
+.navbar-default .navbar-nav > .active > a:focus {
+    background-color: hsl(209, 51%, 88%);
+}
+
+.navbar-default .navbar-nav > li > a:hover,
+.navbar-default .navbar-nav > li > a:focus {
+    color: #000;
+}
+
+.panel-default > .panel-heading {
+    color: #333;
+    background-color: transparent;
+}
+
+#x-file .highlight {
+    color: red;
+    text-decoration: underline;
+}
+
+a.permalink {
+    display: none;
+}
+
+h1:hover .permalink, h2:hover .permalink, h3:hover .permalink, h4:hover .permalink, h5:hover .permalink, h6:hover .permalink {
+    display: inline;
+}
+
diff --git a/gddo-server/assets/site.js b/gddo-server/assets/site.js
new file mode 100644
index 0000000..d0b6b36
--- /dev/null
+++ b/gddo-server/assets/site.js
@@ -0,0 +1,105 @@
+$(function() {
+    var prevCh = null, prevTime = 0, modal = false;
+
+    $('.modal').on({
+        show: function() { modal = true; },
+        hidden: function() { modal = false; }
+    });
+
+    $(document).on('keypress', function(e) {
+        var combo = e.timeStamp - prevTime <= 1000;
+        prevTime = 0;
+
+        if (modal) {
+            return true;
+        }
+
+        var t = e.target.tagName
+        if (t == 'INPUT' ||
+            t == 'SELECT' ||
+            t == 'TEXTAREA' ) {
+            return true;
+        }
+
+        if (e.target.contentEditable && e.target.contentEditable == 'true') {
+            return true;
+        }
+
+        var ch = String.fromCharCode(e.which);
+
+        if (combo) {
+            switch (prevCh + ch) {
+            case "gg":
+                $('html,body').animate({scrollTop: 0},'fast');
+                return false;
+            case "gb":
+                $('html,body').animate({scrollTop: $(document).height()},'fast');
+                return false;
+            case "gi":
+                if ($('#pkg-index').length > 0) {
+                    $('html,body').animate({scrollTop: $("#pkg-index").offset().top},'fast');
+                    return false;
+                }
+            case "ge":
+                if ($('#pkg-examples').length > 0) {
+                    $('html,body').animate({scrollTop: $("#pkg-examples").offset().top},'fast');
+                    return false;
+                }
+            }
+        }
+
+        switch (ch) {
+        case "/":
+            $('#x-search-query').focus();
+            return false;
+        case "?":
+            $('#x-shortcuts').modal();
+            return false;
+        case  ".":
+            if ($('#x-jump').length > 0) {
+                window.setTimeout(function() {
+                    $('#x-jump-text').typeahead({local: symbols()});
+                    $('#x-jump').modal();
+                }, 0);
+                return false;
+            }
+        }
+
+        prevCh = ch
+        prevTime = e.timeStamp
+        return true;
+    });
+
+    $('span.timeago').timeago();
+    if (window.location.hash.substring(0, 9) == '#example-') {
+        var id = '#ex-' + window.location.hash.substring(9);
+        console.log(id);
+        console.log($(id));
+       $(id).addClass('in').removeClass('collapse').height('auto');
+    }
+
+    var highlighted;
+
+    function highlightHash() {
+        if (highlighted) {
+            highlighted.removeClass("highlight");
+        }
+        if (window.location.hash) {
+            highlighted = $('#x-file ' + window.location.hash.replace(/([.])/g,'\\$1'));
+            highlighted.addClass("highlight");
+        } else {
+            highlighted = null;
+        }
+    }
+    window.onhashchange = highlightHash;
+    highlightHash();
+
+    $(document).on("click", "input.click-select", function(e) {
+        $(e.target).select();
+    });
+
+    $('body').scrollspy({
+        target: '.gddo-sidebar',
+        offset: 10
+    });
+});
diff --git a/gddo-server/assets/status.png b/gddo-server/assets/status.png
new file mode 100644
index 0000000..8aa8b1b
--- /dev/null
+++ b/gddo-server/assets/status.png
Binary files differ
diff --git a/gddo-server/assets/templates/about.html b/gddo-server/assets/templates/about.html
new file mode 100644
index 0000000..095c35e
--- /dev/null
+++ b/gddo-server/assets/templates/about.html
@@ -0,0 +1,85 @@
+{{define "Head"}}<title>About - GoDoc</title>{{end}}
+
+{{define "Body"}}
+<h1>About</h1>
+
+<p>GoDoc hosts documentation for <a href="http://golang.org/">Go</a>
+packages on <a href="https://bitbucket.org/">Bitbucket</a>, <a
+  href="https://github.com/">GitHub</a>, <a
+  href="https://launchpad.net/">Launchpad</a> and <a
+  href="http://code.google.com/hosting/">Google Project Hosting</a>.
+
+<p>The source code for GoDoc is available <a
+  href="https://github.com/garyburd/gddo">on GitHub</a>.
+
+<p>GoDoc displays documentation for GOOS=linux unless otherwise noted at the
+bottom of the documentation page.
+
+<h4 id="howto">Add a package to GoDoc</h4>
+
+<p>GoDoc generates documentation from Go source code. The <a
+  href="http://blog.golang.org/godoc-documenting-go-code">guidelines</a>
+for writing documentation for the <a
+  href="http://golang.org/cmd/godoc/">godoc</a> tool apply to GoDoc.
+
+<p>It's important to write a good summary of the package in the first sentence
+of the package comment. GoDoc indexes the first sentence and displays the first
+sentence in package lists.
+
+<p>To add a package to GoDoc, <a href="/">search</a> for the package by import
+path. If GoDoc does not already have the documentation for the package, then
+GoDoc will fetch the source from the version control system on the fly and add
+the documentation.
+
+<p>GoDoc scrapes the <a href="https://github.com/languages/Go/updated">GitHub
+  recent updates page</a> to find recently updated packages on GitHub. For
+other services, GoDoc checks for updates once per day. You can force GoDoc to
+refresh the documentation immediately by clicking the refresh link at the
+bottom of the package documentation page.
+
+<p>GoDoc crawls package imports to automatically find new packages.
+
+<h4 id="remove">Remove a package from GoDoc</h4>
+
+GoDoc automatically removes packages deleted from the version control system
+when GoDoc checks for updates to the package. You can force GoDoc to remove a
+deleted package immediately by clicking the refresh link at the bottom of the
+package documentation page.
+
+If you do not want GoDoc to display documentation for your package, send mail
+to info@godoc.org with the import path of the path of the package that you want
+to remove.
+
+<h4 id="feedback">Feedback</h4>
+
+<p>Send your ideas, feature requests and questions to
+info@godoc.org.  Report bugs using the <a
+  href="https://github.com/garyburd/gddo/issues/new">GitHub Issue
+  Tracker</a>.  Join the discussion at the <a
+  href="https://groups.google.com/d/forum/godocorg">GoDoc group</a>.  You
+should follow <a href="https://twitter.com/godocdotorg">GoDoc on Twitter</a>.
+
+<h4 id="plain-text">Plain Text</h4>
+
+GoDoc provides plain text output for integration with shell scripts. Use the
+HTTP Accept header to request a plain text response.
+
+<p>Search for packages with the term 'sql':
+<pre>$ curl -H 'Accept: text/plain' http://godoc.org/?q=sql</pre>
+
+<p>Get the documentation for the standard math package:
+<pre>$ curl -H 'Accept: text/plain' http://godoc.org/math</pre>
+
+<h4 id="shortcuts">Keyboard Shortcuts</h4>
+
+<p>GoDoc has keyboard shortcuts for navigating package documentation
+pages. Type '?' on a package page for help.
+
+<h4 id="bookmarklet">Bookmarklet</h4>
+
+<p>The GoDoc bookmarklet navigates from pages on Bitbucket, GitHub Launchpad
+and Google Project Hosting to the package documentation. To install the
+bookmarklet, click and drag the following link to your bookmark bar: <a
+ href="javascript:window.location='http://{{.Host}}/?q='+encodeURIComponent(window.location)">GoDoc</a>
+
+{{end}}
diff --git a/gddo-server/assets/templates/bot.html b/gddo-server/assets/templates/bot.html
new file mode 100644
index 0000000..2a851b5
--- /dev/null
+++ b/gddo-server/assets/templates/bot.html
@@ -0,0 +1,6 @@
+{{define "Head"}}<title>Bot - GoDoc</title>{{end}}
+
+{{define "Body"}}
+  <p>GoDocBot is godoc.org's robot for fetching Go documentation from version control systems.
+  <p>Contact: info@godoc.org
+{{end}}
diff --git a/gddo-server/assets/templates/cmd.html b/gddo-server/assets/templates/cmd.html
new file mode 100644
index 0000000..dbb80b7
--- /dev/null
+++ b/gddo-server/assets/templates/cmd.html
@@ -0,0 +1,8 @@
+{{define "Head"}}{{template "PkgCmdHeader" $}}{{end}}
+
+{{define "Body"}}
+  {{template "ProjectNav" $}}
+  <h2>Command {{$.pdoc.PageName}}</h2>
+  {{$.pdoc.Doc|comment}}
+  {{template "PkgCmdFooter" $}}
+{{end}}
diff --git a/gddo-server/assets/templates/cmd.txt b/gddo-server/assets/templates/cmd.txt
new file mode 100644
index 0000000..4ed5306
--- /dev/null
+++ b/gddo-server/assets/templates/cmd.txt
@@ -0,0 +1,5 @@
+{{define "ROOT"}}{{with .pdoc}}
+COMMAND DOCUMENTATION
+
+{{.Doc|comment}}
+{{template "Subdirs" $}}{{end}}{{end}}
diff --git a/gddo-server/assets/templates/common.html b/gddo-server/assets/templates/common.html
new file mode 100644
index 0000000..735980d
--- /dev/null
+++ b/gddo-server/assets/templates/common.html
@@ -0,0 +1,83 @@
+{{define "Analytics"}}{{with gaAccount}}<script type="text/javascript">
+  var _gaq = _gaq || [];
+  _gaq.push(['_setAccount', '{{.}}']);
+  _gaq.push(['_trackPageview']);
+  (function() {
+    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+  })();
+</script>{{end}}{{end}}
+
+{{define "SearchBox"}}
+  <form>
+    <div class="input-group">
+      <input class="form-control" name="q" autofocus="autofocus" value="{{.}}" placeholder="Search for package by import path or keyword." type="text">
+      <span class="input-group-btn">
+        <button class="btn btn-default" type="submit">Go!</button>
+      </span>
+    </div>
+  </form>
+{{end}}
+
+{{define "ProjectNav"}}<div class="clearfix" id="x-projnav">
+  {{if .pdoc.ProjectRoot}}{{if .pdoc.ProjectURL}}<a href="{{.pdoc.ProjectURL}}"><strong>{{.pdoc.ProjectName}}:</strong></a>{{else}}<strong>{{.pdoc.ProjectName}}:</strong>{{end}}{{else}}<a href="/-/go">Go:</a>{{end}}
+  {{.pdoc.Breadcrumbs templateName}}
+  {{if and .pdoc.Name (equal templateName "pkg.html")}}
+  <span class="pull-right">
+    <a href="#pkg-index">Index</a>
+    {{if .pdoc.AllExamples}}<span class="text-muted">|</span> <a href="#pkg-examples">Examples</a>{{end}}
+    <span class="text-muted">|</span> <a href="#pkg-files">Files</a>
+    {{if .pkgs}}<span class="text-muted">|</span> <a href="#pkg-subdirectories">Directories</a>{{end}}
+  </span>
+  {{end}}
+</div>{{end}}
+
+{{define "Pkgs"}}
+    <table class="table table-condensed">
+    <thead><tr><th>Path</th><th>Synopsis</th></tr></thead>
+    <tbody>{{range .}}<tr><td>{{if .Path|isValidImportPath}}<a href="/{{.Path}}">{{.Path|importPath}}</a>{{else}}{{.Path|importPath}}{{end}}</td><td>{{.Synopsis|importPath}}</td></tr>
+    {{end}}</tbody>
+    </table>
+{{end}}
+
+{{define "PkgCmdHeader"}}{{with .pdoc}}
+  <title>{{.PageName}} - GoDoc</title>
+  {{if .Synopsis}}
+    <meta name="twitter:title" content="{{if .IsCmd}}Command{{else}}Package{{end}} {{.PageName}}">
+    <meta property="og:title" content="{{if .IsCmd}}Command{{else}}Package{{end}} {{.PageName}}">
+    <meta name="description" content="{{.Synopsis}}">
+    <meta name="twitter:description" content="{{.Synopsis}}">
+    <meta property="og:description" content="{{.Synopsis}}">
+    <meta name="twitter:card" content="summary">
+    <meta name="twitter:site" content="@godocdotorg">
+  {{end}}
+  {{if .Errors}}<meta name="robots" content="NOINDEX">{{end}}
+{{end}}{{end}}
+
+{{define "PkgCmdFooter"}}
+{{if $.pkgs}}<h3 id="pkg-subdirectories">Directories <a class="permalink" href="#pkg-subdirectories">&para;</a></h3>
+    <table class="table table-condensed">
+    <thead><tr><th>Path</th><th>Synopsis</th></tr></thead>
+    <tbody>{{range $.pkgs}}<tr><td><a href="/{{.Path}}">{{relativePath .Path $.pdoc.ImportPath}}</a><td>{{.Synopsis}}</td></tr>{{end}}</tbody>
+    </table>
+{{end}}
+<div id="x-pkginfo">
+{{with $.pdoc}}
+  <form name="x-refresh" method="POST" action="/-/refresh"><input type="hidden" name="path" value="{{.ImportPath}}"></form>
+  <p>{{if or .Imports $.importerCount}}Package {{.Name}} {{if .Imports}}imports <a href="?imports">{{.Imports|len}} packages</a> (<a href="?import-graph">graph</a>){{end}}{{if and .Imports $.importerCount}} and {{end}}{{if $.importerCount}}is imported by <a href="?importers">{{$.importerCount}} packages</a>{{end}}.{{end}}
+  {{if not .Updated.IsZero}}Updated <span class="timeago" title="{{.Updated.Format "2006-01-02T15:04:05Z"}}">{{.Updated.Format "2006-01-02"}}</span>{{if or (equal .GOOS "windows") (equal .GOOS "darwin")}} with GOOS={{.GOOS}}{{end}}.{{end}}
+  <a href="javascript:document.getElementsByName('x-refresh')[0].submit();" title="Refresh this page from the source.">Refresh now</a>.
+  <a href="?tools">Tools</a> for package owners.
+{{end}}
+{{with $.pdoc.Errors}}
+    <p>The <a href="http://golang.org/cmd/go/#Download_and_install_packages_and_dependencies">go get</a>
+    command cannot install this package because of the following issues:
+    <ul>
+      {{range .}}<li>{{.}}{{end}}
+  </ul>
+{{end}}
+</div>
+{{end}}
+
+{{define "jQuery"}}<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>{{end}}
diff --git a/gddo-server/assets/templates/common.txt b/gddo-server/assets/templates/common.txt
new file mode 100644
index 0000000..234f652
--- /dev/null
+++ b/gddo-server/assets/templates/common.txt
@@ -0,0 +1,3 @@
+{{define "Subdirs"}}{{with $.pkgs}}SUBDIRECTORIES
+{{range .}}
+      {{.Path}}{{end}}{{end}}{{end}}
diff --git a/gddo-server/assets/templates/dir.html b/gddo-server/assets/templates/dir.html
new file mode 100644
index 0000000..e0eea63
--- /dev/null
+++ b/gddo-server/assets/templates/dir.html
@@ -0,0 +1,10 @@
+{{define "Head"}}
+  {{template "PkgCmdHeader" $}}
+  <meta name="robots" content="NOINDEX">
+{{end}}
+
+{{define "Body"}}
+{{template "ProjectNav" $}}
+{{template "PkgCmdFooter" $}}
+
+{{end}}
diff --git a/gddo-server/assets/templates/dir.txt b/gddo-server/assets/templates/dir.txt
new file mode 100644
index 0000000..4930f58
--- /dev/null
+++ b/gddo-server/assets/templates/dir.txt
@@ -0,0 +1 @@
+{{define "ROOT"}}{{with .pdoc}}{{template "Subdirs" $}}{{end}}{{end}}
diff --git a/gddo-server/assets/templates/file.html b/gddo-server/assets/templates/file.html
new file mode 100644
index 0000000..fa5b534
--- /dev/null
+++ b/gddo-server/assets/templates/file.html
@@ -0,0 +1,8 @@
+{{define "Head"}}<title>{{.pdoc.PageName}} - GoDoc</title><meta name="robots" content="NOINDEX, NOFOLLOW">{{end}}
+
+{{define "Body"}}
+  {{template "ProjectNav" $}}
+  <h3>{{.fname}}</h3>
+  <pre id="x-file">{{.src}}</pre>
+  {{with host .url}}<p>View the file on <a href="{{$.url}}">{{.}}</a>.{{end}}
+{{end}}
diff --git a/gddo-server/assets/templates/graph.html b/gddo-server/assets/templates/graph.html
new file mode 100644
index 0000000..6c6b451
--- /dev/null
+++ b/gddo-server/assets/templates/graph.html
@@ -0,0 +1,18 @@
+{{define "ROOT"}}<!DOCTYPE html><html lang="en">
+    <head>
+      <title>{{.pdoc.PageName}} graph - GoDoc</title>
+      <meta name="robots" content="NOINDEX, NOFOLLOW">
+      <link href="{{staticPath "/-/site.css"}}" rel="stylesheet">
+    </head>
+    <body>
+      <div class="well-small">
+        Package <a href="/{{.pdoc.ImportPath}}">{{.pdoc.Name}}</a>
+        {{if .pdoc.ProjectRoot}}<span class="text-muted">|</span> 
+            {{if .hide}}<a href="?view=import-graph">Show</a>{{else}}<a href="?view=import-graph&hide=1">Hide</a>{{end}} 
+            standard package dependencies.
+        {{end}}
+      </div>
+      {{.svg}}
+  </body>
+  {{template "Analytics"}}
+</html>{{end}}
diff --git a/gddo-server/assets/templates/home.html b/gddo-server/assets/templates/home.html
new file mode 100644
index 0000000..a8cddfa
--- /dev/null
+++ b/gddo-server/assets/templates/home.html
@@ -0,0 +1,38 @@
+{{define "Head"}}<title>GoDoc</title>
+{{/* <link type="application/opensearchdescription+xml" rel="search" href="/-/opensearch.xml?v={{fileHash "templates/opensearch.xml"}}"/> */}}{{end}}
+
+{{define "Body"}}
+<div class="jumbotron">
+    <h2>Search for Go Packages</h2>
+    {{template "SearchBox" ""}}
+</div>
+
+<p>GoDoc hosts documentation for <a href="http://golang.org/">Go</a> packages
+on Bitbucket, GitHub, Google Project Hosting and Launchpad.  Read the <a
+  href="/-/about">About Page</a> for information about adding packages to GoDoc
+and more.
+
+<div class="row">
+  <div class="col-sm-6">
+    {{with .Popular}}
+      <h4>Popular Packages</h4>
+      <ul class="list-unstyled">
+        {{range .}}<li><a href="/{{.Path}}">{{.Path}}</a>{{end}}
+      </ul>
+    {{end}}
+  </div>
+  <div class="col-sm-6">
+    <h4>More Packages</h4>
+    <ul class="list-unstyled">
+      <li><a href="/-/index">Index</a>
+      <li><a href="/-/go">Go Standard Packages</a>
+      <li><a href="/-/subrepo">Go Sub-repository Packages</a>
+      <li><a href="https://code.google.com/p/go-wiki/wiki/Projects">Projects @ go-wiki</a>
+      <li><a href="https://github.com/search?o=desc&q=language%3Ago&s=stars&type=Repositories">Most stars</a>, 
+        <a href="https://github.com/search?o=desc&q=language%3Ago&s=forks&type=Repositories">most forks</a>, 
+        <a href="https://github.com/search?o=desc&q=language%3Ago&s=updated&type=Repositories">recently updated</a> on GitHub
+    </ul>
+  </div>
+</div>
+
+{{end}}
diff --git a/gddo-server/assets/templates/home.txt b/gddo-server/assets/templates/home.txt
new file mode 100644
index 0000000..37d2dfc
--- /dev/null
+++ b/gddo-server/assets/templates/home.txt
@@ -0,0 +1,2 @@
+{{define "ROOT"}}
+{{end}}
diff --git a/gddo-server/assets/templates/importers.html b/gddo-server/assets/templates/importers.html
new file mode 100644
index 0000000..d8e3d17
--- /dev/null
+++ b/gddo-server/assets/templates/importers.html
@@ -0,0 +1,7 @@
+{{define "Head"}}<title>{{.pdoc.PageName}} importers - GoDoc</title><meta name="robots" content="NOINDEX, NOFOLLOW">{{end}}
+
+{{define "Body"}}
+  {{template "ProjectNav" $}}
+  <h3>Packages that import {{$.pdoc.Name}}</h3>
+  {{template "Pkgs" $.pkgs}}
+{{end}}
diff --git a/gddo-server/assets/templates/importers_robot.html b/gddo-server/assets/templates/importers_robot.html
new file mode 100644
index 0000000..8d62e98
--- /dev/null
+++ b/gddo-server/assets/templates/importers_robot.html
@@ -0,0 +1,10 @@
+{{define "Head"}}<title>{{.pdoc.PageName}} importers - GoDoc</title><meta name="robots" content="NOINDEX, NOFOLLOW">{{end}}
+
+{{define "Body"}}
+  {{template "ProjectNav" $}}
+  <h3>Packages that import {{$.pdoc.Name}}</h3>
+  <table class="table table-condensed">
+    <thead><tr><th>Path</th><th>Synopsis</th></tr></thead>
+    <tbody>{{range .pkgs}}<tr><td>{{.Path|importPath}}</td><td>{{.Synopsis|importPath}}</td></tr>{{end}}</tbody>
+  </table>
+{{end}}
diff --git a/gddo-server/assets/templates/imports.html b/gddo-server/assets/templates/imports.html
new file mode 100644
index 0000000..1fcc10b
--- /dev/null
+++ b/gddo-server/assets/templates/imports.html
@@ -0,0 +1,7 @@
+{{define "Head"}}<title>{{.pdoc.PageName}} imports - GoDoc</title><meta name="robots" content="NOINDEX, NOFOLLOW">{{end}}
+
+{{define "Body"}}
+  {{template "ProjectNav" $}}
+  <h3>Packages imported by {{.pdoc.Name}}</h3>
+  {{template "Pkgs" $.pkgs}}
+{{end}}
diff --git a/gddo-server/assets/templates/index.html b/gddo-server/assets/templates/index.html
new file mode 100644
index 0000000..a3768dc
--- /dev/null
+++ b/gddo-server/assets/templates/index.html
@@ -0,0 +1,12 @@
+{{define "Head"}}<title>Index - GoDoc</title><meta name="robots" content="NOINDEX">{{end}}
+
+{{define "Body"}}
+  <h1>Index</h1>
+  <p>The following is a list of '<a
+    href="http://golang.org/cmd/go/#hdr-Download_and_install_packages_and_dependencies">go
+    get</a>'able packages on godoc.org. A <a href="/-/go">list of Go standard packages</a> is also available.
+
+  {{htmlComment "\nPlease use http://api.godoc.org/packages instead of scraping this page.\n"}}
+  {{template "Pkgs" .pkgs}}
+  <p>Number of packages: {{len .pkgs}}.
+{{end}}
diff --git a/gddo-server/assets/templates/layout.html b/gddo-server/assets/templates/layout.html
new file mode 100644
index 0000000..b0fce3b
--- /dev/null
+++ b/gddo-server/assets/templates/layout.html
@@ -0,0 +1,70 @@
+{{define "ROOT"}}<!DOCTYPE html><html lang="en">
+<head profile="http://a9.com/-/spec/opensearch/1.1/">
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <link href="{{staticPath "/-/site.css"}}" rel="stylesheet">
+  {{template "Head" $}}
+</head>
+<body>
+<nav class="navbar navbar-default" role="navigation">
+  <div class="container">
+  <div class="navbar-header">
+    <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
+      <span class="sr-only">Toggle navigation</span>
+      <span class="icon-bar"></span>
+      <span class="icon-bar"></span>
+      <span class="icon-bar"></span>
+    </button>
+    <a class="navbar-brand" href="/"><strong>GoDoc</strong></a>
+  </div>
+  <div class="collapse navbar-collapse">
+    <ul class="nav navbar-nav">
+        <li{{if equal "home.html" templateName}} class="active"{{end}}><a href="/">Home</a></li>
+        <li{{if equal "index.html" templateName}} class="active"{{end}}><a href="/-/index">Index</a></li>
+        <li{{if equal "about.html" templateName}} class="active"{{end}}><a href="/-/about">About</a></li>
+    </ul>
+    <form class="navbar-nav navbar-form navbar-right" id="x-search" action="/" role="search"><input class="form-control" id="x-search-query" type="text" name="q" placeholder="Search"></form>
+  </div>
+</div>
+</nav>
+
+<div class="container">
+  {{template "Body" $}}
+</div>
+<div id="x-footer" class="clearfix">
+  <div class="container">
+    <a href="mailto:info@godoc.org">Feedback</a>
+    <span class="text-muted">|</span> <a href="https://github.com/garyburd/gddo/issues">Website Issues</a>
+    <span class="text-muted">|</span> <a href="http://golang.org/">Go Language</a>
+    <span class="pull-right"><a href="#">Back to top</a></span>
+  </div>
+</div>
+
+<div id="x-shortcuts" tabindex="-1" class="modal fade">
+    <div class="modal-dialog">
+      <div class="modal-content">
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+          <h4 class="modal-title">Keyboard shortcuts</h4>
+        </div>
+        <div class="modal-body">
+          <table>{{$mutePkg := not (equal "pkg.html" templateName)}}
+          <tr><td align="right"><b>?</b></td><td> : This menu</td></tr>
+          <tr><td align="right"><b>/</b></td><td> : Search site</td></tr>
+          <tr><td align="right"><b>g</b> then <b>g</b></td><td> : Go to top of page</td></tr>
+          <tr><td align="right"><b>g</b> then <b>b</b></td><td> : Go to end of page</td></tr>
+          <tr{{if $mutePkg}} class="text-muted"{{end}}><td align="right"><b>g</b> then <b>i</b></td><td> : Go to index</td></tr>
+          <tr{{if $mutePkg}} class="text-muted"{{end}}><td align="right"><b>g</b> then <b>e</b></td><td> : Go to examples</td></tr>
+          </table>
+        </div>
+        <div class="modal-footer">
+          <button type="button" class="btn" data-dismiss="modal">Close</button>
+      </div>
+    </div>
+  </div>
+</div>
+{{template "jQuery"}}<script src="{{staticPath "/-/site.js"}}"></script>{{template "Analytics"}}
+</body>
+</html>
+{{end}}
+
diff --git a/gddo-server/assets/templates/notfound.html b/gddo-server/assets/templates/notfound.html
new file mode 100644
index 0000000..684d208
--- /dev/null
+++ b/gddo-server/assets/templates/notfound.html
@@ -0,0 +1,10 @@
+{{define "Head"}}<title>Not Found - GoDoc</title>{{end}}
+
+{{define "Body"}}
+  <h1>Not Found</h1>
+  <p>Oh snap! Our team of gophers could not find the web page you are looking for. Try one of these pages:
+  <ul>
+    <li><a href="/">Home</a>
+    <li><a href="/-/index">Package Index</a>
+  </ul>
+{{end}}
diff --git a/gddo-server/assets/templates/notfound.txt b/gddo-server/assets/templates/notfound.txt
new file mode 100644
index 0000000..a3456ba
--- /dev/null
+++ b/gddo-server/assets/templates/notfound.txt
@@ -0,0 +1,2 @@
+{{define "ROOT"}}NOT FOUND
+{{end}}
diff --git a/gddo-server/assets/templates/opensearch.xml b/gddo-server/assets/templates/opensearch.xml
new file mode 100644
index 0000000..dad5682
--- /dev/null
+++ b/gddo-server/assets/templates/opensearch.xml
@@ -0,0 +1,9 @@
+{{define "ROOT"}}<?xml version="1.0"?>
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
+    <InputEncoding>UTF-8</InputEncoding>
+    <ShortName>GoDoc</ShortName>
+    <Description>GoDoc: Go Documentation Service</Description>
+    <Url type="text/html" method="get" template="http://{{.}}/?q={searchTerms}"/>
+    <Url type="application/x-suggestions+json" template="http://{{.}}/-/suggest?q={searchTerms}"/>
+</OpenSearchDescription>
+{{end}}
diff --git a/gddo-server/assets/templates/pkg.html b/gddo-server/assets/templates/pkg.html
new file mode 100644
index 0000000..7027c8e
--- /dev/null
+++ b/gddo-server/assets/templates/pkg.html
@@ -0,0 +1,176 @@
+{{define "Head"}}
+  {{template "PkgCmdHeader" $}}
+  {{if sidebarEnabled}}
+    <link href="{{staticPath "/-/sidebar.css"}}" rel="stylesheet">
+  {{end}}
+{{end}}
+
+{{define "Body"}}
+  {{with .pdoc}}
+
+{{if sidebarEnabled}}
+    <div class="row">
+
+      <!-- Sidebar -->
+      <div class="gddo-sidebar col-md-3 hidden-xs hidden-sm">
+        <ul id="sidebar-nav" class="nav" data-spy="affix" data-offset-top="70">
+          <li class="active"><a href="#pkg-overview">Overview</a></li>
+          <li><a href="#pkg-index">Index</a></li>
+          {{if .Examples}}<li><a href="#pkg-examples">Examples</a></li>{{end}}
+          {{if .Consts}}<li><a href="#pkg-constants">Constants</a></li>{{end}}
+          {{if .Vars}}<li><a href="#pkg-variables">Variables</a></li>{{end}}
+
+          {{if .Funcs}}
+            <li>
+              <a href="#pkg-functions">Functions</a>
+              <ul class="nav">
+                {{range .Funcs}}<li><a href="#{{.Name}}">{{.Name}}</a></li>{{end}}
+              </ul>
+            </li>
+          {{end}}
+
+          {{if .Types}}
+            <li>
+              <a href="#pkg-types">Types</a>
+              <ul class="nav">
+                {{range .Types}}<li><a href="#{{.Name}}">{{.Name}}</a></li>{{end}}
+              </ul>
+            </li>
+          {{end}}
+
+          {{if .Notes.BUG}}<li><a href="#pkg-note-bug">Bugs</a></li>{{end}}
+          {{if $.pkgs}}<li><a href="#pkg-subdirectories">Directories</a></li>{{end}}
+        </ul>
+      </div>
+
+      <!-- Content -->
+      <div class="col-md-9">
+
+{{end}}<!-- end sidebarEnabled -->
+
+        {{template "ProjectNav" $}}
+
+        <h2 id="pkg-overview">package {{.Name}}</h2>
+
+        <p><code>import "{{.ImportPath}}"</code>
+
+        {{.Doc|comment}}
+
+        {{template "Examples" .|$.pdoc.ObjExamples}}
+
+        <!-- Index -->
+        <h3 id="pkg-index" class="section-header">Index <a class="permalink" href="#pkg-index">&para;</a></h3>
+
+        {{if .Truncated}}
+          <div class="alert">The documentation displayed here is incomplete. Use the godoc command to read the complete documentation.</div>
+        {{end}}
+
+        <ul class="list-unstyled">
+          {{if .Consts}}<li><a href="#pkg-constants">Constants</a></li>{{end}}
+          {{if .Vars}}<li><a href="#pkg-variables">Variables</a></li>{{end}}
+          {{range .Funcs}}<li><a href="#{{.Name}}">{{.Decl.Text}}</a></li>{{end}}
+          {{range $t := .Types}}
+            <li><a href="#{{.Name}}">type {{.Name}}</a></li>
+            {{if or .Funcs .Methods}}<ul>{{end}}
+            {{range .Funcs}}<li><a href="#{{.Name}}">{{.Decl.Text}}</a></li>{{end}}
+            {{range .Methods}}<li><a href="#{{$t.Name}}.{{.Name}}">{{.Decl.Text}}</a></li>{{end}}
+            {{if or .Funcs .Methods}}</ul>{{end}}
+          {{end}}
+        </ul>
+
+        <!-- Examples -->
+        {{with .AllExamples}}
+          <h4 id="pkg-examples">Examples <a class="permalink" href="#pkg-examples">&para;</a></h4>
+          <ul class="list-unstyled">
+            {{range . }}<li><a href="#example-{{.Id}}" onclick="$('#ex-{{.Id}}').addClass('in').removeClass('collapse').height('auto')">{{.Label}}</a></li>{{end}}
+          </ul>
+        {{else}}
+          <span id="pkg-examples"></span>
+        {{end}}
+
+        <!-- Files -->
+        <h4 id="pkg-files">
+          {{with .BrowseURL}}<a href="{{.}}">Package Files</a>{{else}}Package Files{{end}}
+          <a class="permalink" href="#pkg-files">&para;</a>
+        </h4>
+
+        <p>{{range .Files}}{{if .URL}}<a href="{{.URL}}">{{.Name}}</a>{{else}}{{.Name}}{{end}} {{end}}</p>
+
+        <!-- Contants -->
+        {{if .Consts}}
+          <h3 id="pkg-constants">Constants <a class="permalink" href="#pkg-constants">&para;</a></h3>
+          {{range .Consts}}<pre>{{code .Decl nil}}</pre>{{.Doc|comment}}{{end}}
+        {{end}}
+
+        <!-- Variables -->
+        {{if .Vars}}
+          <h3 id="pkg-variables">Variables <a class="permalink" href="#pkg-variables">&para;</a></h3>
+          {{range .Vars}}<pre>{{code .Decl nil}}</pre>{{.Doc|comment}}{{end}}
+        {{end}}
+
+        <!-- Functions -->
+        {{if sidebarEnabled}}{{if .Funcs}}
+            <h3 id="pkg-functions" class="section-header">Functions <a class="permalink" href="#pkg-functions">&para;</a></h3>
+        {{end}}{{end}}
+        {{range .Funcs}}
+          <h3 id="{{.Name}}">func {{$.pdoc.SourceLink .Pos .Name .Name}} <a class="permalink" href="#{{.Name}}">&para;</a></h3>
+          <pre class="funcdecl">{{code .Decl nil}}</pre>{{.Doc|comment}}
+          {{template "Examples" .|$.pdoc.ObjExamples}}
+        {{end}}
+
+        <!-- Types -->
+        {{if sidebarEnabled}}{{if .Types}}
+            <h3 id="pkg-types" class="section-header">Types <a class="permalink" href="#pkg-types">&para;</a></h3>
+        {{end}}{{end}}
+
+        {{range $t := .Types}}
+          <h3 id="{{.Name}}">type {{$.pdoc.SourceLink .Pos .Name .Name}} <a class="permalink" href="#{{.Name}}">&para;</a></h3>
+          <pre>{{code .Decl $t}}</pre>{{.Doc|comment}}
+          {{range .Consts}}<pre>{{code .Decl nil}}</pre>{{.Doc|comment}}{{end}}
+          {{range .Vars}}<pre>{{code .Decl nil}}</pre>{{.Doc|comment}}{{end}}
+          {{template "Examples" .|$.pdoc.ObjExamples}}
+
+          {{range .Funcs}}
+            <h4 id="{{.Name}}">func {{$.pdoc.SourceLink .Pos .Name .Name}} <a class="permalink" href="#{{.Name}}">&para;</a></h4>
+            <pre class="funcdecl">{{code .Decl nil}}</pre>{{.Doc|comment}}
+            {{template "Examples" .|$.pdoc.ObjExamples}}
+          {{end}}
+
+          {{range .Methods}}
+            <h4 id="{{$t.Name}}.{{.Name}}">func ({{.Recv}}) {{$.pdoc.SourceLink .Pos .Name (printf "%s.%s" $t.Name .Name)}} <a class="permalink" href="#{{$t.Name}}.{{.Name}}">&para;</a></h4>
+            <pre class="funcdecl">{{code .Decl nil}}</pre>{{.Doc|comment}}
+            {{template "Examples" .|$.pdoc.ObjExamples}}
+          {{end}}
+        {{end}}
+
+        <!-- Bugs -->
+        {{with .Notes}}{{with .BUG}}
+          <h3 id="pkg-note-bug">Bugs <a class="permalink" href="#pkg-note-bug">&para;</a></h3>{{range .}}<p>{{$.pdoc.SourceLink .Pos "☞" ""}} {{.Body}}{{end}}
+        {{end}}{{end}}
+        {{template "PkgCmdFooter" $}}
+
+{{if sidebarEnabled}}
+      </div>
+    </div>
+{{end}}
+
+  {{end}}
+{{end}}
+
+{{define "Examples"}}
+  {{if .}}
+    <div class="panel-group">
+    {{range .}}
+      <div class="panel panel-default" id="example-{{.Id}}">
+        <div class="panel-heading"><a class="accordion-toggle" data-toggle="collapse" href="#ex-{{.Id}}">Example{{with .Example.Name}} ({{.}}){{end}}</a></div>
+        <div id="ex-{{.Id}}" class="panel-collapse collapse"><div class="panel-body">
+          {{with .Example.Doc}}<p>{{.|comment}}{{end}}
+          <p>Code:{{if .Example.Play}}<span class="pull-right"><a href="?play={{.Id}}">play</a>&nbsp;</span>{{end}}
+          <pre>{{code .Example.Code nil}}</pre>
+          {{with .Example.Output}}<p>Output:<pre>{{.}}</pre>{{end}}
+        </div></div>
+      </div>
+    {{end}}
+    </div>
+  {{end}}
+{{end}}
diff --git a/gddo-server/assets/templates/pkg.txt b/gddo-server/assets/templates/pkg.txt
new file mode 100644
index 0000000..5de2389
--- /dev/null
+++ b/gddo-server/assets/templates/pkg.txt
@@ -0,0 +1,38 @@
+{{define "ROOT"}}{{with .pdoc}}PACKAGE{{if .Name}}
+
+package {{.Name}}
+    import "{{.ImportPath}}"
+
+{{.Doc|comment}}
+{{if .Consts}}
+CONSTANTS
+
+{{range .Consts}}{{.Decl.Text}}
+{{.Doc|comment}}{{end}}
+{{end}}{{if .Vars}}
+VARIABLES
+
+{{range .Vars}}{{.Decl.Text}}
+{{.Doc|comment}}{{end}}
+{{end}}{{if .Funcs}}
+FUNCTIONS
+
+{{range .Funcs}}{{.Decl.Text}}
+{{.Doc|comment}}
+{{end}}{{end}}{{if .Types}}
+TYPES
+
+{{range .Types}}{{.Decl.Text}}
+{{.Doc|comment}}
+{{range .Consts}}{{.Decl.Text}}
+{{.Doc|comment}}
+{{end}}{{range .Vars}}{{.Decl.Text}}
+{{.Doc|comment}}
+{{end}}{{range .Funcs}}{{.Decl.Text}}
+{{.Doc|comment}}
+{{end}}{{range .Methods}}{{.Decl.Text}}
+{{.Doc|comment}}
+{{end}}{{end}}
+{{end}}
+{{template "Subdirs" $}}
+{{end}}{{end}}{{end}}
diff --git a/gddo-server/assets/templates/results.html b/gddo-server/assets/templates/results.html
new file mode 100644
index 0000000..5160a9b
--- /dev/null
+++ b/gddo-server/assets/templates/results.html
@@ -0,0 +1,14 @@
+{{define "Head"}}<title>{{.q}} - GoDoc</title><meta name="robots" content="NOINDEX">{{end}}
+
+{{define "Body"}}
+  <div class="well">
+    {{template "SearchBox" .q}}
+  </div>
+  <p>Try this search on <a href="http://go-search.org/search?q={{.q}}">Go-Search</a> 
+  or <a href="https://github.com/search?q={{.q}}+language:go">GitHub</a>.
+  {{if .pkgs}}
+    {{template "Pkgs" .pkgs}}
+  {{else}}
+    <p>No packages found.
+  {{end}}
+{{end}}
diff --git a/gddo-server/assets/templates/results.txt b/gddo-server/assets/templates/results.txt
new file mode 100644
index 0000000..02ce749
--- /dev/null
+++ b/gddo-server/assets/templates/results.txt
@@ -0,0 +1,2 @@
+{{define "ROOT"}}{{range .pkgs}}{{.Path}} {{.Synopsis}}
+{{end}}{{end}}
diff --git a/gddo-server/assets/templates/std.html b/gddo-server/assets/templates/std.html
new file mode 100644
index 0000000..42463e0
--- /dev/null
+++ b/gddo-server/assets/templates/std.html
@@ -0,0 +1,8 @@
+{{define "Head"}}<title>Standard Packages - GoDoc</title><meta name="robots" content="NOINDEX">{{end}}
+
+{{define "Body"}}
+  <h1>Go Standard Packages</h1>
+  {{template "Pkgs" .pkgs}}
+  <p>View the official documentation at <a href="http://golang.org/pkg/">golang.org</a>.
+{{end}}
+
diff --git a/gddo-server/assets/templates/subrepo.html b/gddo-server/assets/templates/subrepo.html
new file mode 100644
index 0000000..500ba35
--- /dev/null
+++ b/gddo-server/assets/templates/subrepo.html
@@ -0,0 +1,22 @@
+{{define "Head"}}<title>Go Sub-Repository Packages - GoDoc</title><meta name="robots" content="NOINDEX">{{end}}
+
+{{define "Body"}}
+  <h1>Go Sub-repository Packages</h1>
+  These packages are part of the Go Project but outside the main Go tree. They are developed under looser compatibility requirements than the Go core.
+  <h2>Repositories</h2>
+  <ul class="list-unstyled">
+    {{template "subrepo" map "name" "tools" "desc" "godoc, vet, cover, and other tools."}}
+    {{template "subrepo" map "name" "crypto" "desc" "additional cryptography packages."}}
+    {{template "subrepo" map "name" "image" "desc" "additional imaging packages."}}
+    {{template "subrepo" map "name" "net" "desc" "additional networking packages."}}
+    {{template "subrepo" map "name" "text" "desc" "packages for working with text."}}
+    {{template "subrepo" map "name" "blog" "desc" "the content and server program for blog.golang.org."}}
+    {{template "subrepo" map "name" "talks" "desc" "the content and server program for talks.golang.org."}}
+    {{template "subrepo" map "name" "exp" "desc" "experimental code (handle with care)."}}
+    {{template "subrepo" map "name" "codereview" "desc" "tools for code review."}}
+  </ul>
+  <h2>Packages</h2>
+  {{template "Pkgs" .pkgs}}
+{{end}}
+
+{{define "subrepo"}}<li><a href="https://code.google.com/p/go/source/browse/?repo={{.name}}">code.google.com/p/go.{{.name}}</a> — {{.desc}}{{end}}
diff --git a/gddo-server/assets/templates/tools.html b/gddo-server/assets/templates/tools.html
new file mode 100644
index 0000000..0689b9c
--- /dev/null
+++ b/gddo-server/assets/templates/tools.html
@@ -0,0 +1,36 @@
+{{define "Head"}}<title>{{.pdoc.PageName}} tools - GoDoc</title><meta name="robots" content="NOINDEX, NOFOLLOW">{{end}}
+
+{{define "Body"}}
+  {{template "ProjectNav" $}}
+  <h2>Tools for {{$.pdoc.PageName}}</h2>
+
+  <h3>Badge</h3>
+
+  <p><a href="{{.uri}}"><img src="{{.uri}}?status.png" alt="GoDoc"></a>
+
+  <p>Use one of the snippets below to add a link to GoDoc from your project
+  website or README file:</a>
+
+  <h5>HTML</h5>
+  <input type="text" value='<a href="{{.uri}}"><img src="{{.uri}}?status.png" alt="GoDoc"></a>' class="click-select form-control">
+
+  <h5>Markdown</h5>
+  <input type="text" value="[![GoDoc]({{.uri}}?status.png)]({{.uri}})" class="click-select form-control">
+
+  {{if .pdoc.Name}}
+    <h3>Lint</h3>
+    <form name="x-lint" method="POST" action="http://go-lint.appspot.com/-/refresh"><input name="importPath" type="hidden" value="{{.pdoc.ImportPath}}"></form>
+    <p><a href="javascript:document.getElementsByName('x-lint')[0].submit();">Run lint</a> on {{.pdoc.PageName}}.
+
+    {{if and (not .pdoc.IsCmd) (not .pdoc.Doc)}}
+      <p>The {{.pdoc.Name}} package does not have a package declaration
+      comment.  See the <a
+        href="http://blog.golang.org/godoc-documenting-go-code">Go
+        documentation guidelines</a> for information on how to write a package
+      comment. It's important to write a good summary of the package in the
+      first sentence of the package comment. GoDoc indexes the first sentence
+      and displays the first sentence in package lists.
+    {{end}}
+  {{end}}
+  <p>&nbsp;
+{{end}}
diff --git a/gddo-server/assets/third_party/bootstrap/css/bootstrap.css b/gddo-server/assets/third_party/bootstrap/css/bootstrap.css
new file mode 100644
index 0000000..f08b8e2
--- /dev/null
+++ b/gddo-server/assets/third_party/bootstrap/css/bootstrap.css
@@ -0,0 +1,4228 @@
+/*!
+ * Bootstrap v3.0.0
+ *
+ * Copyright 2013 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+/*! normalize.css v2.1.0 | MIT License | git.io/normalize */
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+  display: block;
+}
+audio,
+canvas,
+video {
+  display: inline-block;
+}
+audio:not([controls]) {
+  display: none;
+  height: 0;
+}
+[hidden] {
+  display: none;
+}
+html {
+  font-family: sans-serif;
+  -webkit-text-size-adjust: 100%;
+  -ms-text-size-adjust: 100%;
+}
+body {
+  margin: 0;
+}
+a:focus {
+  outline: thin dotted;
+}
+a:active,
+a:hover {
+  outline: 0;
+}
+h1 {
+  font-size: 2em;
+  margin: 0.67em 0;
+}
+abbr[title] {
+  border-bottom: 1px dotted;
+}
+b,
+strong {
+  font-weight: bold;
+}
+dfn {
+  font-style: italic;
+}
+hr {
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+  height: 0;
+}
+mark {
+  background: #ff0;
+  color: #000;
+}
+code,
+kbd,
+pre,
+samp {
+  font-family: monospace, serif;
+  font-size: 1em;
+}
+pre {
+  white-space: pre-wrap;
+}
+q {
+  quotes: "\201C" "\201D" "\2018" "\2019";
+}
+small {
+  font-size: 80%;
+}
+sub,
+sup {
+  font-size: 75%;
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline;
+}
+sup {
+  top: -0.5em;
+}
+sub {
+  bottom: -0.25em;
+}
+img {
+  border: 0;
+}
+svg:not(:root) {
+  overflow: hidden;
+}
+figure {
+  margin: 0;
+}
+fieldset {
+  border: 1px solid #c0c0c0;
+  margin: 0 2px;
+  padding: 0.35em 0.625em 0.75em;
+}
+legend {
+  border: 0;
+  padding: 0;
+}
+button,
+input,
+select,
+textarea {
+  font-family: inherit;
+  font-size: 100%;
+  margin: 0;
+}
+button,
+input {
+  line-height: normal;
+}
+button,
+select {
+  text-transform: none;
+}
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+  -webkit-appearance: button;
+  cursor: pointer;
+}
+button[disabled],
+html input[disabled] {
+  cursor: default;
+}
+input[type="checkbox"],
+input[type="radio"] {
+  box-sizing: border-box;
+  padding: 0;
+}
+input[type="search"] {
+  -webkit-appearance: textfield;
+  -moz-box-sizing: content-box;
+  -webkit-box-sizing: content-box;
+  box-sizing: content-box;
+}
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  border: 0;
+  padding: 0;
+}
+textarea {
+  overflow: auto;
+  vertical-align: top;
+}
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+*,
+*:before,
+*:after {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+html {
+  font-size: 62.5%;
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+body {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 14px;
+  line-height: 1.428571429;
+  color: #333333;
+  background-color: #ffffff;
+}
+input,
+button,
+select,
+textarea {
+  font-family: inherit;
+  font-size: inherit;
+  line-height: inherit;
+}
+button,
+input,
+select[multiple],
+textarea {
+  background-image: none;
+}
+a {
+  color: #428bca;
+  text-decoration: none;
+}
+a:hover,
+a:focus {
+  color: #2a6496;
+  text-decoration: underline;
+}
+a:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+img {
+  vertical-align: middle;
+}
+.img-responsive {
+  display: block;
+  max-width: 100%;
+  height: auto;
+}
+.img-rounded {
+  border-radius: 6px;
+}
+.img-thumbnail {
+  padding: 4px;
+  line-height: 1.428571429;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-radius: 4px;
+  -webkit-transition: all 0.2s ease-in-out;
+  transition: all 0.2s ease-in-out;
+  display: inline-block;
+  max-width: 100%;
+  height: auto;
+}
+.img-circle {
+  border-radius: 50%;
+}
+hr {
+  margin-top: 20px;
+  margin-bottom: 20px;
+  border: 0;
+  border-top: 1px solid #eeeeee;
+}
+.sr-only {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  margin: -1px;
+  padding: 0;
+  overflow: hidden;
+  clip: rect(0 0 0 0);
+  border: 0;
+}
+@media print {
+  * {
+    text-shadow: none !important;
+    color: #000 !important;
+    background: transparent !important;
+    box-shadow: none !important;
+  }
+  a,
+  a:visited {
+    text-decoration: underline;
+  }
+  a[href]:after {
+    content: " (" attr(href) ")";
+  }
+  abbr[title]:after {
+    content: " (" attr(title) ")";
+  }
+  .ir a:after,
+  a[href^="javascript:"]:after,
+  a[href^="#"]:after {
+    content: "";
+  }
+  pre,
+  blockquote {
+    border: 1px solid #999;
+    page-break-inside: avoid;
+  }
+  thead {
+    display: table-header-group;
+  }
+  tr,
+  img {
+    page-break-inside: avoid;
+  }
+  img {
+    max-width: 100% !important;
+  }
+  @page  {
+    margin: 2cm .5cm;
+  }
+  p,
+  h2,
+  h3 {
+    orphans: 3;
+    widows: 3;
+  }
+  h2,
+  h3 {
+    page-break-after: avoid;
+  }
+  .navbar {
+    display: none;
+  }
+  .table td,
+  .table th {
+    background-color: #fff !important;
+  }
+  .btn > .caret,
+  .dropup > .btn > .caret {
+    border-top-color: #000 !important;
+  }
+  .label {
+    border: 1px solid #000;
+  }
+  .table {
+    border-collapse: collapse !important;
+  }
+  .table-bordered th,
+  .table-bordered td {
+    border: 1px solid #ddd !important;
+  }
+}
+p {
+  margin: 0 0 10px;
+}
+.lead {
+  margin-bottom: 20px;
+  font-size: 16.099999999999998px;
+  font-weight: 200;
+  line-height: 1.4;
+}
+@media (min-width: 768px) {
+  .lead {
+    font-size: 21px;
+  }
+}
+small {
+  font-size: 85%;
+}
+cite {
+  font-style: normal;
+}
+.text-muted {
+  color: #999999;
+}
+.text-primary {
+  color: #428bca;
+}
+.text-warning {
+  color: #c09853;
+}
+.text-danger {
+  color: #b94a48;
+}
+.text-success {
+  color: #468847;
+}
+.text-info {
+  color: #3a87ad;
+}
+.text-left {
+  text-align: left;
+}
+.text-right {
+  text-align: right;
+}
+.text-center {
+  text-align: center;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+.h1,
+.h2,
+.h3,
+.h4,
+.h5,
+.h6 {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-weight: 500;
+  line-height: 1.1;
+}
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small,
+.h1 small,
+.h2 small,
+.h3 small,
+.h4 small,
+.h5 small,
+.h6 small {
+  font-weight: normal;
+  line-height: 1;
+  color: #999999;
+}
+h1,
+h2,
+h3 {
+  margin-top: 20px;
+  margin-bottom: 10px;
+}
+h4,
+h5,
+h6 {
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+h1,
+.h1 {
+  font-size: 36px;
+}
+h2,
+.h2 {
+  font-size: 30px;
+}
+h3,
+.h3 {
+  font-size: 24px;
+}
+h4,
+.h4 {
+  font-size: 18px;
+}
+h5,
+.h5 {
+  font-size: 14px;
+}
+h6,
+.h6 {
+  font-size: 12px;
+}
+h1 small,
+.h1 small {
+  font-size: 24px;
+}
+h2 small,
+.h2 small {
+  font-size: 18px;
+}
+h3 small,
+.h3 small,
+h4 small,
+.h4 small {
+  font-size: 14px;
+}
+.page-header {
+  padding-bottom: 9px;
+  margin: 40px 0 20px;
+  border-bottom: 1px solid #eeeeee;
+}
+ul,
+ol {
+  margin-top: 0;
+  margin-bottom: 10px;
+}
+ul ul,
+ol ul,
+ul ol,
+ol ol {
+  margin-bottom: 0;
+}
+.list-unstyled {
+  padding-left: 0;
+  list-style: none;
+}
+.list-inline {
+  padding-left: 0;
+  list-style: none;
+}
+.list-inline > li {
+  display: inline-block;
+  padding-left: 5px;
+  padding-right: 5px;
+}
+dl {
+  margin-bottom: 20px;
+}
+dt,
+dd {
+  line-height: 1.428571429;
+}
+dt {
+  font-weight: bold;
+}
+dd {
+  margin-left: 0;
+}
+@media (min-width: 768px) {
+  .dl-horizontal dt {
+    float: left;
+    width: 160px;
+    clear: left;
+    text-align: right;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .dl-horizontal dd {
+    margin-left: 180px;
+  }
+  .dl-horizontal dd:before,
+  .dl-horizontal dd:after {
+    content: " ";
+    /* 1 */
+  
+    display: table;
+    /* 2 */
+  
+  }
+  .dl-horizontal dd:after {
+    clear: both;
+  }
+  .dl-horizontal dd:before,
+  .dl-horizontal dd:after {
+    content: " ";
+    /* 1 */
+  
+    display: table;
+    /* 2 */
+  
+  }
+  .dl-horizontal dd:after {
+    clear: both;
+  }
+}
+abbr[title],
+abbr[data-original-title] {
+  cursor: help;
+  border-bottom: 1px dotted #999999;
+}
+abbr.initialism {
+  font-size: 90%;
+  text-transform: uppercase;
+}
+blockquote {
+  padding: 10px 20px;
+  margin: 0 0 20px;
+  border-left: 5px solid #eeeeee;
+}
+blockquote p {
+  font-size: 17.5px;
+  font-weight: 300;
+  line-height: 1.25;
+}
+blockquote p:last-child {
+  margin-bottom: 0;
+}
+blockquote small {
+  display: block;
+  line-height: 1.428571429;
+  color: #999999;
+}
+blockquote small:before {
+  content: '\2014 \00A0';
+}
+blockquote.pull-right {
+  padding-right: 15px;
+  padding-left: 0;
+  border-right: 5px solid #eeeeee;
+  border-left: 0;
+}
+blockquote.pull-right p,
+blockquote.pull-right small {
+  text-align: right;
+}
+blockquote.pull-right small:before {
+  content: '';
+}
+blockquote.pull-right small:after {
+  content: '\00A0 \2014';
+}
+q:before,
+q:after,
+blockquote:before,
+blockquote:after {
+  content: "";
+}
+address {
+  display: block;
+  margin-bottom: 20px;
+  font-style: normal;
+  line-height: 1.428571429;
+}
+code,
+pre {
+  font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
+}
+code {
+  padding: 2px 4px;
+  font-size: 90%;
+  color: #c7254e;
+  background-color: #f9f2f4;
+  white-space: nowrap;
+  border-radius: 4px;
+}
+pre {
+  display: block;
+  padding: 9.5px;
+  margin: 0 0 10px;
+  font-size: 13px;
+  line-height: 1.428571429;
+  word-break: break-all;
+  word-wrap: break-word;
+  color: #333333;
+  background-color: #f5f5f5;
+  border: 1px solid #cccccc;
+  border-radius: 4px;
+}
+pre.prettyprint {
+  margin-bottom: 20px;
+}
+pre code {
+  padding: 0;
+  font-size: inherit;
+  color: inherit;
+  white-space: pre-wrap;
+  background-color: transparent;
+  border: 0;
+}
+.pre-scrollable {
+  max-height: 340px;
+  overflow-y: scroll;
+}
+.container {
+  margin-right: auto;
+  margin-left: auto;
+  padding-left: 15px;
+  padding-right: 15px;
+}
+.container:before,
+.container:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.container:after {
+  clear: both;
+}
+.container:before,
+.container:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.container:after {
+  clear: both;
+}
+.row {
+  margin-left: -15px;
+  margin-right: -15px;
+}
+.row:before,
+.row:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.row:after {
+  clear: both;
+}
+.row:before,
+.row:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.row:after {
+  clear: both;
+}
+.col-xs-1,
+.col-xs-2,
+.col-xs-3,
+.col-xs-4,
+.col-xs-5,
+.col-xs-6,
+.col-xs-7,
+.col-xs-8,
+.col-xs-9,
+.col-xs-10,
+.col-xs-11,
+.col-xs-12,
+.col-sm-1,
+.col-sm-2,
+.col-sm-3,
+.col-sm-4,
+.col-sm-5,
+.col-sm-6,
+.col-sm-7,
+.col-sm-8,
+.col-sm-9,
+.col-sm-10,
+.col-sm-11,
+.col-sm-12,
+.col-md-1,
+.col-md-2,
+.col-md-3,
+.col-md-4,
+.col-md-5,
+.col-md-6,
+.col-md-7,
+.col-md-8,
+.col-md-9,
+.col-md-10,
+.col-md-11,
+.col-md-12,
+.col-lg-1,
+.col-lg-2,
+.col-lg-3,
+.col-lg-4,
+.col-lg-5,
+.col-lg-6,
+.col-lg-7,
+.col-lg-8,
+.col-lg-9,
+.col-lg-10,
+.col-lg-11,
+.col-lg-12 {
+  position: relative;
+  min-height: 1px;
+  padding-left: 15px;
+  padding-right: 15px;
+}
+.col-xs-1,
+.col-xs-2,
+.col-xs-3,
+.col-xs-4,
+.col-xs-5,
+.col-xs-6,
+.col-xs-7,
+.col-xs-8,
+.col-xs-9,
+.col-xs-10,
+.col-xs-11 {
+  float: left;
+}
+.col-xs-1 {
+  width: 8.333333333333332%;
+}
+.col-xs-2 {
+  width: 16.666666666666664%;
+}
+.col-xs-3 {
+  width: 25%;
+}
+.col-xs-4 {
+  width: 33.33333333333333%;
+}
+.col-xs-5 {
+  width: 41.66666666666667%;
+}
+.col-xs-6 {
+  width: 50%;
+}
+.col-xs-7 {
+  width: 58.333333333333336%;
+}
+.col-xs-8 {
+  width: 66.66666666666666%;
+}
+.col-xs-9 {
+  width: 75%;
+}
+.col-xs-10 {
+  width: 83.33333333333334%;
+}
+.col-xs-11 {
+  width: 91.66666666666666%;
+}
+.col-xs-12 {
+  width: 100%;
+}
+@media (min-width: 768px) {
+  .container {
+    max-width: 750px;
+  }
+  .col-sm-1,
+  .col-sm-2,
+  .col-sm-3,
+  .col-sm-4,
+  .col-sm-5,
+  .col-sm-6,
+  .col-sm-7,
+  .col-sm-8,
+  .col-sm-9,
+  .col-sm-10,
+  .col-sm-11 {
+    float: left;
+  }
+  .col-sm-1 {
+    width: 8.333333333333332%;
+  }
+  .col-sm-2 {
+    width: 16.666666666666664%;
+  }
+  .col-sm-3 {
+    width: 25%;
+  }
+  .col-sm-4 {
+    width: 33.33333333333333%;
+  }
+  .col-sm-5 {
+    width: 41.66666666666667%;
+  }
+  .col-sm-6 {
+    width: 50%;
+  }
+  .col-sm-7 {
+    width: 58.333333333333336%;
+  }
+  .col-sm-8 {
+    width: 66.66666666666666%;
+  }
+  .col-sm-9 {
+    width: 75%;
+  }
+  .col-sm-10 {
+    width: 83.33333333333334%;
+  }
+  .col-sm-11 {
+    width: 91.66666666666666%;
+  }
+  .col-sm-12 {
+    width: 100%;
+  }
+  .col-sm-push-1 {
+    left: 8.333333333333332%;
+  }
+  .col-sm-push-2 {
+    left: 16.666666666666664%;
+  }
+  .col-sm-push-3 {
+    left: 25%;
+  }
+  .col-sm-push-4 {
+    left: 33.33333333333333%;
+  }
+  .col-sm-push-5 {
+    left: 41.66666666666667%;
+  }
+  .col-sm-push-6 {
+    left: 50%;
+  }
+  .col-sm-push-7 {
+    left: 58.333333333333336%;
+  }
+  .col-sm-push-8 {
+    left: 66.66666666666666%;
+  }
+  .col-sm-push-9 {
+    left: 75%;
+  }
+  .col-sm-push-10 {
+    left: 83.33333333333334%;
+  }
+  .col-sm-push-11 {
+    left: 91.66666666666666%;
+  }
+  .col-sm-pull-1 {
+    right: 8.333333333333332%;
+  }
+  .col-sm-pull-2 {
+    right: 16.666666666666664%;
+  }
+  .col-sm-pull-3 {
+    right: 25%;
+  }
+  .col-sm-pull-4 {
+    right: 33.33333333333333%;
+  }
+  .col-sm-pull-5 {
+    right: 41.66666666666667%;
+  }
+  .col-sm-pull-6 {
+    right: 50%;
+  }
+  .col-sm-pull-7 {
+    right: 58.333333333333336%;
+  }
+  .col-sm-pull-8 {
+    right: 66.66666666666666%;
+  }
+  .col-sm-pull-9 {
+    right: 75%;
+  }
+  .col-sm-pull-10 {
+    right: 83.33333333333334%;
+  }
+  .col-sm-pull-11 {
+    right: 91.66666666666666%;
+  }
+  .col-sm-offset-1 {
+    margin-left: 8.333333333333332%;
+  }
+  .col-sm-offset-2 {
+    margin-left: 16.666666666666664%;
+  }
+  .col-sm-offset-3 {
+    margin-left: 25%;
+  }
+  .col-sm-offset-4 {
+    margin-left: 33.33333333333333%;
+  }
+  .col-sm-offset-5 {
+    margin-left: 41.66666666666667%;
+  }
+  .col-sm-offset-6 {
+    margin-left: 50%;
+  }
+  .col-sm-offset-7 {
+    margin-left: 58.333333333333336%;
+  }
+  .col-sm-offset-8 {
+    margin-left: 66.66666666666666%;
+  }
+  .col-sm-offset-9 {
+    margin-left: 75%;
+  }
+  .col-sm-offset-10 {
+    margin-left: 83.33333333333334%;
+  }
+  .col-sm-offset-11 {
+    margin-left: 91.66666666666666%;
+  }
+}
+@media (min-width: 992px) {
+  .container {
+    max-width: 970px;
+  }
+  .col-md-1,
+  .col-md-2,
+  .col-md-3,
+  .col-md-4,
+  .col-md-5,
+  .col-md-6,
+  .col-md-7,
+  .col-md-8,
+  .col-md-9,
+  .col-md-10,
+  .col-md-11 {
+    float: left;
+  }
+  .col-md-1 {
+    width: 8.333333333333332%;
+  }
+  .col-md-2 {
+    width: 16.666666666666664%;
+  }
+  .col-md-3 {
+    width: 25%;
+  }
+  .col-md-4 {
+    width: 33.33333333333333%;
+  }
+  .col-md-5 {
+    width: 41.66666666666667%;
+  }
+  .col-md-6 {
+    width: 50%;
+  }
+  .col-md-7 {
+    width: 58.333333333333336%;
+  }
+  .col-md-8 {
+    width: 66.66666666666666%;
+  }
+  .col-md-9 {
+    width: 75%;
+  }
+  .col-md-10 {
+    width: 83.33333333333334%;
+  }
+  .col-md-11 {
+    width: 91.66666666666666%;
+  }
+  .col-md-12 {
+    width: 100%;
+  }
+  .col-md-push-0 {
+    left: auto;
+  }
+  .col-md-push-1 {
+    left: 8.333333333333332%;
+  }
+  .col-md-push-2 {
+    left: 16.666666666666664%;
+  }
+  .col-md-push-3 {
+    left: 25%;
+  }
+  .col-md-push-4 {
+    left: 33.33333333333333%;
+  }
+  .col-md-push-5 {
+    left: 41.66666666666667%;
+  }
+  .col-md-push-6 {
+    left: 50%;
+  }
+  .col-md-push-7 {
+    left: 58.333333333333336%;
+  }
+  .col-md-push-8 {
+    left: 66.66666666666666%;
+  }
+  .col-md-push-9 {
+    left: 75%;
+  }
+  .col-md-push-10 {
+    left: 83.33333333333334%;
+  }
+  .col-md-push-11 {
+    left: 91.66666666666666%;
+  }
+  .col-md-pull-0 {
+    right: auto;
+  }
+  .col-md-pull-1 {
+    right: 8.333333333333332%;
+  }
+  .col-md-pull-2 {
+    right: 16.666666666666664%;
+  }
+  .col-md-pull-3 {
+    right: 25%;
+  }
+  .col-md-pull-4 {
+    right: 33.33333333333333%;
+  }
+  .col-md-pull-5 {
+    right: 41.66666666666667%;
+  }
+  .col-md-pull-6 {
+    right: 50%;
+  }
+  .col-md-pull-7 {
+    right: 58.333333333333336%;
+  }
+  .col-md-pull-8 {
+    right: 66.66666666666666%;
+  }
+  .col-md-pull-9 {
+    right: 75%;
+  }
+  .col-md-pull-10 {
+    right: 83.33333333333334%;
+  }
+  .col-md-pull-11 {
+    right: 91.66666666666666%;
+  }
+  .col-md-offset-0 {
+    margin-left: 0;
+  }
+  .col-md-offset-1 {
+    margin-left: 8.333333333333332%;
+  }
+  .col-md-offset-2 {
+    margin-left: 16.666666666666664%;
+  }
+  .col-md-offset-3 {
+    margin-left: 25%;
+  }
+  .col-md-offset-4 {
+    margin-left: 33.33333333333333%;
+  }
+  .col-md-offset-5 {
+    margin-left: 41.66666666666667%;
+  }
+  .col-md-offset-6 {
+    margin-left: 50%;
+  }
+  .col-md-offset-7 {
+    margin-left: 58.333333333333336%;
+  }
+  .col-md-offset-8 {
+    margin-left: 66.66666666666666%;
+  }
+  .col-md-offset-9 {
+    margin-left: 75%;
+  }
+  .col-md-offset-10 {
+    margin-left: 83.33333333333334%;
+  }
+  .col-md-offset-11 {
+    margin-left: 91.66666666666666%;
+  }
+}
+@media (min-width: 1200px) {
+  .container {
+    max-width: 1170px;
+  }
+  .col-lg-1,
+  .col-lg-2,
+  .col-lg-3,
+  .col-lg-4,
+  .col-lg-5,
+  .col-lg-6,
+  .col-lg-7,
+  .col-lg-8,
+  .col-lg-9,
+  .col-lg-10,
+  .col-lg-11 {
+    float: left;
+  }
+  .col-lg-1 {
+    width: 8.333333333333332%;
+  }
+  .col-lg-2 {
+    width: 16.666666666666664%;
+  }
+  .col-lg-3 {
+    width: 25%;
+  }
+  .col-lg-4 {
+    width: 33.33333333333333%;
+  }
+  .col-lg-5 {
+    width: 41.66666666666667%;
+  }
+  .col-lg-6 {
+    width: 50%;
+  }
+  .col-lg-7 {
+    width: 58.333333333333336%;
+  }
+  .col-lg-8 {
+    width: 66.66666666666666%;
+  }
+  .col-lg-9 {
+    width: 75%;
+  }
+  .col-lg-10 {
+    width: 83.33333333333334%;
+  }
+  .col-lg-11 {
+    width: 91.66666666666666%;
+  }
+  .col-lg-12 {
+    width: 100%;
+  }
+  .col-lg-push-0 {
+    left: auto;
+  }
+  .col-lg-push-1 {
+    left: 8.333333333333332%;
+  }
+  .col-lg-push-2 {
+    left: 16.666666666666664%;
+  }
+  .col-lg-push-3 {
+    left: 25%;
+  }
+  .col-lg-push-4 {
+    left: 33.33333333333333%;
+  }
+  .col-lg-push-5 {
+    left: 41.66666666666667%;
+  }
+  .col-lg-push-6 {
+    left: 50%;
+  }
+  .col-lg-push-7 {
+    left: 58.333333333333336%;
+  }
+  .col-lg-push-8 {
+    left: 66.66666666666666%;
+  }
+  .col-lg-push-9 {
+    left: 75%;
+  }
+  .col-lg-push-10 {
+    left: 83.33333333333334%;
+  }
+  .col-lg-push-11 {
+    left: 91.66666666666666%;
+  }
+  .col-lg-pull-0 {
+    right: auto;
+  }
+  .col-lg-pull-1 {
+    right: 8.333333333333332%;
+  }
+  .col-lg-pull-2 {
+    right: 16.666666666666664%;
+  }
+  .col-lg-pull-3 {
+    right: 25%;
+  }
+  .col-lg-pull-4 {
+    right: 33.33333333333333%;
+  }
+  .col-lg-pull-5 {
+    right: 41.66666666666667%;
+  }
+  .col-lg-pull-6 {
+    right: 50%;
+  }
+  .col-lg-pull-7 {
+    right: 58.333333333333336%;
+  }
+  .col-lg-pull-8 {
+    right: 66.66666666666666%;
+  }
+  .col-lg-pull-9 {
+    right: 75%;
+  }
+  .col-lg-pull-10 {
+    right: 83.33333333333334%;
+  }
+  .col-lg-pull-11 {
+    right: 91.66666666666666%;
+  }
+  .col-lg-offset-0 {
+    margin-left: 0;
+  }
+  .col-lg-offset-1 {
+    margin-left: 8.333333333333332%;
+  }
+  .col-lg-offset-2 {
+    margin-left: 16.666666666666664%;
+  }
+  .col-lg-offset-3 {
+    margin-left: 25%;
+  }
+  .col-lg-offset-4 {
+    margin-left: 33.33333333333333%;
+  }
+  .col-lg-offset-5 {
+    margin-left: 41.66666666666667%;
+  }
+  .col-lg-offset-6 {
+    margin-left: 50%;
+  }
+  .col-lg-offset-7 {
+    margin-left: 58.333333333333336%;
+  }
+  .col-lg-offset-8 {
+    margin-left: 66.66666666666666%;
+  }
+  .col-lg-offset-9 {
+    margin-left: 75%;
+  }
+  .col-lg-offset-10 {
+    margin-left: 83.33333333333334%;
+  }
+  .col-lg-offset-11 {
+    margin-left: 91.66666666666666%;
+  }
+}
+table {
+  max-width: 100%;
+  background-color: transparent;
+}
+th {
+  text-align: left;
+}
+.table {
+  width: 100%;
+  margin-bottom: 20px;
+}
+.table thead > tr > th,
+.table tbody > tr > th,
+.table tfoot > tr > th,
+.table thead > tr > td,
+.table tbody > tr > td,
+.table tfoot > tr > td {
+  padding: 8px;
+  line-height: 1.428571429;
+  vertical-align: top;
+  border-top: 1px solid #dddddd;
+}
+.table thead > tr > th {
+  vertical-align: bottom;
+  border-bottom: 2px solid #dddddd;
+}
+.table caption + thead tr:first-child th,
+.table colgroup + thead tr:first-child th,
+.table thead:first-child tr:first-child th,
+.table caption + thead tr:first-child td,
+.table colgroup + thead tr:first-child td,
+.table thead:first-child tr:first-child td {
+  border-top: 0;
+}
+.table tbody + tbody {
+  border-top: 2px solid #dddddd;
+}
+.table .table {
+  background-color: #ffffff;
+}
+.table-condensed thead > tr > th,
+.table-condensed tbody > tr > th,
+.table-condensed tfoot > tr > th,
+.table-condensed thead > tr > td,
+.table-condensed tbody > tr > td,
+.table-condensed tfoot > tr > td {
+  padding: 5px;
+}
+.table-bordered {
+  border: 1px solid #dddddd;
+}
+.table-bordered > thead > tr > th,
+.table-bordered > tbody > tr > th,
+.table-bordered > tfoot > tr > th,
+.table-bordered > thead > tr > td,
+.table-bordered > tbody > tr > td,
+.table-bordered > tfoot > tr > td {
+  border: 1px solid #dddddd;
+}
+.table-bordered > thead > tr > th,
+.table-bordered > thead > tr > td {
+  border-bottom-width: 2px;
+}
+.table-striped > tbody > tr:nth-child(odd) > td,
+.table-striped > tbody > tr:nth-child(odd) > th {
+  background-color: #f9f9f9;
+}
+.table-hover > tbody > tr:hover > td,
+.table-hover > tbody > tr:hover > th {
+  background-color: #f5f5f5;
+}
+table col[class*="col-"] {
+  float: none;
+  display: table-column;
+}
+table td[class*="col-"],
+table th[class*="col-"] {
+  float: none;
+  display: table-cell;
+}
+.table > thead > tr > td.active,
+.table > tbody > tr > td.active,
+.table > tfoot > tr > td.active,
+.table > thead > tr > th.active,
+.table > tbody > tr > th.active,
+.table > tfoot > tr > th.active,
+.table > thead > tr.active > td,
+.table > tbody > tr.active > td,
+.table > tfoot > tr.active > td,
+.table > thead > tr.active > th,
+.table > tbody > tr.active > th,
+.table > tfoot > tr.active > th {
+  background-color: #f5f5f5;
+}
+.table > thead > tr > td.success,
+.table > tbody > tr > td.success,
+.table > tfoot > tr > td.success,
+.table > thead > tr > th.success,
+.table > tbody > tr > th.success,
+.table > tfoot > tr > th.success,
+.table > thead > tr.success > td,
+.table > tbody > tr.success > td,
+.table > tfoot > tr.success > td,
+.table > thead > tr.success > th,
+.table > tbody > tr.success > th,
+.table > tfoot > tr.success > th {
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+}
+.table-hover > tbody > tr > td.success:hover,
+.table-hover > tbody > tr > th.success:hover,
+.table-hover > tbody > tr.success:hover > td {
+  background-color: #d0e9c6;
+  border-color: #c9e2b3;
+}
+.table > thead > tr > td.danger,
+.table > tbody > tr > td.danger,
+.table > tfoot > tr > td.danger,
+.table > thead > tr > th.danger,
+.table > tbody > tr > th.danger,
+.table > tfoot > tr > th.danger,
+.table > thead > tr.danger > td,
+.table > tbody > tr.danger > td,
+.table > tfoot > tr.danger > td,
+.table > thead > tr.danger > th,
+.table > tbody > tr.danger > th,
+.table > tfoot > tr.danger > th {
+  background-color: #f2dede;
+  border-color: #eed3d7;
+}
+.table-hover > tbody > tr > td.danger:hover,
+.table-hover > tbody > tr > th.danger:hover,
+.table-hover > tbody > tr.danger:hover > td {
+  background-color: #ebcccc;
+  border-color: #e6c1c7;
+}
+.table > thead > tr > td.warning,
+.table > tbody > tr > td.warning,
+.table > tfoot > tr > td.warning,
+.table > thead > tr > th.warning,
+.table > tbody > tr > th.warning,
+.table > tfoot > tr > th.warning,
+.table > thead > tr.warning > td,
+.table > tbody > tr.warning > td,
+.table > tfoot > tr.warning > td,
+.table > thead > tr.warning > th,
+.table > tbody > tr.warning > th,
+.table > tfoot > tr.warning > th {
+  background-color: #fcf8e3;
+  border-color: #fbeed5;
+}
+.table-hover > tbody > tr > td.warning:hover,
+.table-hover > tbody > tr > th.warning:hover,
+.table-hover > tbody > tr.warning:hover > td {
+  background-color: #faf2cc;
+  border-color: #f8e5be;
+}
+@media (max-width: 768px) {
+  .table-responsive {
+    width: 100%;
+    margin-bottom: 15px;
+    overflow-y: hidden;
+    overflow-x: scroll;
+    border: 1px solid #dddddd;
+  }
+  .table-responsive > .table {
+    margin-bottom: 0;
+    background-color: #fff;
+  }
+  .table-responsive > .table > thead > tr > th,
+  .table-responsive > .table > tbody > tr > th,
+  .table-responsive > .table > tfoot > tr > th,
+  .table-responsive > .table > thead > tr > td,
+  .table-responsive > .table > tbody > tr > td,
+  .table-responsive > .table > tfoot > tr > td {
+    white-space: nowrap;
+  }
+  .table-responsive > .table-bordered {
+    border: 0;
+  }
+  .table-responsive > .table-bordered > thead > tr > th:first-child,
+  .table-responsive > .table-bordered > tbody > tr > th:first-child,
+  .table-responsive > .table-bordered > tfoot > tr > th:first-child,
+  .table-responsive > .table-bordered > thead > tr > td:first-child,
+  .table-responsive > .table-bordered > tbody > tr > td:first-child,
+  .table-responsive > .table-bordered > tfoot > tr > td:first-child {
+    border-left: 0;
+  }
+  .table-responsive > .table-bordered > thead > tr > th:last-child,
+  .table-responsive > .table-bordered > tbody > tr > th:last-child,
+  .table-responsive > .table-bordered > tfoot > tr > th:last-child,
+  .table-responsive > .table-bordered > thead > tr > td:last-child,
+  .table-responsive > .table-bordered > tbody > tr > td:last-child,
+  .table-responsive > .table-bordered > tfoot > tr > td:last-child {
+    border-right: 0;
+  }
+  .table-responsive > .table-bordered > thead > tr:last-child > th,
+  .table-responsive > .table-bordered > tbody > tr:last-child > th,
+  .table-responsive > .table-bordered > tfoot > tr:last-child > th,
+  .table-responsive > .table-bordered > thead > tr:last-child > td,
+  .table-responsive > .table-bordered > tbody > tr:last-child > td,
+  .table-responsive > .table-bordered > tfoot > tr:last-child > td {
+    border-bottom: 0;
+  }
+}
+fieldset {
+  padding: 0;
+  margin: 0;
+  border: 0;
+}
+legend {
+  display: block;
+  width: 100%;
+  padding: 0;
+  margin-bottom: 20px;
+  font-size: 21px;
+  line-height: inherit;
+  color: #333333;
+  border: 0;
+  border-bottom: 1px solid #e5e5e5;
+}
+label {
+  display: inline-block;
+  margin-bottom: 5px;
+  font-weight: bold;
+}
+input[type="search"] {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+input[type="radio"],
+input[type="checkbox"] {
+  margin: 4px 0 0;
+  margin-top: 1px \9;
+  /* IE8-9 */
+
+  line-height: normal;
+}
+input[type="file"] {
+  display: block;
+}
+select[multiple],
+select[size] {
+  height: auto;
+}
+select optgroup {
+  font-size: inherit;
+  font-style: inherit;
+  font-family: inherit;
+}
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+input[type="number"]::-webkit-outer-spin-button,
+input[type="number"]::-webkit-inner-spin-button {
+  height: auto;
+}
+.form-control:-moz-placeholder {
+  color: #999999;
+}
+.form-control::-moz-placeholder {
+  color: #999999;
+}
+.form-control:-ms-input-placeholder {
+  color: #999999;
+}
+.form-control::-webkit-input-placeholder {
+  color: #999999;
+}
+.form-control {
+  display: block;
+  width: 100%;
+  height: 34px;
+  padding: 6px 12px;
+  font-size: 14px;
+  line-height: 1.428571429;
+  color: #555555;
+  vertical-align: middle;
+  background-color: #ffffff;
+  border: 1px solid #cccccc;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+  transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+}
+.form-control:focus {
+  border-color: #66afe9;
+  outline: 0;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
+  box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
+}
+.form-control[disabled],
+.form-control[readonly],
+fieldset[disabled] .form-control {
+  cursor: not-allowed;
+  background-color: #eeeeee;
+}
+textarea.form-control {
+  height: auto;
+}
+.form-group {
+  margin-bottom: 15px;
+}
+.radio,
+.checkbox {
+  display: block;
+  min-height: 20px;
+  margin-top: 10px;
+  margin-bottom: 10px;
+  padding-left: 20px;
+  vertical-align: middle;
+}
+.radio label,
+.checkbox label {
+  display: inline;
+  margin-bottom: 0;
+  font-weight: normal;
+  cursor: pointer;
+}
+.radio input[type="radio"],
+.radio-inline input[type="radio"],
+.checkbox input[type="checkbox"],
+.checkbox-inline input[type="checkbox"] {
+  float: left;
+  margin-left: -20px;
+}
+.radio + .radio,
+.checkbox + .checkbox {
+  margin-top: -5px;
+}
+.radio-inline,
+.checkbox-inline {
+  display: inline-block;
+  padding-left: 20px;
+  margin-bottom: 0;
+  vertical-align: middle;
+  font-weight: normal;
+  cursor: pointer;
+}
+.radio-inline + .radio-inline,
+.checkbox-inline + .checkbox-inline {
+  margin-top: 0;
+  margin-left: 10px;
+}
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+.radio[disabled],
+.radio-inline[disabled],
+.checkbox[disabled],
+.checkbox-inline[disabled],
+fieldset[disabled] input[type="radio"],
+fieldset[disabled] input[type="checkbox"],
+fieldset[disabled] .radio,
+fieldset[disabled] .radio-inline,
+fieldset[disabled] .checkbox,
+fieldset[disabled] .checkbox-inline {
+  cursor: not-allowed;
+}
+.input-sm {
+  height: 30px;
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+select.input-sm {
+  height: 30px;
+  line-height: 30px;
+}
+textarea.input-sm {
+  height: auto;
+}
+.input-lg {
+  height: 45px;
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+  border-radius: 6px;
+}
+select.input-lg {
+  height: 45px;
+  line-height: 45px;
+}
+textarea.input-lg {
+  height: auto;
+}
+.has-warning .help-block,
+.has-warning .control-label {
+  color: #c09853;
+}
+.has-warning .form-control {
+  border-color: #c09853;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+.has-warning .form-control:focus {
+  border-color: #a47e3c;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+}
+.has-warning .input-group-addon {
+  color: #c09853;
+  border-color: #c09853;
+  background-color: #fcf8e3;
+}
+.has-error .help-block,
+.has-error .control-label {
+  color: #b94a48;
+}
+.has-error .form-control {
+  border-color: #b94a48;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+.has-error .form-control:focus {
+  border-color: #953b39;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+}
+.has-error .input-group-addon {
+  color: #b94a48;
+  border-color: #b94a48;
+  background-color: #f2dede;
+}
+.has-success .help-block,
+.has-success .control-label {
+  color: #468847;
+}
+.has-success .form-control {
+  border-color: #468847;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+.has-success .form-control:focus {
+  border-color: #356635;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+}
+.has-success .input-group-addon {
+  color: #468847;
+  border-color: #468847;
+  background-color: #dff0d8;
+}
+.form-control-static {
+  margin-bottom: 0;
+  padding-top: 7px;
+}
+.help-block {
+  display: block;
+  margin-top: 5px;
+  margin-bottom: 10px;
+  color: #737373;
+}
+@media (min-width: 768px) {
+  .form-inline .form-group {
+    display: inline-block;
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .form-inline .form-control {
+    display: inline-block;
+  }
+  .form-inline .radio,
+  .form-inline .checkbox {
+    display: inline-block;
+    margin-top: 0;
+    margin-bottom: 0;
+    padding-left: 0;
+  }
+  .form-inline .radio input[type="radio"],
+  .form-inline .checkbox input[type="checkbox"] {
+    float: none;
+    margin-left: 0;
+  }
+}
+.form-horizontal .control-label,
+.form-horizontal .radio,
+.form-horizontal .checkbox,
+.form-horizontal .radio-inline,
+.form-horizontal .checkbox-inline {
+  margin-top: 0;
+  margin-bottom: 0;
+  padding-top: 7px;
+}
+.form-horizontal .form-group {
+  margin-left: -15px;
+  margin-right: -15px;
+}
+.form-horizontal .form-group:before,
+.form-horizontal .form-group:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.form-horizontal .form-group:after {
+  clear: both;
+}
+.form-horizontal .form-group:before,
+.form-horizontal .form-group:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.form-horizontal .form-group:after {
+  clear: both;
+}
+@media (min-width: 768px) {
+  .form-horizontal .control-label {
+    text-align: right;
+  }
+}
+.btn {
+  display: inline-block;
+  padding: 6px 12px;
+  margin-bottom: 0;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 1.428571429;
+  text-align: center;
+  vertical-align: middle;
+  cursor: pointer;
+  border: 1px solid transparent;
+  border-radius: 4px;
+  white-space: nowrap;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  -o-user-select: none;
+  user-select: none;
+}
+.btn:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+.btn:hover,
+.btn:focus {
+  color: #333333;
+  text-decoration: none;
+}
+.btn:active,
+.btn.active {
+  outline: 0;
+  background-image: none;
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+  box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
+.btn.disabled,
+.btn[disabled],
+fieldset[disabled] .btn {
+  cursor: not-allowed;
+  pointer-events: none;
+  opacity: 0.65;
+  filter: alpha(opacity=65);
+  -webkit-box-shadow: none;
+  box-shadow: none;
+}
+.btn-default {
+  color: #333333;
+  background-color: #ffffff;
+  border-color: #cccccc;
+}
+.btn-default:hover,
+.btn-default:focus,
+.btn-default:active,
+.btn-default.active,
+.open .dropdown-toggle.btn-default {
+  color: #333333;
+  background-color: #ebebeb;
+  border-color: #adadad;
+}
+.btn-default:active,
+.btn-default.active,
+.open .dropdown-toggle.btn-default {
+  background-image: none;
+}
+.btn-default.disabled,
+.btn-default[disabled],
+fieldset[disabled] .btn-default,
+.btn-default.disabled:hover,
+.btn-default[disabled]:hover,
+fieldset[disabled] .btn-default:hover,
+.btn-default.disabled:focus,
+.btn-default[disabled]:focus,
+fieldset[disabled] .btn-default:focus,
+.btn-default.disabled:active,
+.btn-default[disabled]:active,
+fieldset[disabled] .btn-default:active,
+.btn-default.disabled.active,
+.btn-default[disabled].active,
+fieldset[disabled] .btn-default.active {
+  background-color: #ffffff;
+  border-color: #cccccc;
+}
+.btn-primary {
+  color: #ffffff;
+  background-color: #428bca;
+  border-color: #357ebd;
+}
+.btn-primary:hover,
+.btn-primary:focus,
+.btn-primary:active,
+.btn-primary.active,
+.open .dropdown-toggle.btn-primary {
+  color: #ffffff;
+  background-color: #3276b1;
+  border-color: #285e8e;
+}
+.btn-primary:active,
+.btn-primary.active,
+.open .dropdown-toggle.btn-primary {
+  background-image: none;
+}
+.btn-primary.disabled,
+.btn-primary[disabled],
+fieldset[disabled] .btn-primary,
+.btn-primary.disabled:hover,
+.btn-primary[disabled]:hover,
+fieldset[disabled] .btn-primary:hover,
+.btn-primary.disabled:focus,
+.btn-primary[disabled]:focus,
+fieldset[disabled] .btn-primary:focus,
+.btn-primary.disabled:active,
+.btn-primary[disabled]:active,
+fieldset[disabled] .btn-primary:active,
+.btn-primary.disabled.active,
+.btn-primary[disabled].active,
+fieldset[disabled] .btn-primary.active {
+  background-color: #428bca;
+  border-color: #357ebd;
+}
+.btn-warning {
+  color: #ffffff;
+  background-color: #f0ad4e;
+  border-color: #eea236;
+}
+.btn-warning:hover,
+.btn-warning:focus,
+.btn-warning:active,
+.btn-warning.active,
+.open .dropdown-toggle.btn-warning {
+  color: #ffffff;
+  background-color: #ed9c28;
+  border-color: #d58512;
+}
+.btn-warning:active,
+.btn-warning.active,
+.open .dropdown-toggle.btn-warning {
+  background-image: none;
+}
+.btn-warning.disabled,
+.btn-warning[disabled],
+fieldset[disabled] .btn-warning,
+.btn-warning.disabled:hover,
+.btn-warning[disabled]:hover,
+fieldset[disabled] .btn-warning:hover,
+.btn-warning.disabled:focus,
+.btn-warning[disabled]:focus,
+fieldset[disabled] .btn-warning:focus,
+.btn-warning.disabled:active,
+.btn-warning[disabled]:active,
+fieldset[disabled] .btn-warning:active,
+.btn-warning.disabled.active,
+.btn-warning[disabled].active,
+fieldset[disabled] .btn-warning.active {
+  background-color: #f0ad4e;
+  border-color: #eea236;
+}
+.btn-danger {
+  color: #ffffff;
+  background-color: #d9534f;
+  border-color: #d43f3a;
+}
+.btn-danger:hover,
+.btn-danger:focus,
+.btn-danger:active,
+.btn-danger.active,
+.open .dropdown-toggle.btn-danger {
+  color: #ffffff;
+  background-color: #d2322d;
+  border-color: #ac2925;
+}
+.btn-danger:active,
+.btn-danger.active,
+.open .dropdown-toggle.btn-danger {
+  background-image: none;
+}
+.btn-danger.disabled,
+.btn-danger[disabled],
+fieldset[disabled] .btn-danger,
+.btn-danger.disabled:hover,
+.btn-danger[disabled]:hover,
+fieldset[disabled] .btn-danger:hover,
+.btn-danger.disabled:focus,
+.btn-danger[disabled]:focus,
+fieldset[disabled] .btn-danger:focus,
+.btn-danger.disabled:active,
+.btn-danger[disabled]:active,
+fieldset[disabled] .btn-danger:active,
+.btn-danger.disabled.active,
+.btn-danger[disabled].active,
+fieldset[disabled] .btn-danger.active {
+  background-color: #d9534f;
+  border-color: #d43f3a;
+}
+.btn-success {
+  color: #ffffff;
+  background-color: #5cb85c;
+  border-color: #4cae4c;
+}
+.btn-success:hover,
+.btn-success:focus,
+.btn-success:active,
+.btn-success.active,
+.open .dropdown-toggle.btn-success {
+  color: #ffffff;
+  background-color: #47a447;
+  border-color: #398439;
+}
+.btn-success:active,
+.btn-success.active,
+.open .dropdown-toggle.btn-success {
+  background-image: none;
+}
+.btn-success.disabled,
+.btn-success[disabled],
+fieldset[disabled] .btn-success,
+.btn-success.disabled:hover,
+.btn-success[disabled]:hover,
+fieldset[disabled] .btn-success:hover,
+.btn-success.disabled:focus,
+.btn-success[disabled]:focus,
+fieldset[disabled] .btn-success:focus,
+.btn-success.disabled:active,
+.btn-success[disabled]:active,
+fieldset[disabled] .btn-success:active,
+.btn-success.disabled.active,
+.btn-success[disabled].active,
+fieldset[disabled] .btn-success.active {
+  background-color: #5cb85c;
+  border-color: #4cae4c;
+}
+.btn-info {
+  color: #ffffff;
+  background-color: #5bc0de;
+  border-color: #46b8da;
+}
+.btn-info:hover,
+.btn-info:focus,
+.btn-info:active,
+.btn-info.active,
+.open .dropdown-toggle.btn-info {
+  color: #ffffff;
+  background-color: #39b3d7;
+  border-color: #269abc;
+}
+.btn-info:active,
+.btn-info.active,
+.open .dropdown-toggle.btn-info {
+  background-image: none;
+}
+.btn-info.disabled,
+.btn-info[disabled],
+fieldset[disabled] .btn-info,
+.btn-info.disabled:hover,
+.btn-info[disabled]:hover,
+fieldset[disabled] .btn-info:hover,
+.btn-info.disabled:focus,
+.btn-info[disabled]:focus,
+fieldset[disabled] .btn-info:focus,
+.btn-info.disabled:active,
+.btn-info[disabled]:active,
+fieldset[disabled] .btn-info:active,
+.btn-info.disabled.active,
+.btn-info[disabled].active,
+fieldset[disabled] .btn-info.active {
+  background-color: #5bc0de;
+  border-color: #46b8da;
+}
+.btn-link {
+  color: #428bca;
+  font-weight: normal;
+  cursor: pointer;
+  border-radius: 0;
+}
+.btn-link,
+.btn-link:active,
+.btn-link[disabled],
+fieldset[disabled] .btn-link {
+  background-color: transparent;
+  -webkit-box-shadow: none;
+  box-shadow: none;
+}
+.btn-link,
+.btn-link:hover,
+.btn-link:focus,
+.btn-link:active {
+  border-color: transparent;
+}
+.btn-link:hover,
+.btn-link:focus {
+  color: #2a6496;
+  text-decoration: underline;
+  background-color: transparent;
+}
+.btn-link[disabled]:hover,
+fieldset[disabled] .btn-link:hover,
+.btn-link[disabled]:focus,
+fieldset[disabled] .btn-link:focus {
+  color: #999999;
+  text-decoration: none;
+}
+.btn-lg {
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+  border-radius: 6px;
+}
+.btn-sm,
+.btn-xs {
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+.btn-xs {
+  padding: 1px 5px;
+}
+.btn-block {
+  display: block;
+  width: 100%;
+  padding-left: 0;
+  padding-right: 0;
+}
+.btn-block + .btn-block {
+  margin-top: 5px;
+}
+input[type="submit"].btn-block,
+input[type="reset"].btn-block,
+input[type="button"].btn-block {
+  width: 100%;
+}
+.input-group {
+  position: relative;
+  display: table;
+  border-collapse: separate;
+}
+.input-group.col {
+  float: none;
+  padding-left: 0;
+  padding-right: 0;
+}
+.input-group .form-control {
+  width: 100%;
+  margin-bottom: 0;
+}
+.input-group-lg > .form-control,
+.input-group-lg > .input-group-addon,
+.input-group-lg > .input-group-btn > .btn {
+  height: 45px;
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+  border-radius: 6px;
+}
+select.input-group-lg > .form-control,
+select.input-group-lg > .input-group-addon,
+select.input-group-lg > .input-group-btn > .btn {
+  height: 45px;
+  line-height: 45px;
+}
+textarea.input-group-lg > .form-control,
+textarea.input-group-lg > .input-group-addon,
+textarea.input-group-lg > .input-group-btn > .btn {
+  height: auto;
+}
+.input-group-sm > .form-control,
+.input-group-sm > .input-group-addon,
+.input-group-sm > .input-group-btn > .btn {
+  height: 30px;
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+select.input-group-sm > .form-control,
+select.input-group-sm > .input-group-addon,
+select.input-group-sm > .input-group-btn > .btn {
+  height: 30px;
+  line-height: 30px;
+}
+textarea.input-group-sm > .form-control,
+textarea.input-group-sm > .input-group-addon,
+textarea.input-group-sm > .input-group-btn > .btn {
+  height: auto;
+}
+.input-group-addon,
+.input-group-btn,
+.input-group .form-control {
+  display: table-cell;
+}
+.input-group-addon:not(:first-child):not(:last-child),
+.input-group-btn:not(:first-child):not(:last-child),
+.input-group .form-control:not(:first-child):not(:last-child) {
+  border-radius: 0;
+}
+.input-group-addon,
+.input-group-btn {
+  width: 1%;
+  white-space: nowrap;
+  vertical-align: middle;
+}
+.input-group-addon {
+  padding: 6px 12px;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 1;
+  text-align: center;
+  background-color: #eeeeee;
+  border: 1px solid #cccccc;
+  border-radius: 4px;
+}
+.input-group-addon.input-sm {
+  padding: 5px 10px;
+  font-size: 12px;
+  border-radius: 3px;
+}
+.input-group-addon.input-lg {
+  padding: 10px 16px;
+  font-size: 18px;
+  border-radius: 6px;
+}
+.input-group-addon input[type="radio"],
+.input-group-addon input[type="checkbox"] {
+  margin-top: 0;
+}
+.input-group .form-control:first-child,
+.input-group-addon:first-child,
+.input-group-btn:first-child > .btn,
+.input-group-btn:first-child > .dropdown-toggle,
+.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) {
+  border-bottom-right-radius: 0;
+  border-top-right-radius: 0;
+}
+.input-group-addon:first-child {
+  border-right: 0;
+}
+.input-group .form-control:last-child,
+.input-group-addon:last-child,
+.input-group-btn:last-child > .btn,
+.input-group-btn:last-child > .dropdown-toggle,
+.input-group-btn:first-child > .btn:not(:first-child) {
+  border-bottom-left-radius: 0;
+  border-top-left-radius: 0;
+}
+.input-group-addon:last-child {
+  border-left: 0;
+}
+.input-group-btn {
+  position: relative;
+  white-space: nowrap;
+}
+.input-group-btn > .btn {
+  position: relative;
+}
+.input-group-btn > .btn + .btn {
+  margin-left: -4px;
+}
+.input-group-btn > .btn:hover,
+.input-group-btn > .btn:active {
+  z-index: 2;
+}
+.nav {
+  margin-bottom: 0;
+  padding-left: 0;
+  list-style: none;
+}
+.nav:before,
+.nav:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.nav:after {
+  clear: both;
+}
+.nav:before,
+.nav:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.nav:after {
+  clear: both;
+}
+.nav > li {
+  position: relative;
+  display: block;
+}
+.nav > li > a {
+  position: relative;
+  display: block;
+  padding: 10px 15px;
+}
+.nav > li > a:hover,
+.nav > li > a:focus {
+  text-decoration: none;
+  background-color: #eeeeee;
+}
+.nav > li.disabled > a {
+  color: #999999;
+}
+.nav > li.disabled > a:hover,
+.nav > li.disabled > a:focus {
+  color: #999999;
+  text-decoration: none;
+  background-color: transparent;
+  cursor: not-allowed;
+}
+.nav .open > a,
+.nav .open > a:hover,
+.nav .open > a:focus {
+  background-color: #eeeeee;
+  border-color: #428bca;
+}
+.nav .nav-divider {
+  height: 1px;
+  margin: 9px 0;
+  overflow: hidden;
+  background-color: #e5e5e5;
+}
+.nav > li > a > img {
+  max-width: none;
+}
+.nav-tabs {
+  border-bottom: 1px solid #dddddd;
+}
+.nav-tabs > li {
+  float: left;
+  margin-bottom: -1px;
+}
+.nav-tabs > li > a {
+  margin-right: 2px;
+  line-height: 1.428571429;
+  border: 1px solid transparent;
+  border-radius: 4px 4px 0 0;
+}
+.nav-tabs > li > a:hover {
+  border-color: #eeeeee #eeeeee #dddddd;
+}
+.nav-tabs > li.active > a,
+.nav-tabs > li.active > a:hover,
+.nav-tabs > li.active > a:focus {
+  color: #555555;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-bottom-color: transparent;
+  cursor: default;
+}
+.nav-tabs.nav-justified {
+  width: 100%;
+  border-bottom: 0;
+}
+.nav-tabs.nav-justified > li {
+  float: none;
+}
+.nav-tabs.nav-justified > li > a {
+  text-align: center;
+}
+@media (min-width: 768px) {
+  .nav-tabs.nav-justified > li {
+    display: table-cell;
+    width: 1%;
+  }
+}
+.nav-tabs.nav-justified > li > a {
+  border-bottom: 1px solid #dddddd;
+  margin-right: 0;
+}
+.nav-tabs.nav-justified > .active > a {
+  border-bottom-color: #ffffff;
+}
+.nav-pills > li {
+  float: left;
+}
+.nav-pills > li > a {
+  border-radius: 5px;
+}
+.nav-pills > li + li {
+  margin-left: 2px;
+}
+.nav-pills > li.active > a,
+.nav-pills > li.active > a:hover,
+.nav-pills > li.active > a:focus {
+  color: #ffffff;
+  background-color: #428bca;
+}
+.nav-stacked > li {
+  float: none;
+}
+.nav-stacked > li + li {
+  margin-top: 2px;
+  margin-left: 0;
+}
+.nav-justified {
+  width: 100%;
+}
+.nav-justified > li {
+  float: none;
+}
+.nav-justified > li > a {
+  text-align: center;
+}
+@media (min-width: 768px) {
+  .nav-justified > li {
+    display: table-cell;
+    width: 1%;
+  }
+}
+.nav-tabs-justified {
+  border-bottom: 0;
+}
+.nav-tabs-justified > li > a {
+  border-bottom: 1px solid #dddddd;
+  margin-right: 0;
+}
+.nav-tabs-justified > .active > a {
+  border-bottom-color: #ffffff;
+}
+.tabbable:before,
+.tabbable:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.tabbable:after {
+  clear: both;
+}
+.tabbable:before,
+.tabbable:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.tabbable:after {
+  clear: both;
+}
+.tab-content > .tab-pane,
+.pill-content > .pill-pane {
+  display: none;
+}
+.tab-content > .active,
+.pill-content > .active {
+  display: block;
+}
+.nav .caret {
+  border-top-color: #428bca;
+  border-bottom-color: #428bca;
+}
+.nav a:hover .caret {
+  border-top-color: #2a6496;
+  border-bottom-color: #2a6496;
+}
+.nav-tabs .dropdown-menu {
+  margin-top: -1px;
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+.navbar {
+  position: relative;
+  z-index: 1000;
+  min-height: 50px;
+  margin-bottom: 20px;
+  border: 1px solid transparent;
+}
+.navbar:before,
+.navbar:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.navbar:after {
+  clear: both;
+}
+.navbar:before,
+.navbar:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.navbar:after {
+  clear: both;
+}
+@media (min-width: 768px) {
+  .navbar {
+    border-radius: 4px;
+  }
+}
+.navbar-header:before,
+.navbar-header:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.navbar-header:after {
+  clear: both;
+}
+.navbar-header:before,
+.navbar-header:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.navbar-header:after {
+  clear: both;
+}
+@media (min-width: 768px) {
+  .navbar-header {
+    float: left;
+  }
+}
+.navbar-collapse {
+  max-height: 340px;
+  overflow-x: visible;
+  padding-right: 15px;
+  padding-left: 15px;
+  border-top: 1px solid transparent;
+  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
+  -webkit-overflow-scrolling: touch;
+}
+.navbar-collapse:before,
+.navbar-collapse:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.navbar-collapse:after {
+  clear: both;
+}
+.navbar-collapse:before,
+.navbar-collapse:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.navbar-collapse:after {
+  clear: both;
+}
+.navbar-collapse.in {
+  overflow-y: auto;
+}
+@media (min-width: 768px) {
+  .navbar-collapse {
+    width: auto;
+    border-top: 0;
+    box-shadow: none;
+  }
+  .navbar-collapse.collapse {
+    display: block !important;
+    height: auto !important;
+    padding-bottom: 0;
+    overflow: visible !important;
+  }
+  .navbar-collapse.in {
+    overflow-y: visible;
+  }
+  .navbar-collapse .navbar-nav.navbar-left:first-child {
+    margin-left: -15px;
+  }
+  .navbar-collapse .navbar-nav.navbar-right:last-child {
+    margin-right: -15px;
+  }
+  .navbar-collapse .navbar-text:last-child {
+    margin-right: 0;
+  }
+}
+.container > .navbar-header,
+.container > .navbar-collapse {
+  margin-right: -15px;
+  margin-left: -15px;
+}
+@media (min-width: 768px) {
+  .container > .navbar-header,
+  .container > .navbar-collapse {
+    margin-right: 0;
+    margin-left: 0;
+  }
+}
+.navbar-static-top {
+  border-width: 0 0 1px;
+}
+@media (min-width: 768px) {
+  .navbar-static-top {
+    border-radius: 0;
+  }
+}
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+  position: fixed;
+  right: 0;
+  left: 0;
+  border-width: 0 0 1px;
+}
+@media (min-width: 768px) {
+  .navbar-fixed-top,
+  .navbar-fixed-bottom {
+    border-radius: 0;
+  }
+}
+.navbar-fixed-top {
+  z-index: 1030;
+  top: 0;
+}
+.navbar-fixed-bottom {
+  bottom: 0;
+  margin-bottom: 0;
+}
+.navbar-brand {
+  float: left;
+  padding: 15px 15px;
+  font-size: 18px;
+  line-height: 20px;
+}
+.navbar-brand:hover,
+.navbar-brand:focus {
+  text-decoration: none;
+}
+@media (min-width: 768px) {
+  .navbar > .container .navbar-brand {
+    margin-left: -15px;
+  }
+}
+.navbar-toggle {
+  position: relative;
+  float: right;
+  margin-right: 15px;
+  padding: 9px 10px;
+  margin-top: 8px;
+  margin-bottom: 8px;
+  background-color: transparent;
+  border: 1px solid transparent;
+  border-radius: 4px;
+}
+.navbar-toggle .icon-bar {
+  display: block;
+  width: 22px;
+  height: 2px;
+  border-radius: 1px;
+}
+.navbar-toggle .icon-bar + .icon-bar {
+  margin-top: 4px;
+}
+@media (min-width: 768px) {
+  .navbar-toggle {
+    display: none;
+  }
+}
+.navbar-nav {
+  margin: 7.5px -15px;
+}
+.navbar-nav > li > a {
+  padding-top: 10px;
+  padding-bottom: 10px;
+  line-height: 20px;
+}
+@media (max-width: 767px) {
+  .navbar-nav .open .dropdown-menu {
+    position: static;
+    float: none;
+    width: auto;
+    margin-top: 0;
+    background-color: transparent;
+    border: 0;
+    box-shadow: none;
+  }
+  .navbar-nav .open .dropdown-menu > li > a,
+  .navbar-nav .open .dropdown-menu .dropdown-header {
+    padding: 5px 15px 5px 25px;
+  }
+  .navbar-nav .open .dropdown-menu > li > a {
+    line-height: 20px;
+  }
+  .navbar-nav .open .dropdown-menu > li > a:hover,
+  .navbar-nav .open .dropdown-menu > li > a:focus {
+    background-image: none;
+  }
+}
+@media (min-width: 768px) {
+  .navbar-nav {
+    float: left;
+    margin: 0;
+  }
+  .navbar-nav > li {
+    float: left;
+  }
+  .navbar-nav > li > a {
+    padding-top: 15px;
+    padding-bottom: 15px;
+  }
+}
+@media (min-width: 768px) {
+  .navbar-left {
+    float: left !important;
+  }
+  .navbar-right {
+    float: right !important;
+  }
+}
+.navbar-form {
+  margin-left: -15px;
+  margin-right: -15px;
+  padding: 10px 15px;
+  border-top: 1px solid transparent;
+  border-bottom: 1px solid transparent;
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+  margin-top: 8px;
+  margin-bottom: 8px;
+}
+@media (min-width: 768px) {
+  .navbar-form .form-group {
+    display: inline-block;
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .navbar-form .form-control {
+    display: inline-block;
+  }
+  .navbar-form .radio,
+  .navbar-form .checkbox {
+    display: inline-block;
+    margin-top: 0;
+    margin-bottom: 0;
+    padding-left: 0;
+  }
+  .navbar-form .radio input[type="radio"],
+  .navbar-form .checkbox input[type="checkbox"] {
+    float: none;
+    margin-left: 0;
+  }
+}
+@media (max-width: 767px) {
+  .navbar-form .form-group {
+    margin-bottom: 5px;
+  }
+}
+@media (min-width: 768px) {
+  .navbar-form {
+    width: auto;
+    border: 0;
+    margin-left: 0;
+    margin-right: 0;
+    padding-top: 0;
+    padding-bottom: 0;
+    -webkit-box-shadow: none;
+    box-shadow: none;
+  }
+}
+.navbar-nav > li > .dropdown-menu {
+  margin-top: 0;
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+.navbar-nav.pull-right > li > .dropdown-menu,
+.navbar-nav > li > .dropdown-menu.pull-right {
+  left: auto;
+  right: 0;
+}
+.navbar-btn {
+  margin-top: 8px;
+  margin-bottom: 8px;
+}
+.navbar-text {
+  float: left;
+  margin-top: 15px;
+  margin-bottom: 15px;
+}
+@media (min-width: 768px) {
+  .navbar-text {
+    margin-left: 15px;
+    margin-right: 15px;
+  }
+}
+.navbar-default {
+  background-color: #f8f8f8;
+  border-color: #e7e7e7;
+}
+.navbar-default .navbar-brand {
+  color: #777777;
+}
+.navbar-default .navbar-brand:hover,
+.navbar-default .navbar-brand:focus {
+  color: #5e5e5e;
+  background-color: transparent;
+}
+.navbar-default .navbar-text {
+  color: #777777;
+}
+.navbar-default .navbar-nav > li > a {
+  color: #777777;
+}
+.navbar-default .navbar-nav > li > a:hover,
+.navbar-default .navbar-nav > li > a:focus {
+  color: #333333;
+  background-color: transparent;
+}
+.navbar-default .navbar-nav > .active > a,
+.navbar-default .navbar-nav > .active > a:hover,
+.navbar-default .navbar-nav > .active > a:focus {
+  color: #555555;
+  background-color: #e7e7e7;
+}
+.navbar-default .navbar-nav > .disabled > a,
+.navbar-default .navbar-nav > .disabled > a:hover,
+.navbar-default .navbar-nav > .disabled > a:focus {
+  color: #cccccc;
+  background-color: transparent;
+}
+.navbar-default .navbar-toggle {
+  border-color: #dddddd;
+}
+.navbar-default .navbar-toggle:hover,
+.navbar-default .navbar-toggle:focus {
+  background-color: #dddddd;
+}
+.navbar-default .navbar-toggle .icon-bar {
+  background-color: #cccccc;
+}
+.navbar-default .navbar-collapse,
+.navbar-default .navbar-form {
+  border-color: #e6e6e6;
+}
+.navbar-default .navbar-nav > .dropdown > a:hover .caret,
+.navbar-default .navbar-nav > .dropdown > a:focus .caret {
+  border-top-color: #333333;
+  border-bottom-color: #333333;
+}
+.navbar-default .navbar-nav > .open > a,
+.navbar-default .navbar-nav > .open > a:hover,
+.navbar-default .navbar-nav > .open > a:focus {
+  background-color: #e7e7e7;
+  color: #555555;
+}
+.navbar-default .navbar-nav > .open > a .caret,
+.navbar-default .navbar-nav > .open > a:hover .caret,
+.navbar-default .navbar-nav > .open > a:focus .caret {
+  border-top-color: #555555;
+  border-bottom-color: #555555;
+}
+.navbar-default .navbar-nav > .dropdown > a .caret {
+  border-top-color: #777777;
+  border-bottom-color: #777777;
+}
+@media (max-width: 767px) {
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a {
+    color: #777777;
+  }
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {
+    color: #333333;
+    background-color: transparent;
+  }
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a,
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {
+    color: #555555;
+    background-color: #e7e7e7;
+  }
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+    color: #cccccc;
+    background-color: transparent;
+  }
+}
+.navbar-default .navbar-link {
+  color: #777777;
+}
+.navbar-default .navbar-link:hover {
+  color: #333333;
+}
+.navbar-inverse {
+  background-color: #222222;
+  border-color: #080808;
+}
+.navbar-inverse .navbar-brand {
+  color: #999999;
+}
+.navbar-inverse .navbar-brand:hover,
+.navbar-inverse .navbar-brand:focus {
+  color: #ffffff;
+  background-color: transparent;
+}
+.navbar-inverse .navbar-text {
+  color: #999999;
+}
+.navbar-inverse .navbar-nav > li > a {
+  color: #999999;
+}
+.navbar-inverse .navbar-nav > li > a:hover,
+.navbar-inverse .navbar-nav > li > a:focus {
+  color: #ffffff;
+  background-color: transparent;
+}
+.navbar-inverse .navbar-nav > .active > a,
+.navbar-inverse .navbar-nav > .active > a:hover,
+.navbar-inverse .navbar-nav > .active > a:focus {
+  color: #ffffff;
+  background-color: #080808;
+}
+.navbar-inverse .navbar-nav > .disabled > a,
+.navbar-inverse .navbar-nav > .disabled > a:hover,
+.navbar-inverse .navbar-nav > .disabled > a:focus {
+  color: #444444;
+  background-color: transparent;
+}
+.navbar-inverse .navbar-toggle {
+  border-color: #333333;
+}
+.navbar-inverse .navbar-toggle:hover,
+.navbar-inverse .navbar-toggle:focus {
+  background-color: #333333;
+}
+.navbar-inverse .navbar-toggle .icon-bar {
+  background-color: #ffffff;
+}
+.navbar-inverse .navbar-collapse,
+.navbar-inverse .navbar-form {
+  border-color: #101010;
+}
+.navbar-inverse .navbar-nav > .open > a,
+.navbar-inverse .navbar-nav > .open > a:hover,
+.navbar-inverse .navbar-nav > .open > a:focus {
+  background-color: #080808;
+  color: #ffffff;
+}
+.navbar-inverse .navbar-nav > .dropdown > a:hover .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+.navbar-inverse .navbar-nav > .dropdown > a .caret {
+  border-top-color: #999999;
+  border-bottom-color: #999999;
+}
+.navbar-inverse .navbar-nav > .open > a .caret,
+.navbar-inverse .navbar-nav > .open > a:hover .caret,
+.navbar-inverse .navbar-nav > .open > a:focus .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+@media (max-width: 767px) {
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {
+    border-color: #080808;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {
+    color: #999999;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {
+    color: #ffffff;
+    background-color: transparent;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {
+    color: #ffffff;
+    background-color: #080808;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+    color: #444444;
+    background-color: transparent;
+  }
+}
+.navbar-inverse .navbar-link {
+  color: #999999;
+}
+.navbar-inverse .navbar-link:hover {
+  color: #ffffff;
+}
+.jumbotron {
+  padding: 30px;
+  margin-bottom: 30px;
+  font-size: 21px;
+  font-weight: 200;
+  line-height: 2.1428571435;
+  color: inherit;
+  background-color: #eeeeee;
+}
+.jumbotron h1 {
+  line-height: 1;
+  color: inherit;
+}
+.jumbotron p {
+  line-height: 1.4;
+}
+.container .jumbotron {
+  border-radius: 6px;
+}
+@media screen and (min-width: 768px) {
+  .jumbotron {
+    padding-top: 48px;
+    padding-bottom: 48px;
+  }
+  .container .jumbotron {
+    padding-left: 60px;
+    padding-right: 60px;
+  }
+  .jumbotron h1 {
+    font-size: 63px;
+  }
+}
+.alert {
+  padding: 15px;
+  margin-bottom: 20px;
+  border: 1px solid transparent;
+  border-radius: 4px;
+}
+.alert h4 {
+  margin-top: 0;
+  color: inherit;
+}
+.alert .alert-link {
+  font-weight: bold;
+}
+.alert > p,
+.alert > ul {
+  margin-bottom: 0;
+}
+.alert > p + p {
+  margin-top: 5px;
+}
+.alert-dismissable {
+  padding-right: 35px;
+}
+.alert-dismissable .close {
+  position: relative;
+  top: -2px;
+  right: -21px;
+  color: inherit;
+}
+.alert-success {
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+  color: #468847;
+}
+.alert-success hr {
+  border-top-color: #c9e2b3;
+}
+.alert-success .alert-link {
+  color: #356635;
+}
+.alert-info {
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+  color: #3a87ad;
+}
+.alert-info hr {
+  border-top-color: #a6e1ec;
+}
+.alert-info .alert-link {
+  color: #2d6987;
+}
+.alert-warning {
+  background-color: #fcf8e3;
+  border-color: #fbeed5;
+  color: #c09853;
+}
+.alert-warning hr {
+  border-top-color: #f8e5be;
+}
+.alert-warning .alert-link {
+  color: #a47e3c;
+}
+.alert-danger {
+  background-color: #f2dede;
+  border-color: #eed3d7;
+  color: #b94a48;
+}
+.alert-danger hr {
+  border-top-color: #e6c1c7;
+}
+.alert-danger .alert-link {
+  color: #953b39;
+}
+.list-group {
+  margin-bottom: 20px;
+  padding-left: 0;
+}
+.list-group-item {
+  position: relative;
+  display: block;
+  padding: 10px 15px;
+  margin-bottom: -1px;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+}
+.list-group-item:first-child {
+  border-top-right-radius: 4px;
+  border-top-left-radius: 4px;
+}
+.list-group-item:last-child {
+  margin-bottom: 0;
+  border-bottom-right-radius: 4px;
+  border-bottom-left-radius: 4px;
+}
+.list-group-item > .badge {
+  float: right;
+}
+.list-group-item > .badge + .badge {
+  margin-right: 5px;
+}
+a.list-group-item {
+  color: #555555;
+}
+a.list-group-item .list-group-item-heading {
+  color: #333333;
+}
+a.list-group-item:hover,
+a.list-group-item:focus {
+  text-decoration: none;
+  background-color: #f5f5f5;
+}
+.list-group-item.active,
+.list-group-item.active:hover,
+.list-group-item.active:focus {
+  z-index: 2;
+  color: #ffffff;
+  background-color: #428bca;
+  border-color: #428bca;
+}
+.list-group-item.active .list-group-item-heading,
+.list-group-item.active:hover .list-group-item-heading,
+.list-group-item.active:focus .list-group-item-heading {
+  color: inherit;
+}
+.list-group-item.active .list-group-item-text,
+.list-group-item.active:hover .list-group-item-text,
+.list-group-item.active:focus .list-group-item-text {
+  color: #e1edf7;
+}
+.list-group-item-heading {
+  margin-top: 0;
+  margin-bottom: 5px;
+}
+.list-group-item-text {
+  margin-bottom: 0;
+  line-height: 1.3;
+}
+.panel {
+  margin-bottom: 20px;
+  background-color: #ffffff;
+  border: 1px solid transparent;
+  border-radius: 4px;
+  -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+.panel-body {
+  padding: 15px;
+}
+.panel-body:before,
+.panel-body:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.panel-body:after {
+  clear: both;
+}
+.panel-body:before,
+.panel-body:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.panel-body:after {
+  clear: both;
+}
+.panel > .list-group {
+  margin-bottom: 0;
+}
+.panel > .list-group .list-group-item {
+  border-width: 1px 0;
+}
+.panel > .list-group .list-group-item:first-child {
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+.panel > .list-group .list-group-item:last-child {
+  border-bottom: 0;
+}
+.panel-heading + .list-group .list-group-item:first-child {
+  border-top-width: 0;
+}
+.panel > .table {
+  margin-bottom: 0;
+}
+.panel > .panel-body + .table {
+  border-top: 1px solid #dddddd;
+}
+.panel-heading {
+  padding: 10px 15px;
+  border-bottom: 1px solid transparent;
+  border-top-right-radius: 3px;
+  border-top-left-radius: 3px;
+}
+.panel-title {
+  margin-top: 0;
+  margin-bottom: 0;
+  font-size: 16px;
+}
+.panel-title > a {
+  color: inherit;
+}
+.panel-footer {
+  padding: 10px 15px;
+  background-color: #f5f5f5;
+  border-top: 1px solid #dddddd;
+  border-bottom-right-radius: 3px;
+  border-bottom-left-radius: 3px;
+}
+.panel-group .panel {
+  margin-bottom: 0;
+  border-radius: 4px;
+  overflow: hidden;
+}
+.panel-group .panel + .panel {
+  margin-top: 5px;
+}
+.panel-group .panel-heading {
+  border-bottom: 0;
+}
+.panel-group .panel-heading + .panel-collapse .panel-body {
+  border-top: 1px solid #dddddd;
+}
+.panel-group .panel-footer {
+  border-top: 0;
+}
+.panel-group .panel-footer + .panel-collapse .panel-body {
+  border-bottom: 1px solid #dddddd;
+}
+.panel-default {
+  border-color: #dddddd;
+}
+.panel-default > .panel-heading {
+  color: #333333;
+  background-color: #f5f5f5;
+  border-color: #dddddd;
+}
+.panel-default > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #dddddd;
+}
+.panel-default > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #dddddd;
+}
+.panel-primary {
+  border-color: #428bca;
+}
+.panel-primary > .panel-heading {
+  color: #ffffff;
+  background-color: #428bca;
+  border-color: #428bca;
+}
+.panel-primary > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #428bca;
+}
+.panel-primary > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #428bca;
+}
+.panel-success {
+  border-color: #d6e9c6;
+}
+.panel-success > .panel-heading {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+}
+.panel-success > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #d6e9c6;
+}
+.panel-success > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #d6e9c6;
+}
+.panel-warning {
+  border-color: #fbeed5;
+}
+.panel-warning > .panel-heading {
+  color: #c09853;
+  background-color: #fcf8e3;
+  border-color: #fbeed5;
+}
+.panel-warning > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #fbeed5;
+}
+.panel-warning > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #fbeed5;
+}
+.panel-danger {
+  border-color: #eed3d7;
+}
+.panel-danger > .panel-heading {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #eed3d7;
+}
+.panel-danger > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #eed3d7;
+}
+.panel-danger > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #eed3d7;
+}
+.panel-info {
+  border-color: #bce8f1;
+}
+.panel-info > .panel-heading {
+  color: #3a87ad;
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+}
+.panel-info > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #bce8f1;
+}
+.panel-info > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #bce8f1;
+}
+.well {
+  min-height: 20px;
+  padding: 19px;
+  margin-bottom: 20px;
+  background-color: #f5f5f5;
+  border: 1px solid #e3e3e3;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+.well blockquote {
+  border-color: #ddd;
+  border-color: rgba(0, 0, 0, 0.15);
+}
+.well-lg {
+  padding: 24px;
+  border-radius: 6px;
+}
+.well-sm {
+  padding: 9px;
+  border-radius: 3px;
+}
+.close {
+  float: right;
+  font-size: 21px;
+  font-weight: bold;
+  line-height: 1;
+  color: #000000;
+  text-shadow: 0 1px 0 #ffffff;
+  opacity: 0.2;
+  filter: alpha(opacity=20);
+}
+.close:hover,
+.close:focus {
+  color: #000000;
+  text-decoration: none;
+  cursor: pointer;
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+button.close {
+  padding: 0;
+  cursor: pointer;
+  background: transparent;
+  border: 0;
+  -webkit-appearance: none;
+}
+.popover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 1010;
+  display: none;
+  max-width: 276px;
+  padding: 1px;
+  text-align: left;
+  background-color: #ffffff;
+  background-clip: padding-box;
+  border: 1px solid #cccccc;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  border-radius: 6px;
+  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  white-space: normal;
+}
+.popover.top {
+  margin-top: -10px;
+}
+.popover.right {
+  margin-left: 10px;
+}
+.popover.bottom {
+  margin-top: 10px;
+}
+.popover.left {
+  margin-left: -10px;
+}
+.popover-title {
+  margin: 0;
+  padding: 8px 14px;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 18px;
+  background-color: #f7f7f7;
+  border-bottom: 1px solid #ebebeb;
+  border-radius: 5px 5px 0 0;
+}
+.popover-content {
+  padding: 9px 14px;
+}
+.popover .arrow,
+.popover .arrow:after {
+  position: absolute;
+  display: block;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+.popover .arrow {
+  border-width: 11px;
+}
+.popover .arrow:after {
+  border-width: 10px;
+  content: "";
+}
+.popover.top .arrow {
+  left: 50%;
+  margin-left: -11px;
+  border-bottom-width: 0;
+  border-top-color: #999999;
+  border-top-color: rgba(0, 0, 0, 0.25);
+  bottom: -11px;
+}
+.popover.top .arrow:after {
+  content: " ";
+  bottom: 1px;
+  margin-left: -10px;
+  border-bottom-width: 0;
+  border-top-color: #ffffff;
+}
+.popover.right .arrow {
+  top: 50%;
+  left: -11px;
+  margin-top: -11px;
+  border-left-width: 0;
+  border-right-color: #999999;
+  border-right-color: rgba(0, 0, 0, 0.25);
+}
+.popover.right .arrow:after {
+  content: " ";
+  left: 1px;
+  bottom: -10px;
+  border-left-width: 0;
+  border-right-color: #ffffff;
+}
+.popover.bottom .arrow {
+  left: 50%;
+  margin-left: -11px;
+  border-top-width: 0;
+  border-bottom-color: #999999;
+  border-bottom-color: rgba(0, 0, 0, 0.25);
+  top: -11px;
+}
+.popover.bottom .arrow:after {
+  content: " ";
+  top: 1px;
+  margin-left: -10px;
+  border-top-width: 0;
+  border-bottom-color: #ffffff;
+}
+.popover.left .arrow {
+  top: 50%;
+  right: -11px;
+  margin-top: -11px;
+  border-right-width: 0;
+  border-left-color: #999999;
+  border-left-color: rgba(0, 0, 0, 0.25);
+}
+.popover.left .arrow:after {
+  content: " ";
+  right: 1px;
+  border-right-width: 0;
+  border-left-color: #ffffff;
+  bottom: -10px;
+}
+.modal-open {
+  overflow: hidden;
+}
+body.modal-open,
+.modal-open .navbar-fixed-top,
+.modal-open .navbar-fixed-bottom {
+  margin-right: 15px;
+}
+.modal {
+  display: none;
+  overflow: auto;
+  overflow-y: scroll;
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1040;
+}
+.modal.fade .modal-dialog {
+  -webkit-transform: translate(0, -25%);
+  -ms-transform: translate(0, -25%);
+  transform: translate(0, -25%);
+  -webkit-transition: -webkit-transform 0.3s ease-out;
+  -moz-transition: -moz-transform 0.3s ease-out;
+  -o-transition: -o-transform 0.3s ease-out;
+  transition: transform 0.3s ease-out;
+}
+.modal.in .modal-dialog {
+  -webkit-transform: translate(0, 0);
+  -ms-transform: translate(0, 0);
+  transform: translate(0, 0);
+}
+.modal-dialog {
+  margin-left: auto;
+  margin-right: auto;
+  width: auto;
+  padding: 10px;
+  z-index: 1050;
+}
+.modal-content {
+  position: relative;
+  background-color: #ffffff;
+  border: 1px solid #999999;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  border-radius: 6px;
+  -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
+  box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
+  background-clip: padding-box;
+  outline: none;
+}
+.modal-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1030;
+  background-color: #000000;
+}
+.modal-backdrop.fade {
+  opacity: 0;
+  filter: alpha(opacity=0);
+}
+.modal-backdrop.in {
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+.modal-header {
+  padding: 15px;
+  border-bottom: 1px solid #e5e5e5;
+  min-height: 16.428571429px;
+}
+.modal-header .close {
+  margin-top: -2px;
+}
+.modal-title {
+  margin: 0;
+  line-height: 1.428571429;
+}
+.modal-body {
+  position: relative;
+  padding: 20px;
+}
+.modal-footer {
+  margin-top: 15px;
+  padding: 19px 20px 20px;
+  text-align: right;
+  border-top: 1px solid #e5e5e5;
+}
+.modal-footer:before,
+.modal-footer:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.modal-footer:after {
+  clear: both;
+}
+.modal-footer:before,
+.modal-footer:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.modal-footer:after {
+  clear: both;
+}
+.modal-footer .btn + .btn {
+  margin-left: 5px;
+  margin-bottom: 0;
+}
+.modal-footer .btn-group .btn + .btn {
+  margin-left: -1px;
+}
+.modal-footer .btn-block + .btn-block {
+  margin-left: 0;
+}
+@media screen and (min-width: 768px) {
+  .modal-dialog {
+    left: 50%;
+    right: auto;
+    width: 600px;
+    padding-top: 30px;
+    padding-bottom: 30px;
+  }
+  .modal-content {
+    -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
+    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
+  }
+}
+.clearfix:before,
+.clearfix:after {
+  content: " ";
+  /* 1 */
+
+  display: table;
+  /* 2 */
+
+}
+.clearfix:after {
+  clear: both;
+}
+.pull-right {
+  float: right !important;
+}
+.pull-left {
+  float: left !important;
+}
+.hide {
+  display: none !important;
+}
+.show {
+  display: block !important;
+}
+.invisible {
+  visibility: hidden;
+}
+.text-hide {
+  font: 0/0 a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+.affix {
+  position: fixed;
+}
+@-ms-viewport {
+  width: device-width;
+}
+@media screen and (max-width: 400px) {
+  @-ms-viewport {
+    width: 320px;
+  }
+}
+.hidden {
+  display: none !important;
+  visibility: hidden !important;
+}
+.visible-xs {
+  display: none !important;
+}
+tr.visible-xs {
+  display: none !important;
+}
+th.visible-xs,
+td.visible-xs {
+  display: none !important;
+}
+@media (max-width: 767px) {
+  .visible-xs {
+    display: block !important;
+  }
+  tr.visible-xs {
+    display: table-row !important;
+  }
+  th.visible-xs,
+  td.visible-xs {
+    display: table-cell !important;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-xs.visible-sm {
+    display: block !important;
+  }
+  tr.visible-xs.visible-sm {
+    display: table-row !important;
+  }
+  th.visible-xs.visible-sm,
+  td.visible-xs.visible-sm {
+    display: table-cell !important;
+  }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-xs.visible-md {
+    display: block !important;
+  }
+  tr.visible-xs.visible-md {
+    display: table-row !important;
+  }
+  th.visible-xs.visible-md,
+  td.visible-xs.visible-md {
+    display: table-cell !important;
+  }
+}
+@media (min-width: 1200px) {
+  .visible-xs.visible-lg {
+    display: block !important;
+  }
+  tr.visible-xs.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-xs.visible-lg,
+  td.visible-xs.visible-lg {
+    display: table-cell !important;
+  }
+}
+.visible-sm {
+  display: none !important;
+}
+tr.visible-sm {
+  display: none !important;
+}
+th.visible-sm,
+td.visible-sm {
+  display: none !important;
+}
+@media (max-width: 767px) {
+  .visible-sm.visible-xs {
+    display: block !important;
+  }
+  tr.visible-sm.visible-xs {
+    display: table-row !important;
+  }
+  th.visible-sm.visible-xs,
+  td.visible-sm.visible-xs {
+    display: table-cell !important;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-sm {
+    display: block !important;
+  }
+  tr.visible-sm {
+    display: table-row !important;
+  }
+  th.visible-sm,
+  td.visible-sm {
+    display: table-cell !important;
+  }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-sm.visible-md {
+    display: block !important;
+  }
+  tr.visible-sm.visible-md {
+    display: table-row !important;
+  }
+  th.visible-sm.visible-md,
+  td.visible-sm.visible-md {
+    display: table-cell !important;
+  }
+}
+@media (min-width: 1200px) {
+  .visible-sm.visible-lg {
+    display: block !important;
+  }
+  tr.visible-sm.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-sm.visible-lg,
+  td.visible-sm.visible-lg {
+    display: table-cell !important;
+  }
+}
+.visible-md {
+  display: none !important;
+}
+tr.visible-md {
+  display: none !important;
+}
+th.visible-md,
+td.visible-md {
+  display: none !important;
+}
+@media (max-width: 767px) {
+  .visible-md.visible-xs {
+    display: block !important;
+  }
+  tr.visible-md.visible-xs {
+    display: table-row !important;
+  }
+  th.visible-md.visible-xs,
+  td.visible-md.visible-xs {
+    display: table-cell !important;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-md.visible-sm {
+    display: block !important;
+  }
+  tr.visible-md.visible-sm {
+    display: table-row !important;
+  }
+  th.visible-md.visible-sm,
+  td.visible-md.visible-sm {
+    display: table-cell !important;
+  }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-md {
+    display: block !important;
+  }
+  tr.visible-md {
+    display: table-row !important;
+  }
+  th.visible-md,
+  td.visible-md {
+    display: table-cell !important;
+  }
+}
+@media (min-width: 1200px) {
+  .visible-md.visible-lg {
+    display: block !important;
+  }
+  tr.visible-md.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-md.visible-lg,
+  td.visible-md.visible-lg {
+    display: table-cell !important;
+  }
+}
+.visible-lg {
+  display: none !important;
+}
+tr.visible-lg {
+  display: none !important;
+}
+th.visible-lg,
+td.visible-lg {
+  display: none !important;
+}
+@media (max-width: 767px) {
+  .visible-lg.visible-xs {
+    display: block !important;
+  }
+  tr.visible-lg.visible-xs {
+    display: table-row !important;
+  }
+  th.visible-lg.visible-xs,
+  td.visible-lg.visible-xs {
+    display: table-cell !important;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-lg.visible-sm {
+    display: block !important;
+  }
+  tr.visible-lg.visible-sm {
+    display: table-row !important;
+  }
+  th.visible-lg.visible-sm,
+  td.visible-lg.visible-sm {
+    display: table-cell !important;
+  }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-lg.visible-md {
+    display: block !important;
+  }
+  tr.visible-lg.visible-md {
+    display: table-row !important;
+  }
+  th.visible-lg.visible-md,
+  td.visible-lg.visible-md {
+    display: table-cell !important;
+  }
+}
+@media (min-width: 1200px) {
+  .visible-lg {
+    display: block !important;
+  }
+  tr.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-lg,
+  td.visible-lg {
+    display: table-cell !important;
+  }
+}
+.hidden-xs {
+  display: block !important;
+}
+tr.hidden-xs {
+  display: table-row !important;
+}
+th.hidden-xs,
+td.hidden-xs {
+  display: table-cell !important;
+}
+@media (max-width: 767px) {
+  .hidden-xs {
+    display: none !important;
+  }
+  tr.hidden-xs {
+    display: none !important;
+  }
+  th.hidden-xs,
+  td.hidden-xs {
+    display: none !important;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .hidden-xs.hidden-sm {
+    display: none !important;
+  }
+  tr.hidden-xs.hidden-sm {
+    display: none !important;
+  }
+  th.hidden-xs.hidden-sm,
+  td.hidden-xs.hidden-sm {
+    display: none !important;
+  }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+  .hidden-xs.hidden-md {
+    display: none !important;
+  }
+  tr.hidden-xs.hidden-md {
+    display: none !important;
+  }
+  th.hidden-xs.hidden-md,
+  td.hidden-xs.hidden-md {
+    display: none !important;
+  }
+}
+@media (min-width: 1200px) {
+  .hidden-xs.hidden-lg {
+    display: none !important;
+  }
+  tr.hidden-xs.hidden-lg {
+    display: none !important;
+  }
+  th.hidden-xs.hidden-lg,
+  td.hidden-xs.hidden-lg {
+    display: none !important;
+  }
+}
+.hidden-sm {
+  display: block !important;
+}
+tr.hidden-sm {
+  display: table-row !important;
+}
+th.hidden-sm,
+td.hidden-sm {
+  display: table-cell !important;
+}
+@media (max-width: 767px) {
+  .hidden-sm.hidden-xs {
+    display: none !important;
+  }
+  tr.hidden-sm.hidden-xs {
+    display: none !important;
+  }
+  th.hidden-sm.hidden-xs,
+  td.hidden-sm.hidden-xs {
+    display: none !important;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .hidden-sm {
+    display: none !important;
+  }
+  tr.hidden-sm {
+    display: none !important;
+  }
+  th.hidden-sm,
+  td.hidden-sm {
+    display: none !important;
+  }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+  .hidden-sm.hidden-md {
+    display: none !important;
+  }
+  tr.hidden-sm.hidden-md {
+    display: none !important;
+  }
+  th.hidden-sm.hidden-md,
+  td.hidden-sm.hidden-md {
+    display: none !important;
+  }
+}
+@media (min-width: 1200px) {
+  .hidden-sm.hidden-lg {
+    display: none !important;
+  }
+  tr.hidden-sm.hidden-lg {
+    display: none !important;
+  }
+  th.hidden-sm.hidden-lg,
+  td.hidden-sm.hidden-lg {
+    display: none !important;
+  }
+}
+.hidden-md {
+  display: block !important;
+}
+tr.hidden-md {
+  display: table-row !important;
+}
+th.hidden-md,
+td.hidden-md {
+  display: table-cell !important;
+}
+@media (max-width: 767px) {
+  .hidden-md.hidden-xs {
+    display: none !important;
+  }
+  tr.hidden-md.hidden-xs {
+    display: none !important;
+  }
+  th.hidden-md.hidden-xs,
+  td.hidden-md.hidden-xs {
+    display: none !important;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .hidden-md.hidden-sm {
+    display: none !important;
+  }
+  tr.hidden-md.hidden-sm {
+    display: none !important;
+  }
+  th.hidden-md.hidden-sm,
+  td.hidden-md.hidden-sm {
+    display: none !important;
+  }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+  .hidden-md {
+    display: none !important;
+  }
+  tr.hidden-md {
+    display: none !important;
+  }
+  th.hidden-md,
+  td.hidden-md {
+    display: none !important;
+  }
+}
+@media (min-width: 1200px) {
+  .hidden-md.hidden-lg {
+    display: none !important;
+  }
+  tr.hidden-md.hidden-lg {
+    display: none !important;
+  }
+  th.hidden-md.hidden-lg,
+  td.hidden-md.hidden-lg {
+    display: none !important;
+  }
+}
+.hidden-lg {
+  display: block !important;
+}
+tr.hidden-lg {
+  display: table-row !important;
+}
+th.hidden-lg,
+td.hidden-lg {
+  display: table-cell !important;
+}
+@media (max-width: 767px) {
+  .hidden-lg.hidden-xs {
+    display: none !important;
+  }
+  tr.hidden-lg.hidden-xs {
+    display: none !important;
+  }
+  th.hidden-lg.hidden-xs,
+  td.hidden-lg.hidden-xs {
+    display: none !important;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .hidden-lg.hidden-sm {
+    display: none !important;
+  }
+  tr.hidden-lg.hidden-sm {
+    display: none !important;
+  }
+  th.hidden-lg.hidden-sm,
+  td.hidden-lg.hidden-sm {
+    display: none !important;
+  }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+  .hidden-lg.hidden-md {
+    display: none !important;
+  }
+  tr.hidden-lg.hidden-md {
+    display: none !important;
+  }
+  th.hidden-lg.hidden-md,
+  td.hidden-lg.hidden-md {
+    display: none !important;
+  }
+}
+@media (min-width: 1200px) {
+  .hidden-lg {
+    display: none !important;
+  }
+  tr.hidden-lg {
+    display: none !important;
+  }
+  th.hidden-lg,
+  td.hidden-lg {
+    display: none !important;
+  }
+}
+.visible-print {
+  display: none !important;
+}
+tr.visible-print {
+  display: none !important;
+}
+th.visible-print,
+td.visible-print {
+  display: none !important;
+}
+@media print {
+  .visible-print {
+    display: block !important;
+  }
+  tr.visible-print {
+    display: table-row !important;
+  }
+  th.visible-print,
+  td.visible-print {
+    display: table-cell !important;
+  }
+  .hidden-print {
+    display: none !important;
+  }
+  tr.hidden-print {
+    display: none !important;
+  }
+  th.hidden-print,
+  td.hidden-print {
+    display: none !important;
+  }
+}
+.fade {
+  opacity: 0;
+  -webkit-transition: opacity 0.15s linear;
+  transition: opacity 0.15s linear;
+}
+.fade.in {
+  opacity: 1;
+}
+.collapse {
+  display: none;
+}
+.collapse.in {
+  display: block;
+}
+.collapsing {
+  position: relative;
+  height: 0;
+  overflow: hidden;
+  -webkit-transition: height 0.35s ease;
+  transition: height 0.35s ease;
+}
diff --git a/gddo-server/assets/third_party/bootstrap/css/bootstrap.min.css b/gddo-server/assets/third_party/bootstrap/css/bootstrap.min.css
new file mode 100644
index 0000000..b9c08cc
--- /dev/null
+++ b/gddo-server/assets/third_party/bootstrap/css/bootstrap.min.css
@@ -0,0 +1,491 @@
+/*!
+ * Bootstrap v3.0.0
+ *
+ * Copyright 2013 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+
+article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block;}
+audio,canvas,video{display:inline-block;}
+audio:not([controls]){display:none;height:0;}
+[hidden]{display:none;}
+html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
+body{margin:0;}
+a:focus{outline:thin dotted;}
+a:active,a:hover{outline:0;}
+h1{font-size:2em;margin:0.67em 0;}
+abbr[title]{border-bottom:1px dotted;}
+b,strong{font-weight:bold;}
+dfn{font-style:italic;}
+hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0;}
+mark{background:#ff0;color:#000;}
+code,kbd,pre,samp{font-family:monospace, serif;font-size:1em;}
+pre{white-space:pre-wrap;}
+q{quotes:"\201C" "\201D" "\2018" "\2019";}
+small{font-size:80%;}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}
+sup{top:-0.5em;}
+sub{bottom:-0.25em;}
+img{border:0;}
+svg:not(:root){overflow:hidden;}
+figure{margin:0;}
+fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em;}
+legend{border:0;padding:0;}
+button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}
+button,input{line-height:normal;}
+button,select{text-transform:none;}
+button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;}
+button[disabled],html input[disabled]{cursor:default;}
+input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;}
+input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}
+input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}
+textarea{overflow:auto;vertical-align:top;}
+table{border-collapse:collapse;border-spacing:0;}
+*,*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}
+html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0, 0, 0, 0);}
+body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.428571429;color:#333333;background-color:#ffffff;}
+input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;}
+button,input,select[multiple],textarea{background-image:none;}
+a{color:#428bca;text-decoration:none;}a:hover,a:focus{color:#2a6496;text-decoration:underline;}
+a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
+img{vertical-align:middle;}
+.img-responsive{display:block;max-width:100%;height:auto;}
+.img-rounded{border-radius:6px;}
+.img-thumbnail{padding:4px;line-height:1.428571429;background-color:#ffffff;border:1px solid #dddddd;border-radius:4px;-webkit-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out;display:inline-block;max-width:100%;height:auto;}
+.img-circle{border-radius:50%;}
+hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eeeeee;}
+.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);border:0;}
+@media print{*{text-shadow:none !important;color:#000 !important;background:transparent !important;box-shadow:none !important;} a,a:visited{text-decoration:underline;} a[href]:after{content:" (" attr(href) ")";} abbr[title]:after{content:" (" attr(title) ")";} .ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:"";} pre,blockquote{border:1px solid #999;page-break-inside:avoid;} thead{display:table-header-group;} tr,img{page-break-inside:avoid;} img{max-width:100% !important;} @page {margin:2cm .5cm;}p,h2,h3{orphans:3;widows:3;} h2,h3{page-break-after:avoid;} .navbar{display:none;} .table td,.table th{background-color:#fff !important;} .btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important;} .label{border:1px solid #000;} .table{border-collapse:collapse !important;} .table-bordered th,.table-bordered td{border:1px solid #ddd !important;}}p{margin:0 0 10px;}
+.lead{margin-bottom:20px;font-size:16.099999999999998px;font-weight:200;line-height:1.4;}@media (min-width:768px){.lead{font-size:21px;}}
+small{font-size:85%;}
+cite{font-style:normal;}
+.text-muted{color:#999999;}
+.text-primary{color:#428bca;}
+.text-warning{color:#c09853;}
+.text-danger{color:#b94a48;}
+.text-success{color:#468847;}
+.text-info{color:#3a87ad;}
+.text-left{text-align:left;}
+.text-right{text-align:right;}
+.text-center{text-align:center;}
+h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:500;line-height:1.1;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small{font-weight:normal;line-height:1;color:#999999;}
+h1,h2,h3{margin-top:20px;margin-bottom:10px;}
+h4,h5,h6{margin-top:10px;margin-bottom:10px;}
+h1,.h1{font-size:36px;}
+h2,.h2{font-size:30px;}
+h3,.h3{font-size:24px;}
+h4,.h4{font-size:18px;}
+h5,.h5{font-size:14px;}
+h6,.h6{font-size:12px;}
+h1 small,.h1 small{font-size:24px;}
+h2 small,.h2 small{font-size:18px;}
+h3 small,.h3 small,h4 small,.h4 small{font-size:14px;}
+.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eeeeee;}
+ul,ol{margin-top:0;margin-bottom:10px;}ul ul,ol ul,ul ol,ol ol{margin-bottom:0;}
+.list-unstyled{padding-left:0;list-style:none;}
+.list-inline{padding-left:0;list-style:none;}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px;}
+dl{margin-bottom:20px;}
+dt,dd{line-height:1.428571429;}
+dt{font-weight:bold;}
+dd{margin-left:0;}
+@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;} .dl-horizontal dd{margin-left:180px;}.dl-horizontal dd:before,.dl-horizontal dd:after{content:" ";display:table;} .dl-horizontal dd:after{clear:both;} .dl-horizontal dd:before,.dl-horizontal dd:after{content:" ";display:table;} .dl-horizontal dd:after{clear:both;}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999999;}
+abbr.initialism{font-size:90%;text-transform:uppercase;}
+blockquote{padding:10px 20px;margin:0 0 20px;border-left:5px solid #eeeeee;}blockquote p{font-size:17.5px;font-weight:300;line-height:1.25;}
+blockquote p:last-child{margin-bottom:0;}
+blockquote small{display:block;line-height:1.428571429;color:#999999;}blockquote small:before{content:'\2014 \00A0';}
+blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eeeeee;border-left:0;}blockquote.pull-right p,blockquote.pull-right small{text-align:right;}
+blockquote.pull-right small:before{content:'';}
+blockquote.pull-right small:after{content:'\00A0 \2014';}
+q:before,q:after,blockquote:before,blockquote:after{content:"";}
+address{display:block;margin-bottom:20px;font-style:normal;line-height:1.428571429;}
+code,pre{font-family:Monaco,Menlo,Consolas,"Courier New",monospace;}
+code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;white-space:nowrap;border-radius:4px;}
+pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.428571429;word-break:break-all;word-wrap:break-word;color:#333333;background-color:#f5f5f5;border:1px solid #cccccc;border-radius:4px;}pre.prettyprint{margin-bottom:20px;}
+pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border:0;}
+.pre-scrollable{max-height:340px;overflow-y:scroll;}
+.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px;}.container:before,.container:after{content:" ";display:table;}
+.container:after{clear:both;}
+.container:before,.container:after{content:" ";display:table;}
+.container:after{clear:both;}
+.row{margin-left:-15px;margin-right:-15px;}.row:before,.row:after{content:" ";display:table;}
+.row:after{clear:both;}
+.row:before,.row:after{content:" ";display:table;}
+.row:after{clear:both;}
+.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px;}
+.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11{float:left;}
+.col-xs-1{width:8.333333333333332%;}
+.col-xs-2{width:16.666666666666664%;}
+.col-xs-3{width:25%;}
+.col-xs-4{width:33.33333333333333%;}
+.col-xs-5{width:41.66666666666667%;}
+.col-xs-6{width:50%;}
+.col-xs-7{width:58.333333333333336%;}
+.col-xs-8{width:66.66666666666666%;}
+.col-xs-9{width:75%;}
+.col-xs-10{width:83.33333333333334%;}
+.col-xs-11{width:91.66666666666666%;}
+.col-xs-12{width:100%;}
+@media (min-width:768px){.container{max-width:750px;} .col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11{float:left;} .col-sm-1{width:8.333333333333332%;} .col-sm-2{width:16.666666666666664%;} .col-sm-3{width:25%;} .col-sm-4{width:33.33333333333333%;} .col-sm-5{width:41.66666666666667%;} .col-sm-6{width:50%;} .col-sm-7{width:58.333333333333336%;} .col-sm-8{width:66.66666666666666%;} .col-sm-9{width:75%;} .col-sm-10{width:83.33333333333334%;} .col-sm-11{width:91.66666666666666%;} .col-sm-12{width:100%;} .col-sm-push-1{left:8.333333333333332%;} .col-sm-push-2{left:16.666666666666664%;} .col-sm-push-3{left:25%;} .col-sm-push-4{left:33.33333333333333%;} .col-sm-push-5{left:41.66666666666667%;} .col-sm-push-6{left:50%;} .col-sm-push-7{left:58.333333333333336%;} .col-sm-push-8{left:66.66666666666666%;} .col-sm-push-9{left:75%;} .col-sm-push-10{left:83.33333333333334%;} .col-sm-push-11{left:91.66666666666666%;} .col-sm-pull-1{right:8.333333333333332%;} .col-sm-pull-2{right:16.666666666666664%;} .col-sm-pull-3{right:25%;} .col-sm-pull-4{right:33.33333333333333%;} .col-sm-pull-5{right:41.66666666666667%;} .col-sm-pull-6{right:50%;} .col-sm-pull-7{right:58.333333333333336%;} .col-sm-pull-8{right:66.66666666666666%;} .col-sm-pull-9{right:75%;} .col-sm-pull-10{right:83.33333333333334%;} .col-sm-pull-11{right:91.66666666666666%;} .col-sm-offset-1{margin-left:8.333333333333332%;} .col-sm-offset-2{margin-left:16.666666666666664%;} .col-sm-offset-3{margin-left:25%;} .col-sm-offset-4{margin-left:33.33333333333333%;} .col-sm-offset-5{margin-left:41.66666666666667%;} .col-sm-offset-6{margin-left:50%;} .col-sm-offset-7{margin-left:58.333333333333336%;} .col-sm-offset-8{margin-left:66.66666666666666%;} .col-sm-offset-9{margin-left:75%;} .col-sm-offset-10{margin-left:83.33333333333334%;} .col-sm-offset-11{margin-left:91.66666666666666%;}}@media (min-width:992px){.container{max-width:970px;} .col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11{float:left;} .col-md-1{width:8.333333333333332%;} .col-md-2{width:16.666666666666664%;} .col-md-3{width:25%;} .col-md-4{width:33.33333333333333%;} .col-md-5{width:41.66666666666667%;} .col-md-6{width:50%;} .col-md-7{width:58.333333333333336%;} .col-md-8{width:66.66666666666666%;} .col-md-9{width:75%;} .col-md-10{width:83.33333333333334%;} .col-md-11{width:91.66666666666666%;} .col-md-12{width:100%;} .col-md-push-0{left:auto;} .col-md-push-1{left:8.333333333333332%;} .col-md-push-2{left:16.666666666666664%;} .col-md-push-3{left:25%;} .col-md-push-4{left:33.33333333333333%;} .col-md-push-5{left:41.66666666666667%;} .col-md-push-6{left:50%;} .col-md-push-7{left:58.333333333333336%;} .col-md-push-8{left:66.66666666666666%;} .col-md-push-9{left:75%;} .col-md-push-10{left:83.33333333333334%;} .col-md-push-11{left:91.66666666666666%;} .col-md-pull-0{right:auto;} .col-md-pull-1{right:8.333333333333332%;} .col-md-pull-2{right:16.666666666666664%;} .col-md-pull-3{right:25%;} .col-md-pull-4{right:33.33333333333333%;} .col-md-pull-5{right:41.66666666666667%;} .col-md-pull-6{right:50%;} .col-md-pull-7{right:58.333333333333336%;} .col-md-pull-8{right:66.66666666666666%;} .col-md-pull-9{right:75%;} .col-md-pull-10{right:83.33333333333334%;} .col-md-pull-11{right:91.66666666666666%;} .col-md-offset-0{margin-left:0;} .col-md-offset-1{margin-left:8.333333333333332%;} .col-md-offset-2{margin-left:16.666666666666664%;} .col-md-offset-3{margin-left:25%;} .col-md-offset-4{margin-left:33.33333333333333%;} .col-md-offset-5{margin-left:41.66666666666667%;} .col-md-offset-6{margin-left:50%;} .col-md-offset-7{margin-left:58.333333333333336%;} .col-md-offset-8{margin-left:66.66666666666666%;} .col-md-offset-9{margin-left:75%;} .col-md-offset-10{margin-left:83.33333333333334%;} .col-md-offset-11{margin-left:91.66666666666666%;}}@media (min-width:1200px){.container{max-width:1170px;} .col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11{float:left;} .col-lg-1{width:8.333333333333332%;} .col-lg-2{width:16.666666666666664%;} .col-lg-3{width:25%;} .col-lg-4{width:33.33333333333333%;} .col-lg-5{width:41.66666666666667%;} .col-lg-6{width:50%;} .col-lg-7{width:58.333333333333336%;} .col-lg-8{width:66.66666666666666%;} .col-lg-9{width:75%;} .col-lg-10{width:83.33333333333334%;} .col-lg-11{width:91.66666666666666%;} .col-lg-12{width:100%;} .col-lg-push-0{left:auto;} .col-lg-push-1{left:8.333333333333332%;} .col-lg-push-2{left:16.666666666666664%;} .col-lg-push-3{left:25%;} .col-lg-push-4{left:33.33333333333333%;} .col-lg-push-5{left:41.66666666666667%;} .col-lg-push-6{left:50%;} .col-lg-push-7{left:58.333333333333336%;} .col-lg-push-8{left:66.66666666666666%;} .col-lg-push-9{left:75%;} .col-lg-push-10{left:83.33333333333334%;} .col-lg-push-11{left:91.66666666666666%;} .col-lg-pull-0{right:auto;} .col-lg-pull-1{right:8.333333333333332%;} .col-lg-pull-2{right:16.666666666666664%;} .col-lg-pull-3{right:25%;} .col-lg-pull-4{right:33.33333333333333%;} .col-lg-pull-5{right:41.66666666666667%;} .col-lg-pull-6{right:50%;} .col-lg-pull-7{right:58.333333333333336%;} .col-lg-pull-8{right:66.66666666666666%;} .col-lg-pull-9{right:75%;} .col-lg-pull-10{right:83.33333333333334%;} .col-lg-pull-11{right:91.66666666666666%;} .col-lg-offset-0{margin-left:0;} .col-lg-offset-1{margin-left:8.333333333333332%;} .col-lg-offset-2{margin-left:16.666666666666664%;} .col-lg-offset-3{margin-left:25%;} .col-lg-offset-4{margin-left:33.33333333333333%;} .col-lg-offset-5{margin-left:41.66666666666667%;} .col-lg-offset-6{margin-left:50%;} .col-lg-offset-7{margin-left:58.333333333333336%;} .col-lg-offset-8{margin-left:66.66666666666666%;} .col-lg-offset-9{margin-left:75%;} .col-lg-offset-10{margin-left:83.33333333333334%;} .col-lg-offset-11{margin-left:91.66666666666666%;}}table{max-width:100%;background-color:transparent;}
+th{text-align:left;}
+.table{width:100%;margin-bottom:20px;}.table thead>tr>th,.table tbody>tr>th,.table tfoot>tr>th,.table thead>tr>td,.table tbody>tr>td,.table tfoot>tr>td{padding:8px;line-height:1.428571429;vertical-align:top;border-top:1px solid #dddddd;}
+.table thead>tr>th{vertical-align:bottom;border-bottom:2px solid #dddddd;}
+.table caption+thead tr:first-child th,.table colgroup+thead tr:first-child th,.table thead:first-child tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child td{border-top:0;}
+.table tbody+tbody{border-top:2px solid #dddddd;}
+.table .table{background-color:#ffffff;}
+.table-condensed thead>tr>th,.table-condensed tbody>tr>th,.table-condensed tfoot>tr>th,.table-condensed thead>tr>td,.table-condensed tbody>tr>td,.table-condensed tfoot>tr>td{padding:5px;}
+.table-bordered{border:1px solid #dddddd;}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #dddddd;}
+.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px;}
+.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9;}
+.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5;}
+table col[class*="col-"]{float:none;display:table-column;}
+table td[class*="col-"],table th[class*="col-"]{float:none;display:table-cell;}
+.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5;}
+.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8;border-color:#d6e9c6;}
+.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td{background-color:#d0e9c6;border-color:#c9e2b3;}
+.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede;border-color:#eed3d7;}
+.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td{background-color:#ebcccc;border-color:#e6c1c7;}
+.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3;border-color:#fbeed5;}
+.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td{background-color:#faf2cc;border-color:#f8e5be;}
+@media (max-width:768px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;overflow-x:scroll;border:1px solid #dddddd;}.table-responsive>.table{margin-bottom:0;background-color:#fff;}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap;} .table-responsive>.table-bordered{border:0;}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0;} .table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0;} .table-responsive>.table-bordered>thead>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>thead>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0;}}fieldset{padding:0;margin:0;border:0;}
+legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333333;border:0;border-bottom:1px solid #e5e5e5;}
+label{display:inline-block;margin-bottom:5px;font-weight:bold;}
+input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}
+input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal;}
+input[type="file"]{display:block;}
+select[multiple],select[size]{height:auto;}
+select optgroup{font-size:inherit;font-style:inherit;font-family:inherit;}
+input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
+input[type="number"]::-webkit-outer-spin-button,input[type="number"]::-webkit-inner-spin-button{height:auto;}
+.form-control:-moz-placeholder{color:#999999;}
+.form-control::-moz-placeholder{color:#999999;}
+.form-control:-ms-input-placeholder{color:#999999;}
+.form-control::-webkit-input-placeholder{color:#999999;}
+.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.428571429;color:#555555;vertical-align:middle;background-color:#ffffff;border:1px solid #cccccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s;}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);}
+.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eeeeee;}
+textarea.form-control{height:auto;}
+.form-group{margin-bottom:15px;}
+.radio,.checkbox{display:block;min-height:20px;margin-top:10px;margin-bottom:10px;padding-left:20px;vertical-align:middle;}.radio label,.checkbox label{display:inline;margin-bottom:0;font-weight:normal;cursor:pointer;}
+.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{float:left;margin-left:-20px;}
+.radio+.radio,.checkbox+.checkbox{margin-top:-5px;}
+.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer;}
+.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px;}
+input[type="radio"][disabled],input[type="checkbox"][disabled],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed;}
+.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px;}select.input-sm{height:30px;line-height:30px;}
+textarea.input-sm{height:auto;}
+.input-lg{height:45px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px;}select.input-lg{height:45px;line-height:45px;}
+textarea.input-lg{height:auto;}
+.has-warning .help-block,.has-warning .control-label{color:#c09853;}
+.has-warning .form-control{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.has-warning .form-control:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #dbc59e;}
+.has-warning .input-group-addon{color:#c09853;border-color:#c09853;background-color:#fcf8e3;}
+.has-error .help-block,.has-error .control-label{color:#b94a48;}
+.has-error .form-control{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.has-error .form-control:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #d59392;}
+.has-error .input-group-addon{color:#b94a48;border-color:#b94a48;background-color:#f2dede;}
+.has-success .help-block,.has-success .control-label{color:#468847;}
+.has-success .form-control{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.has-success .form-control:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7aba7b;}
+.has-success .input-group-addon{color:#468847;border-color:#468847;background-color:#dff0d8;}
+.form-control-static{margin-bottom:0;padding-top:7px;}
+.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373;}
+@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle;} .form-inline .form-control{display:inline-block;} .form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;} .form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:none;margin-left:0;}}
+.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:7px;}
+.form-horizontal .form-group{margin-left:-15px;margin-right:-15px;}.form-horizontal .form-group:before,.form-horizontal .form-group:after{content:" ";display:table;}
+.form-horizontal .form-group:after{clear:both;}
+.form-horizontal .form-group:before,.form-horizontal .form-group:after{content:" ";display:table;}
+.form-horizontal .form-group:after{clear:both;}
+@media (min-width:768px){.form-horizontal .control-label{text-align:right;}}
+.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:normal;line-height:1.428571429;text-align:center;vertical-align:middle;cursor:pointer;border:1px solid transparent;border-radius:4px;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
+.btn:hover,.btn:focus{color:#333333;text-decoration:none;}
+.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);box-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);}
+.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;pointer-events:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;}
+.btn-default{color:#333333;background-color:#ffffff;border-color:#cccccc;}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#333333;background-color:#ebebeb;border-color:#adadad;}
+.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{background-image:none;}
+.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#ffffff;border-color:#cccccc;}
+.btn-primary{color:#ffffff;background-color:#428bca;border-color:#357ebd;}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#ffffff;background-color:#3276b1;border-color:#285e8e;}
+.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{background-image:none;}
+.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd;}
+.btn-warning{color:#ffffff;background-color:#f0ad4e;border-color:#eea236;}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#ffffff;background-color:#ed9c28;border-color:#d58512;}
+.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{background-image:none;}
+.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236;}
+.btn-danger{color:#ffffff;background-color:#d9534f;border-color:#d43f3a;}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#ffffff;background-color:#d2322d;border-color:#ac2925;}
+.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{background-image:none;}
+.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a;}
+.btn-success{color:#ffffff;background-color:#5cb85c;border-color:#4cae4c;}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#ffffff;background-color:#47a447;border-color:#398439;}
+.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{background-image:none;}
+.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c;}
+.btn-info{color:#ffffff;background-color:#5bc0de;border-color:#46b8da;}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#ffffff;background-color:#39b3d7;border-color:#269abc;}
+.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{background-image:none;}
+.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da;}
+.btn-link{color:#428bca;font-weight:normal;cursor:pointer;border-radius:0;}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none;}
+.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent;}
+.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent;}
+.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999999;text-decoration:none;}
+.btn-lg{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px;}
+.btn-sm,.btn-xs{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px;}
+.btn-xs{padding:1px 5px;}
+.btn-block{display:block;width:100%;padding-left:0;padding-right:0;}
+.btn-block+.btn-block{margin-top:5px;}
+input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%;}
+.input-group{position:relative;display:table;border-collapse:separate;}.input-group.col{float:none;padding-left:0;padding-right:0;}
+.input-group .form-control{width:100%;margin-bottom:0;}
+.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:45px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px;}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:45px;line-height:45px;}
+textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto;}
+.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px;}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px;}
+textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto;}
+.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell;}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0;}
+.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle;}
+.input-group-addon{padding:6px 12px;font-size:14px;font-weight:normal;line-height:1;text-align:center;background-color:#eeeeee;border:1px solid #cccccc;border-radius:4px;}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px;}
+.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px;}
+.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0;}
+.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0;}
+.input-group-addon:first-child{border-right:0;}
+.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;}
+.input-group-addon:last-child{border-left:0;}
+.input-group-btn{position:relative;white-space:nowrap;}
+.input-group-btn>.btn{position:relative;}.input-group-btn>.btn+.btn{margin-left:-4px;}
+.input-group-btn>.btn:hover,.input-group-btn>.btn:active{z-index:2;}
+.nav{margin-bottom:0;padding-left:0;list-style:none;}.nav:before,.nav:after{content:" ";display:table;}
+.nav:after{clear:both;}
+.nav:before,.nav:after{content:" ";display:table;}
+.nav:after{clear:both;}
+.nav>li{position:relative;display:block;}.nav>li>a{position:relative;display:block;padding:10px 15px;}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eeeeee;}
+.nav>li.disabled>a{color:#999999;}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999999;text-decoration:none;background-color:transparent;cursor:not-allowed;}
+.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eeeeee;border-color:#428bca;}
+.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5;}
+.nav>li>a>img{max-width:none;}
+.nav-tabs{border-bottom:1px solid #dddddd;}.nav-tabs>li{float:left;margin-bottom:-1px;}.nav-tabs>li>a{margin-right:2px;line-height:1.428571429;border:1px solid transparent;border-radius:4px 4px 0 0;}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #dddddd;}
+.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555555;background-color:#ffffff;border:1px solid #dddddd;border-bottom-color:transparent;cursor:default;}
+.nav-tabs.nav-justified{width:100%;border-bottom:0;}.nav-tabs.nav-justified>li{float:none;}.nav-tabs.nav-justified>li>a{text-align:center;}
+@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%;}}.nav-tabs.nav-justified>li>a{border-bottom:1px solid #dddddd;margin-right:0;}
+.nav-tabs.nav-justified>.active>a{border-bottom-color:#ffffff;}
+.nav-pills>li{float:left;}.nav-pills>li>a{border-radius:5px;}
+.nav-pills>li+li{margin-left:2px;}
+.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#ffffff;background-color:#428bca;}
+.nav-stacked>li{float:none;}.nav-stacked>li+li{margin-top:2px;margin-left:0;}
+.nav-justified{width:100%;}.nav-justified>li{float:none;}.nav-justified>li>a{text-align:center;}
+@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%;}}
+.nav-tabs-justified{border-bottom:0;}.nav-tabs-justified>li>a{border-bottom:1px solid #dddddd;margin-right:0;}
+.nav-tabs-justified>.active>a{border-bottom-color:#ffffff;}
+.tabbable:before,.tabbable:after{content:" ";display:table;}
+.tabbable:after{clear:both;}
+.tabbable:before,.tabbable:after{content:" ";display:table;}
+.tabbable:after{clear:both;}
+.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;}
+.tab-content>.active,.pill-content>.active{display:block;}
+.nav .caret{border-top-color:#428bca;border-bottom-color:#428bca;}
+.nav a:hover .caret{border-top-color:#2a6496;border-bottom-color:#2a6496;}
+.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0;}
+.navbar{position:relative;z-index:1000;min-height:50px;margin-bottom:20px;border:1px solid transparent;}.navbar:before,.navbar:after{content:" ";display:table;}
+.navbar:after{clear:both;}
+.navbar:before,.navbar:after{content:" ";display:table;}
+.navbar:after{clear:both;}
+@media (min-width:768px){.navbar{border-radius:4px;}}
+.navbar-header:before,.navbar-header:after{content:" ";display:table;}
+.navbar-header:after{clear:both;}
+.navbar-header:before,.navbar-header:after{content:" ";display:table;}
+.navbar-header:after{clear:both;}
+@media (min-width:768px){.navbar-header{float:left;}}
+.navbar-collapse{max-height:340px;overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1);-webkit-overflow-scrolling:touch;}.navbar-collapse:before,.navbar-collapse:after{content:" ";display:table;}
+.navbar-collapse:after{clear:both;}
+.navbar-collapse:before,.navbar-collapse:after{content:" ";display:table;}
+.navbar-collapse:after{clear:both;}
+.navbar-collapse.in{overflow-y:auto;}
+@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none;}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important;} .navbar-collapse.in{overflow-y:visible;} .navbar-collapse .navbar-nav.navbar-left:first-child{margin-left:-15px;} .navbar-collapse .navbar-nav.navbar-right:last-child{margin-right:-15px;} .navbar-collapse .navbar-text:last-child{margin-right:0;}}
+.container>.navbar-header,.container>.navbar-collapse{margin-right:-15px;margin-left:-15px;}@media (min-width:768px){.container>.navbar-header,.container>.navbar-collapse{margin-right:0;margin-left:0;}}
+.navbar-static-top{border-width:0 0 1px;}@media (min-width:768px){.navbar-static-top{border-radius:0;}}
+.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;border-width:0 0 1px;}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0;}}
+.navbar-fixed-top{z-index:1030;top:0;}
+.navbar-fixed-bottom{bottom:0;margin-bottom:0;}
+.navbar-brand{float:left;padding:15px 15px;font-size:18px;line-height:20px;}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none;}
+@media (min-width:768px){.navbar>.container .navbar-brand{margin-left:-15px;}}
+.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;border:1px solid transparent;border-radius:4px;}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px;}
+.navbar-toggle .icon-bar+.icon-bar{margin-top:4px;}
+@media (min-width:768px){.navbar-toggle{display:none;}}
+.navbar-nav{margin:7.5px -15px;}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px;}
+@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none;}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px;} .navbar-nav .open .dropdown-menu>li>a{line-height:20px;}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none;}}@media (min-width:768px){.navbar-nav{float:left;margin:0;}.navbar-nav>li{float:left;}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px;}}
+@media (min-width:768px){.navbar-left{float:left !important;} .navbar-right{float:right !important;}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.1);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.1);margin-top:8px;margin-bottom:8px;}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle;} .navbar-form .form-control{display:inline-block;} .navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;} .navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{float:none;margin-left:0;}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px;}}
+@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none;}}
+.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0;}
+.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0;}
+.navbar-nav.pull-right>li>.dropdown-menu,.navbar-nav>li>.dropdown-menu.pull-right{left:auto;right:0;}
+.navbar-btn{margin-top:8px;margin-bottom:8px;}
+.navbar-text{float:left;margin-top:15px;margin-bottom:15px;}@media (min-width:768px){.navbar-text{margin-left:15px;margin-right:15px;}}
+.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7;}.navbar-default .navbar-brand{color:#777777;}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent;}
+.navbar-default .navbar-text{color:#777777;}
+.navbar-default .navbar-nav>li>a{color:#777777;}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333333;background-color:transparent;}
+.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555555;background-color:#e7e7e7;}
+.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#cccccc;background-color:transparent;}
+.navbar-default .navbar-toggle{border-color:#dddddd;}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#dddddd;}
+.navbar-default .navbar-toggle .icon-bar{background-color:#cccccc;}
+.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e6e6e6;}
+.navbar-default .navbar-nav>.dropdown>a:hover .caret,.navbar-default .navbar-nav>.dropdown>a:focus .caret{border-top-color:#333333;border-bottom-color:#333333;}
+.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#e7e7e7;color:#555555;}.navbar-default .navbar-nav>.open>a .caret,.navbar-default .navbar-nav>.open>a:hover .caret,.navbar-default .navbar-nav>.open>a:focus .caret{border-top-color:#555555;border-bottom-color:#555555;}
+.navbar-default .navbar-nav>.dropdown>a .caret{border-top-color:#777777;border-bottom-color:#777777;}
+@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777777;}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333333;background-color:transparent;} .navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555555;background-color:#e7e7e7;} .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#cccccc;background-color:transparent;}}
+.navbar-default .navbar-link{color:#777777;}.navbar-default .navbar-link:hover{color:#333333;}
+.navbar-inverse{background-color:#222222;border-color:#080808;}.navbar-inverse .navbar-brand{color:#999999;}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#ffffff;background-color:transparent;}
+.navbar-inverse .navbar-text{color:#999999;}
+.navbar-inverse .navbar-nav>li>a{color:#999999;}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#ffffff;background-color:transparent;}
+.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#ffffff;background-color:#080808;}
+.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444444;background-color:transparent;}
+.navbar-inverse .navbar-toggle{border-color:#333333;}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333333;}
+.navbar-inverse .navbar-toggle .icon-bar{background-color:#ffffff;}
+.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010;}
+.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#080808;color:#ffffff;}
+.navbar-inverse .navbar-nav>.dropdown>a:hover .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
+.navbar-inverse .navbar-nav>.dropdown>a .caret{border-top-color:#999999;border-bottom-color:#999999;}
+.navbar-inverse .navbar-nav>.open>a .caret,.navbar-inverse .navbar-nav>.open>a:hover .caret,.navbar-inverse .navbar-nav>.open>a:focus .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
+@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808;} .navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999999;}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#ffffff;background-color:transparent;} .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#ffffff;background-color:#080808;} .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444444;background-color:transparent;}}
+.navbar-inverse .navbar-link{color:#999999;}.navbar-inverse .navbar-link:hover{color:#ffffff;}
+.jumbotron{padding:30px;margin-bottom:30px;font-size:21px;font-weight:200;line-height:2.1428571435;color:inherit;background-color:#eeeeee;}.jumbotron h1{line-height:1;color:inherit;}
+.jumbotron p{line-height:1.4;}
+.container .jumbotron{border-radius:6px;}
+@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px;}.container .jumbotron{padding-left:60px;padding-right:60px;} .jumbotron h1{font-size:63px;}}
+.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px;}.alert h4{margin-top:0;color:inherit;}
+.alert .alert-link{font-weight:bold;}
+.alert>p,.alert>ul{margin-bottom:0;}
+.alert>p+p{margin-top:5px;}
+.alert-dismissable{padding-right:35px;}.alert-dismissable .close{position:relative;top:-2px;right:-21px;color:inherit;}
+.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#468847;}.alert-success hr{border-top-color:#c9e2b3;}
+.alert-success .alert-link{color:#356635;}
+.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad;}.alert-info hr{border-top-color:#a6e1ec;}
+.alert-info .alert-link{color:#2d6987;}
+.alert-warning{background-color:#fcf8e3;border-color:#fbeed5;color:#c09853;}.alert-warning hr{border-top-color:#f8e5be;}
+.alert-warning .alert-link{color:#a47e3c;}
+.alert-danger{background-color:#f2dede;border-color:#eed3d7;color:#b94a48;}.alert-danger hr{border-top-color:#e6c1c7;}
+.alert-danger .alert-link{color:#953b39;}
+.list-group{margin-bottom:20px;padding-left:0;}
+.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#ffffff;border:1px solid #dddddd;}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px;}
+.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px;}
+.list-group-item>.badge{float:right;}
+.list-group-item>.badge+.badge{margin-right:5px;}
+a.list-group-item{color:#555555;}a.list-group-item .list-group-item-heading{color:#333333;}
+a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;background-color:#f5f5f5;}
+.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#ffffff;background-color:#428bca;border-color:#428bca;}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading{color:inherit;}
+.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#e1edf7;}
+.list-group-item-heading{margin-top:0;margin-bottom:5px;}
+.list-group-item-text{margin-bottom:0;line-height:1.3;}
+.panel{margin-bottom:20px;background-color:#ffffff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:0 1px 1px rgba(0, 0, 0, 0.05);}
+.panel-body{padding:15px;}.panel-body:before,.panel-body:after{content:" ";display:table;}
+.panel-body:after{clear:both;}
+.panel-body:before,.panel-body:after{content:" ";display:table;}
+.panel-body:after{clear:both;}
+.panel>.list-group{margin-bottom:0;}.panel>.list-group .list-group-item{border-width:1px 0;}.panel>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0;}
+.panel>.list-group .list-group-item:last-child{border-bottom:0;}
+.panel-heading+.list-group .list-group-item:first-child{border-top-width:0;}
+.panel>.table{margin-bottom:0;}
+.panel>.panel-body+.table{border-top:1px solid #dddddd;}
+.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px;}
+.panel-title{margin-top:0;margin-bottom:0;font-size:16px;}.panel-title>a{color:inherit;}
+.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #dddddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px;}
+.panel-group .panel{margin-bottom:0;border-radius:4px;overflow:hidden;}.panel-group .panel+.panel{margin-top:5px;}
+.panel-group .panel-heading{border-bottom:0;}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #dddddd;}
+.panel-group .panel-footer{border-top:0;}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #dddddd;}
+.panel-default{border-color:#dddddd;}.panel-default>.panel-heading{color:#333333;background-color:#f5f5f5;border-color:#dddddd;}.panel-default>.panel-heading+.panel-collapse .panel-body{border-top-color:#dddddd;}
+.panel-default>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#dddddd;}
+.panel-primary{border-color:#428bca;}.panel-primary>.panel-heading{color:#ffffff;background-color:#428bca;border-color:#428bca;}.panel-primary>.panel-heading+.panel-collapse .panel-body{border-top-color:#428bca;}
+.panel-primary>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#428bca;}
+.panel-success{border-color:#d6e9c6;}.panel-success>.panel-heading{color:#468847;background-color:#dff0d8;border-color:#d6e9c6;}.panel-success>.panel-heading+.panel-collapse .panel-body{border-top-color:#d6e9c6;}
+.panel-success>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#d6e9c6;}
+.panel-warning{border-color:#fbeed5;}.panel-warning>.panel-heading{color:#c09853;background-color:#fcf8e3;border-color:#fbeed5;}.panel-warning>.panel-heading+.panel-collapse .panel-body{border-top-color:#fbeed5;}
+.panel-warning>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#fbeed5;}
+.panel-danger{border-color:#eed3d7;}.panel-danger>.panel-heading{color:#b94a48;background-color:#f2dede;border-color:#eed3d7;}.panel-danger>.panel-heading+.panel-collapse .panel-body{border-top-color:#eed3d7;}
+.panel-danger>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#eed3d7;}
+.panel-info{border-color:#bce8f1;}.panel-info>.panel-heading{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1;}.panel-info>.panel-heading+.panel-collapse .panel-body{border-top-color:#bce8f1;}
+.panel-info>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#bce8f1;}
+.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);}
+.well-lg{padding:24px;border-radius:6px;}
+.well-sm{padding:9px;border-radius:3px;}
+.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20);}.close:hover,.close:focus{color:#000000;text-decoration:none;cursor:pointer;opacity:0.5;filter:alpha(opacity=50);}
+button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;}
+.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;background-color:#ffffff;background-clip:padding-box;border:1px solid #cccccc;border:1px solid rgba(0, 0, 0, 0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);white-space:normal;}.popover.top{margin-top:-10px;}
+.popover.right{margin-left:10px;}
+.popover.bottom{margin-top:10px;}
+.popover.left{margin-left:-10px;}
+.popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0;}
+.popover-content{padding:9px 14px;}
+.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid;}
+.popover .arrow{border-width:11px;}
+.popover .arrow:after{border-width:10px;content:"";}
+.popover.top .arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999999;border-top-color:rgba(0, 0, 0, 0.25);bottom:-11px;}.popover.top .arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#ffffff;}
+.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999999;border-right-color:rgba(0, 0, 0, 0.25);}.popover.right .arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#ffffff;}
+.popover.bottom .arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999999;border-bottom-color:rgba(0, 0, 0, 0.25);top:-11px;}.popover.bottom .arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#ffffff;}
+.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999999;border-left-color:rgba(0, 0, 0, 0.25);}.popover.left .arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#ffffff;bottom:-10px;}
+.modal-open{overflow:hidden;}body.modal-open,.modal-open .navbar-fixed-top,.modal-open .navbar-fixed-bottom{margin-right:15px;}
+.modal{display:none;overflow:auto;overflow-y:scroll;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform 0.3s ease-out;-moz-transition:-moz-transform 0.3s ease-out;-o-transition:-o-transform 0.3s ease-out;transition:transform 0.3s ease-out;}
+.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);transform:translate(0, 0);}
+.modal-dialog{margin-left:auto;margin-right:auto;width:auto;padding:10px;z-index:1050;}
+.modal-content{position:relative;background-color:#ffffff;border:1px solid #999999;border:1px solid rgba(0, 0, 0, 0.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0, 0, 0, 0.5);box-shadow:0 3px 9px rgba(0, 0, 0, 0.5);background-clip:padding-box;outline:none;}
+.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1030;background-color:#000000;}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0);}
+.modal-backdrop.in{opacity:0.5;filter:alpha(opacity=50);}
+.modal-header{padding:15px;border-bottom:1px solid #e5e5e5;min-height:16.428571429px;}
+.modal-header .close{margin-top:-2px;}
+.modal-title{margin:0;line-height:1.428571429;}
+.modal-body{position:relative;padding:20px;}
+.modal-footer{margin-top:15px;padding:19px 20px 20px;text-align:right;border-top:1px solid #e5e5e5;}.modal-footer:before,.modal-footer:after{content:" ";display:table;}
+.modal-footer:after{clear:both;}
+.modal-footer:before,.modal-footer:after{content:" ";display:table;}
+.modal-footer:after{clear:both;}
+.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0;}
+.modal-footer .btn-group .btn+.btn{margin-left:-1px;}
+.modal-footer .btn-block+.btn-block{margin-left:0;}
+@media screen and (min-width:768px){.modal-dialog{left:50%;right:auto;width:600px;padding-top:30px;padding-bottom:30px;} .modal-content{-webkit-box-shadow:0 5px 15px rgba(0, 0, 0, 0.5);box-shadow:0 5px 15px rgba(0, 0, 0, 0.5);}}.clearfix:before,.clearfix:after{content:" ";display:table;}
+.clearfix:after{clear:both;}
+.pull-right{float:right !important;}
+.pull-left{float:left !important;}
+.hide{display:none !important;}
+.show{display:block !important;}
+.invisible{visibility:hidden;}
+.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;}
+.affix{position:fixed;}
+@-ms-viewport{width:device-width;}@media screen and (max-width:400px){@-ms-viewport{width:320px;}}.hidden{display:none !important;visibility:hidden !important;}
+.visible-xs{display:none !important;}tr.visible-xs{display:none !important;}
+th.visible-xs,td.visible-xs{display:none !important;}
+@media (max-width:767px){.visible-xs{display:block !important;}tr.visible-xs{display:table-row !important;} th.visible-xs,td.visible-xs{display:table-cell !important;}}@media (min-width:768px) and (max-width:991px){.visible-xs.visible-sm{display:block !important;}tr.visible-xs.visible-sm{display:table-row !important;} th.visible-xs.visible-sm,td.visible-xs.visible-sm{display:table-cell !important;}}
+@media (min-width:992px) and (max-width:1199px){.visible-xs.visible-md{display:block !important;}tr.visible-xs.visible-md{display:table-row !important;} th.visible-xs.visible-md,td.visible-xs.visible-md{display:table-cell !important;}}
+@media (min-width:1200px){.visible-xs.visible-lg{display:block !important;}tr.visible-xs.visible-lg{display:table-row !important;} th.visible-xs.visible-lg,td.visible-xs.visible-lg{display:table-cell !important;}}
+.visible-sm{display:none !important;}tr.visible-sm{display:none !important;}
+th.visible-sm,td.visible-sm{display:none !important;}
+@media (max-width:767px){.visible-sm.visible-xs{display:block !important;}tr.visible-sm.visible-xs{display:table-row !important;} th.visible-sm.visible-xs,td.visible-sm.visible-xs{display:table-cell !important;}}
+@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important;}tr.visible-sm{display:table-row !important;} th.visible-sm,td.visible-sm{display:table-cell !important;}}@media (min-width:992px) and (max-width:1199px){.visible-sm.visible-md{display:block !important;}tr.visible-sm.visible-md{display:table-row !important;} th.visible-sm.visible-md,td.visible-sm.visible-md{display:table-cell !important;}}
+@media (min-width:1200px){.visible-sm.visible-lg{display:block !important;}tr.visible-sm.visible-lg{display:table-row !important;} th.visible-sm.visible-lg,td.visible-sm.visible-lg{display:table-cell !important;}}
+.visible-md{display:none !important;}tr.visible-md{display:none !important;}
+th.visible-md,td.visible-md{display:none !important;}
+@media (max-width:767px){.visible-md.visible-xs{display:block !important;}tr.visible-md.visible-xs{display:table-row !important;} th.visible-md.visible-xs,td.visible-md.visible-xs{display:table-cell !important;}}
+@media (min-width:768px) and (max-width:991px){.visible-md.visible-sm{display:block !important;}tr.visible-md.visible-sm{display:table-row !important;} th.visible-md.visible-sm,td.visible-md.visible-sm{display:table-cell !important;}}
+@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important;}tr.visible-md{display:table-row !important;} th.visible-md,td.visible-md{display:table-cell !important;}}@media (min-width:1200px){.visible-md.visible-lg{display:block !important;}tr.visible-md.visible-lg{display:table-row !important;} th.visible-md.visible-lg,td.visible-md.visible-lg{display:table-cell !important;}}
+.visible-lg{display:none !important;}tr.visible-lg{display:none !important;}
+th.visible-lg,td.visible-lg{display:none !important;}
+@media (max-width:767px){.visible-lg.visible-xs{display:block !important;}tr.visible-lg.visible-xs{display:table-row !important;} th.visible-lg.visible-xs,td.visible-lg.visible-xs{display:table-cell !important;}}
+@media (min-width:768px) and (max-width:991px){.visible-lg.visible-sm{display:block !important;}tr.visible-lg.visible-sm{display:table-row !important;} th.visible-lg.visible-sm,td.visible-lg.visible-sm{display:table-cell !important;}}
+@media (min-width:992px) and (max-width:1199px){.visible-lg.visible-md{display:block !important;}tr.visible-lg.visible-md{display:table-row !important;} th.visible-lg.visible-md,td.visible-lg.visible-md{display:table-cell !important;}}
+@media (min-width:1200px){.visible-lg{display:block !important;}tr.visible-lg{display:table-row !important;} th.visible-lg,td.visible-lg{display:table-cell !important;}}
+.hidden-xs{display:block !important;}tr.hidden-xs{display:table-row !important;}
+th.hidden-xs,td.hidden-xs{display:table-cell !important;}
+@media (max-width:767px){.hidden-xs{display:none !important;}tr.hidden-xs{display:none !important;} th.hidden-xs,td.hidden-xs{display:none !important;}}@media (min-width:768px) and (max-width:991px){.hidden-xs.hidden-sm{display:none !important;}tr.hidden-xs.hidden-sm{display:none !important;} th.hidden-xs.hidden-sm,td.hidden-xs.hidden-sm{display:none !important;}}
+@media (min-width:992px) and (max-width:1199px){.hidden-xs.hidden-md{display:none !important;}tr.hidden-xs.hidden-md{display:none !important;} th.hidden-xs.hidden-md,td.hidden-xs.hidden-md{display:none !important;}}
+@media (min-width:1200px){.hidden-xs.hidden-lg{display:none !important;}tr.hidden-xs.hidden-lg{display:none !important;} th.hidden-xs.hidden-lg,td.hidden-xs.hidden-lg{display:none !important;}}
+.hidden-sm{display:block !important;}tr.hidden-sm{display:table-row !important;}
+th.hidden-sm,td.hidden-sm{display:table-cell !important;}
+@media (max-width:767px){.hidden-sm.hidden-xs{display:none !important;}tr.hidden-sm.hidden-xs{display:none !important;} th.hidden-sm.hidden-xs,td.hidden-sm.hidden-xs{display:none !important;}}
+@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important;}tr.hidden-sm{display:none !important;} th.hidden-sm,td.hidden-sm{display:none !important;}}@media (min-width:992px) and (max-width:1199px){.hidden-sm.hidden-md{display:none !important;}tr.hidden-sm.hidden-md{display:none !important;} th.hidden-sm.hidden-md,td.hidden-sm.hidden-md{display:none !important;}}
+@media (min-width:1200px){.hidden-sm.hidden-lg{display:none !important;}tr.hidden-sm.hidden-lg{display:none !important;} th.hidden-sm.hidden-lg,td.hidden-sm.hidden-lg{display:none !important;}}
+.hidden-md{display:block !important;}tr.hidden-md{display:table-row !important;}
+th.hidden-md,td.hidden-md{display:table-cell !important;}
+@media (max-width:767px){.hidden-md.hidden-xs{display:none !important;}tr.hidden-md.hidden-xs{display:none !important;} th.hidden-md.hidden-xs,td.hidden-md.hidden-xs{display:none !important;}}
+@media (min-width:768px) and (max-width:991px){.hidden-md.hidden-sm{display:none !important;}tr.hidden-md.hidden-sm{display:none !important;} th.hidden-md.hidden-sm,td.hidden-md.hidden-sm{display:none !important;}}
+@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important;}tr.hidden-md{display:none !important;} th.hidden-md,td.hidden-md{display:none !important;}}@media (min-width:1200px){.hidden-md.hidden-lg{display:none !important;}tr.hidden-md.hidden-lg{display:none !important;} th.hidden-md.hidden-lg,td.hidden-md.hidden-lg{display:none !important;}}
+.hidden-lg{display:block !important;}tr.hidden-lg{display:table-row !important;}
+th.hidden-lg,td.hidden-lg{display:table-cell !important;}
+@media (max-width:767px){.hidden-lg.hidden-xs{display:none !important;}tr.hidden-lg.hidden-xs{display:none !important;} th.hidden-lg.hidden-xs,td.hidden-lg.hidden-xs{display:none !important;}}
+@media (min-width:768px) and (max-width:991px){.hidden-lg.hidden-sm{display:none !important;}tr.hidden-lg.hidden-sm{display:none !important;} th.hidden-lg.hidden-sm,td.hidden-lg.hidden-sm{display:none !important;}}
+@media (min-width:992px) and (max-width:1199px){.hidden-lg.hidden-md{display:none !important;}tr.hidden-lg.hidden-md{display:none !important;} th.hidden-lg.hidden-md,td.hidden-lg.hidden-md{display:none !important;}}
+@media (min-width:1200px){.hidden-lg{display:none !important;}tr.hidden-lg{display:none !important;} th.hidden-lg,td.hidden-lg{display:none !important;}}
+.visible-print{display:none !important;}tr.visible-print{display:none !important;}
+th.visible-print,td.visible-print{display:none !important;}
+@media print{.visible-print{display:block !important;}tr.visible-print{display:table-row !important;} th.visible-print,td.visible-print{display:table-cell !important;} .hidden-print{display:none !important;}tr.hidden-print{display:none !important;} th.hidden-print,td.hidden-print{display:none !important;}}.fade{opacity:0;-webkit-transition:opacity 0.15s linear;transition:opacity 0.15s linear;}.fade.in{opacity:1;}
+.collapse{display:none;}.collapse.in{display:block;}
+.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease;transition:height 0.35s ease;}
diff --git a/gddo-server/assets/third_party/bootstrap/js/bootstrap.js b/gddo-server/assets/third_party/bootstrap/js/bootstrap.js
new file mode 100644
index 0000000..2c64257
--- /dev/null
+++ b/gddo-server/assets/third_party/bootstrap/js/bootstrap.js
@@ -0,0 +1,1999 @@
+/**
+* bootstrap.js v3.0.0 by @fat and @mdo
+* Copyright 2013 Twitter Inc.
+* http://www.apache.org/licenses/LICENSE-2.0
+*/
+if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
+
+/* ========================================================================
+ * Bootstrap: transition.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#transitions
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
+  // ============================================================
+
+  function transitionEnd() {
+    var el = document.createElement('bootstrap')
+
+    var transEndEventNames = {
+      'WebkitTransition' : 'webkitTransitionEnd'
+    , 'MozTransition'    : 'transitionend'
+    , 'OTransition'      : 'oTransitionEnd otransitionend'
+    , 'transition'       : 'transitionend'
+    }
+
+    for (var name in transEndEventNames) {
+      if (el.style[name] !== undefined) {
+        return { end: transEndEventNames[name] }
+      }
+    }
+  }
+
+  // http://blog.alexmaccaw.com/css-transitions
+  $.fn.emulateTransitionEnd = function (duration) {
+    var called = false, $el = this
+    $(this).one($.support.transition.end, function () { called = true })
+    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
+    setTimeout(callback, duration)
+    return this
+  }
+
+  $(function () {
+    $.support.transition = transitionEnd()
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: alert.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#alerts
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // ALERT CLASS DEFINITION
+  // ======================
+
+  var dismiss = '[data-dismiss="alert"]'
+  var Alert   = function (el) {
+    $(el).on('click', dismiss, this.close)
+  }
+
+  Alert.prototype.close = function (e) {
+    var $this    = $(this)
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+    }
+
+    var $parent = $(selector)
+
+    if (e) e.preventDefault()
+
+    if (!$parent.length) {
+      $parent = $this.hasClass('alert') ? $this : $this.parent()
+    }
+
+    $parent.trigger(e = $.Event('close.bs.alert'))
+
+    if (e.isDefaultPrevented()) return
+
+    $parent.removeClass('in')
+
+    function removeElement() {
+      $parent.trigger('closed.bs.alert').remove()
+    }
+
+    $.support.transition && $parent.hasClass('fade') ?
+      $parent
+        .one($.support.transition.end, removeElement)
+        .emulateTransitionEnd(150) :
+      removeElement()
+  }
+
+
+  // ALERT PLUGIN DEFINITION
+  // =======================
+
+  var old = $.fn.alert
+
+  $.fn.alert = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.alert')
+
+      if (!data) $this.data('bs.alert', (data = new Alert(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  $.fn.alert.Constructor = Alert
+
+
+  // ALERT NO CONFLICT
+  // =================
+
+  $.fn.alert.noConflict = function () {
+    $.fn.alert = old
+    return this
+  }
+
+
+  // ALERT DATA-API
+  // ==============
+
+  $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: button.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#buttons
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // BUTTON PUBLIC CLASS DEFINITION
+  // ==============================
+
+  var Button = function (element, options) {
+    this.$element = $(element)
+    this.options  = $.extend({}, Button.DEFAULTS, options)
+  }
+
+  Button.DEFAULTS = {
+    loadingText: 'loading...'
+  }
+
+  Button.prototype.setState = function (state) {
+    var d    = 'disabled'
+    var $el  = this.$element
+    var val  = $el.is('input') ? 'val' : 'html'
+    var data = $el.data()
+
+    state = state + 'Text'
+
+    if (!data.resetText) $el.data('resetText', $el[val]())
+
+    $el[val](data[state] || this.options[state])
+
+    // push to event loop to allow forms to submit
+    setTimeout(function () {
+      state == 'loadingText' ?
+        $el.addClass(d).attr(d, d) :
+        $el.removeClass(d).removeAttr(d);
+    }, 0)
+  }
+
+  Button.prototype.toggle = function () {
+    var $parent = this.$element.closest('[data-toggle="buttons"]')
+
+    if ($parent.length) {
+      var $input = this.$element.find('input')
+        .prop('checked', !this.$element.hasClass('active'))
+        .trigger('change')
+      if ($input.prop('type') === 'radio') $parent.find('.active').removeClass('active')
+    }
+
+    this.$element.toggleClass('active')
+  }
+
+
+  // BUTTON PLUGIN DEFINITION
+  // ========================
+
+  var old = $.fn.button
+
+  $.fn.button = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.button')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.button', (data = new Button(this, options)))
+
+      if (option == 'toggle') data.toggle()
+      else if (option) data.setState(option)
+    })
+  }
+
+  $.fn.button.Constructor = Button
+
+
+  // BUTTON NO CONFLICT
+  // ==================
+
+  $.fn.button.noConflict = function () {
+    $.fn.button = old
+    return this
+  }
+
+
+  // BUTTON DATA-API
+  // ===============
+
+  $(document).on('click.bs.button.data-api', '[data-toggle^=button]', function (e) {
+    var $btn = $(e.target)
+    if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+    $btn.button('toggle')
+    e.preventDefault()
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: carousel.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#carousel
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // CAROUSEL CLASS DEFINITION
+  // =========================
+
+  var Carousel = function (element, options) {
+    this.$element    = $(element)
+    this.$indicators = this.$element.find('.carousel-indicators')
+    this.options     = options
+    this.paused      =
+    this.sliding     =
+    this.interval    =
+    this.$active     =
+    this.$items      = null
+
+    this.options.pause == 'hover' && this.$element
+      .on('mouseenter', $.proxy(this.pause, this))
+      .on('mouseleave', $.proxy(this.cycle, this))
+  }
+
+  Carousel.DEFAULTS = {
+    interval: 5000
+  , pause: 'hover'
+  , wrap: true
+  }
+
+  Carousel.prototype.cycle =  function (e) {
+    e || (this.paused = false)
+
+    this.interval && clearInterval(this.interval)
+
+    this.options.interval
+      && !this.paused
+      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+
+    return this
+  }
+
+  Carousel.prototype.getActiveIndex = function () {
+    this.$active = this.$element.find('.item.active')
+    this.$items  = this.$active.parent().children()
+
+    return this.$items.index(this.$active)
+  }
+
+  Carousel.prototype.to = function (pos) {
+    var that        = this
+    var activeIndex = this.getActiveIndex()
+
+    if (pos > (this.$items.length - 1) || pos < 0) return
+
+    if (this.sliding)       return this.$element.one('slid', function () { that.to(pos) })
+    if (activeIndex == pos) return this.pause().cycle()
+
+    return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos]))
+  }
+
+  Carousel.prototype.pause = function (e) {
+    e || (this.paused = true)
+
+    if (this.$element.find('.next, .prev').length && $.support.transition.end) {
+      this.$element.trigger($.support.transition.end)
+      this.cycle(true)
+    }
+
+    this.interval = clearInterval(this.interval)
+
+    return this
+  }
+
+  Carousel.prototype.next = function () {
+    if (this.sliding) return
+    return this.slide('next')
+  }
+
+  Carousel.prototype.prev = function () {
+    if (this.sliding) return
+    return this.slide('prev')
+  }
+
+  Carousel.prototype.slide = function (type, next) {
+    var $active   = this.$element.find('.item.active')
+    var $next     = next || $active[type]()
+    var isCycling = this.interval
+    var direction = type == 'next' ? 'left' : 'right'
+    var fallback  = type == 'next' ? 'first' : 'last'
+    var that      = this
+
+    if (!$next.length) {
+      if (!this.options.wrap) return
+      $next = this.$element.find('.item')[fallback]()
+    }
+
+    this.sliding = true
+
+    isCycling && this.pause()
+
+    var e = $.Event('slide.bs.carousel', { relatedTarget: $next[0], direction: direction })
+
+    if ($next.hasClass('active')) return
+
+    if (this.$indicators.length) {
+      this.$indicators.find('.active').removeClass('active')
+      this.$element.one('slid', function () {
+        var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()])
+        $nextIndicator && $nextIndicator.addClass('active')
+      })
+    }
+
+    if ($.support.transition && this.$element.hasClass('slide')) {
+      this.$element.trigger(e)
+      if (e.isDefaultPrevented()) return
+      $next.addClass(type)
+      $next[0].offsetWidth // force reflow
+      $active.addClass(direction)
+      $next.addClass(direction)
+      $active
+        .one($.support.transition.end, function () {
+          $next.removeClass([type, direction].join(' ')).addClass('active')
+          $active.removeClass(['active', direction].join(' '))
+          that.sliding = false
+          setTimeout(function () { that.$element.trigger('slid') }, 0)
+        })
+        .emulateTransitionEnd(600)
+    } else {
+      this.$element.trigger(e)
+      if (e.isDefaultPrevented()) return
+      $active.removeClass('active')
+      $next.addClass('active')
+      this.sliding = false
+      this.$element.trigger('slid')
+    }
+
+    isCycling && this.cycle()
+
+    return this
+  }
+
+
+  // CAROUSEL PLUGIN DEFINITION
+  // ==========================
+
+  var old = $.fn.carousel
+
+  $.fn.carousel = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.carousel')
+      var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
+      var action  = typeof option == 'string' ? option : options.slide
+
+      if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
+      if (typeof option == 'number') data.to(option)
+      else if (action) data[action]()
+      else if (options.interval) data.pause().cycle()
+    })
+  }
+
+  $.fn.carousel.Constructor = Carousel
+
+
+  // CAROUSEL NO CONFLICT
+  // ====================
+
+  $.fn.carousel.noConflict = function () {
+    $.fn.carousel = old
+    return this
+  }
+
+
+  // CAROUSEL DATA-API
+  // =================
+
+  $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) {
+    var $this   = $(this), href
+    var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+    var options = $.extend({}, $target.data(), $this.data())
+    var slideIndex = $this.attr('data-slide-to')
+    if (slideIndex) options.interval = false
+
+    $target.carousel(options)
+
+    if (slideIndex = $this.attr('data-slide-to')) {
+      $target.data('bs.carousel').to(slideIndex)
+    }
+
+    e.preventDefault()
+  })
+
+  $(window).on('load', function () {
+    $('[data-ride="carousel"]').each(function () {
+      var $carousel = $(this)
+      $carousel.carousel($carousel.data())
+    })
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: collapse.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#collapse
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // COLLAPSE PUBLIC CLASS DEFINITION
+  // ================================
+
+  var Collapse = function (element, options) {
+    this.$element      = $(element)
+    this.options       = $.extend({}, Collapse.DEFAULTS, options)
+    this.transitioning = null
+
+    if (this.options.parent) this.$parent = $(this.options.parent)
+    if (this.options.toggle) this.toggle()
+  }
+
+  Collapse.DEFAULTS = {
+    toggle: true
+  }
+
+  Collapse.prototype.dimension = function () {
+    var hasWidth = this.$element.hasClass('width')
+    return hasWidth ? 'width' : 'height'
+  }
+
+  Collapse.prototype.show = function () {
+    if (this.transitioning || this.$element.hasClass('in')) return
+
+    var startEvent = $.Event('show.bs.collapse')
+    this.$element.trigger(startEvent)
+    if (startEvent.isDefaultPrevented()) return
+
+    var actives = this.$parent && this.$parent.find('> .panel > .in')
+
+    if (actives && actives.length) {
+      var hasData = actives.data('bs.collapse')
+      if (hasData && hasData.transitioning) return
+      actives.collapse('hide')
+      hasData || actives.data('bs.collapse', null)
+    }
+
+    var dimension = this.dimension()
+
+    this.$element
+      .removeClass('collapse')
+      .addClass('collapsing')
+      [dimension](0)
+
+    this.transitioning = 1
+
+    var complete = function () {
+      this.$element
+        .removeClass('collapsing')
+        .addClass('in')
+        [dimension]('auto')
+      this.transitioning = 0
+      this.$element.trigger('shown.bs.collapse')
+    }
+
+    if (!$.support.transition) return complete.call(this)
+
+    var scrollSize = $.camelCase(['scroll', dimension].join('-'))
+
+    this.$element
+      .one($.support.transition.end, $.proxy(complete, this))
+      .emulateTransitionEnd(350)
+      [dimension](this.$element[0][scrollSize])
+  }
+
+  Collapse.prototype.hide = function () {
+    if (this.transitioning || !this.$element.hasClass('in')) return
+
+    var startEvent = $.Event('hide.bs.collapse')
+    this.$element.trigger(startEvent)
+    if (startEvent.isDefaultPrevented()) return
+
+    var dimension = this.dimension()
+
+    this.$element
+      [dimension](this.$element[dimension]())
+      [0].offsetHeight
+
+    this.$element
+      .addClass('collapsing')
+      .removeClass('collapse')
+      .removeClass('in')
+
+    this.transitioning = 1
+
+    var complete = function () {
+      this.transitioning = 0
+      this.$element
+        .trigger('hidden.bs.collapse')
+        .removeClass('collapsing')
+        .addClass('collapse')
+    }
+
+    if (!$.support.transition) return complete.call(this)
+
+    this.$element
+      [dimension](0)
+      .one($.support.transition.end, $.proxy(complete, this))
+      .emulateTransitionEnd(350)
+  }
+
+  Collapse.prototype.toggle = function () {
+    this[this.$element.hasClass('in') ? 'hide' : 'show']()
+  }
+
+
+  // COLLAPSE PLUGIN DEFINITION
+  // ==========================
+
+  var old = $.fn.collapse
+
+  $.fn.collapse = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.collapse')
+      var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+      if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.collapse.Constructor = Collapse
+
+
+  // COLLAPSE NO CONFLICT
+  // ====================
+
+  $.fn.collapse.noConflict = function () {
+    $.fn.collapse = old
+    return this
+  }
+
+
+  // COLLAPSE DATA-API
+  // =================
+
+  $(document).on('click.bs.collapse.data-api', '[data-toggle=collapse]', function (e) {
+    var $this   = $(this), href
+    var target  = $this.attr('data-target')
+        || e.preventDefault()
+        || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
+    var $target = $(target)
+    var data    = $target.data('bs.collapse')
+    var option  = data ? 'toggle' : $this.data()
+    var parent  = $this.attr('data-parent')
+    var $parent = parent && $(parent)
+
+    if (!data || !data.transitioning) {
+      if ($parent) $parent.find('[data-toggle=collapse][data-parent="' + parent + '"]').not($this).addClass('collapsed')
+      $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
+    }
+
+    $target.collapse(option)
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: dropdown.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#dropdowns
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // DROPDOWN CLASS DEFINITION
+  // =========================
+
+  var backdrop = '.dropdown-backdrop'
+  var toggle   = '[data-toggle=dropdown]'
+  var Dropdown = function (element) {
+    var $el = $(element).on('click.bs.dropdown', this.toggle)
+  }
+
+  Dropdown.prototype.toggle = function (e) {
+    var $this = $(this)
+
+    if ($this.is('.disabled, :disabled')) return
+
+    var $parent  = getParent($this)
+    var isActive = $parent.hasClass('open')
+
+    clearMenus()
+
+    if (!isActive) {
+      if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
+        // if mobile we we use a backdrop because click events don't delegate
+        $('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus)
+      }
+
+      $parent.trigger(e = $.Event('show.bs.dropdown'))
+
+      if (e.isDefaultPrevented()) return
+
+      $parent
+        .toggleClass('open')
+        .trigger('shown.bs.dropdown')
+
+      $this.focus()
+    }
+
+    return false
+  }
+
+  Dropdown.prototype.keydown = function (e) {
+    if (!/(38|40|27)/.test(e.keyCode)) return
+
+    var $this = $(this)
+
+    e.preventDefault()
+    e.stopPropagation()
+
+    if ($this.is('.disabled, :disabled')) return
+
+    var $parent  = getParent($this)
+    var isActive = $parent.hasClass('open')
+
+    if (!isActive || (isActive && e.keyCode == 27)) {
+      if (e.which == 27) $parent.find(toggle).focus()
+      return $this.click()
+    }
+
+    var $items = $('[role=menu] li:not(.divider):visible a', $parent)
+
+    if (!$items.length) return
+
+    var index = $items.index($items.filter(':focus'))
+
+    if (e.keyCode == 38 && index > 0)                 index--                        // up
+    if (e.keyCode == 40 && index < $items.length - 1) index++                        // down
+    if (!~index)                                      index=0
+
+    $items.eq(index).focus()
+  }
+
+  function clearMenus() {
+    $(backdrop).remove()
+    $(toggle).each(function (e) {
+      var $parent = getParent($(this))
+      if (!$parent.hasClass('open')) return
+      $parent.trigger(e = $.Event('hide.bs.dropdown'))
+      if (e.isDefaultPrevented()) return
+      $parent.removeClass('open').trigger('hidden.bs.dropdown')
+    })
+  }
+
+  function getParent($this) {
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+    }
+
+    var $parent = selector && $(selector)
+
+    return $parent && $parent.length ? $parent : $this.parent()
+  }
+
+
+  // DROPDOWN PLUGIN DEFINITION
+  // ==========================
+
+  var old = $.fn.dropdown
+
+  $.fn.dropdown = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('dropdown')
+
+      if (!data) $this.data('dropdown', (data = new Dropdown(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  $.fn.dropdown.Constructor = Dropdown
+
+
+  // DROPDOWN NO CONFLICT
+  // ====================
+
+  $.fn.dropdown.noConflict = function () {
+    $.fn.dropdown = old
+    return this
+  }
+
+
+  // APPLY TO STANDARD DROPDOWN ELEMENTS
+  // ===================================
+
+  $(document)
+    .on('click.bs.dropdown.data-api', clearMenus)
+    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+    .on('click.bs.dropdown.data-api'  , toggle, Dropdown.prototype.toggle)
+    .on('keydown.bs.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: modal.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#modals
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // MODAL CLASS DEFINITION
+  // ======================
+
+  var Modal = function (element, options) {
+    this.options   = options
+    this.$element  = $(element)
+    this.$backdrop =
+    this.isShown   = null
+
+    if (this.options.remote) this.$element.load(this.options.remote)
+  }
+
+  Modal.DEFAULTS = {
+      backdrop: true
+    , keyboard: true
+    , show: true
+  }
+
+  Modal.prototype.toggle = function (_relatedTarget) {
+    return this[!this.isShown ? 'show' : 'hide'](_relatedTarget)
+  }
+
+  Modal.prototype.show = function (_relatedTarget) {
+    var that = this
+    var e    = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
+
+    this.$element.trigger(e)
+
+    if (this.isShown || e.isDefaultPrevented()) return
+
+    this.isShown = true
+
+    this.escape()
+
+    this.$element.on('click.dismiss.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
+
+    this.backdrop(function () {
+      var transition = $.support.transition && that.$element.hasClass('fade')
+
+      if (!that.$element.parent().length) {
+        that.$element.appendTo(document.body) // don't move modals dom position
+      }
+
+      that.$element.show()
+
+      if (transition) {
+        that.$element[0].offsetWidth // force reflow
+      }
+
+      that.$element
+        .addClass('in')
+        .attr('aria-hidden', false)
+
+      that.enforceFocus()
+
+      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
+
+      transition ?
+        that.$element.find('.modal-dialog') // wait for modal to slide in
+          .one($.support.transition.end, function () {
+            that.$element.focus().trigger(e)
+          })
+          .emulateTransitionEnd(300) :
+        that.$element.focus().trigger(e)
+    })
+  }
+
+  Modal.prototype.hide = function (e) {
+    if (e) e.preventDefault()
+
+    e = $.Event('hide.bs.modal')
+
+    this.$element.trigger(e)
+
+    if (!this.isShown || e.isDefaultPrevented()) return
+
+    this.isShown = false
+
+    this.escape()
+
+    $(document).off('focusin.bs.modal')
+
+    this.$element
+      .removeClass('in')
+      .attr('aria-hidden', true)
+      .off('click.dismiss.modal')
+
+    $.support.transition && this.$element.hasClass('fade') ?
+      this.$element
+        .one($.support.transition.end, $.proxy(this.hideModal, this))
+        .emulateTransitionEnd(300) :
+      this.hideModal()
+  }
+
+  Modal.prototype.enforceFocus = function () {
+    $(document)
+      .off('focusin.bs.modal') // guard against infinite focus loop
+      .on('focusin.bs.modal', $.proxy(function (e) {
+        if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
+          this.$element.focus()
+        }
+      }, this))
+  }
+
+  Modal.prototype.escape = function () {
+    if (this.isShown && this.options.keyboard) {
+      this.$element.on('keyup.dismiss.bs.modal', $.proxy(function (e) {
+        e.which == 27 && this.hide()
+      }, this))
+    } else if (!this.isShown) {
+      this.$element.off('keyup.dismiss.bs.modal')
+    }
+  }
+
+  Modal.prototype.hideModal = function () {
+    var that = this
+    this.$element.hide()
+    this.backdrop(function () {
+      that.removeBackdrop()
+      that.$element.trigger('hidden.bs.modal')
+    })
+  }
+
+  Modal.prototype.removeBackdrop = function () {
+    this.$backdrop && this.$backdrop.remove()
+    this.$backdrop = null
+  }
+
+  Modal.prototype.backdrop = function (callback) {
+    var that    = this
+    var animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+    if (this.isShown && this.options.backdrop) {
+      var doAnimate = $.support.transition && animate
+
+      this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
+        .appendTo(document.body)
+
+      this.$element.on('click.dismiss.modal', $.proxy(function (e) {
+        if (e.target !== e.currentTarget) return
+        this.options.backdrop == 'static'
+          ? this.$element[0].focus.call(this.$element[0])
+          : this.hide.call(this)
+      }, this))
+
+      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+      this.$backdrop.addClass('in')
+
+      if (!callback) return
+
+      doAnimate ?
+        this.$backdrop
+          .one($.support.transition.end, callback)
+          .emulateTransitionEnd(150) :
+        callback()
+
+    } else if (!this.isShown && this.$backdrop) {
+      this.$backdrop.removeClass('in')
+
+      $.support.transition && this.$element.hasClass('fade')?
+        this.$backdrop
+          .one($.support.transition.end, callback)
+          .emulateTransitionEnd(150) :
+        callback()
+
+    } else if (callback) {
+      callback()
+    }
+  }
+
+
+  // MODAL PLUGIN DEFINITION
+  // =======================
+
+  var old = $.fn.modal
+
+  $.fn.modal = function (option, _relatedTarget) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.modal')
+      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
+      if (typeof option == 'string') data[option](_relatedTarget)
+      else if (options.show) data.show(_relatedTarget)
+    })
+  }
+
+  $.fn.modal.Constructor = Modal
+
+
+  // MODAL NO CONFLICT
+  // =================
+
+  $.fn.modal.noConflict = function () {
+    $.fn.modal = old
+    return this
+  }
+
+
+  // MODAL DATA-API
+  // ==============
+
+  $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
+    var $this   = $(this)
+    var href    = $this.attr('href')
+    var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
+    var option  = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
+
+    e.preventDefault()
+
+    $target
+      .modal(option, this)
+      .one('hide', function () {
+        $this.is(':visible') && $this.focus()
+      })
+  })
+
+  $(document)
+    .on('show.bs.modal',  '.modal', function () { $(document.body).addClass('modal-open') })
+    .on('hidden.bs.modal', '.modal', function () { $(document.body).removeClass('modal-open') })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: tooltip.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#tooltip
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // TOOLTIP PUBLIC CLASS DEFINITION
+  // ===============================
+
+  var Tooltip = function (element, options) {
+    this.type       =
+    this.options    =
+    this.enabled    =
+    this.timeout    =
+    this.hoverState =
+    this.$element   = null
+
+    this.init('tooltip', element, options)
+  }
+
+  Tooltip.DEFAULTS = {
+    animation: true
+  , placement: 'top'
+  , selector: false
+  , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
+  , trigger: 'hover focus'
+  , title: ''
+  , delay: 0
+  , html: false
+  , container: false
+  }
+
+  Tooltip.prototype.init = function (type, element, options) {
+    this.enabled  = true
+    this.type     = type
+    this.$element = $(element)
+    this.options  = this.getOptions(options)
+
+    var triggers = this.options.trigger.split(' ')
+
+    for (var i = triggers.length; i--;) {
+      var trigger = triggers[i]
+
+      if (trigger == 'click') {
+        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+      } else if (trigger != 'manual') {
+        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focus'
+        var eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'
+
+        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+      }
+    }
+
+    this.options.selector ?
+      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+      this.fixTitle()
+  }
+
+  Tooltip.prototype.getDefaults = function () {
+    return Tooltip.DEFAULTS
+  }
+
+  Tooltip.prototype.getOptions = function (options) {
+    options = $.extend({}, this.getDefaults(), this.$element.data(), options)
+
+    if (options.delay && typeof options.delay == 'number') {
+      options.delay = {
+        show: options.delay
+      , hide: options.delay
+      }
+    }
+
+    return options
+  }
+
+  Tooltip.prototype.getDelegateOptions = function () {
+    var options  = {}
+    var defaults = this.getDefaults()
+
+    this._options && $.each(this._options, function (key, value) {
+      if (defaults[key] != value) options[key] = value
+    })
+
+    return options
+  }
+
+  Tooltip.prototype.enter = function (obj) {
+    var self = obj instanceof this.constructor ?
+      obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
+
+    clearTimeout(self.timeout)
+
+    self.hoverState = 'in'
+
+    if (!self.options.delay || !self.options.delay.show) return self.show()
+
+    self.timeout = setTimeout(function () {
+      if (self.hoverState == 'in') self.show()
+    }, self.options.delay.show)
+  }
+
+  Tooltip.prototype.leave = function (obj) {
+    var self = obj instanceof this.constructor ?
+      obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
+
+    clearTimeout(self.timeout)
+
+    self.hoverState = 'out'
+
+    if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+    self.timeout = setTimeout(function () {
+      if (self.hoverState == 'out') self.hide()
+    }, self.options.delay.hide)
+  }
+
+  Tooltip.prototype.show = function () {
+    var e = $.Event('show.bs.'+ this.type)
+
+    if (this.hasContent() && this.enabled) {
+      this.$element.trigger(e)
+
+      if (e.isDefaultPrevented()) return
+
+      var $tip = this.tip()
+
+      this.setContent()
+
+      if (this.options.animation) $tip.addClass('fade')
+
+      var placement = typeof this.options.placement == 'function' ?
+        this.options.placement.call(this, $tip[0], this.$element[0]) :
+        this.options.placement
+
+      var autoToken = /\s?auto?\s?/i
+      var autoPlace = autoToken.test(placement)
+      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
+
+      $tip
+        .detach()
+        .css({ top: 0, left: 0, display: 'block' })
+        .addClass(placement)
+
+      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
+
+      var pos          = this.getPosition()
+      var actualWidth  = $tip[0].offsetWidth
+      var actualHeight = $tip[0].offsetHeight
+
+      if (autoPlace) {
+        var $parent = this.$element.parent()
+
+        var orgPlacement = placement
+        var docScroll    = document.documentElement.scrollTop || document.body.scrollTop
+        var parentWidth  = this.options.container == 'body' ? window.innerWidth  : $parent.outerWidth()
+        var parentHeight = this.options.container == 'body' ? window.innerHeight : $parent.outerHeight()
+        var parentLeft   = this.options.container == 'body' ? 0 : $parent.offset().left
+
+        placement = placement == 'bottom' && pos.top   + pos.height  + actualHeight - docScroll > parentHeight  ? 'top'    :
+                    placement == 'top'    && pos.top   - docScroll   - actualHeight < 0                         ? 'bottom' :
+                    placement == 'right'  && pos.right + actualWidth > parentWidth                              ? 'left'   :
+                    placement == 'left'   && pos.left  - actualWidth < parentLeft                               ? 'right'  :
+                    placement
+
+        $tip
+          .removeClass(orgPlacement)
+          .addClass(placement)
+      }
+
+      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
+
+      this.applyPlacement(calculatedOffset, placement)
+      this.$element.trigger('shown.bs.' + this.type)
+    }
+  }
+
+  Tooltip.prototype.applyPlacement = function(offset, placement) {
+    var replace
+    var $tip   = this.tip()
+    var width  = $tip[0].offsetWidth
+    var height = $tip[0].offsetHeight
+
+    // manually read margins because getBoundingClientRect includes difference
+    var marginTop = parseInt($tip.css('margin-top'), 10)
+    var marginLeft = parseInt($tip.css('margin-left'), 10)
+
+    // we must check for NaN for ie 8/9
+    if (isNaN(marginTop))  marginTop  = 0
+    if (isNaN(marginLeft)) marginLeft = 0
+
+    offset.top  = offset.top  + marginTop
+    offset.left = offset.left + marginLeft
+
+    $tip
+      .offset(offset)
+      .addClass('in')
+
+    // check to see if placing tip in new offset caused the tip to resize itself
+    var actualWidth  = $tip[0].offsetWidth
+    var actualHeight = $tip[0].offsetHeight
+
+    if (placement == 'top' && actualHeight != height) {
+      replace = true
+      offset.top = offset.top + height - actualHeight
+    }
+
+    if (/bottom|top/.test(placement)) {
+      var delta = 0
+
+      if (offset.left < 0) {
+        delta       = offset.left * -2
+        offset.left = 0
+
+        $tip.offset(offset)
+
+        actualWidth  = $tip[0].offsetWidth
+        actualHeight = $tip[0].offsetHeight
+      }
+
+      this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
+    } else {
+      this.replaceArrow(actualHeight - height, actualHeight, 'top')
+    }
+
+    if (replace) $tip.offset(offset)
+  }
+
+  Tooltip.prototype.replaceArrow = function(delta, dimension, position) {
+    this.arrow().css(position, delta ? (50 * (1 - delta / dimension) + "%") : '')
+  }
+
+  Tooltip.prototype.setContent = function () {
+    var $tip  = this.tip()
+    var title = this.getTitle()
+
+    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+    $tip.removeClass('fade in top bottom left right')
+  }
+
+  Tooltip.prototype.hide = function () {
+    var that = this
+    var $tip = this.tip()
+    var e    = $.Event('hide.bs.' + this.type)
+
+    function complete() {
+      if (that.hoverState != 'in') $tip.detach()
+    }
+
+    this.$element.trigger(e)
+
+    if (e.isDefaultPrevented()) return
+
+    $tip.removeClass('in')
+
+    $.support.transition && this.$tip.hasClass('fade') ?
+      $tip
+        .one($.support.transition.end, complete)
+        .emulateTransitionEnd(150) :
+      complete()
+
+    this.$element.trigger('hidden.bs.' + this.type)
+
+    return this
+  }
+
+  Tooltip.prototype.fixTitle = function () {
+    var $e = this.$element
+    if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
+      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
+    }
+  }
+
+  Tooltip.prototype.hasContent = function () {
+    return this.getTitle()
+  }
+
+  Tooltip.prototype.getPosition = function () {
+    var el = this.$element[0]
+    return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
+      width: el.offsetWidth
+    , height: el.offsetHeight
+    }, this.$element.offset())
+  }
+
+  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
+    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2  } :
+           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2  } :
+           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
+        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width   }
+  }
+
+  Tooltip.prototype.getTitle = function () {
+    var title
+    var $e = this.$element
+    var o  = this.options
+
+    title = $e.attr('data-original-title')
+      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
+
+    return title
+  }
+
+  Tooltip.prototype.tip = function () {
+    return this.$tip = this.$tip || $(this.options.template)
+  }
+
+  Tooltip.prototype.arrow = function () {
+    return this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')
+  }
+
+  Tooltip.prototype.validate = function () {
+    if (!this.$element[0].parentNode) {
+      this.hide()
+      this.$element = null
+      this.options  = null
+    }
+  }
+
+  Tooltip.prototype.enable = function () {
+    this.enabled = true
+  }
+
+  Tooltip.prototype.disable = function () {
+    this.enabled = false
+  }
+
+  Tooltip.prototype.toggleEnabled = function () {
+    this.enabled = !this.enabled
+  }
+
+  Tooltip.prototype.toggle = function (e) {
+    var self = e ? $(e.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type) : this
+    self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
+  }
+
+  Tooltip.prototype.destroy = function () {
+    this.hide().$element.off('.' + this.type).removeData('bs.' + this.type)
+  }
+
+
+  // TOOLTIP PLUGIN DEFINITION
+  // =========================
+
+  var old = $.fn.tooltip
+
+  $.fn.tooltip = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.tooltip')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.tooltip.Constructor = Tooltip
+
+
+  // TOOLTIP NO CONFLICT
+  // ===================
+
+  $.fn.tooltip.noConflict = function () {
+    $.fn.tooltip = old
+    return this
+  }
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: popover.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#popovers
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // POPOVER PUBLIC CLASS DEFINITION
+  // ===============================
+
+  var Popover = function (element, options) {
+    this.init('popover', element, options)
+  }
+
+  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
+
+  Popover.DEFAULTS = $.extend({} , $.fn.tooltip.Constructor.DEFAULTS, {
+    placement: 'right'
+  , trigger: 'click'
+  , content: ''
+  , template: '<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
+  })
+
+
+  // NOTE: POPOVER EXTENDS tooltip.js
+  // ================================
+
+  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
+
+  Popover.prototype.constructor = Popover
+
+  Popover.prototype.getDefaults = function () {
+    return Popover.DEFAULTS
+  }
+
+  Popover.prototype.setContent = function () {
+    var $tip    = this.tip()
+    var title   = this.getTitle()
+    var content = this.getContent()
+
+    $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+    $tip.find('.popover-content')[this.options.html ? 'html' : 'text'](content)
+
+    $tip.removeClass('fade top bottom left right in')
+
+    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
+    // this manually by checking the contents.
+    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
+  }
+
+  Popover.prototype.hasContent = function () {
+    return this.getTitle() || this.getContent()
+  }
+
+  Popover.prototype.getContent = function () {
+    var $e = this.$element
+    var o  = this.options
+
+    return $e.attr('data-content')
+      || (typeof o.content == 'function' ?
+            o.content.call($e[0]) :
+            o.content)
+  }
+
+  Popover.prototype.arrow = function () {
+    return this.$arrow = this.$arrow || this.tip().find('.arrow')
+  }
+
+  Popover.prototype.tip = function () {
+    if (!this.$tip) this.$tip = $(this.options.template)
+    return this.$tip
+  }
+
+
+  // POPOVER PLUGIN DEFINITION
+  // =========================
+
+  var old = $.fn.popover
+
+  $.fn.popover = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.popover')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.popover.Constructor = Popover
+
+
+  // POPOVER NO CONFLICT
+  // ===================
+
+  $.fn.popover.noConflict = function () {
+    $.fn.popover = old
+    return this
+  }
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: scrollspy.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#scrollspy
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // SCROLLSPY CLASS DEFINITION
+  // ==========================
+
+  function ScrollSpy(element, options) {
+    var href
+    var process  = $.proxy(this.process, this)
+
+    this.$element       = $(element).is('body') ? $(window) : $(element)
+    this.$body          = $('body')
+    this.$scrollElement = this.$element.on('scroll.bs.scroll-spy.data-api', process)
+    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)
+    this.selector       = (this.options.target
+      || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+      || '') + ' .nav li > a'
+    this.offsets        = $([])
+    this.targets        = $([])
+    this.activeTarget   = null
+
+    this.refresh()
+    this.process()
+  }
+
+  ScrollSpy.DEFAULTS = {
+    offset: 10
+  }
+
+  ScrollSpy.prototype.refresh = function () {
+    var offsetMethod = this.$element[0] == window ? 'offset' : 'position'
+
+    this.offsets = $([])
+    this.targets = $([])
+
+    var self     = this
+    var $targets = this.$body
+      .find(this.selector)
+      .map(function () {
+        var $el   = $(this)
+        var href  = $el.data('target') || $el.attr('href')
+        var $href = /^#\w/.test(href) && $(href)
+
+        return ($href
+          && $href.length
+          && [[ $href[offsetMethod]().top + (!$.isWindow(self.$scrollElement.get(0)) && self.$scrollElement.scrollTop()), href ]]) || null
+      })
+      .sort(function (a, b) { return a[0] - b[0] })
+      .each(function () {
+        self.offsets.push(this[0])
+        self.targets.push(this[1])
+      })
+  }
+
+  ScrollSpy.prototype.process = function () {
+    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset
+    var scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight
+    var maxScroll    = scrollHeight - this.$scrollElement.height()
+    var offsets      = this.offsets
+    var targets      = this.targets
+    var activeTarget = this.activeTarget
+    var i
+
+    if (scrollTop >= maxScroll) {
+      return activeTarget != (i = targets.last()[0]) && this.activate(i)
+    }
+
+    for (i = offsets.length; i--;) {
+      activeTarget != targets[i]
+        && scrollTop >= offsets[i]
+        && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+        && this.activate( targets[i] )
+    }
+  }
+
+  ScrollSpy.prototype.activate = function (target) {
+    this.activeTarget = target
+
+    $(this.selector)
+      .parents('.active')
+      .removeClass('active')
+
+    var selector = this.selector
+      + '[data-target="' + target + '"],'
+      + this.selector + '[href="' + target + '"]'
+
+    var active = $(selector)
+      .parents('li')
+      .addClass('active')
+
+    if (active.parent('.dropdown-menu').length)  {
+      active = active
+        .closest('li.dropdown')
+        .addClass('active')
+    }
+
+    active.trigger('activate')
+  }
+
+
+  // SCROLLSPY PLUGIN DEFINITION
+  // ===========================
+
+  var old = $.fn.scrollspy
+
+  $.fn.scrollspy = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.scrollspy')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.scrollspy.Constructor = ScrollSpy
+
+
+  // SCROLLSPY NO CONFLICT
+  // =====================
+
+  $.fn.scrollspy.noConflict = function () {
+    $.fn.scrollspy = old
+    return this
+  }
+
+
+  // SCROLLSPY DATA-API
+  // ==================
+
+  $(window).on('load', function () {
+    $('[data-spy="scroll"]').each(function () {
+      var $spy = $(this)
+      $spy.scrollspy($spy.data())
+    })
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: tab.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#tabs
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // TAB CLASS DEFINITION
+  // ====================
+
+  var Tab = function (element) {
+    this.element = $(element)
+  }
+
+  Tab.prototype.show = function () {
+    var $this    = this.element
+    var $ul      = $this.closest('ul:not(.dropdown-menu)')
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+    }
+
+    if ($this.parent('li').hasClass('active')) return
+
+    var previous = $ul.find('.active:last a')[0]
+    var e        = $.Event('show.bs.tab', {
+      relatedTarget: previous
+    })
+
+    $this.trigger(e)
+
+    if (e.isDefaultPrevented()) return
+
+    var $target = $(selector)
+
+    this.activate($this.parent('li'), $ul)
+    this.activate($target, $target.parent(), function () {
+      $this.trigger({
+        type: 'shown.bs.tab'
+      , relatedTarget: previous
+      })
+    })
+  }
+
+  Tab.prototype.activate = function (element, container, callback) {
+    var $active    = container.find('> .active')
+    var transition = callback
+      && $.support.transition
+      && $active.hasClass('fade')
+
+    function next() {
+      $active
+        .removeClass('active')
+        .find('> .dropdown-menu > .active')
+        .removeClass('active')
+
+      element.addClass('active')
+
+      if (transition) {
+        element[0].offsetWidth // reflow for transition
+        element.addClass('in')
+      } else {
+        element.removeClass('fade')
+      }
+
+      if (element.parent('.dropdown-menu')) {
+        element.closest('li.dropdown').addClass('active')
+      }
+
+      callback && callback()
+    }
+
+    transition ?
+      $active
+        .one($.support.transition.end, next)
+        .emulateTransitionEnd(150) :
+      next()
+
+    $active.removeClass('in')
+  }
+
+
+  // TAB PLUGIN DEFINITION
+  // =====================
+
+  var old = $.fn.tab
+
+  $.fn.tab = function ( option ) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.tab')
+
+      if (!data) $this.data('bs.tab', (data = new Tab(this)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.tab.Constructor = Tab
+
+
+  // TAB NO CONFLICT
+  // ===============
+
+  $.fn.tab.noConflict = function () {
+    $.fn.tab = old
+    return this
+  }
+
+
+  // TAB DATA-API
+  // ============
+
+  $(document).on('click.bs.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
+    e.preventDefault()
+    $(this).tab('show')
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: affix.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#affix
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // AFFIX CLASS DEFINITION
+  // ======================
+
+  var Affix = function (element, options) {
+    this.options = $.extend({}, Affix.DEFAULTS, options)
+    this.$window = $(window)
+      .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
+      .on('click.bs.affix.data-api',  $.proxy(this.checkPositionWithEventLoop, this))
+
+    this.$element = $(element)
+    this.affixed  =
+    this.unpin    = null
+
+    this.checkPosition()
+  }
+
+  Affix.RESET = 'affix affix-top affix-bottom'
+
+  Affix.DEFAULTS = {
+    offset: 0
+  }
+
+  Affix.prototype.checkPositionWithEventLoop = function () {
+    setTimeout($.proxy(this.checkPosition, this), 1)
+  }
+
+  Affix.prototype.checkPosition = function () {
+    if (!this.$element.is(':visible')) return
+
+    var scrollHeight = $(document).height()
+    var scrollTop    = this.$window.scrollTop()
+    var position     = this.$element.offset()
+    var offset       = this.options.offset
+    var offsetTop    = offset.top
+    var offsetBottom = offset.bottom
+
+    if (typeof offset != 'object')         offsetBottom = offsetTop = offset
+    if (typeof offsetTop == 'function')    offsetTop    = offset.top()
+    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()
+
+    var affix = this.unpin   != null && (scrollTop + this.unpin <= position.top) ? false :
+                offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? 'bottom' :
+                offsetTop    != null && (scrollTop <= offsetTop) ? 'top' : false
+
+    if (this.affixed === affix) return
+    if (this.unpin) this.$element.css('top', '')
+
+    this.affixed = affix
+    this.unpin   = affix == 'bottom' ? position.top - scrollTop : null
+
+    this.$element.removeClass(Affix.RESET).addClass('affix' + (affix ? '-' + affix : ''))
+
+    if (affix == 'bottom') {
+      this.$element.offset({ top: document.body.offsetHeight - offsetBottom - this.$element.height() })
+    }
+  }
+
+
+  // AFFIX PLUGIN DEFINITION
+  // =======================
+
+  var old = $.fn.affix
+
+  $.fn.affix = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.affix')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.affix.Constructor = Affix
+
+
+  // AFFIX NO CONFLICT
+  // =================
+
+  $.fn.affix.noConflict = function () {
+    $.fn.affix = old
+    return this
+  }
+
+
+  // AFFIX DATA-API
+  // ==============
+
+  $(window).on('load', function () {
+    $('[data-spy="affix"]').each(function () {
+      var $spy = $(this)
+      var data = $spy.data()
+
+      data.offset = data.offset || {}
+
+      if (data.offsetBottom) data.offset.bottom = data.offsetBottom
+      if (data.offsetTop)    data.offset.top    = data.offsetTop
+
+      $spy.affix(data)
+    })
+  })
+
+}(window.jQuery);
diff --git a/gddo-server/assets/third_party/bootstrap/js/bootstrap.min.js b/gddo-server/assets/third_party/bootstrap/js/bootstrap.min.js
new file mode 100644
index 0000000..1765631
--- /dev/null
+++ b/gddo-server/assets/third_party/bootstrap/js/bootstrap.min.js
@@ -0,0 +1,6 @@
+/**
+* bootstrap.js v3.0.0 by @fat and @mdo
+* Copyright 2013 Twitter Inc.
+* http://www.apache.org/licenses/LICENSE-2.0
+*/
+if(!jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(window.jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]');if(a.length){var b=this.$element.find("input").prop("checked",!this.$element.hasClass("active")).trigger("change");"radio"===b.prop("type")&&a.find(".active").removeClass("active")}this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(window.jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}this.sliding=!0,f&&this.pause();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(window.jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('<div class="dropdown-backdrop"/>').insertAfter(a(this)).on("click",b),f.trigger(d=a.Event("show.bs.dropdown")),d.isDefaultPrevented())return;f.toggleClass("open").trigger("shown.bs.dropdown"),e.focus()}return!1}},f.prototype.keydown=function(b){if(/(38|40|27)/.test(b.keyCode)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var f=c(d),g=f.hasClass("open");if(!g||g&&27==b.keyCode)return 27==b.which&&f.find(e).focus(),d.click();var h=a("[role=menu] li:not(.divider):visible a",f);if(h.length){var i=h.index(h.filter(":focus"));38==b.keyCode&&i>0&&i--,40==b.keyCode&&i<h.length-1&&i++,~i||(i=0),h.eq(i).focus()}}}};var g=a.fn.dropdown;a.fn.dropdown=function(b){return this.each(function(){var c=a(this),d=c.data("dropdown");d||c.data("dropdown",d=new f(this)),"string"==typeof b&&d[b].call(c)})},a.fn.dropdown.Constructor=f,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=g,this},a(document).on("click.bs.dropdown.data-api",b).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",e,f.prototype.toggle).on("keydown.bs.dropdown.data-api",e+", [role=menu]",f.prototype.keydown)}(window.jQuery),+function(a){"use strict";var b=function(b,c){this.options=c,this.$element=a(b),this.$backdrop=this.isShown=null,this.options.remote&&this.$element.load(this.options.remote)};b.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},b.prototype.toggle=function(a){return this[this.isShown?"hide":"show"](a)},b.prototype.show=function(b){var c=this,d=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(d),this.isShown||d.isDefaultPrevented()||(this.isShown=!0,this.escape(),this.$element.on("click.dismiss.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.backdrop(function(){var d=a.support.transition&&c.$element.hasClass("fade");c.$element.parent().length||c.$element.appendTo(document.body),c.$element.show(),d&&c.$element[0].offsetWidth,c.$element.addClass("in").attr("aria-hidden",!1),c.enforceFocus();var e=a.Event("shown.bs.modal",{relatedTarget:b});d?c.$element.find(".modal-dialog").one(a.support.transition.end,function(){c.$element.focus().trigger(e)}).emulateTransitionEnd(300):c.$element.focus().trigger(e)}))},b.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").attr("aria-hidden",!0).off("click.dismiss.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one(a.support.transition.end,a.proxy(this.hideModal,this)).emulateTransitionEnd(300):this.hideModal())},b.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.focus()},this))},b.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keyup.dismiss.bs.modal")},b.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.removeBackdrop(),a.$element.trigger("hidden.bs.modal")})},b.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},b.prototype.backdrop=function(b){var c=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var d=a.support.transition&&c;if(this.$backdrop=a('<div class="modal-backdrop '+c+'" />').appendTo(document.body),this.$element.on("click.dismiss.modal",a.proxy(function(a){a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),d&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;d?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()):b&&b()};var c=a.fn.modal;a.fn.modal=function(c,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},b.DEFAULTS,e.data(),"object"==typeof c&&c);f||e.data("bs.modal",f=new b(this,g)),"string"==typeof c?f[c](d):g.show&&f.show(d)})},a.fn.modal.Constructor=b,a.fn.modal.noConflict=function(){return a.fn.modal=c,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d=c.attr("href"),e=a(c.attr("data-target")||d&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({remote:!/#/.test(d)&&d},e.data(),c.data());b.preventDefault(),e.modal(f,this).one("hide",function(){c.is(":visible")&&c.focus()})}),a(document).on("show.bs.modal",".modal",function(){a(document.body).addClass("modal-open")}).on("hidden.bs.modal",".modal",function(){a(document.body).removeClass("modal-open")})}(window.jQuery),+function(a){"use strict";var b=function(a,b){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",a,b)};b.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},b.prototype.init=function(b,c,d){this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d);for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focus",i="hover"==g?"mouseleave":"blur";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},b.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},b.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);return clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show),void 0):c.show()},b.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide),void 0):c.hide()},b.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){if(this.$element.trigger(b),b.isDefaultPrevented())return;var c=this.tip();this.setContent(),this.options.animation&&c.addClass("fade");var d="function"==typeof this.options.placement?this.options.placement.call(this,c[0],this.$element[0]):this.options.placement,e=/\s?auto?\s?/i,f=e.test(d);f&&(d=d.replace(e,"")||"top"),c.detach().css({top:0,left:0,display:"block"}).addClass(d),this.options.container?c.appendTo(this.options.container):c.insertAfter(this.$element);var g=this.getPosition(),h=c[0].offsetWidth,i=c[0].offsetHeight;if(f){var j=this.$element.parent(),k=d,l=document.documentElement.scrollTop||document.body.scrollTop,m="body"==this.options.container?window.innerWidth:j.outerWidth(),n="body"==this.options.container?window.innerHeight:j.outerHeight(),o="body"==this.options.container?0:j.offset().left;d="bottom"==d&&g.top+g.height+i-l>n?"top":"top"==d&&g.top-l-i<0?"bottom":"right"==d&&g.right+h>m?"left":"left"==d&&g.left-h<o?"right":d,c.removeClass(k).addClass(d)}var p=this.getCalculatedOffset(d,g,h,i);this.applyPlacement(p,d),this.$element.trigger("shown.bs."+this.type)}},b.prototype.applyPlacement=function(a,b){var c,d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),a.top=a.top+g,a.left=a.left+h,d.offset(a).addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;if("top"==b&&j!=f&&(c=!0,a.top=a.top+f-j),/bottom|top/.test(b)){var k=0;a.left<0&&(k=-2*a.left,a.left=0,d.offset(a),i=d[0].offsetWidth,j=d[0].offsetHeight),this.replaceArrow(k-e+i,i,"left")}else this.replaceArrow(j-f,j,"top");c&&d.offset(a)},b.prototype.replaceArrow=function(a,b,c){this.arrow().css(c,a?50*(1-a/b)+"%":"")},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},b.prototype.hide=function(){function b(){"in"!=c.hoverState&&d.detach()}var c=this,d=this.tip(),e=a.Event("hide.bs."+this.type);return this.$element.trigger(e),e.isDefaultPrevented()?void 0:(d.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?d.one(a.support.transition.end,b).emulateTransitionEnd(150):b(),this.$element.trigger("hidden.bs."+this.type),this)},b.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},b.prototype.hasContent=function(){return this.getTitle()},b.prototype.getPosition=function(){var b=this.$element[0];return a.extend({},"function"==typeof b.getBoundingClientRect?b.getBoundingClientRect():{width:b.offsetWidth,height:b.offsetHeight},this.$element.offset())},b.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},b.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},b.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},b.prototype.validate=function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},b.prototype.enable=function(){this.enabled=!0},b.prototype.disable=function(){this.enabled=!1},b.prototype.toggleEnabled=function(){this.enabled=!this.enabled},b.prototype.toggle=function(b){var c=b?a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type):this;c.tip().hasClass("in")?c.leave(c):c.enter(c)},b.prototype.destroy=function(){this.hide().$element.off("."+this.type).removeData("bs."+this.type)};var c=a.fn.tooltip;a.fn.tooltip=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof c&&c;e||d.data("bs.tooltip",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.tooltip.Constructor=b,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=c,this}}(window.jQuery),+function(a){"use strict";var b=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");b.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(window.jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(window.jQuery);
\ No newline at end of file
diff --git a/gddo-server/assets/third_party/jquery.timeago.js b/gddo-server/assets/third_party/jquery.timeago.js
new file mode 100644
index 0000000..e3731b2
--- /dev/null
+++ b/gddo-server/assets/third_party/jquery.timeago.js
@@ -0,0 +1,184 @@
+/**
+ * Timeago is a jQuery plugin that makes it easy to support automatically
+ * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
+ *
+ * @name timeago
+ * @version 1.1.0
+ * @requires jQuery v1.2.3+
+ * @author Ryan McGeary
+ * @license MIT License - http://www.opensource.org/licenses/mit-license.php
+ *
+ * For usage and examples, visit:
+ * http://timeago.yarp.com/
+ *
+ * Copyright (c) 2008-2013, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org)
+ */
+
+(function (factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module.
+    define(['jquery'], factory);
+  } else {
+    // Browser globals
+    factory(jQuery);
+  }
+}(function ($) {
+  $.timeago = function(timestamp) {
+    if (timestamp instanceof Date) {
+      return inWords(timestamp);
+    } else if (typeof timestamp === "string") {
+      return inWords($.timeago.parse(timestamp));
+    } else if (typeof timestamp === "number") {
+      return inWords(new Date(timestamp));
+    } else {
+      return inWords($.timeago.datetime(timestamp));
+    }
+  };
+  var $t = $.timeago;
+
+  $.extend($.timeago, {
+    settings: {
+      refreshMillis: 60000,
+      allowFuture: false,
+      localeTitle: false,
+      strings: {
+        prefixAgo: null,
+        prefixFromNow: null,
+        suffixAgo: "ago",
+        suffixFromNow: "from now",
+        seconds: "less than a minute",
+        minute: "about a minute",
+        minutes: "%d minutes",
+        hour: "about an hour",
+        hours: "about %d hours",
+        day: "a day",
+        days: "%d days",
+        month: "about a month",
+        months: "%d months",
+        year: "about a year",
+        years: "%d years",
+        wordSeparator: " ",
+        numbers: []
+      }
+    },
+    inWords: function(distanceMillis) {
+      var $l = this.settings.strings;
+      var prefix = $l.prefixAgo;
+      var suffix = $l.suffixAgo;
+      if (this.settings.allowFuture) {
+        if (distanceMillis < 0) {
+          prefix = $l.prefixFromNow;
+          suffix = $l.suffixFromNow;
+        }
+      }
+
+      var seconds = Math.abs(distanceMillis) / 1000;
+      var minutes = seconds / 60;
+      var hours = minutes / 60;
+      var days = hours / 24;
+      var years = days / 365;
+
+      function substitute(stringOrFunction, number) {
+        var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
+        var value = ($l.numbers && $l.numbers[number]) || number;
+        return string.replace(/%d/i, value);
+      }
+
+      var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
+        seconds < 90 && substitute($l.minute, 1) ||
+        minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
+        minutes < 90 && substitute($l.hour, 1) ||
+        hours < 24 && substitute($l.hours, Math.round(hours)) ||
+        hours < 42 && substitute($l.day, 1) ||
+        days < 30 && substitute($l.days, Math.round(days)) ||
+        days < 45 && substitute($l.month, 1) ||
+        days < 365 && substitute($l.months, Math.round(days / 30)) ||
+        years < 1.5 && substitute($l.year, 1) ||
+        substitute($l.years, Math.round(years));
+
+      var separator = $l.wordSeparator || "";
+      if ($l.wordSeparator === undefined) { separator = " "; }
+      return $.trim([prefix, words, suffix].join(separator));
+    },
+    parse: function(iso8601) {
+      var s = $.trim(iso8601);
+      s = s.replace(/\.\d+/,""); // remove milliseconds
+      s = s.replace(/-/,"/").replace(/-/,"/");
+      s = s.replace(/T/," ").replace(/Z/," UTC");
+      s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
+      return new Date(s);
+    },
+    datetime: function(elem) {
+      var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title");
+      return $t.parse(iso8601);
+    },
+    isTime: function(elem) {
+      // jQuery's `is()` doesn't play well with HTML5 in IE
+      return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
+    }
+  });
+
+  // functions that can be called via $(el).timeago('action')
+  // init is default when no action is given
+  // functions are called with context of a single element
+  var functions = {
+    init: function(){
+      var refresh_el = $.proxy(refresh, this);
+      refresh_el();
+      var $s = $t.settings;
+      if ($s.refreshMillis > 0) {
+        setInterval(refresh_el, $s.refreshMillis);
+      }
+    },
+    update: function(time){
+      $(this).data('timeago', { datetime: $t.parse(time) });
+      refresh.apply(this);
+    }
+  };
+
+  $.fn.timeago = function(action, options) {
+    var fn = action ? functions[action] : functions.init;
+    if(!fn){
+      throw new Error("Unknown function name '"+ action +"' for timeago");
+    }
+    // each over objects here and call the requested function
+    this.each(function(){
+      fn.call(this, options);
+    });
+    return this;
+  };
+
+  function refresh() {
+    var data = prepareData(this);
+    if (!isNaN(data.datetime)) {
+      $(this).text(inWords(data.datetime));
+    }
+    return this;
+  }
+
+  function prepareData(element) {
+    element = $(element);
+    if (!element.data("timeago")) {
+      element.data("timeago", { datetime: $t.datetime(element) });
+      var text = $.trim(element.text());
+      if ($t.settings.localeTitle) {
+        element.attr("title", element.data('timeago').datetime.toLocaleString());
+      } else if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) {
+        element.attr("title", text);
+      }
+    }
+    return element.data("timeago");
+  }
+
+  function inWords(date) {
+    return $t.inWords(distance(date));
+  }
+
+  function distance(date) {
+    return (new Date().getTime() - date.getTime());
+  }
+
+  // fix for IE6 suckage
+  document.createElement("abbr");
+  document.createElement("time");
+}));
diff --git a/gddo-server/assets/third_party/typeahead.min.js b/gddo-server/assets/third_party/typeahead.min.js
new file mode 100644
index 0000000..c085f8a
--- /dev/null
+++ b/gddo-server/assets/third_party/typeahead.min.js
@@ -0,0 +1,7 @@
+/*!
+ * typeahead.js 0.9.3
+ * https://github.com/twitter/typeahead
+ * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT
+ */
+
+!function(a){var b="0.9.3",c={isMsie:function(){var a=/(msie) ([\w.]+)/i.exec(navigator.userAgent);return a?parseInt(a[2],10):!1},isBlankString:function(a){return!a||/^\s*$/.test(a)},escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},isString:function(a){return"string"==typeof a},isNumber:function(a){return"number"==typeof a},isArray:a.isArray,isFunction:a.isFunction,isObject:a.isPlainObject,isUndefined:function(a){return"undefined"==typeof a},bind:a.proxy,bindAll:function(b){var c;for(var d in b)a.isFunction(c=b[d])&&(b[d]=a.proxy(c,b))},indexOf:function(a,b){for(var c=0;c<a.length;c++)if(a[c]===b)return c;return-1},each:a.each,map:a.map,filter:a.grep,every:function(b,c){var d=!0;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?void 0:!1}),!!d):d},some:function(b,c){var d=!1;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?!1:void 0}),!!d):d},mixin:a.extend,getUniqueId:function(){var a=0;return function(){return a++}}(),defer:function(a){setTimeout(a,0)},debounce:function(a,b,c){var d,e;return function(){var f,g,h=this,i=arguments;return f=function(){d=null,c||(e=a.apply(h,i))},g=c&&!d,clearTimeout(d),d=setTimeout(f,b),g&&(e=a.apply(h,i)),e}},throttle:function(a,b){var c,d,e,f,g,h;return g=0,h=function(){g=new Date,e=null,f=a.apply(c,d)},function(){var i=new Date,j=b-(i-g);return c=this,d=arguments,0>=j?(clearTimeout(e),e=null,g=i,f=a.apply(c,d)):e||(e=setTimeout(h,j)),f}},tokenizeQuery:function(b){return a.trim(b).toLowerCase().split(/[\s]+/)},tokenizeText:function(b){return a.trim(b).toLowerCase().split(/[\s\-_]+/)},getProtocol:function(){return location.protocol},noop:function(){}},d=function(){var a=/\s+/;return{on:function(b,c){var d;if(!c)return this;for(this._callbacks=this._callbacks||{},b=b.split(a);d=b.shift();)this._callbacks[d]=this._callbacks[d]||[],this._callbacks[d].push(c);return this},trigger:function(b,c){var d,e;if(!this._callbacks)return this;for(b=b.split(a);d=b.shift();)if(e=this._callbacks[d])for(var f=0;f<e.length;f+=1)e[f].call(this,{type:d,data:c});return this}}}(),e=function(){function b(b){b&&b.el||a.error("EventBus initialized without el"),this.$el=a(b.el)}var d="typeahead:";return c.mixin(b.prototype,{trigger:function(a){var b=[].slice.call(arguments,1);this.$el.trigger(d+a,b)}}),b}(),f=function(){function a(a){this.prefix=["__",a,"__"].join(""),this.ttlKey="__ttl__",this.keyMatcher=new RegExp("^"+this.prefix)}function b(){return(new Date).getTime()}function d(a){return JSON.stringify(c.isUndefined(a)?null:a)}function e(a){return JSON.parse(a)}var f,g;try{f=window.localStorage,f.setItem("~~~","!"),f.removeItem("~~~")}catch(h){f=null}return g=f&&window.JSON?{_prefix:function(a){return this.prefix+a},_ttlKey:function(a){return this._prefix(a)+this.ttlKey},get:function(a){return this.isExpired(a)&&this.remove(a),e(f.getItem(this._prefix(a)))},set:function(a,e,g){return c.isNumber(g)?f.setItem(this._ttlKey(a),d(b()+g)):f.removeItem(this._ttlKey(a)),f.setItem(this._prefix(a),d(e))},remove:function(a){return f.removeItem(this._ttlKey(a)),f.removeItem(this._prefix(a)),this},clear:function(){var a,b,c=[],d=f.length;for(a=0;d>a;a++)(b=f.key(a)).match(this.keyMatcher)&&c.push(b.replace(this.keyMatcher,""));for(a=c.length;a--;)this.remove(c[a]);return this},isExpired:function(a){var d=e(f.getItem(this._ttlKey(a)));return c.isNumber(d)&&b()>d?!0:!1}}:{get:c.noop,set:c.noop,remove:c.noop,clear:c.noop,isExpired:c.noop},c.mixin(a.prototype,g),a}(),g=function(){function a(a){c.bindAll(this),a=a||{},this.sizeLimit=a.sizeLimit||10,this.cache={},this.cachedKeysByAge=[]}return c.mixin(a.prototype,{get:function(a){return this.cache[a]},set:function(a,b){var c;this.cachedKeysByAge.length===this.sizeLimit&&(c=this.cachedKeysByAge.shift(),delete this.cache[c]),this.cache[a]=b,this.cachedKeysByAge.push(a)}}),a}(),h=function(){function b(a){c.bindAll(this),a=c.isString(a)?{url:a}:a,i=i||new g,h=c.isNumber(a.maxParallelRequests)?a.maxParallelRequests:h||6,this.url=a.url,this.wildcard=a.wildcard||"%QUERY",this.filter=a.filter,this.replace=a.replace,this.ajaxSettings={type:"get",cache:a.cache,timeout:a.timeout,dataType:a.dataType||"json",beforeSend:a.beforeSend},this._get=(/^throttle$/i.test(a.rateLimitFn)?c.throttle:c.debounce)(this._get,a.rateLimitWait||300)}function d(){j++}function e(){j--}function f(){return h>j}var h,i,j=0,k={};return c.mixin(b.prototype,{_get:function(a,b){function c(c){var e=d.filter?d.filter(c):c;b&&b(e),i.set(a,c)}var d=this;f()?this._sendRequest(a).done(c):this.onDeckRequestArgs=[].slice.call(arguments,0)},_sendRequest:function(b){function c(){e(),k[b]=null,f.onDeckRequestArgs&&(f._get.apply(f,f.onDeckRequestArgs),f.onDeckRequestArgs=null)}var f=this,g=k[b];return g||(d(),g=k[b]=a.ajax(b,this.ajaxSettings).always(c)),g},get:function(a,b){var d,e,f=this,g=encodeURIComponent(a||"");return b=b||c.noop,d=this.replace?this.replace(this.url,g):this.url.replace(this.wildcard,g),(e=i.get(d))?c.defer(function(){b(f.filter?f.filter(e):e)}):this._get(d,b),!!e}}),b}(),i=function(){function d(b){c.bindAll(this),c.isString(b.template)&&!b.engine&&a.error("no template engine specified"),b.local||b.prefetch||b.remote||a.error("one of local, prefetch, or remote is required"),this.name=b.name||c.getUniqueId(),this.limit=b.limit||5,this.minLength=b.minLength||1,this.header=b.header,this.footer=b.footer,this.valueKey=b.valueKey||"value",this.template=e(b.template,b.engine,this.valueKey),this.local=b.local,this.prefetch=b.prefetch,this.remote=b.remote,this.itemHash={},this.adjacencyList={},this.storage=b.name?new f(b.name):null}function e(a,b,d){var e,f;return c.isFunction(a)?e=a:c.isString(a)?(f=b.compile(a),e=c.bind(f.render,f)):e=function(a){return"<p>"+a[d]+"</p>"},e}var g={thumbprint:"thumbprint",protocol:"protocol",itemHash:"itemHash",adjacencyList:"adjacencyList"};return c.mixin(d.prototype,{_processLocalData:function(a){this._mergeProcessedData(this._processData(a))},_loadPrefetchData:function(d){function e(a){var b=d.filter?d.filter(a):a,e=m._processData(b),f=e.itemHash,h=e.adjacencyList;m.storage&&(m.storage.set(g.itemHash,f,d.ttl),m.storage.set(g.adjacencyList,h,d.ttl),m.storage.set(g.thumbprint,n,d.ttl),m.storage.set(g.protocol,c.getProtocol(),d.ttl)),m._mergeProcessedData(e)}var f,h,i,j,k,l,m=this,n=b+(d.thumbprint||"");return this.storage&&(f=this.storage.get(g.thumbprint),h=this.storage.get(g.protocol),i=this.storage.get(g.itemHash),j=this.storage.get(g.adjacencyList)),k=f!==n||h!==c.getProtocol(),d=c.isString(d)?{url:d}:d,d.ttl=c.isNumber(d.ttl)?d.ttl:864e5,i&&j&&!k?(this._mergeProcessedData({itemHash:i,adjacencyList:j}),l=a.Deferred().resolve()):l=a.getJSON(d.url).done(e),l},_transformDatum:function(a){var b=c.isString(a)?a:a[this.valueKey],d=a.tokens||c.tokenizeText(b),e={value:b,tokens:d};return c.isString(a)?(e.datum={},e.datum[this.valueKey]=a):e.datum=a,e.tokens=c.filter(e.tokens,function(a){return!c.isBlankString(a)}),e.tokens=c.map(e.tokens,function(a){return a.toLowerCase()}),e},_processData:function(a){var b=this,d={},e={};return c.each(a,function(a,f){var g=b._transformDatum(f),h=c.getUniqueId(g.value);d[h]=g,c.each(g.tokens,function(a,b){var d=b.charAt(0),f=e[d]||(e[d]=[h]);!~c.indexOf(f,h)&&f.push(h)})}),{itemHash:d,adjacencyList:e}},_mergeProcessedData:function(a){var b=this;c.mixin(this.itemHash,a.itemHash),c.each(a.adjacencyList,function(a,c){var d=b.adjacencyList[a];b.adjacencyList[a]=d?d.concat(c):c})},_getLocalSuggestions:function(a){var b,d=this,e=[],f=[],g=[];return c.each(a,function(a,b){var d=b.charAt(0);!~c.indexOf(e,d)&&e.push(d)}),c.each(e,function(a,c){var e=d.adjacencyList[c];return e?(f.push(e),(!b||e.length<b.length)&&(b=e),void 0):!1}),f.length<e.length?[]:(c.each(b,function(b,e){var h,i,j=d.itemHash[e];h=c.every(f,function(a){return~c.indexOf(a,e)}),i=h&&c.every(a,function(a){return c.some(j.tokens,function(b){return 0===b.indexOf(a)})}),i&&g.push(j)}),g)},initialize:function(){var b;return this.local&&this._processLocalData(this.local),this.transport=this.remote?new h(this.remote):null,b=this.prefetch?this._loadPrefetchData(this.prefetch):a.Deferred().resolve(),this.local=this.prefetch=this.remote=null,this.initialize=function(){return b},b},getSuggestions:function(a,b){function d(a){f=f.slice(0),c.each(a,function(a,b){var d,e=g._transformDatum(b);return d=c.some(f,function(a){return e.value===a.value}),!d&&f.push(e),f.length<g.limit}),b&&b(f)}var e,f,g=this,h=!1;a.length<this.minLength||(e=c.tokenizeQuery(a),f=this._getLocalSuggestions(e).slice(0,this.limit),f.length<this.limit&&this.transport&&(h=this.transport.get(a,d)),!h&&b&&b(f))}}),d}(),j=function(){function b(b){var d=this;c.bindAll(this),this.specialKeyCodeMap={9:"tab",27:"esc",37:"left",39:"right",13:"enter",38:"up",40:"down"},this.$hint=a(b.hint),this.$input=a(b.input).on("blur.tt",this._handleBlur).on("focus.tt",this._handleFocus).on("keydown.tt",this._handleSpecialKeyEvent),c.isMsie()?this.$input.on("keydown.tt keypress.tt cut.tt paste.tt",function(a){d.specialKeyCodeMap[a.which||a.keyCode]||c.defer(d._compareQueryToInputValue)}):this.$input.on("input.tt",this._compareQueryToInputValue),this.query=this.$input.val(),this.$overflowHelper=e(this.$input)}function e(b){return a("<span></span>").css({position:"absolute",left:"-9999px",visibility:"hidden",whiteSpace:"nowrap",fontFamily:b.css("font-family"),fontSize:b.css("font-size"),fontStyle:b.css("font-style"),fontVariant:b.css("font-variant"),fontWeight:b.css("font-weight"),wordSpacing:b.css("word-spacing"),letterSpacing:b.css("letter-spacing"),textIndent:b.css("text-indent"),textRendering:b.css("text-rendering"),textTransform:b.css("text-transform")}).insertAfter(b)}function f(a,b){return a=(a||"").replace(/^\s*/g,"").replace(/\s{2,}/g," "),b=(b||"").replace(/^\s*/g,"").replace(/\s{2,}/g," "),a===b}return c.mixin(b.prototype,d,{_handleFocus:function(){this.trigger("focused")},_handleBlur:function(){this.trigger("blured")},_handleSpecialKeyEvent:function(a){var b=this.specialKeyCodeMap[a.which||a.keyCode];b&&this.trigger(b+"Keyed",a)},_compareQueryToInputValue:function(){var a=this.getInputValue(),b=f(this.query,a),c=b?this.query.length!==a.length:!1;c?this.trigger("whitespaceChanged",{value:this.query}):b||this.trigger("queryChanged",{value:this.query=a})},destroy:function(){this.$hint.off(".tt"),this.$input.off(".tt"),this.$hint=this.$input=this.$overflowHelper=null},focus:function(){this.$input.focus()},blur:function(){this.$input.blur()},getQuery:function(){return this.query},setQuery:function(a){this.query=a},getInputValue:function(){return this.$input.val()},setInputValue:function(a,b){this.$input.val(a),!b&&this._compareQueryToInputValue()},getHintValue:function(){return this.$hint.val()},setHintValue:function(a){this.$hint.val(a)},getLanguageDirection:function(){return(this.$input.css("direction")||"ltr").toLowerCase()},isOverflow:function(){return this.$overflowHelper.text(this.getInputValue()),this.$overflowHelper.width()>this.$input.width()},isCursorAtEnd:function(){var a,b=this.$input.val().length,d=this.$input[0].selectionStart;return c.isNumber(d)?d===b:document.selection?(a=document.selection.createRange(),a.moveStart("character",-b),b===a.text.length):!0}}),b}(),k=function(){function b(b){c.bindAll(this),this.isOpen=!1,this.isEmpty=!0,this.isMouseOverDropdown=!1,this.$menu=a(b.menu).on("mouseenter.tt",this._handleMouseenter).on("mouseleave.tt",this._handleMouseleave).on("click.tt",".tt-suggestion",this._handleSelection).on("mouseover.tt",".tt-suggestion",this._handleMouseover)}function e(a){return a.data("suggestion")}var f={suggestionsList:'<span class="tt-suggestions"></span>'},g={suggestionsList:{display:"block"},suggestion:{whiteSpace:"nowrap",cursor:"pointer"},suggestionChild:{whiteSpace:"normal"}};return c.mixin(b.prototype,d,{_handleMouseenter:function(){this.isMouseOverDropdown=!0},_handleMouseleave:function(){this.isMouseOverDropdown=!1},_handleMouseover:function(b){var c=a(b.currentTarget);this._getSuggestions().removeClass("tt-is-under-cursor"),c.addClass("tt-is-under-cursor")},_handleSelection:function(b){var c=a(b.currentTarget);this.trigger("suggestionSelected",e(c))},_show:function(){this.$menu.css("display","block")},_hide:function(){this.$menu.hide()},_moveCursor:function(a){var b,c,d,f;if(this.isVisible()){if(b=this._getSuggestions(),c=b.filter(".tt-is-under-cursor"),c.removeClass("tt-is-under-cursor"),d=b.index(c)+a,d=(d+1)%(b.length+1)-1,-1===d)return this.trigger("cursorRemoved"),void 0;-1>d&&(d=b.length-1),f=b.eq(d).addClass("tt-is-under-cursor"),this._ensureVisibility(f),this.trigger("cursorMoved",e(f))}},_getSuggestions:function(){return this.$menu.find(".tt-suggestions > .tt-suggestion")},_ensureVisibility:function(a){var b=this.$menu.height()+parseInt(this.$menu.css("paddingTop"),10)+parseInt(this.$menu.css("paddingBottom"),10),c=this.$menu.scrollTop(),d=a.position().top,e=d+a.outerHeight(!0);0>d?this.$menu.scrollTop(c+d):e>b&&this.$menu.scrollTop(c+(e-b))},destroy:function(){this.$menu.off(".tt"),this.$menu=null},isVisible:function(){return this.isOpen&&!this.isEmpty},closeUnlessMouseIsOverDropdown:function(){this.isMouseOverDropdown||this.close()},close:function(){this.isOpen&&(this.isOpen=!1,this.isMouseOverDropdown=!1,this._hide(),this.$menu.find(".tt-suggestions > .tt-suggestion").removeClass("tt-is-under-cursor"),this.trigger("closed"))},open:function(){this.isOpen||(this.isOpen=!0,!this.isEmpty&&this._show(),this.trigger("opened"))},setLanguageDirection:function(a){var b={left:"0",right:"auto"},c={left:"auto",right:" 0"};"ltr"===a?this.$menu.css(b):this.$menu.css(c)},moveCursorUp:function(){this._moveCursor(-1)},moveCursorDown:function(){this._moveCursor(1)},getSuggestionUnderCursor:function(){var a=this._getSuggestions().filter(".tt-is-under-cursor").first();return a.length>0?e(a):null},getFirstSuggestion:function(){var a=this._getSuggestions().first();return a.length>0?e(a):null},renderSuggestions:function(b,d){var e,h,i,j,k,l="tt-dataset-"+b.name,m='<div class="tt-suggestion">%body</div>',n=this.$menu.find("."+l);0===n.length&&(h=a(f.suggestionsList).css(g.suggestionsList),n=a("<div></div>").addClass(l).append(b.header).append(h).append(b.footer).appendTo(this.$menu)),d.length>0?(this.isEmpty=!1,this.isOpen&&this._show(),i=document.createElement("div"),j=document.createDocumentFragment(),c.each(d,function(c,d){d.dataset=b.name,e=b.template(d.datum),i.innerHTML=m.replace("%body",e),k=a(i.firstChild).css(g.suggestion).data("suggestion",d),k.children().each(function(){a(this).css(g.suggestionChild)}),j.appendChild(k[0])}),n.show().find(".tt-suggestions").html(j)):this.clearSuggestions(b.name),this.trigger("suggestionsRendered")},clearSuggestions:function(a){var b=a?this.$menu.find(".tt-dataset-"+a):this.$menu.find('[class^="tt-dataset-"]'),c=b.find(".tt-suggestions");b.hide(),c.empty(),0===this._getSuggestions().length&&(this.isEmpty=!0,this._hide())}}),b}(),l=function(){function b(a){var b,d,f;c.bindAll(this),this.$node=e(a.input),this.datasets=a.datasets,this.dir=null,this.eventBus=a.eventBus,b=this.$node.find(".tt-dropdown-menu"),d=this.$node.find(".tt-query"),f=this.$node.find(".tt-hint"),this.dropdownView=new k({menu:b}).on("suggestionSelected",this._handleSelection).on("cursorMoved",this._clearHint).on("cursorMoved",this._setInputValueToSuggestionUnderCursor).on("cursorRemoved",this._setInputValueToQuery).on("cursorRemoved",this._updateHint).on("suggestionsRendered",this._updateHint).on("opened",this._updateHint).on("closed",this._clearHint).on("opened closed",this._propagateEvent),this.inputView=new j({input:d,hint:f}).on("focused",this._openDropdown).on("blured",this._closeDropdown).on("blured",this._setInputValueToQuery).on("enterKeyed tabKeyed",this._handleSelection).on("queryChanged",this._clearHint).on("queryChanged",this._clearSuggestions).on("queryChanged",this._getSuggestions).on("whitespaceChanged",this._updateHint).on("queryChanged whitespaceChanged",this._openDropdown).on("queryChanged whitespaceChanged",this._setLanguageDirection).on("escKeyed",this._closeDropdown).on("escKeyed",this._setInputValueToQuery).on("tabKeyed upKeyed downKeyed",this._managePreventDefault).on("upKeyed downKeyed",this._moveDropdownCursor).on("upKeyed downKeyed",this._openDropdown).on("tabKeyed leftKeyed rightKeyed",this._autocomplete)}function e(b){var c=a(g.wrapper),d=a(g.dropdown),e=a(b),f=a(g.hint);c=c.css(h.wrapper),d=d.css(h.dropdown),f.css(h.hint).css({backgroundAttachment:e.css("background-attachment"),backgroundClip:e.css("background-clip"),backgroundColor:e.css("background-color"),backgroundImage:e.css("background-image"),backgroundOrigin:e.css("background-origin"),backgroundPosition:e.css("background-position"),backgroundRepeat:e.css("background-repeat"),backgroundSize:e.css("background-size")}),e.data("ttAttrs",{dir:e.attr("dir"),autocomplete:e.attr("autocomplete"),spellcheck:e.attr("spellcheck"),style:e.attr("style")}),e.addClass("tt-query").attr({autocomplete:"off",spellcheck:!1}).css(h.query);try{!e.attr("dir")&&e.attr("dir","auto")}catch(i){}return e.wrap(c).parent().prepend(f).append(d)}function f(a){var b=a.find(".tt-query");c.each(b.data("ttAttrs"),function(a,d){c.isUndefined(d)?b.removeAttr(a):b.attr(a,d)}),b.detach().removeData("ttAttrs").removeClass("tt-query").insertAfter(a),a.remove()}var g={wrapper:'<span class="twitter-typeahead"></span>',hint:'<input class="tt-hint" type="text" autocomplete="off" spellcheck="off" disabled>',dropdown:'<span class="tt-dropdown-menu"></span>'},h={wrapper:{position:"relative",display:"inline-block"},hint:{position:"absolute",top:"0",left:"0",borderColor:"transparent",boxShadow:"none"},query:{position:"relative",verticalAlign:"top",backgroundColor:"transparent"},dropdown:{position:"absolute",top:"100%",left:"0",zIndex:"100",display:"none"}};return c.isMsie()&&c.mixin(h.query,{backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"}),c.isMsie()&&c.isMsie()<=7&&(c.mixin(h.wrapper,{display:"inline",zoom:"1"}),c.mixin(h.query,{marginTop:"-1px"})),c.mixin(b.prototype,d,{_managePreventDefault:function(a){var b,c,d=a.data,e=!1;switch(a.type){case"tabKeyed":b=this.inputView.getHintValue(),c=this.inputView.getInputValue(),e=b&&b!==c;break;case"upKeyed":case"downKeyed":e=!d.shiftKey&&!d.ctrlKey&&!d.metaKey}e&&d.preventDefault()},_setLanguageDirection:function(){var a=this.inputView.getLanguageDirection();a!==this.dir&&(this.dir=a,this.$node.css("direction",a),this.dropdownView.setLanguageDirection(a))},_updateHint:function(){var a,b,d,e,f,g=this.dropdownView.getFirstSuggestion(),h=g?g.value:null,i=this.dropdownView.isVisible(),j=this.inputView.isOverflow();h&&i&&!j&&(a=this.inputView.getInputValue(),b=a.replace(/\s{2,}/g," ").replace(/^\s+/g,""),d=c.escapeRegExChars(b),e=new RegExp("^(?:"+d+")(.*$)","i"),f=e.exec(h),this.inputView.setHintValue(a+(f?f[1]:"")))},_clearHint:function(){this.inputView.setHintValue("")},_clearSuggestions:function(){this.dropdownView.clearSuggestions()},_setInputValueToQuery:function(){this.inputView.setInputValue(this.inputView.getQuery())},_setInputValueToSuggestionUnderCursor:function(a){var b=a.data;this.inputView.setInputValue(b.value,!0)},_openDropdown:function(){this.dropdownView.open()},_closeDropdown:function(a){this.dropdownView["blured"===a.type?"closeUnlessMouseIsOverDropdown":"close"]()},_moveDropdownCursor:function(a){var b=a.data;b.shiftKey||b.ctrlKey||b.metaKey||this.dropdownView["upKeyed"===a.type?"moveCursorUp":"moveCursorDown"]()},_handleSelection:function(a){var b="suggestionSelected"===a.type,d=b?a.data:this.dropdownView.getSuggestionUnderCursor();d&&(this.inputView.setInputValue(d.value),b?this.inputView.focus():a.data.preventDefault(),b&&c.isMsie()?c.defer(this.dropdownView.close):this.dropdownView.close(),this.eventBus.trigger("selected",d.datum,d.dataset))},_getSuggestions:function(){var a=this,b=this.inputView.getQuery();c.isBlankString(b)||c.each(this.datasets,function(c,d){d.getSuggestions(b,function(c){b===a.inputView.getQuery()&&a.dropdownView.renderSuggestions(d,c)})})},_autocomplete:function(a){var b,c,d,e,f;("rightKeyed"!==a.type&&"leftKeyed"!==a.type||(b=this.inputView.isCursorAtEnd(),c="ltr"===this.inputView.getLanguageDirection()?"leftKeyed"===a.type:"rightKeyed"===a.type,b&&!c))&&(d=this.inputView.getQuery(),e=this.inputView.getHintValue(),""!==e&&d!==e&&(f=this.dropdownView.getFirstSuggestion(),this.inputView.setInputValue(f.value),this.eventBus.trigger("autocompleted",f.datum,f.dataset)))},_propagateEvent:function(a){this.eventBus.trigger(a.type)},destroy:function(){this.inputView.destroy(),this.dropdownView.destroy(),f(this.$node),this.$node=null},setQuery:function(a){this.inputView.setQuery(a),this.inputView.setInputValue(a),this._clearHint(),this._clearSuggestions(),this._getSuggestions()}}),b}();!function(){var b,d={},f="ttView";b={initialize:function(b){function g(){var b,d=a(this),g=new e({el:d});b=c.map(h,function(a){return a.initialize()}),d.data(f,new l({input:d,eventBus:g=new e({el:d}),datasets:h})),a.when.apply(a,b).always(function(){c.defer(function(){g.trigger("initialized")})})}var h;return b=c.isArray(b)?b:[b],0===b.length&&a.error("no datasets provided"),h=c.map(b,function(a){var b=d[a.name]?d[a.name]:new i(a);return a.name&&(d[a.name]=b),b}),this.each(g)},destroy:function(){function b(){var b=a(this),c=b.data(f);c&&(c.destroy(),b.removeData(f))}return this.each(b)},setQuery:function(b){function c(){var c=a(this).data(f);c&&c.setQuery(b)}return this.each(c)}},jQuery.fn.typeahead=function(a){return b[a]?b[a].apply(this,[].slice.call(arguments,1)):b.initialize.apply(this,arguments)}}()}(window.jQuery);
\ No newline at end of file
diff --git a/gddo-server/background.go b/gddo-server/background.go
new file mode 100644
index 0000000..cde8d6b
--- /dev/null
+++ b/gddo-server/background.go
@@ -0,0 +1,114 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package main
+
+import (
+	"flag"
+	"github.com/garyburd/gosrc"
+	"log"
+	"time"
+)
+
+var backgroundTasks = []*struct {
+	name     string
+	fn       func() error
+	interval *time.Duration
+	next     time.Time
+}{
+	{
+		name:     "GitHub updates",
+		fn:       readGitHubUpdates,
+		interval: flag.Duration("github_interval", 0, "Github updates crawler sleeps for this duration between fetches. Zero disables the crawler."),
+	},
+	{
+		name:     "Crawl",
+		fn:       doCrawl,
+		interval: flag.Duration("crawl_interval", 0, "Package updater sleeps for this duration between package updates. Zero disables updates."),
+	},
+}
+
+func runBackgroundTasks() {
+	defer log.Println("ERROR: Background exiting!")
+
+	sleep := time.Minute
+	for _, task := range backgroundTasks {
+		if *task.interval > 0 && sleep > *task.interval {
+			sleep = *task.interval
+		}
+	}
+
+	for {
+		for _, task := range backgroundTasks {
+			start := time.Now()
+			if *task.interval > 0 && start.After(task.next) {
+				if err := task.fn(); err != nil {
+					log.Printf("Task %s: %v", task.name, err)
+				}
+				task.next = time.Now().Add(*task.interval)
+			}
+		}
+		time.Sleep(sleep)
+	}
+}
+
+func doCrawl() error {
+	// Look for new package to crawl.
+	importPath, hasSubdirs, err := db.PopNewCrawl()
+	if err != nil {
+		log.Printf("db.PopNewCrawl() returned error %v", err)
+		return nil
+	}
+	if importPath != "" {
+		if pdoc, err := crawlDoc("new", importPath, nil, hasSubdirs, time.Time{}); pdoc == nil && err == nil {
+			if err := db.AddBadCrawl(importPath); err != nil {
+				log.Printf("ERROR db.AddBadCrawl(%q): %v", importPath, err)
+			}
+		}
+		return nil
+	}
+
+	// Crawl existing doc.
+	pdoc, pkgs, nextCrawl, err := db.Get("-")
+	if err != nil {
+		log.Printf("db.Get(\"-\") returned error %v", err)
+		return nil
+	}
+	if pdoc == nil || nextCrawl.After(time.Now()) {
+		return nil
+	}
+	if _, err = crawlDoc("crawl", pdoc.ImportPath, pdoc, len(pkgs) > 0, nextCrawl); err != nil {
+		// Touch package so that crawl advances to next package.
+		if err := db.SetNextCrawlEtag(pdoc.ProjectRoot, pdoc.Etag, time.Now().Add(*maxAge/3)); err != nil {
+			log.Printf("ERROR db.TouchLastCrawl(%q): %v", pdoc.ImportPath, err)
+		}
+	}
+	return nil
+}
+
+func readGitHubUpdates() error {
+	const key = "gitHubUpdates"
+	var last string
+	if err := db.GetGob(key, &last); err != nil {
+		return err
+	}
+	last, names, err := gosrc.GetGitHubUpdates(httpClient, last)
+	if err != nil {
+		return err
+	}
+
+	for _, name := range names {
+		log.Printf("bump crawl github.com/%s", name)
+		if err := db.BumpCrawl("github.com/" + name); err != nil {
+			log.Println("ERROR force crawl:", err)
+		}
+	}
+
+	if err := db.PutGob(key, last); err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/gddo-server/browse.go b/gddo-server/browse.go
new file mode 100644
index 0000000..8c8b199
--- /dev/null
+++ b/gddo-server/browse.go
@@ -0,0 +1,92 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package main
+
+import (
+	"net/url"
+	"path"
+	"regexp"
+	"strings"
+)
+
+func importPathFromGoogleBrowse(m []string) string {
+	project := m[1]
+	dir := m[2]
+	if dir == "" {
+		dir = "/"
+	} else if dir[len(dir)-1] == '/' {
+		dir = dir[:len(dir)-1]
+	}
+	subrepo := ""
+	if len(m[3]) > 0 {
+		v, _ := url.ParseQuery(m[3][1:])
+		subrepo = v.Get("repo")
+		if len(subrepo) > 0 {
+			subrepo = "." + subrepo
+		}
+	}
+	if strings.HasPrefix(m[4], "#hg%2F") {
+		d, _ := url.QueryUnescape(m[4][len("#hg%2f"):])
+		if i := strings.IndexRune(d, '%'); i >= 0 {
+			d = d[:i]
+		}
+		dir = dir + "/" + d
+	}
+	return "code.google.com/p/" + project + subrepo + dir
+}
+
+var browsePatterns = []struct {
+	pat *regexp.Regexp
+	fn  func([]string) string
+}{
+	{
+		// GitHub tree  browser.
+		regexp.MustCompile(`^https?://(github\.com/[^/]+/[^/]+)(?:/tree/[^/]+(/.*))?$`),
+		func(m []string) string { return m[1] + m[2] },
+	},
+	{
+		// GitHub file browser.
+		regexp.MustCompile(`^https?://(github\.com/[^/]+/[^/]+)/blob/[^/]+/(.*)$`),
+		func(m []string) string {
+			d := path.Dir(m[2])
+			if d == "." {
+				return m[1]
+			}
+			return m[1] + "/" + d
+		},
+	},
+	{
+		// Bitbucket source borwser.
+		regexp.MustCompile(`^https?://(bitbucket\.org/[^/]+/[^/]+)(?:/src/[^/]+(/[^?]+)?)?`),
+		func(m []string) string { return m[1] + m[2] },
+	},
+	{
+		// Google Project Hosting source browser.
+		regexp.MustCompile(`^http:/+code\.google\.com/p/([^/]+)/source/browse(/[^?#]*)?(\?[^#]*)?(#.*)?$`),
+		importPathFromGoogleBrowse,
+	},
+	{
+		// Launchpad source browser.
+		regexp.MustCompile(`^https?:/+bazaar\.(launchpad\.net/.*)/files$`),
+		func(m []string) string { return m[1] },
+	},
+	{
+		regexp.MustCompile(`^https?://(.+)$`),
+		func(m []string) string { return strings.Trim(m[1], "/") },
+	},
+}
+
+// isBrowserURL returns importPath and true if URL looks like a URL for a VCS
+// source browser.
+func isBrowseURL(s string) (importPath string, ok bool) {
+	for _, c := range browsePatterns {
+		if m := c.pat.FindStringSubmatch(s); m != nil {
+			return c.fn(m), true
+		}
+	}
+	return "", false
+}
diff --git a/gddo-server/browse_test.go b/gddo-server/browse_test.go
new file mode 100644
index 0000000..3c39229
--- /dev/null
+++ b/gddo-server/browse_test.go
@@ -0,0 +1,39 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package main
+
+import (
+	"testing"
+)
+
+var isBrowseURLTests = []struct {
+	s          string
+	importPath string
+	ok         bool
+}{
+	{"https://github.com/garyburd/gddo/blob/master/doc/code.go", "github.com/garyburd/gddo/doc", true},
+	{"https://github.com/garyburd/go-oauth/blob/master/.gitignore", "github.com/garyburd/go-oauth", true},
+	{"https://bitbucket.org/user/repo/src/bd0b661a263e/p1/p2?at=default", "bitbucket.org/user/repo/p1/p2", true},
+	{"https://bitbucket.org/user/repo/src", "bitbucket.org/user/repo", true},
+	{"https://bitbucket.org/user/repo", "bitbucket.org/user/repo", true},
+	{"https://github.com/user/repo", "github.com/user/repo", true},
+	{"https://github.com/user/repo/tree/master/p1", "github.com/user/repo/p1", true},
+	{"http://code.google.com/p/project", "code.google.com/p/project", true},
+}
+
+func TestIsBrowseURL(t *testing.T) {
+	for _, tt := range isBrowseURLTests {
+		importPath, ok := isBrowseURL(tt.s)
+		if tt.ok {
+			if importPath != tt.importPath || ok != true {
+				t.Errorf("IsBrowseURL(%q) = %q, %v; want %q %v", tt.s, importPath, ok, tt.importPath, true)
+			}
+		} else if ok {
+			t.Errorf("IsBrowseURL(%q) = %q, %v; want _, false", tt.s, importPath, ok)
+		}
+	}
+}
diff --git a/gddo-server/client.go b/gddo-server/client.go
new file mode 100644
index 0000000..759171f
--- /dev/null
+++ b/gddo-server/client.go
@@ -0,0 +1,77 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+// This file implements an http.Client with request timeouts set by command
+// line flags.
+
+package main
+
+import (
+	"flag"
+	"log"
+	"net"
+	"net/http"
+	"time"
+)
+
+var (
+	dialTimeout    = flag.Duration("dial_timeout", 5*time.Second, "Timeout for dialing an HTTP connection.")
+	requestTimeout = flag.Duration("request_timeout", 20*time.Second, "Time out for roundtripping an HTTP request.")
+)
+
+type timeoutConn struct {
+	net.Conn
+}
+
+func (c timeoutConn) Read(p []byte) (int, error) {
+	n, err := c.Conn.Read(p)
+	c.Conn.SetReadDeadline(time.Time{})
+	return n, err
+}
+
+func timeoutDial(network, addr string) (net.Conn, error) {
+	c, err := net.DialTimeout(network, addr, *dialTimeout)
+	if err != nil {
+		return c, err
+	}
+	// The net/http transport CancelRequest feature does not work until after
+	// the TLS handshake is complete. To help catch hangs during the TLS
+	// handshake, we set a deadline on the connection here and clear the
+	// deadline when the first read on the connection completes. This is not
+	// perfect, but it does catch the case where the server accepts and ignores
+	// a connection.
+	c.SetDeadline(time.Now().Add(*requestTimeout))
+	return timeoutConn{c}, nil
+}
+
+type transport struct {
+	t http.Transport
+}
+
+func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
+	timer := time.AfterFunc(*requestTimeout, func() {
+		t.t.CancelRequest(req)
+		log.Printf("Canceled request for %s", req.URL)
+	})
+	defer timer.Stop()
+	if req.URL.Host == "api.github.com" && gitHubCredentials != "" {
+		if req.URL.RawQuery == "" {
+			req.URL.RawQuery = gitHubCredentials
+		} else {
+			req.URL.RawQuery += "&" + gitHubCredentials
+		}
+	}
+	if userAgent != "" {
+		req.Header.Set("User-Agent", userAgent)
+	}
+	return t.t.RoundTrip(req)
+}
+
+var httpClient = &http.Client{Transport: &transport{
+	t: http.Transport{
+		Dial: timeoutDial,
+		ResponseHeaderTimeout: *requestTimeout / 2,
+	}}}
diff --git a/gddo-server/config.go.template b/gddo-server/config.go.template
new file mode 100644
index 0000000..efea18b
--- /dev/null
+++ b/gddo-server/config.go.template
@@ -0,0 +1,7 @@
+package main
+
+func init() {
+	// Register an application at https://github.com/settings/applications/new
+	// and enter the client ID and client secret here.
+	gitHubCredentials = "client_id=<id>&client_secret=<secret>"
+}
diff --git a/gddo-server/crawl.go b/gddo-server/crawl.go
new file mode 100644
index 0000000..9da5ff0
--- /dev/null
+++ b/gddo-server/crawl.go
@@ -0,0 +1,109 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package main
+
+import (
+	"log"
+	"regexp"
+	"strings"
+	"time"
+
+	"github.com/garyburd/gddo/doc"
+	"github.com/garyburd/gosrc"
+)
+
+var nestedProjectPat = regexp.MustCompile(`/(?:github\.com|launchpad\.net|code\.google\.com/p|bitbucket\.org|labix\.org)/`)
+
+func exists(path string) bool {
+	b, err := db.Exists(path)
+	if err != nil {
+		b = false
+	}
+	return b
+}
+
+// crawlDoc fetches the package documentation from the VCS and updates the database.
+func crawlDoc(source string, importPath string, pdoc *doc.Package, hasSubdirs bool, nextCrawl time.Time) (*doc.Package, error) {
+	message := []interface{}{source}
+	defer func() {
+		message = append(message, importPath)
+		log.Println(message...)
+	}()
+
+	if !nextCrawl.IsZero() {
+		d := time.Since(nextCrawl) / time.Hour
+		if d > 0 {
+			message = append(message, "late:", int64(d))
+		}
+	}
+
+	etag := ""
+	if pdoc != nil {
+		etag = pdoc.Etag
+		message = append(message, "etag:", etag)
+	}
+
+	start := time.Now()
+	var err error
+	if i := strings.Index(importPath, "/src/pkg/"); i > 0 && gosrc.IsGoRepoPath(importPath[i+len("/src/pkg/"):]) {
+		// Go source tree mirror.
+		pdoc = nil
+		err = gosrc.NotFoundError{Message: "Go source tree mirror."}
+	} else if i := strings.Index(importPath, "/libgo/go/"); i > 0 && gosrc.IsGoRepoPath(importPath[i+len("/libgo/go/"):]) {
+		// Go Frontend source tree mirror.
+		pdoc = nil
+		err = gosrc.NotFoundError{Message: "Go Frontend source tree mirror."}
+	} else if m := nestedProjectPat.FindStringIndex(importPath); m != nil && exists(importPath[m[0]+1:]) {
+		pdoc = nil
+		err = gosrc.NotFoundError{Message: "Copy of other project."}
+	} else if blocked, e := db.IsBlocked(importPath); blocked && e == nil {
+		pdoc = nil
+		err = gosrc.NotFoundError{Message: "Blocked."}
+	} else {
+		var pdocNew *doc.Package
+		pdocNew, err = doc.Get(httpClient, importPath, etag)
+		message = append(message, "fetch:", int64(time.Since(start)/time.Millisecond))
+		if err == nil && pdocNew.Name == "" && !hasSubdirs {
+			pdoc = nil
+			err = gosrc.NotFoundError{Message: "No Go files or subdirs"}
+		} else if err != gosrc.ErrNotModified {
+			pdoc = pdocNew
+		}
+	}
+
+	nextCrawl = start.Add(*maxAge)
+	switch {
+	case strings.HasPrefix(importPath, "github.com/") || (pdoc != nil && len(pdoc.Errors) > 0):
+		nextCrawl = start.Add(*maxAge * 7)
+	case strings.HasPrefix(importPath, "gist.github.com/"):
+		// Don't spend time on gists. It's silly thing to do.
+		nextCrawl = start.Add(*maxAge * 30)
+	}
+
+	switch {
+	case err == nil:
+		message = append(message, "put:", pdoc.Etag)
+		if err := db.Put(pdoc, nextCrawl, false); err != nil {
+			log.Printf("ERROR db.Put(%q): %v", importPath, err)
+		}
+	case err == gosrc.ErrNotModified:
+		message = append(message, "touch")
+		if err := db.SetNextCrawlEtag(pdoc.ProjectRoot, pdoc.Etag, nextCrawl); err != nil {
+			log.Printf("ERROR db.SetNextCrawl(%q): %v", importPath, err)
+		}
+	case gosrc.IsNotFound(err):
+		message = append(message, "notfound:", err)
+		if err := db.Delete(importPath); err != nil {
+			log.Printf("ERROR db.Delete(%q): %v", importPath, err)
+		}
+	default:
+		message = append(message, "ERROR:", err)
+		return nil, err
+	}
+
+	return pdoc, nil
+}
diff --git a/gddo-server/graph.go b/gddo-server/graph.go
new file mode 100644
index 0000000..eb3c178
--- /dev/null
+++ b/gddo-server/graph.go
@@ -0,0 +1,48 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package main
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"os/exec"
+	"strings"
+
+	"github.com/garyburd/gddo/database"
+	"github.com/garyburd/gddo/doc"
+)
+
+func renderGraph(pdoc *doc.Package, pkgs []database.Package, edges [][2]int) ([]byte, error) {
+	var in, out bytes.Buffer
+
+	fmt.Fprintf(&in, "digraph %s { \n", pdoc.Name)
+	for i, pkg := range pkgs {
+		fmt.Fprintf(&in, " n%d [label=\"%s\", URL=\"/%s\", tooltip=\"%s\"];\n",
+			i, pkg.Path, pkg.Path,
+			strings.Replace(pkg.Synopsis, `"`, `\"`, -1))
+	}
+	for _, edge := range edges {
+		fmt.Fprintf(&in, " n%d -> n%d;\n", edge[0], edge[1])
+	}
+	in.WriteString("}")
+
+	cmd := exec.Command("dot", "-Tsvg")
+	cmd.Stdin = &in
+	cmd.Stdout = &out
+	if err := cmd.Run(); err != nil {
+		return nil, err
+	}
+
+	p := out.Bytes()
+	if i := bytes.Index(p, []byte("<svg")); i < 0 {
+		return nil, errors.New("<svg not found")
+	} else {
+		p = p[i:]
+	}
+	return p, nil
+}
diff --git a/gddo-server/main.go b/gddo-server/main.go
new file mode 100644
index 0000000..7f9adec
--- /dev/null
+++ b/gddo-server/main.go
@@ -0,0 +1,907 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+// Command gddo-server is the GoPkgDoc server.
+package main
+
+import (
+	"archive/zip"
+	"bytes"
+	"crypto/md5"
+	"encoding/gob"
+	"encoding/json"
+	"errors"
+	"flag"
+	"fmt"
+	"go/build"
+	"html/template"
+	"io"
+	"log"
+	"net/http"
+	"os"
+	"path"
+	"path/filepath"
+	"regexp"
+	"runtime/debug"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/garyburd/gddo/database"
+	"github.com/garyburd/gddo/doc"
+	"github.com/garyburd/gddo/httputil"
+	"github.com/garyburd/gosrc"
+)
+
+const (
+	jsonMIMEType = "application/json; charset=utf-8"
+	textMIMEType = "text/plain; charset=utf-8"
+	htmlMIMEType = "text/html; charset=utf-8"
+)
+
+var errUpdateTimeout = errors.New("refresh timeout")
+
+type httpError struct {
+	status int   // HTTP status code.
+	err    error // Optional reason for the HTTP error.
+}
+
+func (err *httpError) Error() string {
+	if err.err != nil {
+		return fmt.Sprintf("status %d, reason %s", err.status, err.err.Error())
+	}
+	return fmt.Sprintf("Status %d", err.status)
+}
+
+const (
+	humanRequest = iota
+	robotRequest
+	queryRequest
+	refreshRequest
+)
+
+type crawlResult struct {
+	pdoc *doc.Package
+	err  error
+}
+
+// getDoc gets the package documentation from the database or from the version
+// control system as needed.
+func getDoc(path string, requestType int) (*doc.Package, []database.Package, error) {
+	if path == "-" {
+		// A hack in the database package uses the path "-" to represent the
+		// next document to crawl. Block "-" here so that requests to /- always
+		// return not found.
+		return nil, nil, nil
+	}
+
+	pdoc, pkgs, nextCrawl, err := db.Get(path)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	needsCrawl := false
+	switch requestType {
+	case queryRequest:
+		needsCrawl = nextCrawl.IsZero() && len(pkgs) == 0
+	case humanRequest:
+		needsCrawl = nextCrawl.Before(time.Now())
+	case robotRequest:
+		needsCrawl = nextCrawl.IsZero() && len(pkgs) > 0
+	}
+
+	if needsCrawl {
+		c := make(chan crawlResult, 1)
+		go func() {
+			pdoc, err := crawlDoc("web  ", path, pdoc, len(pkgs) > 0, nextCrawl)
+			c <- crawlResult{pdoc, err}
+		}()
+		var err error
+		timeout := *getTimeout
+		if pdoc == nil {
+			timeout = *firstGetTimeout
+		}
+		select {
+		case rr := <-c:
+			if rr.err == nil {
+				pdoc = rr.pdoc
+			}
+			err = rr.err
+		case <-time.After(timeout):
+			err = errUpdateTimeout
+		}
+		if err != nil {
+			if pdoc != nil {
+				log.Printf("Serving %q from database after error: %v", path, err)
+				err = nil
+			} else if err == errUpdateTimeout {
+				// Handle timeout on packages never seeen before as not found.
+				log.Printf("Serving %q as not found after timeout", path)
+				err = &httpError{status: http.StatusNotFound}
+			}
+		}
+	}
+	return pdoc, pkgs, err
+}
+
+func templateExt(req *http.Request) string {
+	if httputil.NegotiateContentType(req, []string{"text/html", "text/plain"}, "text/html") == "text/plain" {
+		return ".txt"
+	}
+	return ".html"
+}
+
+var (
+	robotPat = regexp.MustCompile(`(:?\+https?://)|(?:\Wbot\W)|(?:^Python-urllib)|(?:^Go )|(?:^Java/)`)
+)
+
+func isRobot(req *http.Request) bool {
+	if robotPat.MatchString(req.Header.Get("User-Agent")) {
+		return true
+	}
+	host := httputil.StripPort(req.RemoteAddr)
+	n, err := db.IncrementCounter(host, 1)
+	if err != nil {
+		log.Printf("error incrementing counter for %s,  %v\n", host, err)
+		return false
+	}
+	if n > *robot {
+		log.Printf("robot %.2f %s %s", n, host, req.Header.Get("User-Agent"))
+		return true
+	}
+	return false
+}
+
+func popularLinkReferral(req *http.Request) bool {
+	return strings.HasSuffix(req.Header.Get("Referer"), "//"+req.Host+"/")
+}
+
+func isView(req *http.Request, key string) bool {
+	rq := req.URL.RawQuery
+	return strings.HasPrefix(rq, key) &&
+		(len(rq) == len(key) || rq[len(key)] == '=' || rq[len(key)] == '&')
+}
+
+// httpEtag returns the package entity tag used in HTTP transactions.
+func httpEtag(pdoc *doc.Package, pkgs []database.Package, importerCount int) string {
+	b := make([]byte, 0, 128)
+	b = strconv.AppendInt(b, pdoc.Updated.Unix(), 16)
+	b = append(b, 0)
+	b = append(b, pdoc.Etag...)
+	if importerCount >= 8 {
+		importerCount = 8
+	}
+	b = append(b, 0)
+	b = strconv.AppendInt(b, int64(importerCount), 16)
+	for _, pkg := range pkgs {
+		b = append(b, 0)
+		b = append(b, pkg.Path...)
+		b = append(b, 0)
+		b = append(b, pkg.Synopsis...)
+	}
+	if *sidebarEnabled {
+		b = append(b, "\000xsb"...)
+	}
+	h := md5.New()
+	h.Write(b)
+	b = h.Sum(b[:0])
+	return fmt.Sprintf("\"%x\"", b)
+}
+
+func servePackage(resp http.ResponseWriter, req *http.Request) error {
+	p := path.Clean(req.URL.Path)
+	if strings.HasPrefix(p, "/pkg/") {
+		p = p[len("/pkg"):]
+	}
+	if p != req.URL.Path {
+		http.Redirect(resp, req, p, 301)
+		return nil
+	}
+
+	if isView(req, "status.png") {
+		statusImageHandler.ServeHTTP(resp, req)
+		return nil
+	}
+
+	requestType := humanRequest
+	if isRobot(req) {
+		requestType = robotRequest
+	}
+
+	importPath := strings.TrimPrefix(req.URL.Path, "/")
+	pdoc, pkgs, err := getDoc(importPath, requestType)
+	if err != nil {
+		return err
+	}
+
+	if pdoc == nil {
+		if len(pkgs) == 0 {
+			return &httpError{status: http.StatusNotFound}
+		}
+		pdocChild, _, _, err := db.Get(pkgs[0].Path)
+		if err != nil {
+			return err
+		}
+		pdoc = &doc.Package{
+			ProjectName: pdocChild.ProjectName,
+			ProjectRoot: pdocChild.ProjectRoot,
+			ProjectURL:  pdocChild.ProjectURL,
+			ImportPath:  importPath,
+		}
+	}
+
+	switch {
+	case len(req.Form) == 0:
+		importerCount, err := db.ImporterCount(importPath)
+		if err != nil {
+			return err
+		}
+
+		etag := httpEtag(pdoc, pkgs, importerCount)
+		status := http.StatusOK
+		if req.Header.Get("If-None-Match") == etag {
+			status = http.StatusNotModified
+		}
+
+		if requestType == humanRequest &&
+			pdoc.Name != "" && // not a directory
+			pdoc.ProjectRoot != "" && // not a standard package
+			!pdoc.IsCmd &&
+			len(pdoc.Errors) == 0 &&
+			!popularLinkReferral(req) {
+			if err := db.IncrementPopularScore(pdoc.ImportPath); err != nil {
+				log.Print("ERROR db.IncrementPopularScore(%s): %v", pdoc.ImportPath, err)
+			}
+		}
+
+		template := "dir"
+		switch {
+		case pdoc.IsCmd:
+			template = "cmd"
+		case pdoc.Name != "":
+			template = "pkg"
+		}
+		template += templateExt(req)
+
+		if srcFiles[importPath+"/_sourceMap"] != nil {
+			for _, f := range pdoc.Files {
+				if srcFiles[importPath+"/"+f.Name] != nil {
+					f.URL = fmt.Sprintf("/%s?file=%s", importPath, f.Name)
+					pdoc.LineFmt = "%s#L%d"
+				}
+			}
+		}
+
+		return executeTemplate(resp, template, status, http.Header{"Etag": {etag}}, map[string]interface{}{
+			"pkgs":          pkgs,
+			"pdoc":          newTDoc(pdoc),
+			"importerCount": importerCount,
+		})
+	case isView(req, "imports"):
+		if pdoc.Name == "" {
+			break
+		}
+		pkgs, err = db.Packages(pdoc.Imports)
+		if err != nil {
+			return err
+		}
+		return executeTemplate(resp, "imports.html", http.StatusOK, nil, map[string]interface{}{
+			"pkgs": pkgs,
+			"pdoc": newTDoc(pdoc),
+		})
+	case isView(req, "tools"):
+		proto := "http"
+		if req.Host == "godoc.org" {
+			proto = "https"
+		}
+		return executeTemplate(resp, "tools.html", http.StatusOK, nil, map[string]interface{}{
+			"uri":  fmt.Sprintf("%s://%s/%s", proto, req.Host, importPath),
+			"pdoc": newTDoc(pdoc),
+		})
+	case isView(req, "redir"):
+		if srcFiles == nil {
+			break
+		}
+		f := srcFiles[importPath+"/_sourceMap"]
+		if f == nil {
+			break
+		}
+		rc, err := f.Open()
+		if err != nil {
+			return err
+		}
+		defer rc.Close()
+		var sourceMap map[string]string
+		if err := gob.NewDecoder(rc).Decode(&sourceMap); err != nil {
+			return err
+		}
+		id := req.Form.Get("redir")
+		fname := sourceMap[id]
+		if fname == "" {
+			break
+		}
+		http.Redirect(resp, req, fmt.Sprintf("?file=%s#%s", fname, id), 301)
+		return nil
+	case isView(req, "file"):
+		if srcFiles == nil {
+			break
+		}
+		fname := req.Form.Get("file")
+		f := srcFiles[importPath+"/"+fname]
+		if f == nil {
+			break
+		}
+		r, err := f.Open()
+		if err != nil {
+			return err
+		}
+		defer r.Close()
+		src := make([]byte, f.UncompressedSize64)
+		if n, err := io.ReadFull(r, src); err != nil {
+			return err
+		} else {
+			src = src[:n]
+		}
+		var url string
+		for _, f := range pdoc.Files {
+			if f.Name == fname {
+				url = f.URL
+			}
+		}
+		return executeTemplate(resp, "file.html", http.StatusOK, nil, map[string]interface{}{
+			"fname": fname,
+			"url":   url,
+			"src":   template.HTML(src),
+			"pdoc":  newTDoc(pdoc),
+		})
+	case isView(req, "importers"):
+		if pdoc.Name == "" {
+			break
+		}
+		pkgs, err = db.Importers(importPath)
+		if err != nil {
+			return err
+		}
+		template := "importers.html"
+		if requestType == robotRequest {
+			// Hide back links from robots.
+			template = "importers_robot.html"
+		}
+		return executeTemplate(resp, template, http.StatusOK, nil, map[string]interface{}{
+			"pkgs": pkgs,
+			"pdoc": newTDoc(pdoc),
+		})
+	case isView(req, "import-graph"):
+		if pdoc.Name == "" {
+			break
+		}
+		hide := req.Form.Get("hide") == "1"
+		pkgs, edges, err := db.ImportGraph(pdoc, hide)
+		if err != nil {
+			return err
+		}
+		b, err := renderGraph(pdoc, pkgs, edges)
+		if err != nil {
+			return err
+		}
+		return executeTemplate(resp, "graph.html", http.StatusOK, nil, map[string]interface{}{
+			"svg":  template.HTML(b),
+			"pdoc": newTDoc(pdoc),
+			"hide": hide,
+		})
+	case isView(req, "play"):
+		u, err := playURL(pdoc, req.Form.Get("play"))
+		if err != nil {
+			return err
+		}
+		http.Redirect(resp, req, u, 301)
+		return nil
+	case req.Form.Get("view") != "":
+		// Redirect deprecated view= queries.
+		var q string
+		switch view := req.Form.Get("view"); view {
+		case "imports", "importers":
+			q = view
+		case "import-graph":
+			if req.Form.Get("hide") == "1" {
+				q = "import-graph&hide=1"
+			} else {
+				q = "import-graph"
+			}
+		}
+		if q != "" {
+			u := *req.URL
+			u.RawQuery = q
+			http.Redirect(resp, req, u.String(), 301)
+			return nil
+		}
+	}
+	return &httpError{status: http.StatusNotFound}
+}
+
+func serveRefresh(resp http.ResponseWriter, req *http.Request) error {
+	path := req.Form.Get("path")
+	_, pkgs, _, err := db.Get(path)
+	if err != nil {
+		return err
+	}
+	c := make(chan error, 1)
+	go func() {
+		_, err := crawlDoc("rfrsh", path, nil, len(pkgs) > 0, time.Time{})
+		c <- err
+	}()
+	select {
+	case err = <-c:
+	case <-time.After(*getTimeout):
+		err = errUpdateTimeout
+	}
+	if err != nil {
+		return err
+	}
+	http.Redirect(resp, req, "/"+path, 302)
+	return nil
+}
+
+func serveGoIndex(resp http.ResponseWriter, req *http.Request) error {
+	pkgs, err := db.GoIndex()
+	if err != nil {
+		return err
+	}
+	return executeTemplate(resp, "std.html", http.StatusOK, nil, map[string]interface{}{
+		"pkgs": pkgs,
+	})
+}
+
+func serveGoSubrepoIndex(resp http.ResponseWriter, req *http.Request) error {
+	pkgs, err := db.GoSubrepoIndex()
+	if err != nil {
+		return err
+	}
+	return executeTemplate(resp, "subrepo.html", http.StatusOK, nil, map[string]interface{}{
+		"pkgs": pkgs,
+	})
+}
+
+func serveIndex(resp http.ResponseWriter, req *http.Request) error {
+	pkgs, err := db.Index()
+	if err != nil {
+		return err
+	}
+	return executeTemplate(resp, "index.html", http.StatusOK, nil, map[string]interface{}{
+		"pkgs": pkgs,
+	})
+}
+
+type byPath struct {
+	pkgs []database.Package
+	rank []int
+}
+
+func (bp *byPath) Len() int           { return len(bp.pkgs) }
+func (bp *byPath) Less(i, j int) bool { return bp.pkgs[i].Path < bp.pkgs[j].Path }
+func (bp *byPath) Swap(i, j int) {
+	bp.pkgs[i], bp.pkgs[j] = bp.pkgs[j], bp.pkgs[i]
+	bp.rank[i], bp.rank[j] = bp.rank[j], bp.rank[i]
+}
+
+type byRank struct {
+	pkgs []database.Package
+	rank []int
+}
+
+func (br *byRank) Len() int           { return len(br.pkgs) }
+func (br *byRank) Less(i, j int) bool { return br.rank[i] < br.rank[j] }
+func (br *byRank) Swap(i, j int) {
+	br.pkgs[i], br.pkgs[j] = br.pkgs[j], br.pkgs[i]
+	br.rank[i], br.rank[j] = br.rank[j], br.rank[i]
+}
+
+func popular() ([]database.Package, error) {
+	const n = 25
+
+	pkgs, err := db.Popular(2 * n)
+	if err != nil {
+		return nil, err
+	}
+
+	rank := make([]int, len(pkgs))
+	for i := range pkgs {
+		rank[i] = i
+	}
+
+	sort.Sort(&byPath{pkgs, rank})
+
+	j := 0
+	prev := "."
+	for i, pkg := range pkgs {
+		if strings.HasPrefix(pkg.Path, prev) {
+			if rank[j-1] < rank[i] {
+				rank[j-1] = rank[i]
+			}
+			continue
+		}
+		prev = pkg.Path + "/"
+		pkgs[j] = pkg
+		rank[j] = rank[i]
+		j += 1
+	}
+	pkgs = pkgs[:j]
+
+	sort.Sort(&byRank{pkgs, rank})
+
+	if len(pkgs) > n {
+		pkgs = pkgs[:n]
+	}
+
+	sort.Sort(&byPath{pkgs, rank})
+
+	return pkgs, nil
+}
+
+func serveHome(resp http.ResponseWriter, req *http.Request) error {
+	if req.URL.Path != "/" {
+		return servePackage(resp, req)
+	}
+
+	q := strings.TrimSpace(req.Form.Get("q"))
+	if q == "" {
+		pkgs, err := popular()
+		if err != nil {
+			return err
+		}
+
+		return executeTemplate(resp, "home"+templateExt(req), http.StatusOK, nil,
+			map[string]interface{}{"Popular": pkgs})
+	}
+
+	if path, ok := isBrowseURL(q); ok {
+		q = path
+	}
+
+	if gosrc.IsValidRemotePath(q) || (strings.Contains(q, "/") && gosrc.IsGoRepoPath(q)) {
+		pdoc, pkgs, err := getDoc(q, queryRequest)
+		if err == nil && (pdoc != nil || len(pkgs) > 0) {
+			http.Redirect(resp, req, "/"+q, 302)
+			return nil
+		}
+	}
+
+	pkgs, err := db.Query(q)
+	if err != nil {
+		return err
+	}
+
+	return executeTemplate(resp, "results"+templateExt(req), http.StatusOK, nil,
+		map[string]interface{}{"q": q, "pkgs": pkgs})
+}
+
+func serveAbout(resp http.ResponseWriter, req *http.Request) error {
+	return executeTemplate(resp, "about.html", http.StatusOK, nil,
+		map[string]interface{}{"Host": req.Host})
+}
+
+func serveBot(resp http.ResponseWriter, req *http.Request) error {
+	return executeTemplate(resp, "bot.html", http.StatusOK, nil, nil)
+}
+
+func logError(req *http.Request, err error, rv interface{}) {
+	if err != nil {
+		var buf bytes.Buffer
+		fmt.Fprintf(&buf, "Error serving %s: %v\n", req.URL, err)
+		if rv != nil {
+			fmt.Fprintln(&buf, rv)
+			buf.Write(debug.Stack())
+		}
+		log.Print(buf.String())
+	}
+}
+
+func serveAPISearch(resp http.ResponseWriter, req *http.Request) error {
+	q := strings.TrimSpace(req.Form.Get("q"))
+	pkgs, err := db.Query(q)
+	if err != nil {
+		return err
+	}
+
+	var data struct {
+		Results []database.Package `json:"results"`
+	}
+	data.Results = pkgs
+	resp.Header().Set("Content-Type", jsonMIMEType)
+	return json.NewEncoder(resp).Encode(&data)
+}
+
+func serveAPIPackages(resp http.ResponseWriter, req *http.Request) error {
+	pkgs, err := db.AllPackages()
+	if err != nil {
+		return err
+	}
+	data := struct {
+		Results []database.Package `json:"results"`
+	}{
+		pkgs,
+	}
+	resp.Header().Set("Content-Type", jsonMIMEType)
+	return json.NewEncoder(resp).Encode(&data)
+}
+
+func serveAPIImporters(resp http.ResponseWriter, req *http.Request) error {
+	importPath := strings.TrimPrefix(req.URL.Path, "/importers/")
+	pkgs, err := db.Importers(importPath)
+	if err != nil {
+		return err
+	}
+	data := struct {
+		Results []database.Package `json:"results"`
+	}{
+		pkgs,
+	}
+	resp.Header().Set("Content-Type", jsonMIMEType)
+	return json.NewEncoder(resp).Encode(&data)
+}
+
+func serveAPIImports(resp http.ResponseWriter, req *http.Request) error {
+	importPath := strings.TrimPrefix(req.URL.Path, "/imports/")
+	pdoc, _, err := getDoc(importPath, robotRequest)
+	if err != nil {
+		return err
+	}
+	if pdoc == nil || pdoc.Name == "" {
+		return &httpError{status: http.StatusNotFound}
+	}
+	imports, err := db.Packages(pdoc.Imports)
+	if err != nil {
+		return err
+	}
+	testImports, err := db.Packages(pdoc.TestImports)
+	if err != nil {
+		return err
+	}
+	data := struct {
+		Imports     []database.Package `json:"imports"`
+		TestImports []database.Package `json:"testImports"`
+	}{
+		imports,
+		testImports,
+	}
+	resp.Header().Set("Content-Type", jsonMIMEType)
+	return json.NewEncoder(resp).Encode(&data)
+}
+
+func serveAPIHome(resp http.ResponseWriter, req *http.Request) error {
+	return &httpError{status: http.StatusNotFound}
+}
+
+func runHandler(resp http.ResponseWriter, req *http.Request,
+	fn func(resp http.ResponseWriter, req *http.Request) error, errfn httputil.Error) {
+	defer func() {
+		if rv := recover(); rv != nil {
+			err := errors.New("handler panic")
+			logError(req, err, rv)
+			errfn(resp, req, http.StatusInternalServerError, err)
+		}
+	}()
+
+	if s := req.Header.Get("X-Real-Ip"); s != "" && httputil.StripPort(req.RemoteAddr) == "127.0.0.1" {
+		req.RemoteAddr = s
+	}
+
+	req.Body = http.MaxBytesReader(resp, req.Body, 2048)
+	req.ParseForm()
+	var rb httputil.ResponseBuffer
+	err := fn(&rb, req)
+	if err == nil {
+		rb.WriteTo(resp)
+	} else if e, ok := err.(*httpError); ok {
+		if e.status >= 500 {
+			logError(req, err, nil)
+		}
+		errfn(resp, req, e.status, e.err)
+	} else {
+		logError(req, err, nil)
+		errfn(resp, req, http.StatusInternalServerError, err)
+	}
+}
+
+type handler func(resp http.ResponseWriter, req *http.Request) error
+
+func (h handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+	runHandler(resp, req, h, handleError)
+}
+
+type apiHandler func(resp http.ResponseWriter, req *http.Request) error
+
+func (h apiHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+	runHandler(resp, req, h, handleAPIError)
+}
+
+func handleError(resp http.ResponseWriter, req *http.Request, status int, err error) {
+	switch status {
+	case http.StatusNotFound:
+		executeTemplate(resp, "notfound"+templateExt(req), status, nil, nil)
+	default:
+		s := http.StatusText(status)
+		if err == errUpdateTimeout {
+			s = "Timeout getting package files from the version control system."
+		} else if e, ok := err.(*gosrc.RemoteError); ok {
+			s = "Error getting package files from " + e.Host + "."
+		}
+		resp.Header().Set("Content-Type", textMIMEType)
+		resp.WriteHeader(http.StatusInternalServerError)
+		io.WriteString(resp, s)
+	}
+}
+
+func handleAPIError(resp http.ResponseWriter, req *http.Request, status int, err error) {
+	var data struct {
+		Error struct {
+			Message string `json:"message"`
+		} `json:"error"`
+	}
+	data.Error.Message = http.StatusText(status)
+	resp.Header().Set("Content-Type", jsonMIMEType)
+	resp.WriteHeader(status)
+	json.NewEncoder(resp).Encode(&data)
+}
+
+type hostMux []struct {
+	prefix string
+	h      http.Handler
+}
+
+func (m hostMux) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+	var h http.Handler
+	for _, ph := range m {
+		if strings.HasPrefix(req.Host, ph.prefix) {
+			h = ph.h
+			break
+		}
+	}
+	h.ServeHTTP(resp, req)
+}
+
+func defaultBase(path string) string {
+	p, err := build.Default.Import(path, "", build.FindOnly)
+	if err != nil {
+		return "."
+	}
+	return p.Dir
+}
+
+var (
+	db                 *database.Database
+	statusImageHandler http.Handler
+	srcFiles           = make(map[string]*zip.File)
+)
+
+var (
+	robot             = flag.Float64("robot", 100, "Request counter threshold for robots")
+	assetsDir         = flag.String("assets", filepath.Join(defaultBase("github.com/garyburd/gddo/gddo-server"), "assets"), "Base directory for templates and static files.")
+	getTimeout        = flag.Duration("get_timeout", 8*time.Second, "Time to wait for package update from the VCS.")
+	firstGetTimeout   = flag.Duration("first_get_timeout", 5*time.Second, "Time to wait for first fetch of package from the VCS.")
+	maxAge            = flag.Duration("max_age", 24*time.Hour, "Update package documents older than this age.")
+	httpAddr          = flag.String("http", ":8080", "Listen for HTTP connections on this address")
+	srcZip            = flag.String("srcZip", "", "")
+	sidebarEnabled    = flag.Bool("sidebar", false, "Enable package page sidebar.")
+	gitHubCredentials = ""
+	userAgent         = ""
+)
+
+func main() {
+	flag.Parse()
+	log.Printf("Starting server, os.Args=%s", strings.Join(os.Args, " "))
+
+	if *srcZip != "" {
+		r, err := zip.OpenReader(*srcZip)
+		if err != nil {
+			log.Fatal(err)
+		}
+		for _, f := range r.File {
+			if strings.HasPrefix(f.Name, "root/") {
+				srcFiles[f.Name[len("root/"):]] = f
+			}
+		}
+	}
+
+	if err := parseHTMLTemplates([][]string{
+		{"about.html", "common.html", "layout.html"},
+		{"bot.html", "common.html", "layout.html"},
+		{"cmd.html", "common.html", "layout.html"},
+		{"dir.html", "common.html", "layout.html"},
+		{"home.html", "common.html", "layout.html"},
+		{"importers.html", "common.html", "layout.html"},
+		{"importers_robot.html", "common.html", "layout.html"},
+		{"imports.html", "common.html", "layout.html"},
+		{"file.html", "common.html", "layout.html"},
+		{"index.html", "common.html", "layout.html"},
+		{"notfound.html", "common.html", "layout.html"},
+		{"pkg.html", "common.html", "layout.html"},
+		{"results.html", "common.html", "layout.html"},
+		{"tools.html", "common.html", "layout.html"},
+		{"std.html", "common.html", "layout.html"},
+		{"subrepo.html", "common.html", "layout.html"},
+		{"graph.html", "common.html"},
+	}); err != nil {
+		log.Fatal(err)
+	}
+
+	if err := parseTextTemplates([][]string{
+		{"cmd.txt", "common.txt"},
+		{"dir.txt", "common.txt"},
+		{"home.txt", "common.txt"},
+		{"notfound.txt", "common.txt"},
+		{"pkg.txt", "common.txt"},
+		{"results.txt", "common.txt"},
+	}); err != nil {
+		log.Fatal(err)
+	}
+
+	var err error
+	db, err = database.New()
+	if err != nil {
+		log.Fatalf("Error opening database: %v", err)
+	}
+
+	go runBackgroundTasks()
+
+	cssFiles := []string{"third_party/bootstrap/css/bootstrap.min.css", "site.css"}
+
+	staticServer := httputil.StaticServer{
+		Dir:    *assetsDir,
+		MaxAge: time.Hour,
+		MIMETypes: map[string]string{
+			".css": "text/css; charset=utf-8",
+			".js":  "text/javascript; charset=utf-8",
+		},
+	}
+	statusImageHandler = staticServer.FileHandler("status.png")
+
+	apiMux := http.NewServeMux()
+	apiMux.Handle("/favicon.ico", staticServer.FileHandler("favicon.ico"))
+	apiMux.Handle("/google3d2f3cd4cc2bb44b.html", staticServer.FileHandler("google3d2f3cd4cc2bb44b.html"))
+	apiMux.Handle("/humans.txt", staticServer.FileHandler("humans.txt"))
+	apiMux.Handle("/robots.txt", staticServer.FileHandler("apiRobots.txt"))
+	apiMux.Handle("/search", apiHandler(serveAPISearch))
+	apiMux.Handle("/packages", apiHandler(serveAPIPackages))
+	apiMux.Handle("/importers/", apiHandler(serveAPIImporters))
+	apiMux.Handle("/imports/", apiHandler(serveAPIImports))
+	apiMux.Handle("/", apiHandler(serveAPIHome))
+
+	mux := http.NewServeMux()
+	mux.Handle("/-/site.js", staticServer.FilesHandler(
+		"third_party/jquery.timeago.js",
+		"third_party/typeahead.min.js",
+		"third_party/bootstrap/js/bootstrap.min.js",
+		"site.js"))
+	mux.Handle("/-/site.css", staticServer.FilesHandler(cssFiles...))
+	if *sidebarEnabled {
+		mux.Handle("/-/sidebar.css", staticServer.FilesHandler("sidebar.css"))
+	}
+
+	mux.Handle("/-/about", handler(serveAbout))
+	mux.Handle("/-/bot", handler(serveBot))
+	mux.Handle("/-/go", handler(serveGoIndex))
+	mux.Handle("/-/subrepo", handler(serveGoSubrepoIndex))
+	mux.Handle("/-/index", handler(serveIndex))
+	mux.Handle("/-/refresh", handler(serveRefresh))
+	mux.Handle("/a/index", http.RedirectHandler("/-/index", 301))
+	mux.Handle("/about", http.RedirectHandler("/-/about", 301))
+	mux.Handle("/favicon.ico", staticServer.FileHandler("favicon.ico"))
+	mux.Handle("/google3d2f3cd4cc2bb44b.html", staticServer.FileHandler("google3d2f3cd4cc2bb44b.html"))
+	mux.Handle("/humans.txt", staticServer.FileHandler("humans.txt"))
+	mux.Handle("/robots.txt", staticServer.FileHandler("robots.txt"))
+	mux.Handle("/BingSiteAuth.xml", staticServer.FileHandler("BingSiteAuth.xml"))
+	mux.Handle("/C", http.RedirectHandler("http://golang.org/doc/articles/c_go_cgo.html", 301))
+	mux.Handle("/ajax.googleapis.com/", http.NotFoundHandler())
+	mux.Handle("/", handler(serveHome))
+
+	cacheBusters.Handler = mux
+
+	if err := http.ListenAndServe(*httpAddr, hostMux{{"api.", apiMux}, {"", mux}}); err != nil {
+		log.Fatal(err)
+	}
+}
diff --git a/gddo-server/main_test.go b/gddo-server/main_test.go
new file mode 100644
index 0000000..ec6c9ec
--- /dev/null
+++ b/gddo-server/main_test.go
@@ -0,0 +1,33 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package main
+
+import (
+	"net/http"
+	"testing"
+)
+
+var robotTests = []string{
+	"Mozilla/5.0 (compatible; TweetedTimes Bot/1.0; +http://tweetedtimes.com)",
+	"Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)",
+	"Mozilla/5.0 (compatible; MJ12bot/v1.4.3; http://www.majestic12.co.uk/bot.php?+)",
+	"Go 1.1 package http",
+	"Java/1.7.0_25	0.003	0.003",
+	"Python-urllib/2.6",
+	"Mozilla/5.0 (compatible; archive.org_bot +http://www.archive.org/details/archive.org_bot)",
+	"Mozilla/5.0 (compatible; Ezooms/1.0; ezooms.bot@gmail.com)",
+	"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
+}
+
+func TestRobots(t *testing.T) {
+	for _, tt := range robotTests {
+		req := http.Request{Header: http.Header{"User-Agent": {tt}}}
+		if !isRobot(&req) {
+			t.Errorf("%s not a robot", tt)
+		}
+	}
+}
diff --git a/gddo-server/play.go b/gddo-server/play.go
new file mode 100644
index 0000000..4c0aec5
--- /dev/null
+++ b/gddo-server/play.go
@@ -0,0 +1,76 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"regexp"
+	"strings"
+
+	"github.com/garyburd/gddo/doc"
+)
+
+func findExamples(pdoc *doc.Package, export, method string) []*doc.Example {
+	if "package" == export {
+		return pdoc.Examples
+	}
+	for _, f := range pdoc.Funcs {
+		if f.Name == export {
+			return f.Examples
+		}
+	}
+	for _, t := range pdoc.Types {
+		for _, f := range t.Funcs {
+			if f.Name == export {
+				return f.Examples
+			}
+		}
+		if t.Name == export {
+			if method == "" {
+				return t.Examples
+			}
+			for _, m := range t.Methods {
+				if method == m.Name {
+					return m.Examples
+				}
+			}
+			return nil
+		}
+	}
+	return nil
+}
+
+func findExample(pdoc *doc.Package, export, method, name string) *doc.Example {
+	for _, e := range findExamples(pdoc, export, method) {
+		if name == e.Name {
+			return e
+		}
+	}
+	return nil
+}
+
+var exampleIdPat = regexp.MustCompile(`([^-]+)(?:-([^-]*)(?:-(.*))?)?`)
+
+func playURL(pdoc *doc.Package, id string) (string, error) {
+	if m := exampleIdPat.FindStringSubmatch(id); m != nil {
+		if e := findExample(pdoc, m[1], m[2], m[3]); e != nil && e.Play != "" {
+			resp, err := httpClient.Post("http://play.golang.org/share", "text/plain", strings.NewReader(e.Play))
+			if err != nil {
+				return "", err
+			}
+			defer resp.Body.Close()
+			p, err := ioutil.ReadAll(resp.Body)
+			if err != nil {
+				return "", err
+			}
+			return fmt.Sprintf("http://play.golang.org/p/%s", p), nil
+		}
+	}
+	return "", &httpError{status: http.StatusNotFound}
+}
diff --git a/gddo-server/template.go b/gddo-server/template.go
new file mode 100644
index 0000000..db7e3b9
--- /dev/null
+++ b/gddo-server/template.go
@@ -0,0 +1,450 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package main
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	godoc "go/doc"
+	htemp "html/template"
+	"io"
+	"net/http"
+	"net/url"
+	"path"
+	"path/filepath"
+	"reflect"
+	"regexp"
+	"sort"
+	"strings"
+	ttemp "text/template"
+
+	"github.com/garyburd/gddo/doc"
+	"github.com/garyburd/gddo/httputil"
+	"github.com/garyburd/gosrc"
+)
+
+var cacheBusters httputil.CacheBusters
+
+type tdoc struct {
+	*doc.Package
+	allExamples []*texample
+}
+
+type texample struct {
+	Id      string
+	Label   string
+	Example *doc.Example
+	obj     interface{}
+}
+
+func newTDoc(pdoc *doc.Package) *tdoc {
+	return &tdoc{Package: pdoc}
+}
+
+func (pdoc *tdoc) SourceLink(pos doc.Pos, text, anchor string) htemp.HTML {
+	text = htemp.HTMLEscapeString(text)
+	if pos.Line == 0 || pdoc.LineFmt == "" || pdoc.Files[pos.File].URL == "" {
+		return htemp.HTML(text)
+	}
+	var u string
+	if anchor != "" && strings.HasPrefix(pdoc.Files[pos.File].URL, "/") {
+		u = fmt.Sprintf("%s#%s", pdoc.Files[pos.File].URL, anchor)
+	} else {
+		u = fmt.Sprintf(pdoc.LineFmt, pdoc.Files[pos.File].URL, pos.Line)
+	}
+	u = htemp.HTMLEscapeString(u)
+	return htemp.HTML(fmt.Sprintf(`<a title="View Source" href="%s">%s</a>`, u, text))
+}
+
+func (pdoc *tdoc) PageName() string {
+	if pdoc.Name != "" && !pdoc.IsCmd {
+		return pdoc.Name
+	}
+	_, name := path.Split(pdoc.ImportPath)
+	return name
+}
+
+func (pdoc *tdoc) addExamples(obj interface{}, export, method string, examples []*doc.Example) {
+	label := export
+	id := export
+	if method != "" {
+		label += "." + method
+		id += "-" + method
+	}
+	for _, e := range examples {
+		te := &texample{Label: label, Id: id, Example: e, obj: obj}
+		if e.Name != "" {
+			te.Label += " (" + e.Name + ")"
+			if method == "" {
+				te.Id += "-"
+			}
+			te.Id += "-" + e.Name
+		}
+		pdoc.allExamples = append(pdoc.allExamples, te)
+	}
+}
+
+type byExampleId []*texample
+
+func (e byExampleId) Len() int           { return len(e) }
+func (e byExampleId) Less(i, j int) bool { return e[i].Id < e[j].Id }
+func (e byExampleId) Swap(i, j int)      { e[i], e[j] = e[j], e[i] }
+
+func (pdoc *tdoc) AllExamples() []*texample {
+	if pdoc.allExamples != nil {
+		return pdoc.allExamples
+	}
+	pdoc.allExamples = make([]*texample, 0)
+	pdoc.addExamples(pdoc, "package", "", pdoc.Examples)
+	for _, f := range pdoc.Funcs {
+		pdoc.addExamples(f, f.Name, "", f.Examples)
+	}
+	for _, t := range pdoc.Types {
+		pdoc.addExamples(t, t.Name, "", t.Examples)
+		for _, f := range t.Funcs {
+			pdoc.addExamples(f, f.Name, "", f.Examples)
+		}
+		for _, m := range t.Methods {
+			if len(m.Examples) > 0 {
+				pdoc.addExamples(m, t.Name, m.Name, m.Examples)
+			}
+		}
+	}
+	sort.Sort(byExampleId(pdoc.allExamples))
+	return pdoc.allExamples
+}
+
+func (pdoc *tdoc) ObjExamples(obj interface{}) []*texample {
+	var examples []*texample
+	for _, e := range pdoc.allExamples {
+		if e.obj == obj {
+			examples = append(examples, e)
+		}
+	}
+	return examples
+}
+
+func (pdoc *tdoc) Breadcrumbs(templateName string) htemp.HTML {
+	if !strings.HasPrefix(pdoc.ImportPath, pdoc.ProjectRoot) {
+		return ""
+	}
+	var buf bytes.Buffer
+	i := 0
+	j := len(pdoc.ProjectRoot)
+	if j == 0 {
+		j = strings.IndexRune(pdoc.ImportPath, '/')
+		if j < 0 {
+			j = len(pdoc.ImportPath)
+		}
+	}
+	for {
+		if i != 0 {
+			buf.WriteString(`<span class="text-muted">/</span>`)
+		}
+		link := j < len(pdoc.ImportPath) ||
+			(templateName != "dir.html" && templateName != "cmd.html" && templateName != "pkg.html")
+		if link {
+			buf.WriteString(`<a href="`)
+			buf.WriteString(formatPathFrag(pdoc.ImportPath[:j], ""))
+			buf.WriteString(`">`)
+		} else {
+			buf.WriteString(`<span class="text-muted">`)
+		}
+		buf.WriteString(htemp.HTMLEscapeString(pdoc.ImportPath[i:j]))
+		if link {
+			buf.WriteString("</a>")
+		} else {
+			buf.WriteString("</span>")
+		}
+		i = j + 1
+		if i >= len(pdoc.ImportPath) {
+			break
+		}
+		j = strings.IndexRune(pdoc.ImportPath[i:], '/')
+		if j < 0 {
+			j = len(pdoc.ImportPath)
+		} else {
+			j += i
+		}
+	}
+	return htemp.HTML(buf.String())
+}
+
+func formatPathFrag(path, fragment string) string {
+	if len(path) > 0 && path[0] != '/' {
+		path = "/" + path
+	}
+	u := url.URL{Path: path, Fragment: fragment}
+	return u.String()
+}
+
+func hostFn(urlStr string) string {
+	u, err := url.Parse(urlStr)
+	if err != nil {
+		return ""
+	}
+	return u.Host
+}
+
+func mapFn(kvs ...interface{}) (map[string]interface{}, error) {
+	if len(kvs)%2 != 0 {
+		return nil, errors.New("map requires even number of arguments.")
+	}
+	m := make(map[string]interface{})
+	for i := 0; i < len(kvs); i += 2 {
+		s, ok := kvs[i].(string)
+		if !ok {
+			return nil, errors.New("even args to map must be strings.")
+		}
+		m[s] = kvs[i+1]
+	}
+	return m, nil
+}
+
+// relativePathFn formats an import path as HTML.
+func relativePathFn(path string, parentPath interface{}) string {
+	if p, ok := parentPath.(string); ok && p != "" && strings.HasPrefix(path, p) {
+		path = path[len(p)+1:]
+	}
+	return path
+}
+
+// importPathFn formats an import with zero width space characters to allow for breaks.
+func importPathFn(path string) htemp.HTML {
+	path = htemp.HTMLEscapeString(path)
+	if len(path) > 45 {
+		// Allow long import paths to break following "/"
+		path = strings.Replace(path, "/", "/&#8203;", -1)
+	}
+	return htemp.HTML(path)
+}
+
+var (
+	h3Pat      = regexp.MustCompile(`<h3 id="([^"]+)">([^<]+)</h3>`)
+	rfcPat     = regexp.MustCompile(`RFC\s+(\d{3,4})`)
+	packagePat = regexp.MustCompile(`\s+package\s+([-a-z0-9]\S+)`)
+)
+
+func replaceAll(src []byte, re *regexp.Regexp, replace func(out, src []byte, m []int) []byte) []byte {
+	var out []byte
+	for len(src) > 0 {
+		m := re.FindSubmatchIndex(src)
+		if m == nil {
+			break
+		}
+		out = append(out, src[:m[0]]...)
+		out = replace(out, src, m)
+		src = src[m[1]:]
+	}
+	if out == nil {
+		return src
+	}
+	return append(out, src...)
+}
+
+// commentFn formats a source code comment as HTML.
+func commentFn(v string) htemp.HTML {
+	var buf bytes.Buffer
+	godoc.ToHTML(&buf, v, nil)
+	p := buf.Bytes()
+	p = replaceAll(p, h3Pat, func(out, src []byte, m []int) []byte {
+		out = append(out, `<h4 id="`...)
+		out = append(out, src[m[2]:m[3]]...)
+		out = append(out, `">`...)
+		out = append(out, src[m[4]:m[5]]...)
+		out = append(out, ` <a class="permalink" href="#`...)
+		out = append(out, src[m[2]:m[3]]...)
+		out = append(out, `">&para</a></h4>`...)
+		return out
+	})
+	p = replaceAll(p, rfcPat, func(out, src []byte, m []int) []byte {
+		out = append(out, `<a href="http://tools.ietf.org/html/rfc`...)
+		out = append(out, src[m[2]:m[3]]...)
+		out = append(out, `">`...)
+		out = append(out, src[m[0]:m[1]]...)
+		out = append(out, `</a>`...)
+		return out
+	})
+	p = replaceAll(p, packagePat, func(out, src []byte, m []int) []byte {
+		path := bytes.TrimRight(src[m[2]:m[3]], ".!?:")
+		if !gosrc.IsValidPath(string(path)) {
+			return append(out, src[m[0]:m[1]]...)
+		}
+		out = append(out, src[m[0]:m[2]]...)
+		out = append(out, `<a href="/`...)
+		out = append(out, path...)
+		out = append(out, `">`...)
+		out = append(out, path...)
+		out = append(out, `</a>`...)
+		out = append(out, src[m[2]+len(path):m[1]]...)
+		return out
+	})
+	return htemp.HTML(p)
+}
+
+// commentTextFn formats a source code comment as text.
+func commentTextFn(v string) string {
+	const indent = "    "
+	var buf bytes.Buffer
+	godoc.ToText(&buf, v, indent, "\t", 80-2*len(indent))
+	p := buf.Bytes()
+	return string(p)
+}
+
+var period = []byte{'.'}
+
+func codeFn(c doc.Code, typ *doc.Type) htemp.HTML {
+	var buf bytes.Buffer
+	last := 0
+	src := []byte(c.Text)
+	for _, a := range c.Annotations {
+		htemp.HTMLEscape(&buf, src[last:a.Pos])
+		switch a.Kind {
+		case doc.PackageLinkAnnotation:
+			buf.WriteString(`<a href="`)
+			buf.WriteString(formatPathFrag(c.Paths[a.PathIndex], ""))
+			buf.WriteString(`">`)
+			htemp.HTMLEscape(&buf, src[a.Pos:a.End])
+			buf.WriteString(`</a>`)
+		case doc.LinkAnnotation, doc.BuiltinAnnotation:
+			var p string
+			if a.Kind == doc.BuiltinAnnotation {
+				p = "builtin"
+			} else if a.PathIndex >= 0 {
+				p = c.Paths[a.PathIndex]
+			}
+			n := src[a.Pos:a.End]
+			n = n[bytes.LastIndex(n, period)+1:]
+			buf.WriteString(`<a href="`)
+			buf.WriteString(formatPathFrag(p, string(n)))
+			buf.WriteString(`">`)
+			htemp.HTMLEscape(&buf, src[a.Pos:a.End])
+			buf.WriteString(`</a>`)
+		case doc.CommentAnnotation:
+			buf.WriteString(`<span class="com">`)
+			htemp.HTMLEscape(&buf, src[a.Pos:a.End])
+			buf.WriteString(`</span>`)
+		case doc.AnchorAnnotation:
+			buf.WriteString(`<span id="`)
+			if typ != nil {
+				htemp.HTMLEscape(&buf, []byte(typ.Name))
+				buf.WriteByte('.')
+			}
+			htemp.HTMLEscape(&buf, src[a.Pos:a.End])
+			buf.WriteString(`">`)
+			htemp.HTMLEscape(&buf, src[a.Pos:a.End])
+			buf.WriteString(`</span>`)
+		default:
+			htemp.HTMLEscape(&buf, src[a.Pos:a.End])
+		}
+		last = int(a.End)
+	}
+	htemp.HTMLEscape(&buf, src[last:])
+	return htemp.HTML(buf.String())
+}
+
+var gaAccount string
+
+func gaAccountFn() string {
+	return gaAccount
+}
+
+func noteTitleFn(s string) string {
+	return strings.Title(strings.ToLower(s))
+}
+
+func htmlCommentFn(s string) htemp.HTML {
+	return htemp.HTML("<!-- " + s + " -->")
+}
+
+var mimeTypes = map[string]string{
+	".html": htmlMIMEType,
+	".txt":  textMIMEType,
+}
+
+func executeTemplate(resp http.ResponseWriter, name string, status int, header http.Header, data interface{}) error {
+	for k, v := range header {
+		resp.Header()[k] = v
+	}
+	mimeType, ok := mimeTypes[path.Ext(name)]
+	if !ok {
+		mimeType = textMIMEType
+	}
+	resp.Header().Set("Content-Type", mimeType)
+	t := templates[name]
+	if t == nil {
+		return fmt.Errorf("Template %s not found", name)
+	}
+	resp.WriteHeader(status)
+	if status == http.StatusNotModified {
+		return nil
+	}
+	return t.Execute(resp, data)
+}
+
+var templates = map[string]interface {
+	Execute(io.Writer, interface{}) error
+}{}
+
+func joinTemplateDir(base string, files []string) []string {
+	result := make([]string, len(files))
+	for i := range files {
+		result[i] = filepath.Join(base, "templates", files[i])
+	}
+	return result
+}
+
+func parseHTMLTemplates(sets [][]string) error {
+	for _, set := range sets {
+		templateName := set[0]
+		t := htemp.New("")
+		t.Funcs(htemp.FuncMap{
+			"code":              codeFn,
+			"comment":           commentFn,
+			"equal":             reflect.DeepEqual,
+			"gaAccount":         gaAccountFn,
+			"host":              hostFn,
+			"htmlComment":       htmlCommentFn,
+			"importPath":        importPathFn,
+			"isValidImportPath": gosrc.IsValidPath,
+			"map":               mapFn,
+			"noteTitle":         noteTitleFn,
+			"relativePath":      relativePathFn,
+			"sidebarEnabled":    func() bool { return *sidebarEnabled },
+			"staticPath":        func(p string) string { return cacheBusters.AppendQueryParam(p, "v") },
+			"templateName":      func() string { return templateName },
+		})
+		if _, err := t.ParseFiles(joinTemplateDir(*assetsDir, set)...); err != nil {
+			return err
+		}
+		t = t.Lookup("ROOT")
+		if t == nil {
+			return fmt.Errorf("ROOT template not found in %v", set)
+		}
+		templates[set[0]] = t
+	}
+	return nil
+}
+
+func parseTextTemplates(sets [][]string) error {
+	for _, set := range sets {
+		t := ttemp.New("")
+		t.Funcs(ttemp.FuncMap{
+			"comment": commentTextFn,
+		})
+		if _, err := t.ParseFiles(joinTemplateDir(*assetsDir, set)...); err != nil {
+			return err
+		}
+		t = t.Lookup("ROOT")
+		if t == nil {
+			return fmt.Errorf("ROOT template not found in %v", set)
+		}
+		templates[set[0]] = t
+	}
+	return nil
+}
diff --git a/httputil/buster.go b/httputil/buster.go
new file mode 100644
index 0000000..9bbf243
--- /dev/null
+++ b/httputil/buster.go
@@ -0,0 +1,95 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package httputil
+
+import (
+	"io"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"strings"
+	"sync"
+)
+
+type busterWriter struct {
+	headerMap http.Header
+	status    int
+	io.Writer
+}
+
+func (bw *busterWriter) Header() http.Header {
+	return bw.headerMap
+}
+
+func (bw *busterWriter) WriteHeader(status int) {
+	bw.status = status
+}
+
+// CacheBusters maintains a cache of cache busting tokens for static resources served by Handler.
+type CacheBusters struct {
+	Handler http.Handler
+
+	mu     sync.Mutex
+	tokens map[string]string
+}
+
+func sanitizeTokenRune(r rune) rune {
+	if r <= ' ' || r >= 127 {
+		return -1
+	}
+	// Convert percent encoding reserved characters to '-'.
+	if strings.ContainsRune("!#$&'()*+,/:;=?@[]", r) {
+		return '-'
+	}
+	return r
+}
+
+// Token returns the cache busting token for path. If the token is not already
+// cached, Get issues a HEAD request on handler and uses the response ETag and
+// Last-Modified headers to compute a token.
+func (cb *CacheBusters) Get(path string) string {
+	cb.mu.Lock()
+	if cb.tokens == nil {
+		cb.tokens = make(map[string]string)
+	}
+	token, ok := cb.tokens[path]
+	cb.mu.Unlock()
+	if ok {
+		return token
+	}
+
+	w := busterWriter{
+		Writer:    ioutil.Discard,
+		headerMap: make(http.Header),
+	}
+	r := &http.Request{URL: &url.URL{Path: path}, Method: "HEAD"}
+	cb.Handler.ServeHTTP(&w, r)
+
+	if w.status == 200 {
+		token = w.headerMap.Get("Etag")
+		if token == "" {
+			token = w.headerMap.Get("Last-Modified")
+		}
+		token = strings.Trim(token, `" `)
+		token = strings.Map(sanitizeTokenRune, token)
+	}
+
+	cb.mu.Lock()
+	cb.tokens[path] = token
+	cb.mu.Unlock()
+
+	return token
+}
+
+// AppendQueryParam appends the token as a query parameter to path.
+func (cb *CacheBusters) AppendQueryParam(path string, name string) string {
+	token := cb.Get(path)
+	if token == "" {
+		return path
+	}
+	return path + "?" + name + "=" + token
+}
diff --git a/httputil/buster_test.go b/httputil/buster_test.go
new file mode 100644
index 0000000..0d2bb83
--- /dev/null
+++ b/httputil/buster_test.go
@@ -0,0 +1,29 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package httputil
+
+import (
+	"net/http"
+	"testing"
+)
+
+func TestCacheBusters(t *testing.T) {
+	cbs := CacheBusters{Handler: http.FileServer(http.Dir("."))}
+
+	token := cbs.Get("/buster_test.go")
+	if token == "" {
+		t.Errorf("could not extract token from http.FileServer")
+	}
+
+	var ss StaticServer
+	cbs = CacheBusters{Handler: ss.FileHandler("buster_test.go")}
+
+	token = cbs.Get("/xxx")
+	if token == "" {
+		t.Errorf("could not extract token from StaticServer FileHandler")
+	}
+}
diff --git a/httputil/header/header.go b/httputil/header/header.go
new file mode 100644
index 0000000..fd1dcc9
--- /dev/null
+++ b/httputil/header/header.go
@@ -0,0 +1,297 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+// Package header provides functions for parsing HTTP headers.
+package header
+
+import (
+	"net/http"
+	"strings"
+	"time"
+)
+
+// Octet types from RFC 2616.
+var octetTypes [256]octetType
+
+type octetType byte
+
+const (
+	isToken octetType = 1 << iota
+	isSpace
+)
+
+func init() {
+	// OCTET      = <any 8-bit sequence of data>
+	// CHAR       = <any US-ASCII character (octets 0 - 127)>
+	// CTL        = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
+	// CR         = <US-ASCII CR, carriage return (13)>
+	// LF         = <US-ASCII LF, linefeed (10)>
+	// SP         = <US-ASCII SP, space (32)>
+	// HT         = <US-ASCII HT, horizontal-tab (9)>
+	// <">        = <US-ASCII double-quote mark (34)>
+	// CRLF       = CR LF
+	// LWS        = [CRLF] 1*( SP | HT )
+	// TEXT       = <any OCTET except CTLs, but including LWS>
+	// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
+	//              | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
+	// token      = 1*<any CHAR except CTLs or separators>
+	// qdtext     = <any TEXT except <">>
+
+	for c := 0; c < 256; c++ {
+		var t octetType
+		isCtl := c <= 31 || c == 127
+		isChar := 0 <= c && c <= 127
+		isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
+		if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
+			t |= isSpace
+		}
+		if isChar && !isCtl && !isSeparator {
+			t |= isToken
+		}
+		octetTypes[c] = t
+	}
+}
+
+// Copy returns a shallow copy of the header.
+func Copy(header http.Header) http.Header {
+	h := make(http.Header)
+	for k, vs := range header {
+		h[k] = vs
+	}
+	return h
+}
+
+var timeLayouts = []string{"Mon, 02 Jan 2006 15:04:05 GMT", time.RFC850, time.ANSIC}
+
+// ParseTime parses the header as time. The zero value is returned if the
+// header is not present or there is an error parsing the
+// header.
+func ParseTime(header http.Header, key string) time.Time {
+	if s := header.Get(key); s != "" {
+		for _, layout := range timeLayouts {
+			if t, err := time.Parse(layout, s); err == nil {
+				return t.UTC()
+			}
+		}
+	}
+	return time.Time{}
+}
+
+// ParseList parses a comma separated list of values. Commas are ignored in
+// quoted strings. Quoted values are not unescaped or unquoted. Whitespace is
+// trimmed.
+func ParseList(header http.Header, key string) []string {
+	var result []string
+	for _, s := range header[http.CanonicalHeaderKey(key)] {
+		begin := 0
+		end := 0
+		escape := false
+		quote := false
+		for i := 0; i < len(s); i++ {
+			b := s[i]
+			switch {
+			case escape:
+				escape = false
+				end = i + 1
+			case quote:
+				switch b {
+				case '\\':
+					escape = true
+				case '"':
+					quote = false
+				}
+				end = i + 1
+			case b == '"':
+				quote = true
+				end = i + 1
+			case octetTypes[b]&isSpace != 0:
+				if begin == end {
+					begin = i + 1
+					end = begin
+				}
+			case b == ',':
+				if begin < end {
+					result = append(result, s[begin:end])
+				}
+				begin = i + 1
+				end = begin
+			default:
+				end = i + 1
+			}
+		}
+		if begin < end {
+			result = append(result, s[begin:end])
+		}
+	}
+	return result
+}
+
+// ParseValueAndParams parses a comma separated list of values with optional
+// semicolon separated name-value pairs. Content-Type and Content-Disposition
+// headers are in this format.
+func ParseValueAndParams(header http.Header, key string) (value string, params map[string]string) {
+	params = make(map[string]string)
+	s := header.Get(key)
+	value, s = expectTokenSlash(s)
+	if value == "" {
+		return
+	}
+	value = strings.ToLower(value)
+	s = skipSpace(s)
+	for strings.HasPrefix(s, ";") {
+		var pkey string
+		pkey, s = expectToken(skipSpace(s[1:]))
+		if pkey == "" {
+			return
+		}
+		if !strings.HasPrefix(s, "=") {
+			return
+		}
+		var pvalue string
+		pvalue, s = expectTokenOrQuoted(s[1:])
+		if pvalue == "" {
+			return
+		}
+		pkey = strings.ToLower(pkey)
+		params[pkey] = pvalue
+		s = skipSpace(s)
+	}
+	return
+}
+
+type AcceptSpec struct {
+	Value string
+	Q     float64
+}
+
+// ParseAccept parses Accept* headers.
+func ParseAccept(header http.Header, key string) (specs []AcceptSpec) {
+loop:
+	for _, s := range header[key] {
+		for {
+			var spec AcceptSpec
+			spec.Value, s = expectTokenSlash(s)
+			if spec.Value == "" {
+				continue loop
+			}
+			spec.Q = 1.0
+			s = skipSpace(s)
+			if strings.HasPrefix(s, ";") {
+				s = skipSpace(s[1:])
+				if !strings.HasPrefix(s, "q=") {
+					continue loop
+				}
+				spec.Q, s = expectQuality(s[2:])
+				if spec.Q < 0.0 {
+					continue loop
+				}
+			}
+			specs = append(specs, spec)
+			s = skipSpace(s)
+			if !strings.HasPrefix(s, ",") {
+				continue loop
+			}
+			s = skipSpace(s[1:])
+		}
+	}
+	return
+}
+
+func skipSpace(s string) (rest string) {
+	i := 0
+	for ; i < len(s); i++ {
+		if octetTypes[s[i]]&isSpace == 0 {
+			break
+		}
+	}
+	return s[i:]
+}
+
+func expectToken(s string) (token, rest string) {
+	i := 0
+	for ; i < len(s); i++ {
+		if octetTypes[s[i]]&isToken == 0 {
+			break
+		}
+	}
+	return s[:i], s[i:]
+}
+
+func expectTokenSlash(s string) (token, rest string) {
+	i := 0
+	for ; i < len(s); i++ {
+		b := s[i]
+		if (octetTypes[b]&isToken == 0) && b != '/' {
+			break
+		}
+	}
+	return s[:i], s[i:]
+}
+
+func expectQuality(s string) (q float64, rest string) {
+	switch {
+	case len(s) == 0:
+		return -1, ""
+	case s[0] == '0':
+		q = 0
+	case s[0] == '1':
+		q = 1
+	default:
+		return -1, ""
+	}
+	s = s[1:]
+	if !strings.HasPrefix(s, ".") {
+		return q, s
+	}
+	s = s[1:]
+	i := 0
+	n := 0
+	d := 1
+	for ; i < len(s); i++ {
+		b := s[i]
+		if b < '0' || b > '9' {
+			break
+		}
+		n = n*10 + int(b) - '0'
+		d *= 10
+	}
+	return q + float64(n)/float64(d), s[i:]
+}
+
+func expectTokenOrQuoted(s string) (value string, rest string) {
+	if !strings.HasPrefix(s, "\"") {
+		return expectToken(s)
+	}
+	s = s[1:]
+	for i := 0; i < len(s); i++ {
+		switch s[i] {
+		case '"':
+			return s[:i], s[i+1:]
+		case '\\':
+			p := make([]byte, len(s)-1)
+			j := copy(p, s[:i])
+			escape := true
+			for i = i + i; i < len(s); i++ {
+				b := s[i]
+				switch {
+				case escape:
+					escape = false
+					p[j] = b
+					j += 1
+				case b == '\\':
+					escape = true
+				case b == '"':
+					return string(p[:j]), s[i+1:]
+				default:
+					p[j] = b
+					j += 1
+				}
+			}
+			return "", ""
+		}
+	}
+	return "", ""
+}
diff --git a/httputil/header/header_test.go b/httputil/header/header_test.go
new file mode 100644
index 0000000..ca97e9d
--- /dev/null
+++ b/httputil/header/header_test.go
@@ -0,0 +1,138 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package header
+
+import (
+	"net/http"
+	"reflect"
+	"testing"
+	"time"
+)
+
+var getHeaderListTests = []struct {
+	s string
+	l []string
+}{
+	{s: `a`, l: []string{`a`}},
+	{s: `a, b , c `, l: []string{`a`, `b`, `c`}},
+	{s: `a,, b , , c `, l: []string{`a`, `b`, `c`}},
+	{s: `a,b,c`, l: []string{`a`, `b`, `c`}},
+	{s: ` a b, c d `, l: []string{`a b`, `c d`}},
+	{s: `"a, b, c", d `, l: []string{`"a, b, c"`, "d"}},
+	{s: `","`, l: []string{`","`}},
+	{s: `"\""`, l: []string{`"\""`}},
+	{s: `" "`, l: []string{`" "`}},
+}
+
+func TestGetHeaderList(t *testing.T) {
+	for _, tt := range getHeaderListTests {
+		header := http.Header{"Foo": {tt.s}}
+		if l := ParseList(header, "foo"); !reflect.DeepEqual(tt.l, l) {
+			t.Errorf("ParseList for %q = %q, want %q", tt.s, l, tt.l)
+		}
+	}
+}
+
+var parseValueAndParamsTests = []struct {
+	s      string
+	value  string
+	params map[string]string
+}{
+	{`text/html`, "text/html", map[string]string{}},
+	{`text/html  `, "text/html", map[string]string{}},
+	{`text/html ; `, "text/html", map[string]string{}},
+	{`tExt/htMl`, "text/html", map[string]string{}},
+	{`tExt/htMl; fOO=";"; hellO=world`, "text/html", map[string]string{
+		"hello": "world",
+		"foo":   `;`,
+	}},
+	{`text/html; foo=bar, hello=world`, "text/html", map[string]string{"foo": "bar"}},
+	{`text/html ; foo=bar `, "text/html", map[string]string{"foo": "bar"}},
+	{`text/html ;foo=bar `, "text/html", map[string]string{"foo": "bar"}},
+	{`text/html; foo="b\ar"`, "text/html", map[string]string{"foo": "bar"}},
+	{`text/html; foo="b\"a\"r"`, "text/html", map[string]string{"foo": "b\"a\"r"}},
+	{`text/html; foo="b,ar"`, "text/html", map[string]string{"foo": "b,ar"}},
+	{`text/html; foo="b;ar"`, "text/html", map[string]string{"foo": "b;ar"}},
+	{`text/html; FOO="bar"`, "text/html", map[string]string{"foo": "bar"}},
+	{`form-data; filename="file.txt"; name=file`, "form-data", map[string]string{"filename": "file.txt", "name": "file"}},
+}
+
+func TestParseValueAndParams(t *testing.T) {
+	for _, tt := range parseValueAndParamsTests {
+		header := http.Header{"Content-Type": {tt.s}}
+		value, params := ParseValueAndParams(header, "Content-Type")
+		if value != tt.value {
+			t.Errorf("%q, value=%q, want %q", tt.s, value, tt.value)
+		}
+		if !reflect.DeepEqual(params, tt.params) {
+			t.Errorf("%q, param=%#v, want %#v", tt.s, params, tt.params)
+		}
+	}
+}
+
+var parseTimeValidTests = []string{
+	"Sun, 06 Nov 1994 08:49:37 GMT",
+	"Sunday, 06-Nov-94 08:49:37 GMT",
+	"Sun Nov  6 08:49:37 1994",
+}
+
+var parseTimeInvalidTests = []string{
+	"junk",
+}
+
+func TestParseTime(t *testing.T) {
+	expected := time.Date(1994, 11, 6, 8, 49, 37, 0, time.UTC)
+	for _, s := range parseTimeValidTests {
+		header := http.Header{"Date": {s}}
+		actual := ParseTime(header, "Date")
+		if actual != expected {
+			t.Errorf("GetTime(%q)=%v, want %v", s, actual, expected)
+		}
+	}
+	for _, s := range parseTimeInvalidTests {
+		header := http.Header{"Date": {s}}
+		actual := ParseTime(header, "Date")
+		if !actual.IsZero() {
+			t.Errorf("GetTime(%q) did not return zero", s)
+		}
+	}
+}
+
+var parseAcceptTests = []struct {
+	s        string
+	expected []AcceptSpec
+}{
+	{"text/html", []AcceptSpec{{"text/html", 1}}},
+	{"text/html; q=0", []AcceptSpec{{"text/html", 0}}},
+	{"text/html; q=0.0", []AcceptSpec{{"text/html", 0}}},
+	{"text/html; q=1", []AcceptSpec{{"text/html", 1}}},
+	{"text/html; q=1.0", []AcceptSpec{{"text/html", 1}}},
+	{"text/html; q=0.1", []AcceptSpec{{"text/html", 0.1}}},
+	{"text/html;q=0.1", []AcceptSpec{{"text/html", 0.1}}},
+	{"text/html, text/plain", []AcceptSpec{{"text/html", 1}, {"text/plain", 1}}},
+	{"text/html; q=0.1, text/plain", []AcceptSpec{{"text/html", 0.1}, {"text/plain", 1}}},
+	{"iso-8859-5, unicode-1-1;q=0.8,iso-8859-1", []AcceptSpec{{"iso-8859-5", 1}, {"unicode-1-1", 0.8}, {"iso-8859-1", 1}}},
+	{"iso-8859-1", []AcceptSpec{{"iso-8859-1", 1}}},
+	{"*", []AcceptSpec{{"*", 1}}},
+	{"da, en-gb;q=0.8, en;q=0.7", []AcceptSpec{{"da", 1}, {"en-gb", 0.8}, {"en", 0.7}}},
+	{"da, q, en-gb;q=0.8", []AcceptSpec{{"da", 1}, {"q", 1}, {"en-gb", 0.8}}},
+	{"image/png, image/*;q=0.5", []AcceptSpec{{"image/png", 1}, {"image/*", 0.5}}},
+
+	// bad cases
+	{"value1; q=0.1.2", []AcceptSpec{{"value1", 0.1}}},
+	{"da, en-gb;q=foo", []AcceptSpec{{"da", 1}}},
+}
+
+func TestParseAccept(t *testing.T) {
+	for _, tt := range parseAcceptTests {
+		header := http.Header{"Accept": {tt.s}}
+		actual := ParseAccept(header, "Accept")
+		if !reflect.DeepEqual(actual, tt.expected) {
+			t.Errorf("ParseAccept(h, %q)=%v, want %v", tt.s, actual, tt.expected)
+		}
+	}
+}
diff --git a/httputil/httputil.go b/httputil/httputil.go
new file mode 100644
index 0000000..ee38038
--- /dev/null
+++ b/httputil/httputil.go
@@ -0,0 +1,23 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+// Package htputil is a toolkit for the Go net/http package.
+package httputil
+
+import (
+	"net"
+	"net/http"
+)
+
+// StripPort removes the port specification from an address.
+func StripPort(s string) string {
+	if h, _, err := net.SplitHostPort(s); err == nil {
+		s = h
+	}
+	return s
+}
+
+type Error func(w http.ResponseWriter, r *http.Request, status int, err error)
diff --git a/httputil/negotiate.go b/httputil/negotiate.go
new file mode 100644
index 0000000..4c991a3
--- /dev/null
+++ b/httputil/negotiate.go
@@ -0,0 +1,79 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package httputil
+
+import (
+	"github.com/garyburd/gddo/httputil/header"
+	"net/http"
+	"strings"
+)
+
+// NegotiateContentEncoding returns the best offered content encoding for the
+// request's Accept-Encoding header. If two offers match with equal weight and
+// then the offer earlier in the list is preferred. If no offers are
+// acceptable, then "" is returned.
+func NegotiateContentEncoding(r *http.Request, offers []string) string {
+	bestOffer := "identity"
+	bestQ := -1.0
+	specs := header.ParseAccept(r.Header, "Accept-Encoding")
+	for _, offer := range offers {
+		for _, spec := range specs {
+			if spec.Q > bestQ &&
+				(spec.Value == "*" || spec.Value == offer) {
+				bestQ = spec.Q
+				bestOffer = offer
+			}
+		}
+	}
+	if bestQ == 0 {
+		bestOffer = ""
+	}
+	return bestOffer
+}
+
+// NegotiateContentType returns the best offered content type for the request's
+// Accept header. If two offers match with equal weight, then the more specific
+// offer is preferred.  For example, text/* trumps */*. If two offers match
+// with equal weight and specificity, then the offer earlier in the list is
+// preferred. If no offers match, then defaultOffer is returned.
+func NegotiateContentType(r *http.Request, offers []string, defaultOffer string) string {
+	bestOffer := defaultOffer
+	bestQ := -1.0
+	bestWild := 3
+	specs := header.ParseAccept(r.Header, "Accept")
+	for _, offer := range offers {
+		for _, spec := range specs {
+			switch {
+			case spec.Q == 0.0:
+				// ignore
+			case spec.Q < bestQ:
+				// better match found
+			case spec.Value == "*/*":
+				if spec.Q > bestQ || bestWild > 2 {
+					bestQ = spec.Q
+					bestWild = 2
+					bestOffer = offer
+				}
+			case strings.HasSuffix(spec.Value, "/*"):
+				if strings.HasPrefix(offer, spec.Value[:len(spec.Value)-1]) &&
+					(spec.Q > bestQ || bestWild > 1) {
+					bestQ = spec.Q
+					bestWild = 1
+					bestOffer = offer
+				}
+			default:
+				if spec.Value == offer &&
+					(spec.Q > bestQ || bestWild > 0) {
+					bestQ = spec.Q
+					bestWild = 0
+					bestOffer = offer
+				}
+			}
+		}
+	}
+	return bestOffer
+}
diff --git a/httputil/negotiate_test.go b/httputil/negotiate_test.go
new file mode 100644
index 0000000..785d14f
--- /dev/null
+++ b/httputil/negotiate_test.go
@@ -0,0 +1,71 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package httputil_test
+
+import (
+	"github.com/garyburd/gddo/httputil"
+	"net/http"
+	"testing"
+)
+
+var negotiateContentEncodingTests = []struct {
+	s      string
+	offers []string
+	expect string
+}{
+	{"", []string{"identity", "gzip"}, "identity"},
+	{"*;q=0", []string{"identity", "gzip"}, ""},
+	{"gzip", []string{"identity", "gzip"}, "gzip"},
+}
+
+func TestNegotiateContentEnoding(t *testing.T) {
+	for _, tt := range negotiateContentEncodingTests {
+		r := &http.Request{Header: http.Header{"Accept-Encoding": {tt.s}}}
+		actual := httputil.NegotiateContentEncoding(r, tt.offers)
+		if actual != tt.expect {
+			t.Errorf("NegotiateContentEncoding(%q, %#v)=%q, want %q", tt.s, tt.offers, actual, tt.expect)
+		}
+	}
+}
+
+var negotiateContentTypeTests = []struct {
+	s            string
+	offers       []string
+	defaultOffer string
+	expect       string
+}{
+	{"text/html, */*;q=0", []string{"x/y"}, "", ""},
+	{"text/html, */*", []string{"x/y"}, "", "x/y"},
+	{"text/html, image/png", []string{"text/html", "image/png"}, "", "text/html"},
+	{"text/html, image/png", []string{"image/png", "text/html"}, "", "image/png"},
+	{"text/html, image/png; q=0.5", []string{"image/png"}, "", "image/png"},
+	{"text/html, image/png; q=0.5", []string{"text/html"}, "", "text/html"},
+	{"text/html, image/png; q=0.5", []string{"foo/bar"}, "", ""},
+	{"text/html, image/png; q=0.5", []string{"image/png", "text/html"}, "", "text/html"},
+	{"text/html, image/png; q=0.5", []string{"text/html", "image/png"}, "", "text/html"},
+	{"text/html;q=0.5, image/png", []string{"image/png"}, "", "image/png"},
+	{"text/html;q=0.5, image/png", []string{"text/html"}, "", "text/html"},
+	{"text/html;q=0.5, image/png", []string{"image/png", "text/html"}, "", "image/png"},
+	{"text/html;q=0.5, image/png", []string{"text/html", "image/png"}, "", "image/png"},
+	{"image/png, image/*;q=0.5", []string{"image/jpg", "image/png"}, "", "image/png"},
+	{"image/png, image/*;q=0.5", []string{"image/jpg"}, "", "image/jpg"},
+	{"image/png, image/*;q=0.5", []string{"image/jpg", "image/gif"}, "", "image/jpg"},
+	{"image/png, image/*", []string{"image/jpg", "image/gif"}, "", "image/jpg"},
+	{"image/png, image/*", []string{"image/gif", "image/jpg"}, "", "image/gif"},
+	{"image/png, image/*", []string{"image/gif", "image/png"}, "", "image/png"},
+	{"image/png, image/*", []string{"image/png", "image/gif"}, "", "image/png"},
+}
+
+func TestNegotiateContentType(t *testing.T) {
+	for _, tt := range negotiateContentTypeTests {
+		r := &http.Request{Header: http.Header{"Accept": {tt.s}}}
+		actual := httputil.NegotiateContentType(r, tt.offers, tt.defaultOffer)
+		if actual != tt.expect {
+			t.Errorf("NegotiateContentType(%q, %#v, %q)=%q, want %q", tt.s, tt.offers, tt.defaultOffer, actual, tt.expect)
+		}
+	}
+}
diff --git a/httputil/respbuf.go b/httputil/respbuf.go
new file mode 100644
index 0000000..8641c0f
--- /dev/null
+++ b/httputil/respbuf.go
@@ -0,0 +1,52 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package httputil
+
+import (
+	"bytes"
+	"net/http"
+	"strconv"
+)
+
+type ResponseBuffer struct {
+	buf    bytes.Buffer
+	status int
+	header http.Header
+}
+
+func (rb *ResponseBuffer) Write(p []byte) (int, error) {
+	return rb.buf.Write(p)
+}
+
+func (rb *ResponseBuffer) WriteHeader(status int) {
+	rb.status = status
+}
+
+func (rb *ResponseBuffer) Header() http.Header {
+	if rb.header == nil {
+		rb.header = make(http.Header)
+	}
+	return rb.header
+}
+
+func (rb *ResponseBuffer) WriteTo(w http.ResponseWriter) error {
+	for k, v := range rb.header {
+		w.Header()[k] = v
+	}
+	if rb.buf.Len() > 0 {
+		w.Header().Set("Content-Length", strconv.Itoa(rb.buf.Len()))
+	}
+	if rb.status != 0 {
+		w.WriteHeader(rb.status)
+	}
+	if rb.buf.Len() > 0 {
+		if _, err := w.Write(rb.buf.Bytes()); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/httputil/static.go b/httputil/static.go
new file mode 100644
index 0000000..d4b5285
--- /dev/null
+++ b/httputil/static.go
@@ -0,0 +1,265 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package httputil
+
+import (
+	"bytes"
+	"crypto/sha1"
+	"errors"
+	"fmt"
+	"github.com/garyburd/gddo/httputil/header"
+	"io"
+	"io/ioutil"
+	"mime"
+	"net/http"
+	"os"
+	"path"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+)
+
+// StaticServer serves static files.
+type StaticServer struct {
+	// Dir specifies the location of the directory containing the files to serve.
+	Dir string
+
+	// MaxAge specifies the maximum age for the cache control and expiration
+	// headers.
+	MaxAge time.Duration
+
+	// Error specifies the function used to generate error responses. If Error
+	// is nil, then http.Error is used to generate error responses.
+	Error Error
+
+	// MIMETypes is a map from file extensions to MIME types.
+	MIMETypes map[string]string
+
+	mu    sync.Mutex
+	etags map[string]string
+}
+
+func (ss *StaticServer) resolve(fname string) string {
+	if path.IsAbs(fname) {
+		panic("Absolute path not allowed when creating a StaticServer handler")
+	}
+	dir := ss.Dir
+	if dir == "" {
+		dir = "."
+	}
+	fname = filepath.FromSlash(fname)
+	return filepath.Join(dir, fname)
+}
+
+func (ss *StaticServer) mimeType(fname string) string {
+	ext := path.Ext(fname)
+	var mimeType string
+	if ss.MIMETypes != nil {
+		mimeType = ss.MIMETypes[ext]
+	}
+	if mimeType == "" {
+		mimeType = mime.TypeByExtension(ext)
+	}
+	if mimeType == "" {
+		mimeType = "application/octet-stream"
+	}
+	return mimeType
+}
+
+func (ss *StaticServer) openFile(fname string) (io.ReadCloser, int64, string, error) {
+	f, err := os.Open(fname)
+	if err != nil {
+		return nil, 0, "", err
+	}
+	fi, err := f.Stat()
+	if err != nil {
+		f.Close()
+		return nil, 0, "", err
+	}
+	const modeType = os.ModeDir | os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice
+	if fi.Mode()&modeType != 0 {
+		f.Close()
+		return nil, 0, "", errors.New("not a regular file")
+	}
+	return f, fi.Size(), ss.mimeType(fname), nil
+}
+
+// FileHandler returns a handler that serves a single file. The file is
+// specified by a slash separated path relative to the static server's Dir
+// field.
+func (ss *StaticServer) FileHandler(fileName string) http.Handler {
+	id := fileName
+	fileName = ss.resolve(fileName)
+	return &staticHandler{
+		ss:   ss,
+		id:   func(_ string) string { return id },
+		open: func(_ string) (io.ReadCloser, int64, string, error) { return ss.openFile(fileName) },
+	}
+}
+
+// DirectoryHandler returns a handler that serves files from a directory tree.
+// The directory is specified by a slash separated path relative to the static
+// server's Dir field.
+func (ss *StaticServer) DirectoryHandler(prefix, dirName string) http.Handler {
+	if !strings.HasSuffix(prefix, "/") {
+		prefix += "/"
+	}
+	idBase := dirName
+	dirName = ss.resolve(dirName)
+	return &staticHandler{
+		ss: ss,
+		id: func(p string) string {
+			if !strings.HasPrefix(p, prefix) {
+				return "."
+			}
+			return path.Join(idBase, p[len(prefix):])
+		},
+		open: func(p string) (io.ReadCloser, int64, string, error) {
+			if !strings.HasPrefix(p, prefix) {
+				return nil, 0, "", errors.New("request url does not match directory prefix")
+			}
+			p = p[len(prefix):]
+			return ss.openFile(filepath.Join(dirName, filepath.FromSlash(p)))
+		},
+	}
+}
+
+// FilesHandler returns a handler that serves the concatentation of the
+// specified files. The files are specified by slash separated paths relative
+// to the static server's Dir field.
+func (ss *StaticServer) FilesHandler(fileNames ...string) http.Handler {
+
+	// todo: cache concatenated files on disk and serve from there.
+
+	mimeType := ss.mimeType(fileNames[0])
+	var buf []byte
+	var openErr error
+
+	for _, fileName := range fileNames {
+		p, err := ioutil.ReadFile(ss.resolve(fileName))
+		if err != nil {
+			openErr = err
+			buf = nil
+			break
+		}
+		buf = append(buf, p...)
+	}
+
+	id := strings.Join(fileNames, " ")
+
+	return &staticHandler{
+		ss: ss,
+		id: func(_ string) string { return id },
+		open: func(p string) (io.ReadCloser, int64, string, error) {
+			return ioutil.NopCloser(bytes.NewReader(buf)), int64(len(buf)), mimeType, openErr
+		},
+	}
+}
+
+type staticHandler struct {
+	id   func(fname string) string
+	open func(p string) (io.ReadCloser, int64, string, error)
+	ss   *StaticServer
+}
+
+func (h *staticHandler) error(w http.ResponseWriter, r *http.Request, status int, err error) {
+	http.Error(w, http.StatusText(status), status)
+}
+
+func (h *staticHandler) etag(p string) (string, error) {
+	id := h.id(p)
+
+	h.ss.mu.Lock()
+	if h.ss.etags == nil {
+		h.ss.etags = make(map[string]string)
+	}
+	etag := h.ss.etags[id]
+	h.ss.mu.Unlock()
+
+	if etag != "" {
+		return etag, nil
+	}
+
+	// todo: if a concurrent goroutine is calculating the hash, then wait for
+	// it instead of computing it again here.
+
+	rc, _, _, err := h.open(p)
+	if err != nil {
+		return "", err
+	}
+
+	defer rc.Close()
+
+	w := sha1.New()
+	_, err = io.Copy(w, rc)
+	if err != nil {
+		return "", err
+	}
+
+	etag = fmt.Sprintf(`"%x"`, w.Sum(nil))
+
+	h.ss.mu.Lock()
+	h.ss.etags[id] = etag
+	h.ss.mu.Unlock()
+
+	return etag, nil
+}
+
+func (h *staticHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	p := path.Clean(r.URL.Path)
+	if p != r.URL.Path {
+		http.Redirect(w, r, p, 301)
+		return
+	}
+
+	etag, err := h.etag(p)
+	if err != nil {
+		h.error(w, r, http.StatusNotFound, err)
+		return
+	}
+
+	maxAge := h.ss.MaxAge
+	if maxAge == 0 {
+		maxAge = 24 * time.Hour
+	}
+	if r.FormValue("v") != "" {
+		maxAge = 365 * 24 * time.Hour
+	}
+
+	cacheControl := fmt.Sprintf("public, max-age=%d", maxAge/time.Second)
+
+	for _, e := range header.ParseList(r.Header, "If-None-Match") {
+		if e == etag {
+			w.Header().Set("Cache-Control", cacheControl)
+			w.Header().Set("Etag", etag)
+			w.WriteHeader(http.StatusNotModified)
+			return
+		}
+	}
+
+	rc, cl, ct, err := h.open(p)
+	if err != nil {
+		h.error(w, r, http.StatusNotFound, err)
+		return
+	}
+	defer rc.Close()
+
+	w.Header().Set("Cache-Control", cacheControl)
+	w.Header().Set("Etag", etag)
+	if ct != "" {
+		w.Header().Set("Content-Type", ct)
+	}
+	if cl != 0 {
+		w.Header().Set("Content-Length", strconv.FormatInt(cl, 10))
+	}
+	w.WriteHeader(http.StatusOK)
+	if r.Method != "HEAD" {
+		io.Copy(w, rc)
+	}
+}
diff --git a/httputil/static_test.go b/httputil/static_test.go
new file mode 100644
index 0000000..34eeaca
--- /dev/null
+++ b/httputil/static_test.go
@@ -0,0 +1,174 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package httputil_test
+
+import (
+	"crypto/sha1"
+	"encoding/hex"
+	"github.com/garyburd/gddo/httputil"
+	"io/ioutil"
+	"net/http"
+	"net/http/httptest"
+	"net/url"
+	"os"
+	"reflect"
+	"strconv"
+	"testing"
+	"time"
+)
+
+var (
+	testHash          = computeTestHash()
+	testEtag          = `"` + testHash + `"`
+	testContentLength = computeTestContentLength()
+)
+
+func mustParseURL(urlStr string) *url.URL {
+	u, err := url.Parse(urlStr)
+	if err != nil {
+		panic(err)
+	}
+	return u
+}
+
+func computeTestHash() string {
+	p, err := ioutil.ReadFile("static_test.go")
+	if err != nil {
+		panic(err)
+	}
+	w := sha1.New()
+	w.Write(p)
+	return hex.EncodeToString(w.Sum(nil))
+}
+
+func computeTestContentLength() string {
+	info, err := os.Stat("static_test.go")
+	if err != nil {
+		panic(err)
+	}
+	return strconv.FormatInt(info.Size(), 10)
+}
+
+var fileServerTests = []*struct {
+	name   string // test name for log
+	ss     *httputil.StaticServer
+	r      *http.Request
+	header http.Header // expected response headers
+	status int         // expected response status
+	empty  bool        // true if response body not expected.
+}{
+	{
+		name: "get",
+		ss:   &httputil.StaticServer{MaxAge: 3 * time.Second},
+		r: &http.Request{
+			URL:    mustParseURL("/dir/static_test.go"),
+			Method: "GET",
+		},
+		status: http.StatusOK,
+		header: http.Header{
+			"Etag":           {testEtag},
+			"Cache-Control":  {"public, max-age=3"},
+			"Content-Length": {testContentLength},
+			"Content-Type":   {"application/octet-stream"},
+		},
+	},
+	{
+		name: "get .",
+		ss:   &httputil.StaticServer{Dir: ".", MaxAge: 3 * time.Second},
+		r: &http.Request{
+			URL:    mustParseURL("/dir/static_test.go"),
+			Method: "GET",
+		},
+		status: http.StatusOK,
+		header: http.Header{
+			"Etag":           {testEtag},
+			"Cache-Control":  {"public, max-age=3"},
+			"Content-Length": {testContentLength},
+			"Content-Type":   {"application/octet-stream"},
+		},
+	},
+	{
+		name: "get with ?v=",
+		ss:   &httputil.StaticServer{MaxAge: 3 * time.Second},
+		r: &http.Request{
+			URL:    mustParseURL("/dir/static_test.go?v=xxxxx"),
+			Method: "GET",
+		},
+		status: http.StatusOK,
+		header: http.Header{
+			"Etag":           {testEtag},
+			"Cache-Control":  {"public, max-age=31536000"},
+			"Content-Length": {testContentLength},
+			"Content-Type":   {"application/octet-stream"},
+		},
+	},
+	{
+		name: "head",
+		ss:   &httputil.StaticServer{MaxAge: 3 * time.Second},
+		r: &http.Request{
+			URL:    mustParseURL("/dir/static_test.go"),
+			Method: "HEAD",
+		},
+		status: http.StatusOK,
+		header: http.Header{
+			"Etag":           {testEtag},
+			"Cache-Control":  {"public, max-age=3"},
+			"Content-Length": {testContentLength},
+			"Content-Type":   {"application/octet-stream"},
+		},
+		empty: true,
+	},
+	{
+		name: "if-none-match",
+		ss:   &httputil.StaticServer{MaxAge: 3 * time.Second},
+		r: &http.Request{
+			URL:    mustParseURL("/dir/static_test.go"),
+			Method: "GET",
+			Header: http.Header{"If-None-Match": {testEtag}},
+		},
+		status: http.StatusNotModified,
+		header: http.Header{
+			"Cache-Control": {"public, max-age=3"},
+			"Etag":          {testEtag},
+		},
+		empty: true,
+	},
+}
+
+func testStaticServer(t *testing.T, f func(*httputil.StaticServer) http.Handler) {
+	for _, tt := range fileServerTests {
+		w := httptest.NewRecorder()
+
+		h := f(tt.ss)
+		h.ServeHTTP(w, tt.r)
+
+		if w.Code != tt.status {
+			t.Errorf("%s, status=%d, want %d", tt.name, w.Code, tt.status)
+		}
+
+		if !reflect.DeepEqual(w.HeaderMap, tt.header) {
+			t.Errorf("%s\n\theader=%v,\n\twant   %v", tt.name, w.HeaderMap, tt.header)
+		}
+
+		empty := w.Body.Len() == 0
+		if empty != tt.empty {
+			t.Errorf("%s empty=%v, want %v", tt.name, empty, tt.empty)
+		}
+	}
+}
+
+func TestFileHandler(t *testing.T) {
+	testStaticServer(t, func(ss *httputil.StaticServer) http.Handler { return ss.FileHandler("static_test.go") })
+}
+
+func TestDirectoryHandler(t *testing.T) {
+	testStaticServer(t, func(ss *httputil.StaticServer) http.Handler { return ss.DirectoryHandler("/dir", ".") })
+}
+
+func TestFilesHandler(t *testing.T) {
+	testStaticServer(t, func(ss *httputil.StaticServer) http.Handler { return ss.FilesHandler("static_test.go") })
+}