internal/godoc: fork golang.org/x/tools/godoc@123adc86bc

This is a straight copy of golang.org/x/tools/godoc@123adc86bc
(CL 291669) with internal import paths updated, gofmt run,
and go17_test.go folded into godoc_test.go (we are well past Go 1.7 now).

The next CL will change cmd/golangorg to use it, and then
we can start making adjustments to delete lots of unused code,
without worrying about affecting any external importers of
the original.

This CL brings in 16,290 lines of code, but less than 2,000
will actually remain once all the no-longer-needed code is
deleted in followup CLs.

Change-Id: I40ff57fe610b252df8bd6bb5de1114cdd7a750a2
Reviewed-on: https://go-review.googlesource.com/c/website/+/293420
Trust: Russ Cox <rsc@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/internal/godoc/README.md b/internal/godoc/README.md
new file mode 100644
index 0000000..52bc8a4
--- /dev/null
+++ b/internal/godoc/README.md
@@ -0,0 +1,31 @@
+# godoc
+
+This directory contains most of the code for running a godoc server. The
+executable lives at golang.org/x/tools/cmd/godoc.
+
+## Development mode
+
+In production, CSS/JS/template assets need to be compiled into the godoc
+binary. It can be tedious to recompile assets every time, but you can pass a
+flag to load CSS/JS/templates from disk every time a page loads:
+
+```
+godoc -templates=$GOPATH/src/golang.org/x/tools/godoc/static -http=:6060
+```
+
+## Recompiling static assets
+
+The files that live at `static/style.css`, `static/jquery.js` and so on are not
+present in the final binary. They are placed into `static/static.go` by running
+`go generate`. So to compile a change and test it in your browser:
+
+1) Make changes to e.g. `static/style.css`.
+
+2) Run `go generate golang.org/x/tools/godoc/static` so `static/static.go` picks
+up the change.
+
+3) Run `go install golang.org/x/tools/cmd/godoc` so the compiled `godoc` binary
+picks up the change.
+
+4) Run `godoc -http=:6060` and view your changes in the browser. You may need
+to disable your browser's cache to avoid reloading a stale file.
diff --git a/internal/godoc/analysis/README b/internal/godoc/analysis/README
new file mode 100644
index 0000000..d3e732e
--- /dev/null
+++ b/internal/godoc/analysis/README
@@ -0,0 +1,111 @@
+
+Type and Pointer Analysis to-do list
+====================================
+
+Alan Donovan <adonovan@google.com>
+
+
+Overall design
+--------------
+
+We should re-run the type and pointer analyses periodically,
+as we do with the indexer.
+
+Version skew: how to mitigate the bad effects of stale URLs in old pages?
+We could record the file's length/CRC32/mtime in the go/loader, and
+refuse to decorate it with links unless they match at serving time.
+
+Use the VFS mechanism when (a) enumerating packages and (b) loading
+them.  (Requires planned changes to go/loader.)
+
+Future work: shard this using map/reduce for larger corpora.
+
+Testing: how does one test that a web page "looks right"?
+
+
+Bugs
+----
+
+(*ssa.Program).Create requires transitively error-free packages.  We
+can make this more robust by making the requirement transitively free
+of "hard" errors; soft errors are fine.
+
+Markup of compiler errors is slightly buggy because they overlap with
+other selections (e.g. Idents).  Fix.
+
+
+User Interface
+--------------
+
+CALLGRAPH:
+- Add a search box: given a search node, expand path from each entry
+  point to it.
+- Cause hovering over a given node to highlight that node, and all
+  nodes that are logically identical to it.
+- Initially expand the callgraph trees (but not their toggle divs).
+
+CALLEES:
+- The '(' links are not very discoverable.  Highlight them?
+
+Type info:
+- In the source viewer's lower pane, use a toggle div around the
+  IMPLEMENTS and METHODSETS lists, like we do in the package view.
+  Only expand them initially if short.
+- Include IMPLEMENTS and METHOD SETS information in search index.
+- URLs in IMPLEMENTS/METHOD SETS always link to source, even from the
+  package docs view.  This makes sense for links to non-exported
+  types, but links to exported types and funcs should probably go to
+  other package docs.
+- Suppress toggle divs for empty method sets.
+
+Misc:
+- The [X] button in the lower pane is subject to scrolling.
+- Should the lower pane be floating?  An iframe?
+  When we change document.location by clicking on a link, it will go away.
+  How do we prevent that (a la Gmail's chat windows)?
+- Progress/status: for each file, display its analysis status, one of:
+   - not in analysis scope
+   - type analysis running...
+   - type analysis complete
+     (+ optionally: there were type errors in this file)
+   And if PTA requested:
+   - type analysis complete; PTA not attempted due to type errors
+   - PTA running...
+   - PTA complete
+- Scroll the selection into view, e.g. the vertical center, or better
+  still, under the pointer (assuming we have a mouse).
+
+
+More features
+-------------
+
+Display the REFERRERS relation?  (Useful but potentially large.)
+
+Display the INSTANTIATIONS relation? i.e. given a type T, show the set of
+syntactic constructs that can instantiate it:
+    var x T
+    x := T{...}
+    x = new(T)
+    x = make([]T, n)
+    etc
+    + all INSTANTIATIONS of all S defined as struct{t T} or [n]T
+(Potentially a lot of information.)
+(Add this to guru too.)
+
+
+Optimisations
+-------------
+
+Each call to addLink takes a (per-file) lock.  The locking is
+fine-grained so server latency isn't terrible, but overall it makes
+the link computation quite slow.  Batch update might be better.
+
+Memory usage is now about 1.5GB for GOROOT + go.tools.  It used to be 700MB.
+
+Optimize for time and space.  The main slowdown is the network I/O
+time caused by an increase in page size of about 3x: about 2x from
+HTML, and 0.7--2.1x from JSON (unindented vs indented).  The JSON
+contains a lot of filenames (e.g. 820 copies of 16 distinct
+filenames).  20% of the HTML is L%d spans (now disabled).  The HTML
+also contains lots of tooltips for long struct/interface types.
+De-dup or just abbreviate?  The actual formatting is very fast.
diff --git a/internal/godoc/analysis/analysis.go b/internal/godoc/analysis/analysis.go
new file mode 100644
index 0000000..94af35f
--- /dev/null
+++ b/internal/godoc/analysis/analysis.go
@@ -0,0 +1,613 @@
+// Copyright 2014 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.
+
+// Package analysis performs type and pointer analysis
+// and generates mark-up for the Go source view.
+//
+// The Run method populates a Result object by running type and
+// (optionally) pointer analysis.  The Result object is thread-safe
+// and at all times may be accessed by a serving thread, even as it is
+// progressively populated as analysis facts are derived.
+//
+// The Result is a mapping from each godoc file URL
+// (e.g. /src/fmt/print.go) to information about that file.  The
+// information is a list of HTML markup links and a JSON array of
+// structured data values.  Some of the links call client-side
+// JavaScript functions that index this array.
+//
+// The analysis computes mark-up for the following relations:
+//
+// IMPORTS: for each ast.ImportSpec, the package that it denotes.
+//
+// RESOLUTION: for each ast.Ident, its kind and type, and the location
+// of its definition.
+//
+// METHOD SETS, IMPLEMENTS: for each ast.Ident defining a named type,
+// its method-set, the set of interfaces it implements or is
+// implemented by, and its size/align values.
+//
+// CALLERS, CALLEES: for each function declaration ('func' token), its
+// callers, and for each call-site ('(' token), its callees.
+//
+// CALLGRAPH: the package docs include an interactive viewer for the
+// intra-package call graph of "fmt".
+//
+// CHANNEL PEERS: for each channel operation make/<-/close, the set of
+// other channel ops that alias the same channel(s).
+//
+// ERRORS: for each locus of a frontend (scanner/parser/type) error, the
+// location is highlighted in red and hover text provides the compiler
+// error message.
+//
+package analysis // import "golang.org/x/website/internal/godoc/analysis"
+
+import (
+	"fmt"
+	"go/build"
+	"go/scanner"
+	"go/token"
+	"go/types"
+	"html"
+	"io"
+	"log"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+	"sync"
+
+	"golang.org/x/tools/go/loader"
+	"golang.org/x/tools/go/pointer"
+	"golang.org/x/tools/go/ssa"
+	"golang.org/x/tools/go/ssa/ssautil"
+)
+
+// -- links ------------------------------------------------------------
+
+// A Link is an HTML decoration of the bytes [Start, End) of a file.
+// Write is called before/after those bytes to emit the mark-up.
+type Link interface {
+	Start() int
+	End() int
+	Write(w io.Writer, _ int, start bool) // the godoc.LinkWriter signature
+}
+
+// An <a> element.
+type aLink struct {
+	start, end int    // =godoc.Segment
+	title      string // hover text
+	onclick    string // JS code (NB: trusted)
+	href       string // URL     (NB: trusted)
+}
+
+func (a aLink) Start() int { return a.start }
+func (a aLink) End() int   { return a.end }
+func (a aLink) Write(w io.Writer, _ int, start bool) {
+	if start {
+		fmt.Fprintf(w, `<a title='%s'`, html.EscapeString(a.title))
+		if a.onclick != "" {
+			fmt.Fprintf(w, ` onclick='%s'`, html.EscapeString(a.onclick))
+		}
+		if a.href != "" {
+			// TODO(adonovan): I think that in principle, a.href must first be
+			// url.QueryEscape'd, but if I do that, a leading slash becomes "%2F",
+			// which causes the browser to treat the path as relative, not absolute.
+			// WTF?
+			fmt.Fprintf(w, ` href='%s'`, html.EscapeString(a.href))
+		}
+		fmt.Fprintf(w, ">")
+	} else {
+		fmt.Fprintf(w, "</a>")
+	}
+}
+
+// An <a class='error'> element.
+type errorLink struct {
+	start int
+	msg   string
+}
+
+func (e errorLink) Start() int { return e.start }
+func (e errorLink) End() int   { return e.start + 1 }
+
+func (e errorLink) Write(w io.Writer, _ int, start bool) {
+	// <span> causes havoc, not sure why, so use <a>.
+	if start {
+		fmt.Fprintf(w, `<a class='error' title='%s'>`, html.EscapeString(e.msg))
+	} else {
+		fmt.Fprintf(w, "</a>")
+	}
+}
+
+// -- fileInfo ---------------------------------------------------------
+
+// FileInfo holds analysis information for the source file view.
+// Clients must not mutate it.
+type FileInfo struct {
+	Data  []interface{} // JSON serializable values
+	Links []Link        // HTML link markup
+}
+
+// A fileInfo is the server's store of hyperlinks and JSON data for a
+// particular file.
+type fileInfo struct {
+	mu        sync.Mutex
+	data      []interface{} // JSON objects
+	links     []Link
+	sorted    bool
+	hasErrors bool // TODO(adonovan): surface this in the UI
+}
+
+// addLink adds a link to the Go source file fi.
+func (fi *fileInfo) addLink(link Link) {
+	fi.mu.Lock()
+	fi.links = append(fi.links, link)
+	fi.sorted = false
+	if _, ok := link.(errorLink); ok {
+		fi.hasErrors = true
+	}
+	fi.mu.Unlock()
+}
+
+// addData adds the structured value x to the JSON data for the Go
+// source file fi.  Its index is returned.
+func (fi *fileInfo) addData(x interface{}) int {
+	fi.mu.Lock()
+	index := len(fi.data)
+	fi.data = append(fi.data, x)
+	fi.mu.Unlock()
+	return index
+}
+
+// get returns the file info in external form.
+// Callers must not mutate its fields.
+func (fi *fileInfo) get() FileInfo {
+	var r FileInfo
+	// Copy slices, to avoid races.
+	fi.mu.Lock()
+	r.Data = append(r.Data, fi.data...)
+	if !fi.sorted {
+		sort.Sort(linksByStart(fi.links))
+		fi.sorted = true
+	}
+	r.Links = append(r.Links, fi.links...)
+	fi.mu.Unlock()
+	return r
+}
+
+// PackageInfo holds analysis information for the package view.
+// Clients must not mutate it.
+type PackageInfo struct {
+	CallGraph      []*PCGNodeJSON
+	CallGraphIndex map[string]int
+	Types          []*TypeInfoJSON
+}
+
+type pkgInfo struct {
+	mu             sync.Mutex
+	callGraph      []*PCGNodeJSON
+	callGraphIndex map[string]int  // keys are (*ssa.Function).RelString()
+	types          []*TypeInfoJSON // type info for exported types
+}
+
+func (pi *pkgInfo) setCallGraph(callGraph []*PCGNodeJSON, callGraphIndex map[string]int) {
+	pi.mu.Lock()
+	pi.callGraph = callGraph
+	pi.callGraphIndex = callGraphIndex
+	pi.mu.Unlock()
+}
+
+func (pi *pkgInfo) addType(t *TypeInfoJSON) {
+	pi.mu.Lock()
+	pi.types = append(pi.types, t)
+	pi.mu.Unlock()
+}
+
+// get returns the package info in external form.
+// Callers must not mutate its fields.
+func (pi *pkgInfo) get() PackageInfo {
+	var r PackageInfo
+	// Copy slices, to avoid races.
+	pi.mu.Lock()
+	r.CallGraph = append(r.CallGraph, pi.callGraph...)
+	r.CallGraphIndex = pi.callGraphIndex
+	r.Types = append(r.Types, pi.types...)
+	pi.mu.Unlock()
+	return r
+}
+
+// -- Result -----------------------------------------------------------
+
+// Result contains the results of analysis.
+// The result contains a mapping from filenames to a set of HTML links
+// and JavaScript data referenced by the links.
+type Result struct {
+	mu        sync.Mutex           // guards maps (but not their contents)
+	status    string               // global analysis status
+	fileInfos map[string]*fileInfo // keys are godoc file URLs
+	pkgInfos  map[string]*pkgInfo  // keys are import paths
+}
+
+// fileInfo returns the fileInfo for the specified godoc file URL,
+// constructing it as needed.  Thread-safe.
+func (res *Result) fileInfo(url string) *fileInfo {
+	res.mu.Lock()
+	fi, ok := res.fileInfos[url]
+	if !ok {
+		if res.fileInfos == nil {
+			res.fileInfos = make(map[string]*fileInfo)
+		}
+		fi = new(fileInfo)
+		res.fileInfos[url] = fi
+	}
+	res.mu.Unlock()
+	return fi
+}
+
+// Status returns a human-readable description of the current analysis status.
+func (res *Result) Status() string {
+	res.mu.Lock()
+	defer res.mu.Unlock()
+	return res.status
+}
+
+func (res *Result) setStatusf(format string, args ...interface{}) {
+	res.mu.Lock()
+	res.status = fmt.Sprintf(format, args...)
+	log.Printf(format, args...)
+	res.mu.Unlock()
+}
+
+// FileInfo returns new slices containing opaque JSON values and the
+// HTML link markup for the specified godoc file URL.  Thread-safe.
+// Callers must not mutate the elements.
+// It returns "zero" if no data is available.
+//
+func (res *Result) FileInfo(url string) (fi FileInfo) {
+	return res.fileInfo(url).get()
+}
+
+// pkgInfo returns the pkgInfo for the specified import path,
+// constructing it as needed.  Thread-safe.
+func (res *Result) pkgInfo(importPath string) *pkgInfo {
+	res.mu.Lock()
+	pi, ok := res.pkgInfos[importPath]
+	if !ok {
+		if res.pkgInfos == nil {
+			res.pkgInfos = make(map[string]*pkgInfo)
+		}
+		pi = new(pkgInfo)
+		res.pkgInfos[importPath] = pi
+	}
+	res.mu.Unlock()
+	return pi
+}
+
+// PackageInfo returns new slices of JSON values for the callgraph and
+// type info for the specified package.  Thread-safe.
+// Callers must not mutate its fields.
+// PackageInfo returns "zero" if no data is available.
+//
+func (res *Result) PackageInfo(importPath string) PackageInfo {
+	return res.pkgInfo(importPath).get()
+}
+
+// -- analysis ---------------------------------------------------------
+
+type analysis struct {
+	result    *Result
+	prog      *ssa.Program
+	ops       []chanOp       // all channel ops in program
+	allNamed  []*types.Named // all "defined" (formerly "named") types in the program
+	ptaConfig pointer.Config
+	path2url  map[string]string // maps openable path to godoc file URL (/src/fmt/print.go)
+	pcgs      map[*ssa.Package]*packageCallGraph
+}
+
+// fileAndOffset returns the file and offset for a given pos.
+func (a *analysis) fileAndOffset(pos token.Pos) (fi *fileInfo, offset int) {
+	return a.fileAndOffsetPosn(a.prog.Fset.Position(pos))
+}
+
+// fileAndOffsetPosn returns the file and offset for a given position.
+func (a *analysis) fileAndOffsetPosn(posn token.Position) (fi *fileInfo, offset int) {
+	url := a.path2url[posn.Filename]
+	return a.result.fileInfo(url), posn.Offset
+}
+
+// posURL returns the URL of the source extent [pos, pos+len).
+func (a *analysis) posURL(pos token.Pos, len int) string {
+	if pos == token.NoPos {
+		return ""
+	}
+	posn := a.prog.Fset.Position(pos)
+	url := a.path2url[posn.Filename]
+	return fmt.Sprintf("%s?s=%d:%d#L%d",
+		url, posn.Offset, posn.Offset+len, posn.Line)
+}
+
+// ----------------------------------------------------------------------
+
+// Run runs program analysis and computes the resulting markup,
+// populating *result in a thread-safe manner, first with type
+// information then later with pointer analysis information if
+// enabled by the pta flag.
+//
+func Run(pta bool, result *Result) {
+	conf := loader.Config{
+		AllowErrors: true,
+	}
+
+	// Silence the default error handler.
+	// Don't print all errors; we'll report just
+	// one per errant package later.
+	conf.TypeChecker.Error = func(e error) {}
+
+	var roots, args []string // roots[i] ends with os.PathSeparator
+
+	// Enumerate packages in $GOROOT.
+	root := filepath.Join(build.Default.GOROOT, "src") + string(os.PathSeparator)
+	roots = append(roots, root)
+	args = allPackages(root)
+	log.Printf("GOROOT=%s: %s\n", root, args)
+
+	// Enumerate packages in $GOPATH.
+	for i, dir := range filepath.SplitList(build.Default.GOPATH) {
+		root := filepath.Join(dir, "src") + string(os.PathSeparator)
+		roots = append(roots, root)
+		pkgs := allPackages(root)
+		log.Printf("GOPATH[%d]=%s: %s\n", i, root, pkgs)
+		args = append(args, pkgs...)
+	}
+
+	// Uncomment to make startup quicker during debugging.
+	//args = []string{"golang.org/x/tools/cmd/godoc"}
+	//args = []string{"fmt"}
+
+	if _, err := conf.FromArgs(args, true); err != nil {
+		// TODO(adonovan): degrade gracefully, not fail totally.
+		// (The crippling case is a parse error in an external test file.)
+		result.setStatusf("Analysis failed: %s.", err) // import error
+		return
+	}
+
+	result.setStatusf("Loading and type-checking packages...")
+	iprog, err := conf.Load()
+	if iprog != nil {
+		// Report only the first error of each package.
+		for _, info := range iprog.AllPackages {
+			for _, err := range info.Errors {
+				fmt.Fprintln(os.Stderr, err)
+				break
+			}
+		}
+		log.Printf("Loaded %d packages.", len(iprog.AllPackages))
+	}
+	if err != nil {
+		result.setStatusf("Loading failed: %s.\n", err)
+		return
+	}
+
+	// Create SSA-form program representation.
+	// Only the transitively error-free packages are used.
+	prog := ssautil.CreateProgram(iprog, ssa.GlobalDebug)
+
+	// Create a "testmain" package for each package with tests.
+	for _, pkg := range prog.AllPackages() {
+		if testmain := prog.CreateTestMainPackage(pkg); testmain != nil {
+			log.Printf("Adding tests for %s", pkg.Pkg.Path())
+		}
+	}
+
+	// Build SSA code for bodies of all functions in the whole program.
+	result.setStatusf("Constructing SSA form...")
+	prog.Build()
+	log.Print("SSA construction complete")
+
+	a := analysis{
+		result: result,
+		prog:   prog,
+		pcgs:   make(map[*ssa.Package]*packageCallGraph),
+	}
+
+	// Build a mapping from openable filenames to godoc file URLs,
+	// i.e. "/src/" plus path relative to GOROOT/src or GOPATH[i]/src.
+	a.path2url = make(map[string]string)
+	for _, info := range iprog.AllPackages {
+	nextfile:
+		for _, f := range info.Files {
+			if f.Pos() == 0 {
+				continue // e.g. files generated by cgo
+			}
+			abs := iprog.Fset.File(f.Pos()).Name()
+			// Find the root to which this file belongs.
+			for _, root := range roots {
+				rel := strings.TrimPrefix(abs, root)
+				if len(rel) < len(abs) {
+					a.path2url[abs] = "/src/" + filepath.ToSlash(rel)
+					continue nextfile
+				}
+			}
+
+			log.Printf("Can't locate file %s (package %q) beneath any root",
+				abs, info.Pkg.Path())
+		}
+	}
+
+	// Add links for scanner, parser, type-checker errors.
+	// TODO(adonovan): fix: these links can overlap with
+	// identifier markup, causing the renderer to emit some
+	// characters twice.
+	errors := make(map[token.Position][]string)
+	for _, info := range iprog.AllPackages {
+		for _, err := range info.Errors {
+			switch err := err.(type) {
+			case types.Error:
+				posn := a.prog.Fset.Position(err.Pos)
+				errors[posn] = append(errors[posn], err.Msg)
+			case scanner.ErrorList:
+				for _, e := range err {
+					errors[e.Pos] = append(errors[e.Pos], e.Msg)
+				}
+			default:
+				log.Printf("Package %q has error (%T) without position: %v\n",
+					info.Pkg.Path(), err, err)
+			}
+		}
+	}
+	for posn, errs := range errors {
+		fi, offset := a.fileAndOffsetPosn(posn)
+		fi.addLink(errorLink{
+			start: offset,
+			msg:   strings.Join(errs, "\n"),
+		})
+	}
+
+	// ---------- type-based analyses ----------
+
+	// Compute the all-pairs IMPLEMENTS relation.
+	// Collect all named types, even local types
+	// (which can have methods via promotion)
+	// and the built-in "error".
+	errorType := types.Universe.Lookup("error").Type().(*types.Named)
+	a.allNamed = append(a.allNamed, errorType)
+	for _, info := range iprog.AllPackages {
+		for _, obj := range info.Defs {
+			if obj, ok := obj.(*types.TypeName); ok {
+				if named, ok := obj.Type().(*types.Named); ok {
+					a.allNamed = append(a.allNamed, named)
+				}
+			}
+		}
+	}
+	log.Print("Computing implements relation...")
+	facts := computeImplements(&a.prog.MethodSets, a.allNamed)
+
+	// Add the type-based analysis results.
+	log.Print("Extracting type info...")
+	for _, info := range iprog.AllPackages {
+		a.doTypeInfo(info, facts)
+	}
+
+	a.visitInstrs(pta)
+
+	result.setStatusf("Type analysis complete.")
+
+	if pta {
+		mainPkgs := ssautil.MainPackages(prog.AllPackages())
+		log.Print("Transitively error-free main packages: ", mainPkgs)
+		a.pointer(mainPkgs)
+	}
+}
+
+// visitInstrs visits all SSA instructions in the program.
+func (a *analysis) visitInstrs(pta bool) {
+	log.Print("Visit instructions...")
+	for fn := range ssautil.AllFunctions(a.prog) {
+		for _, b := range fn.Blocks {
+			for _, instr := range b.Instrs {
+				// CALLEES (static)
+				// (Dynamic calls require pointer analysis.)
+				//
+				// We use the SSA representation to find the static callee,
+				// since in many cases it does better than the
+				// types.Info.{Refs,Selection} information.  For example:
+				//
+				//   defer func(){}()      // static call to anon function
+				//   f := func(){}; f()    // static call to anon function
+				//   f := fmt.Println; f() // static call to named function
+				//
+				// The downside is that we get no static callee information
+				// for packages that (transitively) contain errors.
+				if site, ok := instr.(ssa.CallInstruction); ok {
+					if callee := site.Common().StaticCallee(); callee != nil {
+						// TODO(adonovan): callgraph: elide wrappers.
+						// (Do static calls ever go to wrappers?)
+						if site.Common().Pos() != token.NoPos {
+							a.addCallees(site, []*ssa.Function{callee})
+						}
+					}
+				}
+
+				if !pta {
+					continue
+				}
+
+				// CHANNEL PEERS
+				// Collect send/receive/close instructions in the whole ssa.Program.
+				for _, op := range chanOps(instr) {
+					a.ops = append(a.ops, op)
+					a.ptaConfig.AddQuery(op.ch) // add channel ssa.Value to PTA query
+				}
+			}
+		}
+	}
+	log.Print("Visit instructions complete")
+}
+
+// pointer runs the pointer analysis.
+func (a *analysis) pointer(mainPkgs []*ssa.Package) {
+	// Run the pointer analysis and build the complete callgraph.
+	a.ptaConfig.Mains = mainPkgs
+	a.ptaConfig.BuildCallGraph = true
+	a.ptaConfig.Reflection = false // (for now)
+
+	a.result.setStatusf("Pointer analysis running...")
+
+	ptares, err := pointer.Analyze(&a.ptaConfig)
+	if err != nil {
+		// If this happens, it indicates a bug.
+		a.result.setStatusf("Pointer analysis failed: %s.", err)
+		return
+	}
+	log.Print("Pointer analysis complete.")
+
+	// Add the results of pointer analysis.
+
+	a.result.setStatusf("Computing channel peers...")
+	a.doChannelPeers(ptares.Queries)
+	a.result.setStatusf("Computing dynamic call graph edges...")
+	a.doCallgraph(ptares.CallGraph)
+
+	a.result.setStatusf("Analysis complete.")
+}
+
+type linksByStart []Link
+
+func (a linksByStart) Less(i, j int) bool { return a[i].Start() < a[j].Start() }
+func (a linksByStart) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a linksByStart) Len() int           { return len(a) }
+
+// allPackages returns a new sorted slice of all packages beneath the
+// specified package root directory, e.g. $GOROOT/src or $GOPATH/src.
+// Derived from from go/ssa/stdlib_test.go
+// root must end with os.PathSeparator.
+//
+// TODO(adonovan): use buildutil.AllPackages when the tree thaws.
+func allPackages(root string) []string {
+	var pkgs []string
+	filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
+		if info == nil {
+			return nil // non-existent root directory?
+		}
+		if !info.IsDir() {
+			return nil // not a directory
+		}
+		// Prune the search if we encounter any of these names:
+		base := filepath.Base(path)
+		if base == "testdata" || strings.HasPrefix(base, ".") {
+			return filepath.SkipDir
+		}
+		pkg := filepath.ToSlash(strings.TrimPrefix(path, root))
+		switch pkg {
+		case "builtin":
+			return filepath.SkipDir
+		case "":
+			return nil // ignore root of tree
+		}
+		pkgs = append(pkgs, pkg)
+		return nil
+	})
+	return pkgs
+}
diff --git a/internal/godoc/analysis/callgraph.go b/internal/godoc/analysis/callgraph.go
new file mode 100644
index 0000000..492022d
--- /dev/null
+++ b/internal/godoc/analysis/callgraph.go
@@ -0,0 +1,351 @@
+// Copyright 2014 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.
+
+package analysis
+
+// This file computes the CALLERS and CALLEES relations from the call
+// graph.  CALLERS/CALLEES information is displayed in the lower pane
+// when a "func" token or ast.CallExpr.Lparen is clicked, respectively.
+
+import (
+	"fmt"
+	"go/ast"
+	"go/token"
+	"go/types"
+	"log"
+	"math/big"
+	"sort"
+
+	"golang.org/x/tools/go/callgraph"
+	"golang.org/x/tools/go/ssa"
+)
+
+// doCallgraph computes the CALLEES and CALLERS relations.
+func (a *analysis) doCallgraph(cg *callgraph.Graph) {
+	log.Print("Deleting synthetic nodes...")
+	// TODO(adonovan): opt: DeleteSyntheticNodes is asymptotically
+	// inefficient and can be (unpredictably) slow.
+	cg.DeleteSyntheticNodes()
+	log.Print("Synthetic nodes deleted")
+
+	// Populate nodes of package call graphs (PCGs).
+	for _, n := range cg.Nodes {
+		a.pcgAddNode(n.Func)
+	}
+	// Within each PCG, sort funcs by name.
+	for _, pcg := range a.pcgs {
+		pcg.sortNodes()
+	}
+
+	calledFuncs := make(map[ssa.CallInstruction]map[*ssa.Function]bool)
+	callingSites := make(map[*ssa.Function]map[ssa.CallInstruction]bool)
+	for _, n := range cg.Nodes {
+		for _, e := range n.Out {
+			if e.Site == nil {
+				continue // a call from a synthetic node such as <root>
+			}
+
+			// Add (site pos, callee) to calledFuncs.
+			// (Dynamic calls only.)
+			callee := e.Callee.Func
+
+			a.pcgAddEdge(n.Func, callee)
+
+			if callee.Synthetic != "" {
+				continue // call of a package initializer
+			}
+
+			if e.Site.Common().StaticCallee() == nil {
+				// dynamic call
+				// (CALLEES information for static calls
+				// is computed using SSA information.)
+				lparen := e.Site.Common().Pos()
+				if lparen != token.NoPos {
+					fns := calledFuncs[e.Site]
+					if fns == nil {
+						fns = make(map[*ssa.Function]bool)
+						calledFuncs[e.Site] = fns
+					}
+					fns[callee] = true
+				}
+			}
+
+			// Add (callee, site) to callingSites.
+			fns := callingSites[callee]
+			if fns == nil {
+				fns = make(map[ssa.CallInstruction]bool)
+				callingSites[callee] = fns
+			}
+			fns[e.Site] = true
+		}
+	}
+
+	// CALLEES.
+	log.Print("Callees...")
+	for site, fns := range calledFuncs {
+		var funcs funcsByPos
+		for fn := range fns {
+			funcs = append(funcs, fn)
+		}
+		sort.Sort(funcs)
+
+		a.addCallees(site, funcs)
+	}
+
+	// CALLERS
+	log.Print("Callers...")
+	for callee, sites := range callingSites {
+		pos := funcToken(callee)
+		if pos == token.NoPos {
+			log.Printf("CALLERS: skipping %s: no pos", callee)
+			continue
+		}
+
+		var this *types.Package // for relativizing names
+		if callee.Pkg != nil {
+			this = callee.Pkg.Pkg
+		}
+
+		// Compute sites grouped by parent, with text and URLs.
+		sitesByParent := make(map[*ssa.Function]sitesByPos)
+		for site := range sites {
+			fn := site.Parent()
+			sitesByParent[fn] = append(sitesByParent[fn], site)
+		}
+		var funcs funcsByPos
+		for fn := range sitesByParent {
+			funcs = append(funcs, fn)
+		}
+		sort.Sort(funcs)
+
+		v := callersJSON{
+			Callee:  callee.String(),
+			Callers: []callerJSON{}, // (JS wants non-nil)
+		}
+		for _, fn := range funcs {
+			caller := callerJSON{
+				Func:  prettyFunc(this, fn),
+				Sites: []anchorJSON{}, // (JS wants non-nil)
+			}
+			sites := sitesByParent[fn]
+			sort.Sort(sites)
+			for _, site := range sites {
+				pos := site.Common().Pos()
+				if pos != token.NoPos {
+					caller.Sites = append(caller.Sites, anchorJSON{
+						Text: fmt.Sprintf("%d", a.prog.Fset.Position(pos).Line),
+						Href: a.posURL(pos, len("(")),
+					})
+				}
+			}
+			v.Callers = append(v.Callers, caller)
+		}
+
+		fi, offset := a.fileAndOffset(pos)
+		fi.addLink(aLink{
+			start:   offset,
+			end:     offset + len("func"),
+			title:   fmt.Sprintf("%d callers", len(sites)),
+			onclick: fmt.Sprintf("onClickCallers(%d)", fi.addData(v)),
+		})
+	}
+
+	// PACKAGE CALLGRAPH
+	log.Print("Package call graph...")
+	for pkg, pcg := range a.pcgs {
+		// Maps (*ssa.Function).RelString() to index in JSON CALLGRAPH array.
+		index := make(map[string]int)
+
+		// Treat exported functions (and exported methods of
+		// exported named types) as roots even if they aren't
+		// actually called from outside the package.
+		for i, n := range pcg.nodes {
+			if i == 0 || n.fn.Object() == nil || !n.fn.Object().Exported() {
+				continue
+			}
+			recv := n.fn.Signature.Recv()
+			if recv == nil || deref(recv.Type()).(*types.Named).Obj().Exported() {
+				roots := &pcg.nodes[0].edges
+				roots.SetBit(roots, i, 1)
+			}
+			index[n.fn.RelString(pkg.Pkg)] = i
+		}
+
+		json := a.pcgJSON(pcg)
+
+		// TODO(adonovan): pkg.Path() is not unique!
+		// It is possible to declare a non-test package called x_test.
+		a.result.pkgInfo(pkg.Pkg.Path()).setCallGraph(json, index)
+	}
+}
+
+// addCallees adds client data and links for the facts that site calls fns.
+func (a *analysis) addCallees(site ssa.CallInstruction, fns []*ssa.Function) {
+	v := calleesJSON{
+		Descr:   site.Common().Description(),
+		Callees: []anchorJSON{}, // (JS wants non-nil)
+	}
+	var this *types.Package // for relativizing names
+	if p := site.Parent().Package(); p != nil {
+		this = p.Pkg
+	}
+
+	for _, fn := range fns {
+		v.Callees = append(v.Callees, anchorJSON{
+			Text: prettyFunc(this, fn),
+			Href: a.posURL(funcToken(fn), len("func")),
+		})
+	}
+
+	fi, offset := a.fileAndOffset(site.Common().Pos())
+	fi.addLink(aLink{
+		start:   offset,
+		end:     offset + len("("),
+		title:   fmt.Sprintf("%d callees", len(v.Callees)),
+		onclick: fmt.Sprintf("onClickCallees(%d)", fi.addData(v)),
+	})
+}
+
+// -- utilities --------------------------------------------------------
+
+// stable order within packages but undefined across packages.
+type funcsByPos []*ssa.Function
+
+func (a funcsByPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() }
+func (a funcsByPos) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a funcsByPos) Len() int           { return len(a) }
+
+type sitesByPos []ssa.CallInstruction
+
+func (a sitesByPos) Less(i, j int) bool { return a[i].Common().Pos() < a[j].Common().Pos() }
+func (a sitesByPos) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a sitesByPos) Len() int           { return len(a) }
+
+func funcToken(fn *ssa.Function) token.Pos {
+	switch syntax := fn.Syntax().(type) {
+	case *ast.FuncLit:
+		return syntax.Type.Func
+	case *ast.FuncDecl:
+		return syntax.Type.Func
+	}
+	return token.NoPos
+}
+
+// prettyFunc pretty-prints fn for the user interface.
+// TODO(adonovan): return HTML so we have more markup freedom.
+func prettyFunc(this *types.Package, fn *ssa.Function) string {
+	if fn.Parent() != nil {
+		return fmt.Sprintf("%s in %s",
+			types.TypeString(fn.Signature, types.RelativeTo(this)),
+			prettyFunc(this, fn.Parent()))
+	}
+	if fn.Synthetic != "" && fn.Name() == "init" {
+		// (This is the actual initializer, not a declared 'func init').
+		if fn.Pkg.Pkg == this {
+			return "package initializer"
+		}
+		return fmt.Sprintf("%q package initializer", fn.Pkg.Pkg.Path())
+	}
+	return fn.RelString(this)
+}
+
+// -- intra-package callgraph ------------------------------------------
+
+// pcgNode represents a node in the package call graph (PCG).
+type pcgNode struct {
+	fn     *ssa.Function
+	pretty string  // cache of prettyFunc(fn)
+	edges  big.Int // set of callee func indices
+}
+
+// A packageCallGraph represents the intra-package edges of the global call graph.
+// The zeroth node indicates "all external functions".
+type packageCallGraph struct {
+	nodeIndex map[*ssa.Function]int // maps func to node index (a small int)
+	nodes     []*pcgNode            // maps node index to node
+}
+
+// sortNodes populates pcg.nodes in name order and updates the nodeIndex.
+func (pcg *packageCallGraph) sortNodes() {
+	nodes := make([]*pcgNode, 0, len(pcg.nodeIndex))
+	nodes = append(nodes, &pcgNode{fn: nil, pretty: "<external>"})
+	for fn := range pcg.nodeIndex {
+		nodes = append(nodes, &pcgNode{
+			fn:     fn,
+			pretty: prettyFunc(fn.Pkg.Pkg, fn),
+		})
+	}
+	sort.Sort(pcgNodesByPretty(nodes[1:]))
+	for i, n := range nodes {
+		pcg.nodeIndex[n.fn] = i
+	}
+	pcg.nodes = nodes
+}
+
+func (pcg *packageCallGraph) addEdge(caller, callee *ssa.Function) {
+	var callerIndex int
+	if caller.Pkg == callee.Pkg {
+		// intra-package edge
+		callerIndex = pcg.nodeIndex[caller]
+		if callerIndex < 1 {
+			panic(caller)
+		}
+	}
+	edges := &pcg.nodes[callerIndex].edges
+	edges.SetBit(edges, pcg.nodeIndex[callee], 1)
+}
+
+func (a *analysis) pcgAddNode(fn *ssa.Function) {
+	if fn.Pkg == nil {
+		return
+	}
+	pcg, ok := a.pcgs[fn.Pkg]
+	if !ok {
+		pcg = &packageCallGraph{nodeIndex: make(map[*ssa.Function]int)}
+		a.pcgs[fn.Pkg] = pcg
+	}
+	pcg.nodeIndex[fn] = -1
+}
+
+func (a *analysis) pcgAddEdge(caller, callee *ssa.Function) {
+	if callee.Pkg != nil {
+		a.pcgs[callee.Pkg].addEdge(caller, callee)
+	}
+}
+
+// pcgJSON returns a new slice of callgraph JSON values.
+func (a *analysis) pcgJSON(pcg *packageCallGraph) []*PCGNodeJSON {
+	var nodes []*PCGNodeJSON
+	for _, n := range pcg.nodes {
+
+		// TODO(adonovan): why is there no good way to iterate
+		// over the set bits of a big.Int?
+		var callees []int
+		nbits := n.edges.BitLen()
+		for j := 0; j < nbits; j++ {
+			if n.edges.Bit(j) == 1 {
+				callees = append(callees, j)
+			}
+		}
+
+		var pos token.Pos
+		if n.fn != nil {
+			pos = funcToken(n.fn)
+		}
+		nodes = append(nodes, &PCGNodeJSON{
+			Func: anchorJSON{
+				Text: n.pretty,
+				Href: a.posURL(pos, len("func")),
+			},
+			Callees: callees,
+		})
+	}
+	return nodes
+}
+
+type pcgNodesByPretty []*pcgNode
+
+func (a pcgNodesByPretty) Less(i, j int) bool { return a[i].pretty < a[j].pretty }
+func (a pcgNodesByPretty) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a pcgNodesByPretty) Len() int           { return len(a) }
diff --git a/internal/godoc/analysis/implements.go b/internal/godoc/analysis/implements.go
new file mode 100644
index 0000000..5a29579
--- /dev/null
+++ b/internal/godoc/analysis/implements.go
@@ -0,0 +1,195 @@
+// Copyright 2014 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.
+
+package analysis
+
+// This file computes the "implements" relation over all pairs of
+// named types in the program.  (The mark-up is done by typeinfo.go.)
+
+// TODO(adonovan): do we want to report implements(C, I) where C and I
+// belong to different packages and at least one is not exported?
+
+import (
+	"go/types"
+	"sort"
+
+	"golang.org/x/tools/go/types/typeutil"
+)
+
+// computeImplements computes the "implements" relation over all pairs
+// of named types in allNamed.
+func computeImplements(cache *typeutil.MethodSetCache, allNamed []*types.Named) map[*types.Named]implementsFacts {
+	// Information about a single type's method set.
+	type msetInfo struct {
+		typ          types.Type
+		mset         *types.MethodSet
+		mask1, mask2 uint64
+	}
+
+	initMsetInfo := func(info *msetInfo, typ types.Type) {
+		info.typ = typ
+		info.mset = cache.MethodSet(typ)
+		for i := 0; i < info.mset.Len(); i++ {
+			name := info.mset.At(i).Obj().Name()
+			info.mask1 |= 1 << methodBit(name[0])
+			info.mask2 |= 1 << methodBit(name[len(name)-1])
+		}
+	}
+
+	// satisfies(T, U) reports whether type T satisfies type U.
+	// U must be an interface.
+	//
+	// Since there are thousands of types (and thus millions of
+	// pairs of types) and types.Assignable(T, U) is relatively
+	// expensive, we compute assignability directly from the
+	// method sets.  (At least one of T and U must be an
+	// interface.)
+	//
+	// We use a trick (thanks gri!) related to a Bloom filter to
+	// quickly reject most tests, which are false.  For each
+	// method set, we precompute a mask, a set of bits, one per
+	// distinct initial byte of each method name.  Thus the mask
+	// for io.ReadWriter would be {'R','W'}.  AssignableTo(T, U)
+	// cannot be true unless mask(T)&mask(U)==mask(U).
+	//
+	// As with a Bloom filter, we can improve precision by testing
+	// additional hashes, e.g. using the last letter of each
+	// method name, so long as the subset mask property holds.
+	//
+	// When analyzing the standard library, there are about 1e6
+	// calls to satisfies(), of which 0.6% return true.  With a
+	// 1-hash filter, 95% of calls avoid the expensive check; with
+	// a 2-hash filter, this grows to 98.2%.
+	satisfies := func(T, U *msetInfo) bool {
+		return T.mask1&U.mask1 == U.mask1 &&
+			T.mask2&U.mask2 == U.mask2 &&
+			containsAllIdsOf(T.mset, U.mset)
+	}
+
+	// Information about a named type N, and perhaps also *N.
+	type namedInfo struct {
+		isInterface bool
+		base        msetInfo // N
+		ptr         msetInfo // *N, iff N !isInterface
+	}
+
+	var infos []namedInfo
+
+	// Precompute the method sets and their masks.
+	for _, N := range allNamed {
+		var info namedInfo
+		initMsetInfo(&info.base, N)
+		_, info.isInterface = N.Underlying().(*types.Interface)
+		if !info.isInterface {
+			initMsetInfo(&info.ptr, types.NewPointer(N))
+		}
+
+		if info.base.mask1|info.ptr.mask1 == 0 {
+			continue // neither N nor *N has methods
+		}
+
+		infos = append(infos, info)
+	}
+
+	facts := make(map[*types.Named]implementsFacts)
+
+	// Test all pairs of distinct named types (T, U).
+	// TODO(adonovan): opt: compute (U, T) at the same time.
+	for t := range infos {
+		T := &infos[t]
+		var to, from, fromPtr []types.Type
+		for u := range infos {
+			if t == u {
+				continue
+			}
+			U := &infos[u]
+			switch {
+			case T.isInterface && U.isInterface:
+				if satisfies(&U.base, &T.base) {
+					to = append(to, U.base.typ)
+				}
+				if satisfies(&T.base, &U.base) {
+					from = append(from, U.base.typ)
+				}
+			case T.isInterface: // U concrete
+				if satisfies(&U.base, &T.base) {
+					to = append(to, U.base.typ)
+				} else if satisfies(&U.ptr, &T.base) {
+					to = append(to, U.ptr.typ)
+				}
+			case U.isInterface: // T concrete
+				if satisfies(&T.base, &U.base) {
+					from = append(from, U.base.typ)
+				} else if satisfies(&T.ptr, &U.base) {
+					fromPtr = append(fromPtr, U.base.typ)
+				}
+			}
+		}
+
+		// Sort types (arbitrarily) to avoid nondeterminism.
+		sort.Sort(typesByString(to))
+		sort.Sort(typesByString(from))
+		sort.Sort(typesByString(fromPtr))
+
+		facts[T.base.typ.(*types.Named)] = implementsFacts{to, from, fromPtr}
+	}
+
+	return facts
+}
+
+type implementsFacts struct {
+	to      []types.Type // named or ptr-to-named types assignable to interface T
+	from    []types.Type // named interfaces assignable from T
+	fromPtr []types.Type // named interfaces assignable only from *T
+}
+
+type typesByString []types.Type
+
+func (p typesByString) Len() int           { return len(p) }
+func (p typesByString) Less(i, j int) bool { return p[i].String() < p[j].String() }
+func (p typesByString) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+
+// methodBit returns the index of x in [a-zA-Z], or 52 if not found.
+func methodBit(x byte) uint64 {
+	switch {
+	case 'a' <= x && x <= 'z':
+		return uint64(x - 'a')
+	case 'A' <= x && x <= 'Z':
+		return uint64(26 + x - 'A')
+	}
+	return 52 // all other bytes
+}
+
+// containsAllIdsOf reports whether the method identifiers of T are a
+// superset of those in U.  If U belongs to an interface type, the
+// result is equal to types.Assignable(T, U), but is cheaper to compute.
+//
+// TODO(gri): make this a method of *types.MethodSet.
+//
+func containsAllIdsOf(T, U *types.MethodSet) bool {
+	t, tlen := 0, T.Len()
+	u, ulen := 0, U.Len()
+	for t < tlen && u < ulen {
+		tMeth := T.At(t).Obj()
+		uMeth := U.At(u).Obj()
+		tId := tMeth.Id()
+		uId := uMeth.Id()
+		if tId > uId {
+			// U has a method T lacks: fail.
+			return false
+		}
+		if tId < uId {
+			// T has a method U lacks: ignore it.
+			t++
+			continue
+		}
+		// U and T both have a method of this Id.  Check types.
+		if !types.Identical(tMeth.Type(), uMeth.Type()) {
+			return false // type mismatch
+		}
+		u++
+		t++
+	}
+	return u == ulen
+}
diff --git a/internal/godoc/analysis/json.go b/internal/godoc/analysis/json.go
new file mode 100644
index 0000000..f897618
--- /dev/null
+++ b/internal/godoc/analysis/json.go
@@ -0,0 +1,69 @@
+// Copyright 2014 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.
+
+package analysis
+
+// This file defines types used by client-side JavaScript.
+
+type anchorJSON struct {
+	Text string // HTML
+	Href string // URL
+}
+
+type commOpJSON struct {
+	Op anchorJSON
+	Fn string
+}
+
+// JavaScript's onClickComm() expects a commJSON.
+type commJSON struct {
+	Ops []commOpJSON
+}
+
+// Indicates one of these forms of fact about a type T:
+// T "is implemented by <ByKind> type <Other>"  (ByKind != "", e.g. "array")
+// T "implements <Other>"                       (ByKind == "")
+type implFactJSON struct {
+	ByKind string `json:",omitempty"`
+	Other  anchorJSON
+}
+
+// Implements facts are grouped by form, for ease of reading.
+type implGroupJSON struct {
+	Descr string
+	Facts []implFactJSON
+}
+
+// JavaScript's onClickIdent() expects a TypeInfoJSON.
+type TypeInfoJSON struct {
+	Name        string // type name
+	Size, Align int64
+	Methods     []anchorJSON
+	ImplGroups  []implGroupJSON
+}
+
+// JavaScript's onClickCallees() expects a calleesJSON.
+type calleesJSON struct {
+	Descr   string
+	Callees []anchorJSON // markup for called function
+}
+
+type callerJSON struct {
+	Func  string
+	Sites []anchorJSON
+}
+
+// JavaScript's onClickCallers() expects a callersJSON.
+type callersJSON struct {
+	Callee  string
+	Callers []callerJSON
+}
+
+// JavaScript's cgAddChild requires a global array of PCGNodeJSON
+// called CALLGRAPH, representing the intra-package call graph.
+// The first element is special and represents "all external callers".
+type PCGNodeJSON struct {
+	Func    anchorJSON
+	Callees []int // indices within CALLGRAPH of nodes called by this one
+}
diff --git a/internal/godoc/analysis/peers.go b/internal/godoc/analysis/peers.go
new file mode 100644
index 0000000..a742f06
--- /dev/null
+++ b/internal/godoc/analysis/peers.go
@@ -0,0 +1,154 @@
+// Copyright 2014 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.
+
+package analysis
+
+// This file computes the channel "peers" relation over all pairs of
+// channel operations in the program.  The peers are displayed in the
+// lower pane when a channel operation (make, <-, close) is clicked.
+
+// TODO(adonovan): handle calls to reflect.{Select,Recv,Send,Close} too,
+// then enable reflection in PTA.
+
+import (
+	"fmt"
+	"go/token"
+	"go/types"
+
+	"golang.org/x/tools/go/pointer"
+	"golang.org/x/tools/go/ssa"
+)
+
+func (a *analysis) doChannelPeers(ptsets map[ssa.Value]pointer.Pointer) {
+	addSendRecv := func(j *commJSON, op chanOp) {
+		j.Ops = append(j.Ops, commOpJSON{
+			Op: anchorJSON{
+				Text: op.mode,
+				Href: a.posURL(op.pos, op.len),
+			},
+			Fn: prettyFunc(nil, op.fn),
+		})
+	}
+
+	// Build an undirected bipartite multigraph (binary relation)
+	// of MakeChan ops and send/recv/close ops.
+	//
+	// TODO(adonovan): opt: use channel element types to partition
+	// the O(n^2) problem into subproblems.
+	aliasedOps := make(map[*ssa.MakeChan][]chanOp)
+	opToMakes := make(map[chanOp][]*ssa.MakeChan)
+	for _, op := range a.ops {
+		// Combine the PT sets from all contexts.
+		var makes []*ssa.MakeChan // aliased ops
+		ptr, ok := ptsets[op.ch]
+		if !ok {
+			continue // e.g. channel op in dead code
+		}
+		for _, label := range ptr.PointsTo().Labels() {
+			makechan, ok := label.Value().(*ssa.MakeChan)
+			if !ok {
+				continue // skip intrinsically-created channels for now
+			}
+			if makechan.Pos() == token.NoPos {
+				continue // not possible?
+			}
+			makes = append(makes, makechan)
+			aliasedOps[makechan] = append(aliasedOps[makechan], op)
+		}
+		opToMakes[op] = makes
+	}
+
+	// Now that complete relation is built, build links for ops.
+	for _, op := range a.ops {
+		v := commJSON{
+			Ops: []commOpJSON{}, // (JS wants non-nil)
+		}
+		ops := make(map[chanOp]bool)
+		for _, makechan := range opToMakes[op] {
+			v.Ops = append(v.Ops, commOpJSON{
+				Op: anchorJSON{
+					Text: "made",
+					Href: a.posURL(makechan.Pos()-token.Pos(len("make")),
+						len("make")),
+				},
+				Fn: makechan.Parent().RelString(op.fn.Package().Pkg),
+			})
+			for _, op := range aliasedOps[makechan] {
+				ops[op] = true
+			}
+		}
+		for op := range ops {
+			addSendRecv(&v, op)
+		}
+
+		// Add links for each aliased op.
+		fi, offset := a.fileAndOffset(op.pos)
+		fi.addLink(aLink{
+			start:   offset,
+			end:     offset + op.len,
+			title:   "show channel ops",
+			onclick: fmt.Sprintf("onClickComm(%d)", fi.addData(v)),
+		})
+	}
+	// Add links for makechan ops themselves.
+	for makechan, ops := range aliasedOps {
+		v := commJSON{
+			Ops: []commOpJSON{}, // (JS wants non-nil)
+		}
+		for _, op := range ops {
+			addSendRecv(&v, op)
+		}
+
+		fi, offset := a.fileAndOffset(makechan.Pos())
+		fi.addLink(aLink{
+			start:   offset - len("make"),
+			end:     offset,
+			title:   "show channel ops",
+			onclick: fmt.Sprintf("onClickComm(%d)", fi.addData(v)),
+		})
+	}
+}
+
+// -- utilities --------------------------------------------------------
+
+// chanOp abstracts an ssa.Send, ssa.Unop(ARROW), close(), or a SelectState.
+// Derived from cmd/guru/peers.go.
+type chanOp struct {
+	ch   ssa.Value
+	mode string // sent|received|closed
+	pos  token.Pos
+	len  int
+	fn   *ssa.Function
+}
+
+// chanOps returns a slice of all the channel operations in the instruction.
+// Derived from cmd/guru/peers.go.
+func chanOps(instr ssa.Instruction) []chanOp {
+	fn := instr.Parent()
+	var ops []chanOp
+	switch instr := instr.(type) {
+	case *ssa.UnOp:
+		if instr.Op == token.ARROW {
+			// TODO(adonovan): don't assume <-ch; could be 'range ch'.
+			ops = append(ops, chanOp{instr.X, "received", instr.Pos(), len("<-"), fn})
+		}
+	case *ssa.Send:
+		ops = append(ops, chanOp{instr.Chan, "sent", instr.Pos(), len("<-"), fn})
+	case *ssa.Select:
+		for _, st := range instr.States {
+			mode := "received"
+			if st.Dir == types.SendOnly {
+				mode = "sent"
+			}
+			ops = append(ops, chanOp{st.Chan, mode, st.Pos, len("<-"), fn})
+		}
+	case ssa.CallInstruction:
+		call := instr.Common()
+		if blt, ok := call.Value.(*ssa.Builtin); ok && blt.Name() == "close" {
+			pos := instr.Common().Pos()
+			ops = append(ops, chanOp{call.Args[0], "closed", pos - token.Pos(len("close")), len("close("), fn})
+		}
+	}
+	return ops
+}
diff --git a/internal/godoc/analysis/typeinfo.go b/internal/godoc/analysis/typeinfo.go
new file mode 100644
index 0000000..e57683f
--- /dev/null
+++ b/internal/godoc/analysis/typeinfo.go
@@ -0,0 +1,234 @@
+// Copyright 2014 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.
+
+package analysis
+
+// This file computes the markup for information from go/types:
+// IMPORTS, identifier RESOLUTION, METHOD SETS, size/alignment, and
+// the IMPLEMENTS relation.
+//
+// IMPORTS links connect import specs to the documentation for the
+// imported package.
+//
+// RESOLUTION links referring identifiers to their defining
+// identifier, and adds tooltips for kind and type.
+//
+// METHOD SETS, size/alignment, and the IMPLEMENTS relation are
+// displayed in the lower pane when a type's defining identifier is
+// clicked.
+
+import (
+	"fmt"
+	"go/types"
+	"reflect"
+	"strconv"
+	"strings"
+
+	"golang.org/x/tools/go/loader"
+	"golang.org/x/tools/go/types/typeutil"
+)
+
+// TODO(adonovan): audit to make sure it's safe on ill-typed packages.
+
+// TODO(adonovan): use same Sizes as loader.Config.
+var sizes = types.StdSizes{WordSize: 8, MaxAlign: 8}
+
+func (a *analysis) doTypeInfo(info *loader.PackageInfo, implements map[*types.Named]implementsFacts) {
+	// We must not assume the corresponding SSA packages were
+	// created (i.e. were transitively error-free).
+
+	// IMPORTS
+	for _, f := range info.Files {
+		// Package decl.
+		fi, offset := a.fileAndOffset(f.Name.Pos())
+		fi.addLink(aLink{
+			start: offset,
+			end:   offset + len(f.Name.Name),
+			title: "Package docs for " + info.Pkg.Path(),
+			// TODO(adonovan): fix: we're putting the untrusted Path()
+			// into a trusted field.  What's the appropriate sanitizer?
+			href: "/pkg/" + info.Pkg.Path(),
+		})
+
+		// Import specs.
+		for _, imp := range f.Imports {
+			// Remove quotes.
+			L := int(imp.End()-imp.Path.Pos()) - len(`""`)
+			path, _ := strconv.Unquote(imp.Path.Value)
+			fi, offset := a.fileAndOffset(imp.Path.Pos())
+			fi.addLink(aLink{
+				start: offset + 1,
+				end:   offset + 1 + L,
+				title: "Package docs for " + path,
+				// TODO(adonovan): fix: we're putting the untrusted path
+				// into a trusted field.  What's the appropriate sanitizer?
+				href: "/pkg/" + path,
+			})
+		}
+	}
+
+	// RESOLUTION
+	qualifier := types.RelativeTo(info.Pkg)
+	for id, obj := range info.Uses {
+		// Position of the object definition.
+		pos := obj.Pos()
+		Len := len(obj.Name())
+
+		// Correct the position for non-renaming import specs.
+		//  import "sync/atomic"
+		//          ^^^^^^^^^^^
+		if obj, ok := obj.(*types.PkgName); ok && id.Name == obj.Imported().Name() {
+			// Assume this is a non-renaming import.
+			// NB: not true for degenerate renamings: `import foo "foo"`.
+			pos++
+			Len = len(obj.Imported().Path())
+		}
+
+		if obj.Pkg() == nil {
+			continue // don't mark up built-ins.
+		}
+
+		fi, offset := a.fileAndOffset(id.NamePos)
+		fi.addLink(aLink{
+			start: offset,
+			end:   offset + len(id.Name),
+			title: types.ObjectString(obj, qualifier),
+			href:  a.posURL(pos, Len),
+		})
+	}
+
+	// IMPLEMENTS & METHOD SETS
+	for _, obj := range info.Defs {
+		if obj, ok := obj.(*types.TypeName); ok {
+			if named, ok := obj.Type().(*types.Named); ok {
+				a.namedType(named, implements)
+			}
+		}
+	}
+}
+
+func (a *analysis) namedType(T *types.Named, implements map[*types.Named]implementsFacts) {
+	obj := T.Obj()
+	qualifier := types.RelativeTo(obj.Pkg())
+	v := &TypeInfoJSON{
+		Name:    obj.Name(),
+		Size:    sizes.Sizeof(T),
+		Align:   sizes.Alignof(T),
+		Methods: []anchorJSON{}, // (JS wants non-nil)
+	}
+
+	// addFact adds the fact "is implemented by T" (by) or
+	// "implements T" (!by) to group.
+	addFact := func(group *implGroupJSON, T types.Type, by bool) {
+		Tobj := deref(T).(*types.Named).Obj()
+		var byKind string
+		if by {
+			// Show underlying kind of implementing type,
+			// e.g. "slice", "array", "struct".
+			s := reflect.TypeOf(T.Underlying()).String()
+			byKind = strings.ToLower(strings.TrimPrefix(s, "*types."))
+		}
+		group.Facts = append(group.Facts, implFactJSON{
+			ByKind: byKind,
+			Other: anchorJSON{
+				Href: a.posURL(Tobj.Pos(), len(Tobj.Name())),
+				Text: types.TypeString(T, qualifier),
+			},
+		})
+	}
+
+	// IMPLEMENTS
+	if r, ok := implements[T]; ok {
+		if isInterface(T) {
+			// "T is implemented by <conc>" ...
+			// "T is implemented by <iface>"...
+			// "T implements        <iface>"...
+			group := implGroupJSON{
+				Descr: types.TypeString(T, qualifier),
+			}
+			// Show concrete types first; use two passes.
+			for _, sub := range r.to {
+				if !isInterface(sub) {
+					addFact(&group, sub, true)
+				}
+			}
+			for _, sub := range r.to {
+				if isInterface(sub) {
+					addFact(&group, sub, true)
+				}
+			}
+			for _, super := range r.from {
+				addFact(&group, super, false)
+			}
+			v.ImplGroups = append(v.ImplGroups, group)
+		} else {
+			// T is concrete.
+			if r.from != nil {
+				// "T implements <iface>"...
+				group := implGroupJSON{
+					Descr: types.TypeString(T, qualifier),
+				}
+				for _, super := range r.from {
+					addFact(&group, super, false)
+				}
+				v.ImplGroups = append(v.ImplGroups, group)
+			}
+			if r.fromPtr != nil {
+				// "*C implements <iface>"...
+				group := implGroupJSON{
+					Descr: "*" + types.TypeString(T, qualifier),
+				}
+				for _, psuper := range r.fromPtr {
+					addFact(&group, psuper, false)
+				}
+				v.ImplGroups = append(v.ImplGroups, group)
+			}
+		}
+	}
+
+	// METHOD SETS
+	for _, sel := range typeutil.IntuitiveMethodSet(T, &a.prog.MethodSets) {
+		meth := sel.Obj().(*types.Func)
+		pos := meth.Pos() // may be 0 for error.Error
+		v.Methods = append(v.Methods, anchorJSON{
+			Href: a.posURL(pos, len(meth.Name())),
+			Text: types.SelectionString(sel, qualifier),
+		})
+	}
+
+	// Since there can be many specs per decl, we
+	// can't attach the link to the keyword 'type'
+	// (as we do with 'func'); we use the Ident.
+	fi, offset := a.fileAndOffset(obj.Pos())
+	fi.addLink(aLink{
+		start:   offset,
+		end:     offset + len(obj.Name()),
+		title:   fmt.Sprintf("type info for %s", obj.Name()),
+		onclick: fmt.Sprintf("onClickTypeInfo(%d)", fi.addData(v)),
+	})
+
+	// Add info for exported package-level types to the package info.
+	if obj.Exported() && isPackageLevel(obj) {
+		// TODO(adonovan): Path is not unique!
+		// It is possible to declare a non-test package called x_test.
+		a.result.pkgInfo(obj.Pkg().Path()).addType(v)
+	}
+}
+
+// -- utilities --------------------------------------------------------
+
+func isInterface(T types.Type) bool { return types.IsInterface(T) }
+
+// deref returns a pointer's element type; otherwise it returns typ.
+func deref(typ types.Type) types.Type {
+	if p, ok := typ.Underlying().(*types.Pointer); ok {
+		return p.Elem()
+	}
+	return typ
+}
+
+// isPackageLevel reports whether obj is a package-level object.
+func isPackageLevel(obj types.Object) bool {
+	return obj.Pkg().Scope().Lookup(obj.Name()) == obj
+}
diff --git a/internal/godoc/corpus.go b/internal/godoc/corpus.go
new file mode 100644
index 0000000..7bdb9aa
--- /dev/null
+++ b/internal/godoc/corpus.go
@@ -0,0 +1,165 @@
+// 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.
+
+package godoc
+
+import (
+	"errors"
+	"sync"
+	"time"
+
+	"golang.org/x/website/internal/godoc/analysis"
+	"golang.org/x/website/internal/godoc/util"
+	"golang.org/x/website/internal/godoc/vfs"
+)
+
+// A Corpus holds all the state related to serving and indexing a
+// collection of Go code.
+//
+// Construct a new Corpus with NewCorpus, then modify options,
+// then call its Init method.
+type Corpus struct {
+	fs vfs.FileSystem
+
+	// Verbose logging.
+	Verbose bool
+
+	// IndexEnabled controls whether indexing is enabled.
+	IndexEnabled bool
+
+	// IndexFiles specifies a glob pattern specifying index files.
+	// If not empty, the index is read from these files in sorted
+	// order.
+	IndexFiles string
+
+	// IndexThrottle specifies the indexing throttle value
+	// between 0.0 and 1.0. At 0.0, the indexer always sleeps.
+	// At 1.0, the indexer never sleeps. Because 0.0 is useless
+	// and redundant with setting IndexEnabled to false, the
+	// zero value for IndexThrottle means 0.9.
+	IndexThrottle float64
+
+	// IndexInterval specifies the time to sleep between reindexing
+	// all the sources.
+	// If zero, a default is used. If negative, the index is only
+	// built once.
+	IndexInterval time.Duration
+
+	// IndexDocs enables indexing of Go documentation.
+	// This will produce search results for exported types, functions,
+	// methods, variables, and constants, and will link to the godoc
+	// documentation for those identifiers.
+	IndexDocs bool
+
+	// IndexGoCode enables indexing of Go source code.
+	// This will produce search results for internal and external identifiers
+	// and will link to both declarations and uses of those identifiers in
+	// source code.
+	IndexGoCode bool
+
+	// IndexFullText enables full-text indexing.
+	// This will provide search results for any matching text in any file that
+	// is indexed, including non-Go files (see whitelisted in index.go).
+	// Regexp searching is supported via full-text indexing.
+	IndexFullText bool
+
+	// MaxResults optionally specifies the maximum results for indexing.
+	MaxResults int
+
+	// SummarizePackage optionally specifies a function to
+	// summarize a package. It exists as an optimization to
+	// avoid reading files to parse package comments.
+	//
+	// If SummarizePackage returns false for ok, the caller
+	// ignores all return values and parses the files in the package
+	// as if SummarizePackage were nil.
+	//
+	// If showList is false, the package is hidden from the
+	// package listing.
+	SummarizePackage func(pkg string) (summary string, showList, ok bool)
+
+	// IndexDirectory optionally specifies a function to determine
+	// whether the provided directory should be indexed.  The dir
+	// will be of the form "/src/cmd/6a", "/doc/play",
+	// "/src/io", etc.
+	// If nil, all directories are indexed if indexing is enabled.
+	IndexDirectory func(dir string) bool
+
+	// Send a value on this channel to trigger a metadata refresh.
+	// It is buffered so that if a signal is not lost if sent
+	// during a refresh.
+	refreshMetadataSignal chan bool
+
+	// file system information
+	fsTree      util.RWValue // *Directory tree of packages, updated with each sync (but sync code is removed now)
+	fsModified  util.RWValue // timestamp of last call to invalidateIndex
+	docMetadata util.RWValue // mapping from paths to *Metadata
+
+	// SearchIndex is the search index in use.
+	searchIndex util.RWValue
+
+	// Analysis is the result of type and pointer analysis.
+	Analysis analysis.Result
+
+	// flag to check whether a corpus is initialized or not
+	initMu   sync.RWMutex
+	initDone bool
+
+	// pkgAPIInfo contains the information about which package API
+	// features were added in which version of Go.
+	pkgAPIInfo apiVersions
+}
+
+// NewCorpus returns a new Corpus from a filesystem.
+// The returned corpus has all indexing enabled and MaxResults set to 1000.
+// Change or set any options on Corpus before calling the Corpus.Init method.
+func NewCorpus(fs vfs.FileSystem) *Corpus {
+	c := &Corpus{
+		fs:                    fs,
+		refreshMetadataSignal: make(chan bool, 1),
+
+		MaxResults:    1000,
+		IndexEnabled:  true,
+		IndexDocs:     true,
+		IndexGoCode:   true,
+		IndexFullText: true,
+	}
+	return c
+}
+
+func (c *Corpus) CurrentIndex() (*Index, time.Time) {
+	v, t := c.searchIndex.Get()
+	idx, _ := v.(*Index)
+	return idx, t
+}
+
+func (c *Corpus) FSModifiedTime() time.Time {
+	_, ts := c.fsModified.Get()
+	return ts
+}
+
+// Init initializes Corpus, once options on Corpus are set.
+// It must be called before any subsequent method calls.
+func (c *Corpus) Init() error {
+	if err := c.initFSTree(); err != nil {
+		return err
+	}
+	c.updateMetadata()
+	go c.refreshMetadataLoop()
+
+	c.initMu.Lock()
+	c.initDone = true
+	c.initMu.Unlock()
+	return nil
+}
+
+func (c *Corpus) initFSTree() error {
+	dir := c.newDirectory("/", -1)
+	if dir == nil {
+		return errors.New("godoc: corpus fstree is nil")
+	}
+	c.fsTree.Set(dir)
+	c.invalidateIndex()
+	return nil
+}
diff --git a/internal/godoc/dirtrees.go b/internal/godoc/dirtrees.go
new file mode 100644
index 0000000..54ace12
--- /dev/null
+++ b/internal/godoc/dirtrees.go
@@ -0,0 +1,383 @@
+// Copyright 2010 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.
+
+// This file contains the code dealing with package directory trees.
+
+package godoc
+
+import (
+	"go/doc"
+	"go/parser"
+	"go/token"
+	"log"
+	"os"
+	pathpkg "path"
+	"runtime"
+	"sort"
+	"strings"
+
+	"golang.org/x/website/internal/godoc/vfs"
+)
+
+// Conventional name for directories containing test data.
+// Excluded from directory trees.
+//
+const testdataDirName = "testdata"
+
+type Directory struct {
+	Depth    int
+	Path     string       // directory path; includes Name
+	Name     string       // directory name
+	HasPkg   bool         // true if the directory contains at least one package
+	Synopsis string       // package documentation, if any
+	RootType vfs.RootType // root type of the filesystem containing the directory
+	Dirs     []*Directory // subdirectories
+}
+
+func isGoFile(fi os.FileInfo) bool {
+	name := fi.Name()
+	return !fi.IsDir() &&
+		len(name) > 0 && name[0] != '.' && // ignore .files
+		pathpkg.Ext(name) == ".go"
+}
+
+func isPkgFile(fi os.FileInfo) bool {
+	return isGoFile(fi) &&
+		!strings.HasSuffix(fi.Name(), "_test.go") // ignore test files
+}
+
+func isPkgDir(fi os.FileInfo) bool {
+	name := fi.Name()
+	return fi.IsDir() && len(name) > 0 &&
+		name[0] != '_' && name[0] != '.' // ignore _files and .files
+}
+
+type treeBuilder struct {
+	c        *Corpus
+	maxDepth int
+}
+
+// ioGate is a semaphore controlling VFS activity (ReadDir, parseFile, etc).
+// Send before an operation and receive after.
+var ioGate = make(chan struct{}, 20)
+
+// workGate controls the number of concurrent workers. Too many concurrent
+// workers and performance degrades and the race detector gets overwhelmed. If
+// we cannot check out a concurrent worker, work is performed by the main thread
+// instead of spinning up another goroutine.
+var workGate = make(chan struct{}, runtime.NumCPU()*4)
+
+func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth int) *Directory {
+	if name == testdataDirName {
+		return nil
+	}
+
+	if depth >= b.maxDepth {
+		// return a dummy directory so that the parent directory
+		// doesn't get discarded just because we reached the max
+		// directory depth
+		return &Directory{
+			Depth: depth,
+			Path:  path,
+			Name:  name,
+		}
+	}
+
+	var synopses [3]string // prioritized package documentation (0 == highest priority)
+
+	show := true // show in package listing
+	hasPkgFiles := false
+	haveSummary := false
+
+	if hook := b.c.SummarizePackage; hook != nil {
+		if summary, show0, ok := hook(strings.TrimPrefix(path, "/src/")); ok {
+			hasPkgFiles = true
+			show = show0
+			synopses[0] = summary
+			haveSummary = true
+		}
+	}
+
+	ioGate <- struct{}{}
+	list, err := b.c.fs.ReadDir(path)
+	<-ioGate
+	if err != nil {
+		// TODO: propagate more. See golang.org/issue/14252.
+		// For now:
+		if b.c.Verbose {
+			log.Printf("newDirTree reading %s: %v", path, err)
+		}
+	}
+
+	// determine number of subdirectories and if there are package files
+	var dirchs []chan *Directory
+	var dirs []*Directory
+
+	for _, d := range list {
+		filename := pathpkg.Join(path, d.Name())
+		switch {
+		case isPkgDir(d):
+			name := d.Name()
+			select {
+			case workGate <- struct{}{}:
+				ch := make(chan *Directory, 1)
+				dirchs = append(dirchs, ch)
+				go func() {
+					ch <- b.newDirTree(fset, filename, name, depth+1)
+					<-workGate
+				}()
+			default:
+				// no free workers, do work synchronously
+				dir := b.newDirTree(fset, filename, name, depth+1)
+				if dir != nil {
+					dirs = append(dirs, dir)
+				}
+			}
+		case !haveSummary && isPkgFile(d):
+			// looks like a package file, but may just be a file ending in ".go";
+			// don't just count it yet (otherwise we may end up with hasPkgFiles even
+			// though the directory doesn't contain any real package files - was bug)
+			// no "optimal" package synopsis yet; continue to collect synopses
+			ioGate <- struct{}{}
+			const flags = parser.ParseComments | parser.PackageClauseOnly
+			file, err := b.c.parseFile(fset, filename, flags)
+			<-ioGate
+			if err != nil {
+				if b.c.Verbose {
+					log.Printf("Error parsing %v: %v", filename, err)
+				}
+				break
+			}
+
+			hasPkgFiles = true
+			if file.Doc != nil {
+				// prioritize documentation
+				i := -1
+				switch file.Name.Name {
+				case name:
+					i = 0 // normal case: directory name matches package name
+				case "main":
+					i = 1 // directory contains a main package
+				default:
+					i = 2 // none of the above
+				}
+				if 0 <= i && i < len(synopses) && synopses[i] == "" {
+					synopses[i] = doc.Synopsis(file.Doc.Text())
+				}
+			}
+			haveSummary = synopses[0] != ""
+		}
+	}
+
+	// create subdirectory tree
+	for _, ch := range dirchs {
+		if d := <-ch; d != nil {
+			dirs = append(dirs, d)
+		}
+	}
+
+	// We need to sort the dirs slice because
+	// it is appended again after reading from dirchs.
+	sort.Slice(dirs, func(i, j int) bool {
+		return dirs[i].Name < dirs[j].Name
+	})
+
+	// if there are no package files and no subdirectories
+	// containing package files, ignore the directory
+	if !hasPkgFiles && len(dirs) == 0 {
+		return nil
+	}
+
+	// select the highest-priority synopsis for the directory entry, if any
+	synopsis := ""
+	for _, synopsis = range synopses {
+		if synopsis != "" {
+			break
+		}
+	}
+
+	return &Directory{
+		Depth:    depth,
+		Path:     path,
+		Name:     name,
+		HasPkg:   hasPkgFiles && show, // TODO(bradfitz): add proper Hide field?
+		Synopsis: synopsis,
+		RootType: b.c.fs.RootType(path),
+		Dirs:     dirs,
+	}
+}
+
+// newDirectory creates a new package directory tree with at most maxDepth
+// levels, anchored at root. The result tree is pruned such that it only
+// contains directories that contain package files or that contain
+// subdirectories containing package files (transitively). If a non-nil
+// pathFilter is provided, directory paths additionally must be accepted
+// by the filter (i.e., pathFilter(path) must be true). If a value >= 0 is
+// provided for maxDepth, nodes at larger depths are pruned as well; they
+// are assumed to contain package files even if their contents are not known
+// (i.e., in this case the tree may contain directories w/o any package files).
+//
+func (c *Corpus) newDirectory(root string, maxDepth int) *Directory {
+	// The root could be a symbolic link so use Stat not Lstat.
+	d, err := c.fs.Stat(root)
+	// If we fail here, report detailed error messages; otherwise
+	// is is hard to see why a directory tree was not built.
+	switch {
+	case err != nil:
+		log.Printf("newDirectory(%s): %s", root, err)
+		return nil
+	case root != "/" && !isPkgDir(d):
+		log.Printf("newDirectory(%s): not a package directory", root)
+		return nil
+	case root == "/" && !d.IsDir():
+		log.Printf("newDirectory(%s): not a directory", root)
+		return nil
+	}
+	if maxDepth < 0 {
+		maxDepth = 1e6 // "infinity"
+	}
+	b := treeBuilder{c, maxDepth}
+	// the file set provided is only for local parsing, no position
+	// information escapes and thus we don't need to save the set
+	return b.newDirTree(token.NewFileSet(), root, d.Name(), 0)
+}
+
+func (dir *Directory) walk(c chan<- *Directory, skipRoot bool) {
+	if dir != nil {
+		if !skipRoot {
+			c <- dir
+		}
+		for _, d := range dir.Dirs {
+			d.walk(c, false)
+		}
+	}
+}
+
+func (dir *Directory) iter(skipRoot bool) <-chan *Directory {
+	c := make(chan *Directory)
+	go func() {
+		dir.walk(c, skipRoot)
+		close(c)
+	}()
+	return c
+}
+
+func (dir *Directory) lookupLocal(name string) *Directory {
+	for _, d := range dir.Dirs {
+		if d.Name == name {
+			return d
+		}
+	}
+	return nil
+}
+
+func splitPath(p string) []string {
+	p = strings.TrimPrefix(p, "/")
+	if p == "" {
+		return nil
+	}
+	return strings.Split(p, "/")
+}
+
+// lookup looks for the *Directory for a given path, relative to dir.
+func (dir *Directory) lookup(path string) *Directory {
+	d := splitPath(dir.Path)
+	p := splitPath(path)
+	i := 0
+	for i < len(d) {
+		if i >= len(p) || d[i] != p[i] {
+			return nil
+		}
+		i++
+	}
+	for dir != nil && i < len(p) {
+		dir = dir.lookupLocal(p[i])
+		i++
+	}
+	return dir
+}
+
+// DirEntry describes a directory entry. The Depth and Height values
+// are useful for presenting an entry in an indented fashion.
+//
+type DirEntry struct {
+	Depth    int          // >= 0
+	Height   int          // = DirList.MaxHeight - Depth, > 0
+	Path     string       // directory path; includes Name, relative to DirList root
+	Name     string       // directory name
+	HasPkg   bool         // true if the directory contains at least one package
+	Synopsis string       // package documentation, if any
+	RootType vfs.RootType // root type of the filesystem containing the direntry
+}
+
+type DirList struct {
+	MaxHeight int // directory tree height, > 0
+	List      []DirEntry
+}
+
+// hasThirdParty checks whether a list of directory entries has packages outside
+// the standard library or not.
+func hasThirdParty(list []DirEntry) bool {
+	for _, entry := range list {
+		if entry.RootType == vfs.RootTypeGoPath {
+			return true
+		}
+	}
+	return false
+}
+
+// listing creates a (linear) directory listing from a directory tree.
+// If skipRoot is set, the root directory itself is excluded from the list.
+// If filter is set, only the directory entries whose paths match the filter
+// are included.
+//
+func (dir *Directory) listing(skipRoot bool, filter func(string) bool) *DirList {
+	if dir == nil {
+		return nil
+	}
+
+	// determine number of entries n and maximum height
+	n := 0
+	minDepth := 1 << 30 // infinity
+	maxDepth := 0
+	for d := range dir.iter(skipRoot) {
+		n++
+		if minDepth > d.Depth {
+			minDepth = d.Depth
+		}
+		if maxDepth < d.Depth {
+			maxDepth = d.Depth
+		}
+	}
+	maxHeight := maxDepth - minDepth + 1
+
+	if n == 0 {
+		return nil
+	}
+
+	// create list
+	list := make([]DirEntry, 0, n)
+	for d := range dir.iter(skipRoot) {
+		if filter != nil && !filter(d.Path) {
+			continue
+		}
+		var p DirEntry
+		p.Depth = d.Depth - minDepth
+		p.Height = maxHeight - p.Depth
+		// the path is relative to root.Path - remove the root.Path
+		// prefix (the prefix should always be present but avoid
+		// crashes and check)
+		path := strings.TrimPrefix(d.Path, dir.Path)
+		// remove leading separator if any - path must be relative
+		path = strings.TrimPrefix(path, "/")
+		p.Path = path
+		p.Name = d.Name
+		p.HasPkg = d.HasPkg
+		p.Synopsis = d.Synopsis
+		p.RootType = d.RootType
+		list = append(list, p)
+	}
+
+	return &DirList{maxHeight, list}
+}
diff --git a/internal/godoc/dirtrees_test.go b/internal/godoc/dirtrees_test.go
new file mode 100644
index 0000000..3fd5cfa
--- /dev/null
+++ b/internal/godoc/dirtrees_test.go
@@ -0,0 +1,64 @@
+// Copyright 2018 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.
+
+package godoc
+
+import (
+	"go/build"
+	"path/filepath"
+	"runtime"
+	"sort"
+	"testing"
+
+	"golang.org/x/website/internal/godoc/vfs"
+	"golang.org/x/website/internal/godoc/vfs/gatefs"
+)
+
+func TestNewDirTree(t *testing.T) {
+	fsGate := make(chan bool, 20)
+	rootfs := gatefs.New(vfs.OS(runtime.GOROOT()), fsGate)
+	fs := vfs.NameSpace{}
+	fs.Bind("/", rootfs, "/", vfs.BindReplace)
+
+	c := NewCorpus(fs)
+	// 3 levels deep is enough for testing
+	dir := c.newDirectory("/", 3)
+
+	processDir(t, dir)
+}
+
+func processDir(t *testing.T, dir *Directory) {
+	var list []string
+	for _, d := range dir.Dirs {
+		list = append(list, d.Name)
+		// recursively process the lower level
+		processDir(t, d)
+	}
+
+	if sort.StringsAreSorted(list) == false {
+		t.Errorf("list: %v is not sorted\n", list)
+	}
+}
+
+func BenchmarkNewDirectory(b *testing.B) {
+	if testing.Short() {
+		b.Skip("not running tests requiring large file scan in short mode")
+	}
+
+	fsGate := make(chan bool, 20)
+
+	goroot := runtime.GOROOT()
+	rootfs := gatefs.New(vfs.OS(goroot), fsGate)
+	fs := vfs.NameSpace{}
+	fs.Bind("/", rootfs, "/", vfs.BindReplace)
+	for _, p := range filepath.SplitList(build.Default.GOPATH) {
+		fs.Bind("/src/golang.org", gatefs.New(vfs.OS(p), fsGate), "/src/golang.org", vfs.BindAfter)
+	}
+	b.ResetTimer()
+	b.ReportAllocs()
+	for tries := 0; tries < b.N; tries++ {
+		corpus := NewCorpus(fs)
+		corpus.newDirectory("/", -1)
+	}
+}
diff --git a/internal/godoc/format.go b/internal/godoc/format.go
new file mode 100644
index 0000000..3e8c867
--- /dev/null
+++ b/internal/godoc/format.go
@@ -0,0 +1,371 @@
+// Copyright 2011 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.
+
+// This file implements FormatSelections and FormatText.
+// FormatText is used to HTML-format Go and non-Go source
+// text with line numbers and highlighted sections. It is
+// built on top of FormatSelections, a generic formatter
+// for "selected" text.
+
+package godoc
+
+import (
+	"fmt"
+	"go/scanner"
+	"go/token"
+	"io"
+	"regexp"
+	"strconv"
+	"text/template"
+)
+
+// ----------------------------------------------------------------------------
+// Implementation of FormatSelections
+
+// A Segment describes a text segment [start, end).
+// The zero value of a Segment is a ready-to-use empty segment.
+//
+type Segment struct {
+	start, end int
+}
+
+func (seg *Segment) isEmpty() bool { return seg.start >= seg.end }
+
+// A Selection is an "iterator" function returning a text segment.
+// Repeated calls to a selection return consecutive, non-overlapping,
+// non-empty segments, followed by an infinite sequence of empty
+// segments. The first empty segment marks the end of the selection.
+//
+type Selection func() Segment
+
+// A LinkWriter writes some start or end "tag" to w for the text offset offs.
+// It is called by FormatSelections at the start or end of each link segment.
+//
+type LinkWriter func(w io.Writer, offs int, start bool)
+
+// A SegmentWriter formats a text according to selections and writes it to w.
+// The selections parameter is a bit set indicating which selections provided
+// to FormatSelections overlap with the text segment: If the n'th bit is set
+// in selections, the n'th selection provided to FormatSelections is overlapping
+// with the text.
+//
+type SegmentWriter func(w io.Writer, text []byte, selections int)
+
+// FormatSelections takes a text and writes it to w using link and segment
+// writers lw and sw as follows: lw is invoked for consecutive segment starts
+// and ends as specified through the links selection, and sw is invoked for
+// consecutive segments of text overlapped by the same selections as specified
+// by selections. The link writer lw may be nil, in which case the links
+// Selection is ignored.
+//
+func FormatSelections(w io.Writer, text []byte, lw LinkWriter, links Selection, sw SegmentWriter, selections ...Selection) {
+	// If we have a link writer, make the links
+	// selection the last entry in selections
+	if lw != nil {
+		selections = append(selections, links)
+	}
+
+	// compute the sequence of consecutive segment changes
+	changes := newMerger(selections)
+
+	// The i'th bit in bitset indicates that the text
+	// at the current offset is covered by selections[i].
+	bitset := 0
+	lastOffs := 0
+
+	// Text segments are written in a delayed fashion
+	// such that consecutive segments belonging to the
+	// same selection can be combined (peephole optimization).
+	// last describes the last segment which has not yet been written.
+	var last struct {
+		begin, end int // valid if begin < end
+		bitset     int
+	}
+
+	// flush writes the last delayed text segment
+	flush := func() {
+		if last.begin < last.end {
+			sw(w, text[last.begin:last.end], last.bitset)
+		}
+		last.begin = last.end // invalidate last
+	}
+
+	// segment runs the segment [lastOffs, end) with the selection
+	// indicated by bitset through the segment peephole optimizer.
+	segment := func(end int) {
+		if lastOffs < end { // ignore empty segments
+			if last.end != lastOffs || last.bitset != bitset {
+				// the last segment is not adjacent to or
+				// differs from the new one
+				flush()
+				// start a new segment
+				last.begin = lastOffs
+			}
+			last.end = end
+			last.bitset = bitset
+		}
+	}
+
+	for {
+		// get the next segment change
+		index, offs, start := changes.next()
+		if index < 0 || offs > len(text) {
+			// no more segment changes or the next change
+			// is past the end of the text - we're done
+			break
+		}
+		// determine the kind of segment change
+		if lw != nil && index == len(selections)-1 {
+			// we have a link segment change (see start of this function):
+			// format the previous selection segment, write the
+			// link tag and start a new selection segment
+			segment(offs)
+			flush()
+			lastOffs = offs
+			lw(w, offs, start)
+		} else {
+			// we have a selection change:
+			// format the previous selection segment, determine
+			// the new selection bitset and start a new segment
+			segment(offs)
+			lastOffs = offs
+			mask := 1 << uint(index)
+			if start {
+				bitset |= mask
+			} else {
+				bitset &^= mask
+			}
+		}
+	}
+	segment(len(text))
+	flush()
+}
+
+// A merger merges a slice of Selections and produces a sequence of
+// consecutive segment change events through repeated next() calls.
+//
+type merger struct {
+	selections []Selection
+	segments   []Segment // segments[i] is the next segment of selections[i]
+}
+
+const infinity int = 2e9
+
+func newMerger(selections []Selection) *merger {
+	segments := make([]Segment, len(selections))
+	for i, sel := range selections {
+		segments[i] = Segment{infinity, infinity}
+		if sel != nil {
+			if seg := sel(); !seg.isEmpty() {
+				segments[i] = seg
+			}
+		}
+	}
+	return &merger{selections, segments}
+}
+
+// next returns the next segment change: index specifies the Selection
+// to which the segment belongs, offs is the segment start or end offset
+// as determined by the start value. If there are no more segment changes,
+// next returns an index value < 0.
+//
+func (m *merger) next() (index, offs int, start bool) {
+	// find the next smallest offset where a segment starts or ends
+	offs = infinity
+	index = -1
+	for i, seg := range m.segments {
+		switch {
+		case seg.start < offs:
+			offs = seg.start
+			index = i
+			start = true
+		case seg.end < offs:
+			offs = seg.end
+			index = i
+			start = false
+		}
+	}
+	if index < 0 {
+		// no offset found => all selections merged
+		return
+	}
+	// offset found - it's either the start or end offset but
+	// either way it is ok to consume the start offset: set it
+	// to infinity so it won't be considered in the following
+	// next call
+	m.segments[index].start = infinity
+	if start {
+		return
+	}
+	// end offset found - consume it
+	m.segments[index].end = infinity
+	// advance to the next segment for that selection
+	seg := m.selections[index]()
+	if !seg.isEmpty() {
+		m.segments[index] = seg
+	}
+	return
+}
+
+// ----------------------------------------------------------------------------
+// Implementation of FormatText
+
+// lineSelection returns the line segments for text as a Selection.
+func lineSelection(text []byte) Selection {
+	i, j := 0, 0
+	return func() (seg Segment) {
+		// find next newline, if any
+		for j < len(text) {
+			j++
+			if text[j-1] == '\n' {
+				break
+			}
+		}
+		if i < j {
+			// text[i:j] constitutes a line
+			seg = Segment{i, j}
+			i = j
+		}
+		return
+	}
+}
+
+// tokenSelection returns, as a selection, the sequence of
+// consecutive occurrences of token sel in the Go src text.
+//
+func tokenSelection(src []byte, sel token.Token) Selection {
+	var s scanner.Scanner
+	fset := token.NewFileSet()
+	file := fset.AddFile("", fset.Base(), len(src))
+	s.Init(file, src, nil, scanner.ScanComments)
+	return func() (seg Segment) {
+		for {
+			pos, tok, lit := s.Scan()
+			if tok == token.EOF {
+				break
+			}
+			offs := file.Offset(pos)
+			if tok == sel {
+				seg = Segment{offs, offs + len(lit)}
+				break
+			}
+		}
+		return
+	}
+}
+
+// makeSelection is a helper function to make a Selection from a slice of pairs.
+// Pairs describing empty segments are ignored.
+//
+func makeSelection(matches [][]int) Selection {
+	i := 0
+	return func() Segment {
+		for i < len(matches) {
+			m := matches[i]
+			i++
+			if m[0] < m[1] {
+				// non-empty segment
+				return Segment{m[0], m[1]}
+			}
+		}
+		return Segment{}
+	}
+}
+
+// regexpSelection computes the Selection for the regular expression expr in text.
+func regexpSelection(text []byte, expr string) Selection {
+	var matches [][]int
+	if rx, err := regexp.Compile(expr); err == nil {
+		matches = rx.FindAllIndex(text, -1)
+	}
+	return makeSelection(matches)
+}
+
+var selRx = regexp.MustCompile(`^([0-9]+):([0-9]+)`)
+
+// RangeSelection computes the Selection for a text range described
+// by the argument str; the range description must match the selRx
+// regular expression.
+func RangeSelection(str string) Selection {
+	m := selRx.FindStringSubmatch(str)
+	if len(m) >= 2 {
+		from, _ := strconv.Atoi(m[1])
+		to, _ := strconv.Atoi(m[2])
+		if from < to {
+			return makeSelection([][]int{{from, to}})
+		}
+	}
+	return nil
+}
+
+// Span tags for all the possible selection combinations that may
+// be generated by FormatText. Selections are indicated by a bitset,
+// and the value of the bitset specifies the tag to be used.
+//
+// bit 0: comments
+// bit 1: highlights
+// bit 2: selections
+//
+var startTags = [][]byte{
+	/* 000 */ []byte(``),
+	/* 001 */ []byte(`<span class="comment">`),
+	/* 010 */ []byte(`<span class="highlight">`),
+	/* 011 */ []byte(`<span class="highlight-comment">`),
+	/* 100 */ []byte(`<span class="selection">`),
+	/* 101 */ []byte(`<span class="selection-comment">`),
+	/* 110 */ []byte(`<span class="selection-highlight">`),
+	/* 111 */ []byte(`<span class="selection-highlight-comment">`),
+}
+
+var endTag = []byte(`</span>`)
+
+func selectionTag(w io.Writer, text []byte, selections int) {
+	if selections < len(startTags) {
+		if tag := startTags[selections]; len(tag) > 0 {
+			w.Write(tag)
+			template.HTMLEscape(w, text)
+			w.Write(endTag)
+			return
+		}
+	}
+	template.HTMLEscape(w, text)
+}
+
+// FormatText HTML-escapes text and writes it to w.
+// Consecutive text segments are wrapped in HTML spans (with tags as
+// defined by startTags and endTag) as follows:
+//
+//	- if line >= 0, line number (ln) spans are inserted before each line,
+//	  starting with the value of line
+//	- if the text is Go source, comments get the "comment" span class
+//	- each occurrence of the regular expression pattern gets the "highlight"
+//	  span class
+//	- text segments covered by selection get the "selection" span class
+//
+// Comments, highlights, and selections may overlap arbitrarily; the respective
+// HTML span classes are specified in the startTags variable.
+//
+func FormatText(w io.Writer, text []byte, line int, goSource bool, pattern string, selection Selection) {
+	var comments, highlights Selection
+	if goSource {
+		comments = tokenSelection(text, token.COMMENT)
+	}
+	if pattern != "" {
+		highlights = regexpSelection(text, pattern)
+	}
+	if line >= 0 || comments != nil || highlights != nil || selection != nil {
+		var lineTag LinkWriter
+		if line >= 0 {
+			lineTag = func(w io.Writer, _ int, start bool) {
+				if start {
+					fmt.Fprintf(w, "<span id=\"L%d\" class=\"ln\">%6d</span>", line, line)
+					line++
+				}
+			}
+		}
+		FormatSelections(w, text, lineTag, lineSelection(text), selectionTag, comments, highlights, selection)
+	} else {
+		template.HTMLEscape(w, text)
+	}
+}
diff --git a/internal/godoc/godoc.go b/internal/godoc/godoc.go
new file mode 100644
index 0000000..e981a1c
--- /dev/null
+++ b/internal/godoc/godoc.go
@@ -0,0 +1,939 @@
+// 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.
+
+// Package godoc is a work-in-progress (2013-07-17) package to
+// begin splitting up the godoc binary into multiple pieces.
+//
+// This package comment will evolve over time as this package splits
+// into smaller pieces.
+package godoc // import "golang.org/x/website/internal/godoc"
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"go/ast"
+	"go/doc"
+	"go/format"
+	"go/printer"
+	"go/token"
+	htmltemplate "html/template"
+	"io"
+	"log"
+	"os"
+	pathpkg "path"
+	"regexp"
+	"strconv"
+	"strings"
+	"text/template"
+	"time"
+	"unicode"
+	"unicode/utf8"
+)
+
+// Fake relative package path for built-ins. Documentation for all globals
+// (not just exported ones) will be shown for packages in this directory,
+// and there will be no association of consts, vars, and factory functions
+// with types (see issue 6645).
+const builtinPkgPath = "builtin"
+
+// FuncMap defines template functions used in godoc templates.
+//
+// Convention: template function names ending in "_html" or "_url" produce
+//             HTML- or URL-escaped strings; all other function results may
+//             require explicit escaping in the template.
+func (p *Presentation) FuncMap() template.FuncMap {
+	p.initFuncMapOnce.Do(p.initFuncMap)
+	return p.funcMap
+}
+
+func (p *Presentation) TemplateFuncs() template.FuncMap {
+	p.initFuncMapOnce.Do(p.initFuncMap)
+	return p.templateFuncs
+}
+
+func (p *Presentation) initFuncMap() {
+	if p.Corpus == nil {
+		panic("nil Presentation.Corpus")
+	}
+	p.templateFuncs = template.FuncMap{
+		"code": p.code,
+	}
+	p.funcMap = template.FuncMap{
+		// various helpers
+		"filename": filenameFunc,
+		"repeat":   strings.Repeat,
+		"since":    p.Corpus.pkgAPIInfo.sinceVersionFunc,
+
+		// access to FileInfos (directory listings)
+		"fileInfoName": fileInfoNameFunc,
+		"fileInfoTime": fileInfoTimeFunc,
+
+		// access to search result information
+		"infoKind_html":    infoKind_htmlFunc,
+		"infoLine":         p.infoLineFunc,
+		"infoSnippet_html": p.infoSnippet_htmlFunc,
+
+		// formatting of AST nodes
+		"node":         p.nodeFunc,
+		"node_html":    p.node_htmlFunc,
+		"comment_html": comment_htmlFunc,
+		"sanitize":     sanitizeFunc,
+
+		// support for URL attributes
+		"pkgLink":       pkgLinkFunc,
+		"srcLink":       srcLinkFunc,
+		"posLink_url":   newPosLink_urlFunc(srcPosLinkFunc),
+		"docLink":       docLinkFunc,
+		"queryLink":     queryLinkFunc,
+		"srcBreadcrumb": srcBreadcrumbFunc,
+		"srcToPkgLink":  srcToPkgLinkFunc,
+
+		// formatting of Examples
+		"example_html":   p.example_htmlFunc,
+		"example_name":   p.example_nameFunc,
+		"example_suffix": p.example_suffixFunc,
+
+		// formatting of analysis information
+		"callgraph_html":  p.callgraph_htmlFunc,
+		"implements_html": p.implements_htmlFunc,
+		"methodset_html":  p.methodset_htmlFunc,
+
+		// formatting of Notes
+		"noteTitle": noteTitle,
+
+		// Number operation
+		"multiply": multiply,
+
+		// formatting of PageInfoMode query string
+		"modeQueryString": modeQueryString,
+
+		// check whether to display third party section or not
+		"hasThirdParty": hasThirdParty,
+
+		// get the no. of columns to split the toc in search page
+		"tocColCount": tocColCount,
+	}
+	if p.URLForSrc != nil {
+		p.funcMap["srcLink"] = p.URLForSrc
+	}
+	if p.URLForSrcPos != nil {
+		p.funcMap["posLink_url"] = newPosLink_urlFunc(p.URLForSrcPos)
+	}
+	if p.URLForSrcQuery != nil {
+		p.funcMap["queryLink"] = p.URLForSrcQuery
+	}
+}
+
+func multiply(a, b int) int { return a * b }
+
+func filenameFunc(path string) string {
+	_, localname := pathpkg.Split(path)
+	return localname
+}
+
+func fileInfoNameFunc(fi os.FileInfo) string {
+	name := fi.Name()
+	if fi.IsDir() {
+		name += "/"
+	}
+	return name
+}
+
+func fileInfoTimeFunc(fi os.FileInfo) string {
+	if t := fi.ModTime(); t.Unix() != 0 {
+		return t.Local().String()
+	}
+	return "" // don't return epoch if time is obviously not set
+}
+
+// The strings in infoKinds must be properly html-escaped.
+var infoKinds = [nKinds]string{
+	PackageClause: "package&nbsp;clause",
+	ImportDecl:    "import&nbsp;decl",
+	ConstDecl:     "const&nbsp;decl",
+	TypeDecl:      "type&nbsp;decl",
+	VarDecl:       "var&nbsp;decl",
+	FuncDecl:      "func&nbsp;decl",
+	MethodDecl:    "method&nbsp;decl",
+	Use:           "use",
+}
+
+func infoKind_htmlFunc(info SpotInfo) string {
+	return infoKinds[info.Kind()] // infoKind entries are html-escaped
+}
+
+func (p *Presentation) infoLineFunc(info SpotInfo) int {
+	line := info.Lori()
+	if info.IsIndex() {
+		index, _ := p.Corpus.searchIndex.Get()
+		if index != nil {
+			line = index.(*Index).Snippet(line).Line
+		} else {
+			// no line information available because
+			// we don't have an index - this should
+			// never happen; be conservative and don't
+			// crash
+			line = 0
+		}
+	}
+	return line
+}
+
+func (p *Presentation) infoSnippet_htmlFunc(info SpotInfo) string {
+	if info.IsIndex() {
+		index, _ := p.Corpus.searchIndex.Get()
+		// Snippet.Text was HTML-escaped when it was generated
+		return index.(*Index).Snippet(info.Lori()).Text
+	}
+	return `<span class="alert">no snippet text available</span>`
+}
+
+func (p *Presentation) nodeFunc(info *PageInfo, node interface{}) string {
+	var buf bytes.Buffer
+	p.writeNode(&buf, info, info.FSet, node)
+	return buf.String()
+}
+
+func (p *Presentation) node_htmlFunc(info *PageInfo, node interface{}, linkify bool) string {
+	var buf1 bytes.Buffer
+	p.writeNode(&buf1, info, info.FSet, node)
+
+	var buf2 bytes.Buffer
+	if n, _ := node.(ast.Node); n != nil && linkify && p.DeclLinks {
+		LinkifyText(&buf2, buf1.Bytes(), n)
+		if st, name := isStructTypeDecl(n); st != nil {
+			addStructFieldIDAttributes(&buf2, name, st)
+		}
+	} else {
+		FormatText(&buf2, buf1.Bytes(), -1, true, "", nil)
+	}
+
+	return buf2.String()
+}
+
+// isStructTypeDecl checks whether n is a struct declaration.
+// It either returns a non-nil StructType and its name, or zero values.
+func isStructTypeDecl(n ast.Node) (st *ast.StructType, name string) {
+	gd, ok := n.(*ast.GenDecl)
+	if !ok || gd.Tok != token.TYPE {
+		return nil, ""
+	}
+	if gd.Lparen > 0 {
+		// Parenthesized type. Who does that, anyway?
+		// TODO: Reportedly gri does. Fix this to handle that too.
+		return nil, ""
+	}
+	if len(gd.Specs) != 1 {
+		return nil, ""
+	}
+	ts, ok := gd.Specs[0].(*ast.TypeSpec)
+	if !ok {
+		return nil, ""
+	}
+	st, ok = ts.Type.(*ast.StructType)
+	if !ok {
+		return nil, ""
+	}
+	return st, ts.Name.Name
+}
+
+// addStructFieldIDAttributes modifies the contents of buf such that
+// all struct fields of the named struct have <span id='name.Field'>
+// in them, so people can link to /#Struct.Field.
+func addStructFieldIDAttributes(buf *bytes.Buffer, name string, st *ast.StructType) {
+	if st.Fields == nil {
+		return
+	}
+	// needsLink is a set of identifiers that still need to be
+	// linked, where value == key, to avoid an allocation in func
+	// linkedField.
+	needsLink := make(map[string]string)
+
+	for _, f := range st.Fields.List {
+		if len(f.Names) == 0 {
+			continue
+		}
+		fieldName := f.Names[0].Name
+		needsLink[fieldName] = fieldName
+	}
+	var newBuf bytes.Buffer
+	foreachLine(buf.Bytes(), func(line []byte) {
+		if fieldName := linkedField(line, needsLink); fieldName != "" {
+			fmt.Fprintf(&newBuf, `<span id="%s.%s"></span>`, name, fieldName)
+			delete(needsLink, fieldName)
+		}
+		newBuf.Write(line)
+	})
+	buf.Reset()
+	buf.Write(newBuf.Bytes())
+}
+
+// foreachLine calls fn for each line of in, where a line includes
+// the trailing "\n", except on the last line, if it doesn't exist.
+func foreachLine(in []byte, fn func(line []byte)) {
+	for len(in) > 0 {
+		nl := bytes.IndexByte(in, '\n')
+		if nl == -1 {
+			fn(in)
+			return
+		}
+		fn(in[:nl+1])
+		in = in[nl+1:]
+	}
+}
+
+// commentPrefix is the line prefix for comments after they've been HTMLified.
+var commentPrefix = []byte(`<span class="comment">// `)
+
+// linkedField determines whether the given line starts with an
+// identifier in the provided ids map (mapping from identifier to the
+// same identifier). The line can start with either an identifier or
+// an identifier in a comment. If one matches, it returns the
+// identifier that matched. Otherwise it returns the empty string.
+func linkedField(line []byte, ids map[string]string) string {
+	line = bytes.TrimSpace(line)
+
+	// For fields with a doc string of the
+	// conventional form, we put the new span into
+	// the comment instead of the field.
+	// The "conventional" form is a complete sentence
+	// per https://golang.org/s/style#comment-sentences like:
+	//
+	//    // Foo is an optional Fooer to foo the foos.
+	//    Foo Fooer
+	//
+	// In this case, we want the #StructName.Foo
+	// link to make the browser go to the comment
+	// line "Foo is an optional Fooer" instead of
+	// the "Foo Fooer" line, which could otherwise
+	// obscure the docs above the browser's "fold".
+	//
+	// TODO: do this better, so it works for all
+	// comments, including unconventional ones.
+	line = bytes.TrimPrefix(line, commentPrefix)
+	id := scanIdentifier(line)
+	if len(id) == 0 {
+		// No leading identifier. Avoid map lookup for
+		// somewhat common case.
+		return ""
+	}
+	return ids[string(id)]
+}
+
+// scanIdentifier scans a valid Go identifier off the front of v and
+// either returns a subslice of v if there's a valid identifier, or
+// returns a zero-length slice.
+func scanIdentifier(v []byte) []byte {
+	var n int // number of leading bytes of v belonging to an identifier
+	for {
+		r, width := utf8.DecodeRune(v[n:])
+		if !(isLetter(r) || n > 0 && isDigit(r)) {
+			break
+		}
+		n += width
+	}
+	return v[:n]
+}
+
+func isLetter(ch rune) bool {
+	return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch)
+}
+
+func isDigit(ch rune) bool {
+	return '0' <= ch && ch <= '9' || ch >= utf8.RuneSelf && unicode.IsDigit(ch)
+}
+
+func comment_htmlFunc(comment string) string {
+	var buf bytes.Buffer
+	// TODO(gri) Provide list of words (e.g. function parameters)
+	//           to be emphasized by ToHTML.
+	doc.ToHTML(&buf, comment, nil) // does html-escaping
+	return buf.String()
+}
+
+// sanitizeFunc sanitizes the argument src by replacing newlines with
+// blanks, removing extra blanks, and by removing trailing whitespace
+// and commas before closing parentheses.
+func sanitizeFunc(src string) string {
+	buf := make([]byte, len(src))
+	j := 0      // buf index
+	comma := -1 // comma index if >= 0
+	for i := 0; i < len(src); i++ {
+		ch := src[i]
+		switch ch {
+		case '\t', '\n', ' ':
+			// ignore whitespace at the beginning, after a blank, or after opening parentheses
+			if j == 0 {
+				continue
+			}
+			if p := buf[j-1]; p == ' ' || p == '(' || p == '{' || p == '[' {
+				continue
+			}
+			// replace all whitespace with blanks
+			ch = ' '
+		case ',':
+			comma = j
+		case ')', '}', ']':
+			// remove any trailing comma
+			if comma >= 0 {
+				j = comma
+			}
+			// remove any trailing whitespace
+			if j > 0 && buf[j-1] == ' ' {
+				j--
+			}
+		default:
+			comma = -1
+		}
+		buf[j] = ch
+		j++
+	}
+	// remove trailing blank, if any
+	if j > 0 && buf[j-1] == ' ' {
+		j--
+	}
+	return string(buf[:j])
+}
+
+type PageInfo struct {
+	Dirname  string // directory containing the package
+	Err      error  // error or nil
+	GoogleCN bool   // page is being served from golang.google.cn
+
+	Mode PageInfoMode // display metadata from query string
+
+	// package info
+	FSet       *token.FileSet         // nil if no package documentation
+	PDoc       *doc.Package           // nil if no package documentation
+	Examples   []*doc.Example         // nil if no example code
+	Notes      map[string][]*doc.Note // nil if no package Notes
+	PAst       map[string]*ast.File   // nil if no AST with package exports
+	IsMain     bool                   // true for package main
+	IsFiltered bool                   // true if results were filtered
+
+	// analysis info
+	TypeInfoIndex  map[string]int  // index of JSON datum for type T (if -analysis=type)
+	AnalysisData   htmltemplate.JS // array of TypeInfoJSON values
+	CallGraph      htmltemplate.JS // array of PCGNodeJSON values    (if -analysis=pointer)
+	CallGraphIndex map[string]int  // maps func name to index in CallGraph
+
+	// directory info
+	Dirs    *DirList  // nil if no directory information
+	DirTime time.Time // directory time stamp
+	DirFlat bool      // if set, show directory in a flat (non-indented) manner
+}
+
+func (info *PageInfo) IsEmpty() bool {
+	return info.Err != nil || info.PAst == nil && info.PDoc == nil && info.Dirs == nil
+}
+
+func pkgLinkFunc(path string) string {
+	// because of the irregular mapping under goroot
+	// we need to correct certain relative paths
+	path = strings.TrimPrefix(path, "/")
+	path = strings.TrimPrefix(path, "src/")
+	path = strings.TrimPrefix(path, "pkg/")
+	return "pkg/" + path
+}
+
+// srcToPkgLinkFunc builds an <a> tag linking to the package
+// documentation of relpath.
+func srcToPkgLinkFunc(relpath string) string {
+	relpath = pkgLinkFunc(relpath)
+	relpath = pathpkg.Dir(relpath)
+	if relpath == "pkg" {
+		return `<a href="/pkg">Index</a>`
+	}
+	return fmt.Sprintf(`<a href="/%s">%s</a>`, relpath, relpath[len("pkg/"):])
+}
+
+// srcBreadcrumbFun converts each segment of relpath to a HTML <a>.
+// Each segment links to its corresponding src directories.
+func srcBreadcrumbFunc(relpath string) string {
+	segments := strings.Split(relpath, "/")
+	var buf bytes.Buffer
+	var selectedSegment string
+	var selectedIndex int
+
+	if strings.HasSuffix(relpath, "/") {
+		// relpath is a directory ending with a "/".
+		// Selected segment is the segment before the last slash.
+		selectedIndex = len(segments) - 2
+		selectedSegment = segments[selectedIndex] + "/"
+	} else {
+		selectedIndex = len(segments) - 1
+		selectedSegment = segments[selectedIndex]
+	}
+
+	for i := range segments[:selectedIndex] {
+		buf.WriteString(fmt.Sprintf(`<a href="/%s">%s</a>/`,
+			strings.Join(segments[:i+1], "/"),
+			segments[i],
+		))
+	}
+
+	buf.WriteString(`<span class="text-muted">`)
+	buf.WriteString(selectedSegment)
+	buf.WriteString(`</span>`)
+	return buf.String()
+}
+
+func newPosLink_urlFunc(srcPosLinkFunc func(s string, line, low, high int) string) func(info *PageInfo, n interface{}) string {
+	// n must be an ast.Node or a *doc.Note
+	return func(info *PageInfo, n interface{}) string {
+		var pos, end token.Pos
+
+		switch n := n.(type) {
+		case ast.Node:
+			pos = n.Pos()
+			end = n.End()
+		case *doc.Note:
+			pos = n.Pos
+			end = n.End
+		default:
+			panic(fmt.Sprintf("wrong type for posLink_url template formatter: %T", n))
+		}
+
+		var relpath string
+		var line int
+		var low, high int // selection offset range
+
+		if pos.IsValid() {
+			p := info.FSet.Position(pos)
+			relpath = p.Filename
+			line = p.Line
+			low = p.Offset
+		}
+		if end.IsValid() {
+			high = info.FSet.Position(end).Offset
+		}
+
+		return srcPosLinkFunc(relpath, line, low, high)
+	}
+}
+
+func srcPosLinkFunc(s string, line, low, high int) string {
+	s = srcLinkFunc(s)
+	var buf bytes.Buffer
+	template.HTMLEscape(&buf, []byte(s))
+	// selection ranges are of form "s=low:high"
+	if low < high {
+		fmt.Fprintf(&buf, "?s=%d:%d", low, high) // no need for URL escaping
+		// if we have a selection, position the page
+		// such that the selection is a bit below the top
+		line -= 10
+		if line < 1 {
+			line = 1
+		}
+	}
+	// line id's in html-printed source are of the
+	// form "L%d" where %d stands for the line number
+	if line > 0 {
+		fmt.Fprintf(&buf, "#L%d", line) // no need for URL escaping
+	}
+	return buf.String()
+}
+
+func srcLinkFunc(s string) string {
+	s = pathpkg.Clean("/" + s)
+	if !strings.HasPrefix(s, "/src/") {
+		s = "/src" + s
+	}
+	return s
+}
+
+// queryLinkFunc returns a URL for a line in a source file with a highlighted
+// query term.
+// s is expected to be a path to a source file.
+// query is expected to be a string that has already been appropriately escaped
+// for use in a URL query.
+func queryLinkFunc(s, query string, line int) string {
+	url := pathpkg.Clean("/"+s) + "?h=" + query
+	if line > 0 {
+		url += "#L" + strconv.Itoa(line)
+	}
+	return url
+}
+
+func docLinkFunc(s string, ident string) string {
+	return pathpkg.Clean("/pkg/"+s) + "/#" + ident
+}
+
+func (p *Presentation) example_htmlFunc(info *PageInfo, funcName string) string {
+	var buf bytes.Buffer
+	for _, eg := range info.Examples {
+		name := stripExampleSuffix(eg.Name)
+
+		if name != funcName {
+			continue
+		}
+
+		// print code
+		cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
+		code := p.node_htmlFunc(info, cnode, true)
+		out := eg.Output
+		wholeFile := true
+
+		// Additional formatting if this is a function body.
+		if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
+			wholeFile = false
+			// remove surrounding braces
+			code = code[1 : n-1]
+			// unindent
+			code = replaceLeadingIndentation(code, strings.Repeat(" ", p.TabWidth), "")
+			// remove output comment
+			if loc := exampleOutputRx.FindStringIndex(code); loc != nil {
+				code = strings.TrimSpace(code[:loc[0]])
+			}
+		}
+
+		// Write out the playground code in standard Go style
+		// (use tabs, no comment highlight, etc).
+		play := ""
+		if eg.Play != nil && p.ShowPlayground {
+			var buf bytes.Buffer
+			eg.Play.Comments = filterOutBuildAnnotations(eg.Play.Comments)
+			if err := format.Node(&buf, info.FSet, eg.Play); err != nil {
+				log.Print(err)
+			} else {
+				play = buf.String()
+			}
+		}
+
+		// Drop output, as the output comment will appear in the code.
+		if wholeFile && play == "" {
+			out = ""
+		}
+
+		if p.ExampleHTML == nil {
+			out = ""
+			return ""
+		}
+
+		err := p.ExampleHTML.Execute(&buf, struct {
+			Name, Doc, Code, Play, Output string
+			GoogleCN                      bool
+		}{eg.Name, eg.Doc, code, play, out, info.GoogleCN})
+		if err != nil {
+			log.Print(err)
+		}
+	}
+	return buf.String()
+}
+
+func filterOutBuildAnnotations(cg []*ast.CommentGroup) []*ast.CommentGroup {
+	if len(cg) == 0 {
+		return cg
+	}
+
+	for i := range cg {
+		if !strings.HasPrefix(cg[i].Text(), "+build ") {
+			// Found the first non-build tag, return from here until the end
+			// of the slice.
+			return cg[i:]
+		}
+	}
+
+	// There weren't any non-build tags, return an empty slice.
+	return []*ast.CommentGroup{}
+}
+
+// example_nameFunc takes an example function name and returns its display
+// name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)".
+func (p *Presentation) example_nameFunc(s string) string {
+	name, suffix := splitExampleName(s)
+	// replace _ with . for method names
+	name = strings.Replace(name, "_", ".", 1)
+	// use "Package" if no name provided
+	if name == "" {
+		name = "Package"
+	}
+	return name + suffix
+}
+
+// example_suffixFunc takes an example function name and returns its suffix in
+// parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)".
+func (p *Presentation) example_suffixFunc(name string) string {
+	_, suffix := splitExampleName(name)
+	return suffix
+}
+
+// implements_html returns the "> Implements" toggle for a package-level named type.
+// Its contents are populated from JSON data by client-side JS at load time.
+func (p *Presentation) implements_htmlFunc(info *PageInfo, typeName string) string {
+	if p.ImplementsHTML == nil {
+		return ""
+	}
+	index, ok := info.TypeInfoIndex[typeName]
+	if !ok {
+		return ""
+	}
+	var buf bytes.Buffer
+	err := p.ImplementsHTML.Execute(&buf, struct{ Index int }{index})
+	if err != nil {
+		log.Print(err)
+	}
+	return buf.String()
+}
+
+// methodset_html returns the "> Method set" toggle for a package-level named type.
+// Its contents are populated from JSON data by client-side JS at load time.
+func (p *Presentation) methodset_htmlFunc(info *PageInfo, typeName string) string {
+	if p.MethodSetHTML == nil {
+		return ""
+	}
+	index, ok := info.TypeInfoIndex[typeName]
+	if !ok {
+		return ""
+	}
+	var buf bytes.Buffer
+	err := p.MethodSetHTML.Execute(&buf, struct{ Index int }{index})
+	if err != nil {
+		log.Print(err)
+	}
+	return buf.String()
+}
+
+// callgraph_html returns the "> Call graph" toggle for a package-level func.
+// Its contents are populated from JSON data by client-side JS at load time.
+func (p *Presentation) callgraph_htmlFunc(info *PageInfo, recv, name string) string {
+	if p.CallGraphHTML == nil {
+		return ""
+	}
+	if recv != "" {
+		// Format must match (*ssa.Function).RelString().
+		name = fmt.Sprintf("(%s).%s", recv, name)
+	}
+	index, ok := info.CallGraphIndex[name]
+	if !ok {
+		return ""
+	}
+	var buf bytes.Buffer
+	err := p.CallGraphHTML.Execute(&buf, struct{ Index int }{index})
+	if err != nil {
+		log.Print(err)
+	}
+	return buf.String()
+}
+
+func noteTitle(note string) string {
+	return strings.Title(strings.ToLower(note))
+}
+
+func startsWithUppercase(s string) bool {
+	r, _ := utf8.DecodeRuneInString(s)
+	return unicode.IsUpper(r)
+}
+
+var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*(unordered )?output:`)
+
+// stripExampleSuffix strips lowercase braz in Foo_braz or Foo_Bar_braz from name
+// while keeping uppercase Braz in Foo_Braz.
+func stripExampleSuffix(name string) string {
+	if i := strings.LastIndex(name, "_"); i != -1 {
+		if i < len(name)-1 && !startsWithUppercase(name[i+1:]) {
+			name = name[:i]
+		}
+	}
+	return name
+}
+
+func splitExampleName(s string) (name, suffix string) {
+	i := strings.LastIndex(s, "_")
+	if 0 <= i && i < len(s)-1 && !startsWithUppercase(s[i+1:]) {
+		name = s[:i]
+		suffix = " (" + strings.Title(s[i+1:]) + ")"
+		return
+	}
+	name = s
+	return
+}
+
+// replaceLeadingIndentation replaces oldIndent at the beginning of each line
+// with newIndent. This is used for formatting examples. Raw strings that
+// span multiple lines are handled specially: oldIndent is not removed (since
+// go/printer will not add any indentation there), but newIndent is added
+// (since we may still want leading indentation).
+func replaceLeadingIndentation(body, oldIndent, newIndent string) string {
+	// Handle indent at the beginning of the first line. After this, we handle
+	// indentation only after a newline.
+	var buf bytes.Buffer
+	if strings.HasPrefix(body, oldIndent) {
+		buf.WriteString(newIndent)
+		body = body[len(oldIndent):]
+	}
+
+	// Use a state machine to keep track of whether we're in a string or
+	// rune literal while we process the rest of the code.
+	const (
+		codeState = iota
+		runeState
+		interpretedStringState
+		rawStringState
+	)
+	searchChars := []string{
+		"'\"`\n", // codeState
+		`\'`,     // runeState
+		`\"`,     // interpretedStringState
+		"`\n",    // rawStringState
+		// newlineState does not need to search
+	}
+	state := codeState
+	for {
+		i := strings.IndexAny(body, searchChars[state])
+		if i < 0 {
+			buf.WriteString(body)
+			break
+		}
+		c := body[i]
+		buf.WriteString(body[:i+1])
+		body = body[i+1:]
+		switch state {
+		case codeState:
+			switch c {
+			case '\'':
+				state = runeState
+			case '"':
+				state = interpretedStringState
+			case '`':
+				state = rawStringState
+			case '\n':
+				if strings.HasPrefix(body, oldIndent) {
+					buf.WriteString(newIndent)
+					body = body[len(oldIndent):]
+				}
+			}
+
+		case runeState:
+			switch c {
+			case '\\':
+				r, size := utf8.DecodeRuneInString(body)
+				buf.WriteRune(r)
+				body = body[size:]
+			case '\'':
+				state = codeState
+			}
+
+		case interpretedStringState:
+			switch c {
+			case '\\':
+				r, size := utf8.DecodeRuneInString(body)
+				buf.WriteRune(r)
+				body = body[size:]
+			case '"':
+				state = codeState
+			}
+
+		case rawStringState:
+			switch c {
+			case '`':
+				state = codeState
+			case '\n':
+				buf.WriteString(newIndent)
+			}
+		}
+	}
+	return buf.String()
+}
+
+// writeNode writes the AST node x to w.
+//
+// The provided fset must be non-nil. The pageInfo is optional. If
+// present, the pageInfo is used to add comments to struct fields to
+// say which version of Go introduced them.
+func (p *Presentation) writeNode(w io.Writer, pageInfo *PageInfo, fset *token.FileSet, x interface{}) {
+	// convert trailing tabs into spaces using a tconv filter
+	// to ensure a good outcome in most browsers (there may still
+	// be tabs in comments and strings, but converting those into
+	// the right number of spaces is much harder)
+	//
+	// TODO(gri) rethink printer flags - perhaps tconv can be eliminated
+	//           with an another printer mode (which is more efficiently
+	//           implemented in the printer than here with another layer)
+
+	var pkgName, structName string
+	var apiInfo pkgAPIVersions
+	if gd, ok := x.(*ast.GenDecl); ok && pageInfo != nil && pageInfo.PDoc != nil &&
+		p.Corpus != nil &&
+		gd.Tok == token.TYPE && len(gd.Specs) != 0 {
+		pkgName = pageInfo.PDoc.ImportPath
+		if ts, ok := gd.Specs[0].(*ast.TypeSpec); ok {
+			if _, ok := ts.Type.(*ast.StructType); ok {
+				structName = ts.Name.Name
+			}
+		}
+		apiInfo = p.Corpus.pkgAPIInfo[pkgName]
+	}
+
+	var out = w
+	var buf bytes.Buffer
+	if structName != "" {
+		out = &buf
+	}
+
+	mode := printer.TabIndent | printer.UseSpaces
+	err := (&printer.Config{Mode: mode, Tabwidth: p.TabWidth}).Fprint(&tconv{p: p, output: out}, fset, x)
+	if err != nil {
+		log.Print(err)
+	}
+
+	// Add comments to struct fields saying which Go version introduced them.
+	if structName != "" {
+		fieldSince := apiInfo.fieldSince[structName]
+		typeSince := apiInfo.typeSince[structName]
+		// Add/rewrite comments on struct fields to note which Go version added them.
+		var buf2 bytes.Buffer
+		buf2.Grow(buf.Len() + len(" // Added in Go 1.n")*10)
+		bs := bufio.NewScanner(&buf)
+		for bs.Scan() {
+			line := bs.Bytes()
+			field := firstIdent(line)
+			var since string
+			if field != "" {
+				since = fieldSince[field]
+				if since != "" && since == typeSince {
+					// Don't highlight field versions if they were the
+					// same as the struct itself.
+					since = ""
+				}
+			}
+			if since == "" {
+				buf2.Write(line)
+			} else {
+				if bytes.Contains(line, slashSlash) {
+					line = bytes.TrimRight(line, " \t.")
+					buf2.Write(line)
+					buf2.WriteString("; added in Go ")
+				} else {
+					buf2.Write(line)
+					buf2.WriteString(" // Go ")
+				}
+				buf2.WriteString(since)
+			}
+			buf2.WriteByte('\n')
+		}
+		w.Write(buf2.Bytes())
+	}
+}
+
+var slashSlash = []byte("//")
+
+// WriteNode writes x to w.
+// TODO(bgarcia) Is this method needed? It's just a wrapper for p.writeNode.
+func (p *Presentation) WriteNode(w io.Writer, fset *token.FileSet, x interface{}) {
+	p.writeNode(w, nil, fset, x)
+}
+
+// firstIdent returns the first identifier in x.
+// This actually parses "identifiers" that begin with numbers too, but we
+// never feed it such input, so it's fine.
+func firstIdent(x []byte) string {
+	x = bytes.TrimSpace(x)
+	i := bytes.IndexFunc(x, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsNumber(r) })
+	if i == -1 {
+		return string(x)
+	}
+	return string(x[:i])
+}
diff --git a/internal/godoc/godoc_test.go b/internal/godoc/godoc_test.go
new file mode 100644
index 0000000..2719ccc
--- /dev/null
+++ b/internal/godoc/godoc_test.go
@@ -0,0 +1,393 @@
+// 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.
+
+package godoc
+
+import (
+	"bytes"
+	"fmt"
+	"go/parser"
+	"go/token"
+	"strings"
+	"testing"
+)
+
+func TestPkgLinkFunc(t *testing.T) {
+	for _, tc := range []struct {
+		path string
+		want string
+	}{
+		{"/src/fmt", "pkg/fmt"},
+		{"src/fmt", "pkg/fmt"},
+		{"/fmt", "pkg/fmt"},
+		{"fmt", "pkg/fmt"},
+	} {
+		if got := pkgLinkFunc(tc.path); got != tc.want {
+			t.Errorf("pkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want)
+		}
+	}
+}
+
+func TestSrcPosLinkFunc(t *testing.T) {
+	for _, tc := range []struct {
+		src  string
+		line int
+		low  int
+		high int
+		want string
+	}{
+		{"/src/fmt/print.go", 42, 30, 50, "/src/fmt/print.go?s=30:50#L32"},
+		{"/src/fmt/print.go", 2, 1, 5, "/src/fmt/print.go?s=1:5#L1"},
+		{"/src/fmt/print.go", 2, 0, 0, "/src/fmt/print.go#L2"},
+		{"/src/fmt/print.go", 0, 0, 0, "/src/fmt/print.go"},
+		{"/src/fmt/print.go", 0, 1, 5, "/src/fmt/print.go?s=1:5#L1"},
+		{"fmt/print.go", 0, 0, 0, "/src/fmt/print.go"},
+		{"fmt/print.go", 0, 1, 5, "/src/fmt/print.go?s=1:5#L1"},
+	} {
+		if got := srcPosLinkFunc(tc.src, tc.line, tc.low, tc.high); got != tc.want {
+			t.Errorf("srcLinkFunc(%v, %v, %v, %v) = %v; want %v", tc.src, tc.line, tc.low, tc.high, got, tc.want)
+		}
+	}
+}
+
+func TestSrcLinkFunc(t *testing.T) {
+	for _, tc := range []struct {
+		src  string
+		want string
+	}{
+		{"/src/fmt/print.go", "/src/fmt/print.go"},
+		{"src/fmt/print.go", "/src/fmt/print.go"},
+		{"/fmt/print.go", "/src/fmt/print.go"},
+		{"fmt/print.go", "/src/fmt/print.go"},
+	} {
+		if got := srcLinkFunc(tc.src); got != tc.want {
+			t.Errorf("srcLinkFunc(%v) = %v; want %v", tc.src, got, tc.want)
+		}
+	}
+}
+
+func TestQueryLinkFunc(t *testing.T) {
+	for _, tc := range []struct {
+		src   string
+		query string
+		line  int
+		want  string
+	}{
+		{"/src/fmt/print.go", "Sprintf", 33, "/src/fmt/print.go?h=Sprintf#L33"},
+		{"/src/fmt/print.go", "Sprintf", 0, "/src/fmt/print.go?h=Sprintf"},
+		{"src/fmt/print.go", "EOF", 33, "/src/fmt/print.go?h=EOF#L33"},
+		{"src/fmt/print.go", "a%3f+%26b", 1, "/src/fmt/print.go?h=a%3f+%26b#L1"},
+	} {
+		if got := queryLinkFunc(tc.src, tc.query, tc.line); got != tc.want {
+			t.Errorf("queryLinkFunc(%v, %v, %v) = %v; want %v", tc.src, tc.query, tc.line, got, tc.want)
+		}
+	}
+}
+
+func TestDocLinkFunc(t *testing.T) {
+	for _, tc := range []struct {
+		src   string
+		ident string
+		want  string
+	}{
+		{"fmt", "Sprintf", "/pkg/fmt/#Sprintf"},
+		{"fmt", "EOF", "/pkg/fmt/#EOF"},
+	} {
+		if got := docLinkFunc(tc.src, tc.ident); got != tc.want {
+			t.Errorf("docLinkFunc(%v, %v) = %v; want %v", tc.src, tc.ident, got, tc.want)
+		}
+	}
+}
+
+func TestSanitizeFunc(t *testing.T) {
+	for _, tc := range []struct {
+		src  string
+		want string
+	}{
+		{},
+		{"foo", "foo"},
+		{"func   f()", "func f()"},
+		{"func f(a int,)", "func f(a int)"},
+		{"func f(a int,\n)", "func f(a int)"},
+		{"func f(\n\ta int,\n\tb int,\n\tc int,\n)", "func f(a int, b int, c int)"},
+		{"  (   a,   b,  c  )  ", "(a, b, c)"},
+		{"(  a,  b, c    int, foo   bar  ,  )", "(a, b, c int, foo bar)"},
+		{"{   a,   b}", "{a, b}"},
+		{"[   a,   b]", "[a, b]"},
+	} {
+		if got := sanitizeFunc(tc.src); got != tc.want {
+			t.Errorf("sanitizeFunc(%v) = %v; want %v", tc.src, got, tc.want)
+		}
+	}
+}
+
+// Test that we add <span id="StructName.FieldName"> elements
+// to the HTML of struct fields.
+func TestStructFieldsIDAttributes(t *testing.T) {
+	got := linkifySource(t, []byte(`
+package foo
+
+type T struct {
+	NoDoc string
+
+	// Doc has a comment.
+	Doc string
+
+	// Opt, if non-nil, is an option.
+	Opt *int
+
+	// Опция - другое поле.
+	Опция bool
+}
+`))
+	want := `type T struct {
+<span id="T.NoDoc"></span>NoDoc <a href="/pkg/builtin/#string">string</a>
+
+<span id="T.Doc"></span><span class="comment">// Doc has a comment.</span>
+Doc <a href="/pkg/builtin/#string">string</a>
+
+<span id="T.Opt"></span><span class="comment">// Opt, if non-nil, is an option.</span>
+Opt *<a href="/pkg/builtin/#int">int</a>
+
+<span id="T.Опция"></span><span class="comment">// Опция - другое поле.</span>
+Опция <a href="/pkg/builtin/#bool">bool</a>
+}`
+	if got != want {
+		t.Errorf("got: %s\n\nwant: %s\n", got, want)
+	}
+}
+
+// Test that we add <span id="ConstName"> elements to the HTML
+// of definitions in const and var specs.
+func TestValueSpecIDAttributes(t *testing.T) {
+	got := linkifySource(t, []byte(`
+package foo
+
+const (
+	NoDoc string = "NoDoc"
+
+	// Doc has a comment
+	Doc = "Doc"
+
+	NoVal
+)`))
+	want := `const (
+<span id="NoDoc">NoDoc</span> <a href="/pkg/builtin/#string">string</a> = &#34;NoDoc&#34;
+
+<span class="comment">// Doc has a comment</span>
+<span id="Doc">Doc</span> = &#34;Doc&#34;
+
+<span id="NoVal">NoVal</span>
+)`
+	if got != want {
+		t.Errorf("got: %s\n\nwant: %s\n", got, want)
+	}
+}
+
+func TestCompositeLitLinkFields(t *testing.T) {
+	got := linkifySource(t, []byte(`
+package foo
+
+type T struct {
+	X int
+}
+
+var S T = T{X: 12}`))
+	want := `type T struct {
+<span id="T.X"></span>X <a href="/pkg/builtin/#int">int</a>
+}
+var <span id="S">S</span> <a href="#T">T</a> = <a href="#T">T</a>{<a href="#T.X">X</a>: 12}`
+	if got != want {
+		t.Errorf("got: %s\n\nwant: %s\n", got, want)
+	}
+}
+
+func TestFuncDeclNotLink(t *testing.T) {
+	// Function.
+	got := linkifySource(t, []byte(`
+package http
+
+func Get(url string) (resp *Response, err error)`))
+	want := `func Get(url <a href="/pkg/builtin/#string">string</a>) (resp *<a href="#Response">Response</a>, err <a href="/pkg/builtin/#error">error</a>)`
+	if got != want {
+		t.Errorf("got: %s\n\nwant: %s\n", got, want)
+	}
+
+	// Method.
+	got = linkifySource(t, []byte(`
+package http
+
+func (h Header) Get(key string) string`))
+	want = `func (h <a href="#Header">Header</a>) Get(key <a href="/pkg/builtin/#string">string</a>) <a href="/pkg/builtin/#string">string</a>`
+	if got != want {
+		t.Errorf("got: %s\n\nwant: %s\n", got, want)
+	}
+}
+
+func linkifySource(t *testing.T, src []byte) string {
+	p := &Presentation{
+		DeclLinks: true,
+	}
+	fset := token.NewFileSet()
+	af, err := parser.ParseFile(fset, "foo.go", src, parser.ParseComments)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var buf bytes.Buffer
+	pi := &PageInfo{
+		FSet: fset,
+	}
+	sep := ""
+	for _, decl := range af.Decls {
+		buf.WriteString(sep)
+		sep = "\n"
+		buf.WriteString(p.node_htmlFunc(pi, decl, true))
+	}
+	return buf.String()
+}
+
+func TestScanIdentifier(t *testing.T) {
+	tests := []struct {
+		in, want string
+	}{
+		{"foo bar", "foo"},
+		{"foo/bar", "foo"},
+		{" foo", ""},
+		{"фоо", "фоо"},
+		{"f123", "f123"},
+		{"123f", ""},
+	}
+	for _, tt := range tests {
+		got := scanIdentifier([]byte(tt.in))
+		if string(got) != tt.want {
+			t.Errorf("scanIdentifier(%q) = %q; want %q", tt.in, got, tt.want)
+		}
+	}
+}
+
+func TestReplaceLeadingIndentation(t *testing.T) {
+	oldIndent := strings.Repeat(" ", 2)
+	newIndent := strings.Repeat(" ", 4)
+	tests := []struct {
+		src, want string
+	}{
+		{"  foo\n    bar\n  baz", "    foo\n      bar\n    baz"},
+		{"  '`'\n  '`'\n", "    '`'\n    '`'\n"},
+		{"  '\\''\n  '`'\n", "    '\\''\n    '`'\n"},
+		{"  \"`\"\n  \"`\"\n", "    \"`\"\n    \"`\"\n"},
+		{"  `foo\n  bar`", "    `foo\n      bar`"},
+		{"  `foo\\`\n  bar", "    `foo\\`\n    bar"},
+		{"  '\\`'`foo\n  bar", "    '\\`'`foo\n      bar"},
+		{
+			"  if true {\n    foo := `One\n    \tTwo\nThree`\n  }\n",
+			"    if true {\n      foo := `One\n        \tTwo\n    Three`\n    }\n",
+		},
+	}
+	for _, tc := range tests {
+		if got := replaceLeadingIndentation(tc.src, oldIndent, newIndent); got != tc.want {
+			t.Errorf("replaceLeadingIndentation:\n%v\n---\nhave:\n%v\n---\nwant:\n%v\n",
+				tc.src, got, tc.want)
+		}
+	}
+}
+
+func TestSrcBreadcrumbFunc(t *testing.T) {
+	for _, tc := range []struct {
+		path string
+		want string
+	}{
+		{"src/", `<span class="text-muted">src/</span>`},
+		{"src/fmt/", `<a href="/src">src</a>/<span class="text-muted">fmt/</span>`},
+		{"src/fmt/print.go", `<a href="/src">src</a>/<a href="/src/fmt">fmt</a>/<span class="text-muted">print.go</span>`},
+	} {
+		if got := srcBreadcrumbFunc(tc.path); got != tc.want {
+			t.Errorf("srcBreadcrumbFunc(%v) = %v; want %v", tc.path, got, tc.want)
+		}
+	}
+}
+
+func TestSrcToPkgLinkFunc(t *testing.T) {
+	for _, tc := range []struct {
+		path string
+		want string
+	}{
+		{"src/", `<a href="/pkg">Index</a>`},
+		{"src/fmt/", `<a href="/pkg/fmt">fmt</a>`},
+		{"pkg/", `<a href="/pkg">Index</a>`},
+		{"pkg/LICENSE", `<a href="/pkg">Index</a>`},
+	} {
+		if got := srcToPkgLinkFunc(tc.path); got != tc.want {
+			t.Errorf("srcToPkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want)
+		}
+	}
+}
+
+func TestFilterOutBuildAnnotations(t *testing.T) {
+	// TODO: simplify this by using a multiline string once we stop
+	// using go vet from 1.10 on the build dashboard.
+	// https://golang.org/issue/26627
+	src := []byte("// +build !foo\n" +
+		"// +build !anothertag\n" +
+		"\n" +
+		"// non-tag comment\n" +
+		"\n" +
+		"package foo\n" +
+		"\n" +
+		"func bar() int {\n" +
+		"	return 42\n" +
+		"}\n")
+
+	fset := token.NewFileSet()
+	af, err := parser.ParseFile(fset, "foo.go", src, parser.ParseComments)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var found bool
+	for _, cg := range af.Comments {
+		if strings.HasPrefix(cg.Text(), "+build ") {
+			found = true
+			break
+		}
+	}
+	if !found {
+		t.Errorf("TestFilterOutBuildAnnotations is broken: missing build tag in test input")
+	}
+
+	found = false
+	for _, cg := range filterOutBuildAnnotations(af.Comments) {
+		if strings.HasPrefix(cg.Text(), "+build ") {
+			t.Errorf("filterOutBuildAnnotations failed to filter build tag")
+		}
+
+		if strings.Contains(cg.Text(), "non-tag comment") {
+			found = true
+		}
+	}
+	if !found {
+		t.Errorf("filterOutBuildAnnotations should not remove non-build tag comment")
+	}
+}
+
+// Verify that scanIdentifier isn't quadratic.
+// This doesn't actually measure and fail on its own, but it was previously
+// very obvious when running by hand.
+//
+// TODO: if there's a reliable and non-flaky way to test this, do so.
+// Maybe count user CPU time instead of wall time? But that's not easy
+// to do portably in Go.
+func TestStructField(t *testing.T) {
+	for _, n := range []int{10, 100, 1000, 10000} {
+		n := n
+		t.Run(fmt.Sprint(n), func(t *testing.T) {
+			var buf bytes.Buffer
+			fmt.Fprintf(&buf, "package foo\n\ntype T struct {\n")
+			for i := 0; i < n; i++ {
+				fmt.Fprintf(&buf, "\t// Field%d is foo.\n\tField%d int\n\n", i, i)
+			}
+			fmt.Fprintf(&buf, "}\n")
+			linkifySource(t, buf.Bytes())
+		})
+	}
+}
diff --git a/internal/godoc/golangorgenv/golangorgenv.go b/internal/godoc/golangorgenv/golangorgenv.go
new file mode 100644
index 0000000..0b96eec
--- /dev/null
+++ b/internal/godoc/golangorgenv/golangorgenv.go
@@ -0,0 +1,42 @@
+// Copyright 2018 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.
+
+// Package golangorgenv provides environment information for programs running at
+// golang.org and its subdomains.
+package golangorgenv
+
+import (
+	"log"
+	"os"
+	"strconv"
+)
+
+var (
+	checkCountry = boolEnv("GOLANGORG_CHECK_COUNTRY")
+	enforceHosts = boolEnv("GOLANGORG_ENFORCE_HOSTS")
+)
+
+// CheckCountry reports whether country restrictions should be enforced.
+func CheckCountry() bool {
+	return checkCountry
+}
+
+// EnforceHosts reports whether host filtering should be enforced.
+func EnforceHosts() bool {
+	return enforceHosts
+}
+
+func boolEnv(key string) bool {
+	v := os.Getenv(key)
+	if v == "" {
+		// TODO(dmitshur): In the future, consider detecting if running in App Engine,
+		// and if so, making the environment variables mandatory rather than optional.
+		return false
+	}
+	b, err := strconv.ParseBool(v)
+	if err != nil {
+		log.Fatalf("environment variable %s (%q) must be a boolean", key, v)
+	}
+	return b
+}
diff --git a/internal/godoc/index.go b/internal/godoc/index.go
new file mode 100644
index 0000000..a9c0859
--- /dev/null
+++ b/internal/godoc/index.go
@@ -0,0 +1,1580 @@
+// Copyright 2009 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.
+
+// This file contains the infrastructure to create an
+// identifier and full-text index for a set of Go files.
+//
+// Algorithm for identifier index:
+// - traverse all .go files of the file tree specified by root
+// - for each identifier (word) encountered, collect all occurrences (spots)
+//   into a list; this produces a list of spots for each word
+// - reduce the lists: from a list of spots to a list of FileRuns,
+//   and from a list of FileRuns into a list of PakRuns
+// - make a HitList from the PakRuns
+//
+// Details:
+// - keep two lists per word: one containing package-level declarations
+//   that have snippets, and one containing all other spots
+// - keep the snippets in a separate table indexed by snippet index
+//   and store the snippet index in place of the line number in a SpotInfo
+//   (the line number for spots with snippets is stored in the snippet)
+// - at the end, create lists of alternative spellings for a given
+//   word
+//
+// Algorithm for full text index:
+// - concatenate all source code in a byte buffer (in memory)
+// - add the files to a file set in lockstep as they are added to the byte
+//   buffer such that a byte buffer offset corresponds to the Pos value for
+//   that file location
+// - create a suffix array from the concatenated sources
+//
+// String lookup in full text index:
+// - use the suffix array to lookup a string's offsets - the offsets
+//   correspond to the Pos values relative to the file set
+// - translate the Pos values back into file and line information and
+//   sort the result
+
+package godoc
+
+import (
+	"bufio"
+	"bytes"
+	"encoding/gob"
+	"errors"
+	"fmt"
+	"go/ast"
+	"go/doc"
+	"go/parser"
+	"go/token"
+	"index/suffixarray"
+	"io"
+	"log"
+	"os"
+	pathpkg "path"
+	"path/filepath"
+	"regexp"
+	"runtime"
+	"sort"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+	"unicode"
+
+	"golang.org/x/website/internal/godoc/util"
+	"golang.org/x/website/internal/godoc/vfs"
+)
+
+// ----------------------------------------------------------------------------
+// InterfaceSlice is a helper type for sorting interface
+// slices according to some slice-specific sort criteria.
+
+type comparer func(x, y interface{}) bool
+
+type interfaceSlice struct {
+	slice []interface{}
+	less  comparer
+}
+
+// ----------------------------------------------------------------------------
+// RunList
+
+// A RunList is a list of entries that can be sorted according to some
+// criteria. A RunList may be compressed by grouping "runs" of entries
+// which are equal (according to the sort criteria) into a new RunList of
+// runs. For instance, a RunList containing pairs (x, y) may be compressed
+// into a RunList containing pair runs (x, {y}) where each run consists of
+// a list of y's with the same x.
+type RunList []interface{}
+
+func (h RunList) sort(less comparer) {
+	sort.Sort(&interfaceSlice{h, less})
+}
+
+func (p *interfaceSlice) Len() int           { return len(p.slice) }
+func (p *interfaceSlice) Less(i, j int) bool { return p.less(p.slice[i], p.slice[j]) }
+func (p *interfaceSlice) Swap(i, j int)      { p.slice[i], p.slice[j] = p.slice[j], p.slice[i] }
+
+// Compress entries which are the same according to a sort criteria
+// (specified by less) into "runs".
+func (h RunList) reduce(less comparer, newRun func(h RunList) interface{}) RunList {
+	if len(h) == 0 {
+		return nil
+	}
+	// len(h) > 0
+
+	// create runs of entries with equal values
+	h.sort(less)
+
+	// for each run, make a new run object and collect them in a new RunList
+	var hh RunList
+	i, x := 0, h[0]
+	for j, y := range h {
+		if less(x, y) {
+			hh = append(hh, newRun(h[i:j]))
+			i, x = j, h[j] // start a new run
+		}
+	}
+	// add final run, if any
+	if i < len(h) {
+		hh = append(hh, newRun(h[i:]))
+	}
+
+	return hh
+}
+
+// ----------------------------------------------------------------------------
+// KindRun
+
+// Debugging support. Disable to see multiple entries per line.
+const removeDuplicates = true
+
+// A KindRun is a run of SpotInfos of the same kind in a given file.
+// The kind (3 bits) is stored in each SpotInfo element; to find the
+// kind of a KindRun, look at any of its elements.
+type KindRun []SpotInfo
+
+// KindRuns are sorted by line number or index. Since the isIndex bit
+// is always the same for all infos in one list we can compare lori's.
+func (k KindRun) Len() int           { return len(k) }
+func (k KindRun) Less(i, j int) bool { return k[i].Lori() < k[j].Lori() }
+func (k KindRun) Swap(i, j int)      { k[i], k[j] = k[j], k[i] }
+
+// FileRun contents are sorted by Kind for the reduction into KindRuns.
+func lessKind(x, y interface{}) bool { return x.(SpotInfo).Kind() < y.(SpotInfo).Kind() }
+
+// newKindRun allocates a new KindRun from the SpotInfo run h.
+func newKindRun(h RunList) interface{} {
+	run := make(KindRun, len(h))
+	for i, x := range h {
+		run[i] = x.(SpotInfo)
+	}
+
+	// Spots were sorted by file and kind to create this run.
+	// Within this run, sort them by line number or index.
+	sort.Sort(run)
+
+	if removeDuplicates {
+		// Since both the lori and kind field must be
+		// same for duplicates, and since the isIndex
+		// bit is always the same for all infos in one
+		// list we can simply compare the entire info.
+		k := 0
+		prev := SpotInfo(1<<32 - 1) // an unlikely value
+		for _, x := range run {
+			if x != prev {
+				run[k] = x
+				k++
+				prev = x
+			}
+		}
+		run = run[0:k]
+	}
+
+	return run
+}
+
+// ----------------------------------------------------------------------------
+// FileRun
+
+// A Pak describes a Go package.
+type Pak struct {
+	Path string // path of directory containing the package
+	Name string // package name as declared by package clause
+}
+
+// Paks are sorted by name (primary key) and by import path (secondary key).
+func (p *Pak) less(q *Pak) bool {
+	return p.Name < q.Name || p.Name == q.Name && p.Path < q.Path
+}
+
+// A File describes a Go file.
+type File struct {
+	Name string // directory-local file name
+	Pak  *Pak   // the package to which the file belongs
+}
+
+// Path returns the file path of f.
+func (f *File) Path() string {
+	return pathpkg.Join(f.Pak.Path, f.Name)
+}
+
+// A Spot describes a single occurrence of a word.
+type Spot struct {
+	File *File
+	Info SpotInfo
+}
+
+// A FileRun is a list of KindRuns belonging to the same file.
+type FileRun struct {
+	File   *File
+	Groups []KindRun
+}
+
+// Spots are sorted by file path for the reduction into FileRuns.
+func lessSpot(x, y interface{}) bool {
+	fx := x.(Spot).File
+	fy := y.(Spot).File
+	// same as "return fx.Path() < fy.Path()" but w/o computing the file path first
+	px := fx.Pak.Path
+	py := fy.Pak.Path
+	return px < py || px == py && fx.Name < fy.Name
+}
+
+// newFileRun allocates a new FileRun from the Spot run h.
+func newFileRun(h RunList) interface{} {
+	file := h[0].(Spot).File
+
+	// reduce the list of Spots into a list of KindRuns
+	h1 := make(RunList, len(h))
+	for i, x := range h {
+		h1[i] = x.(Spot).Info
+	}
+	h2 := h1.reduce(lessKind, newKindRun)
+
+	// create the FileRun
+	groups := make([]KindRun, len(h2))
+	for i, x := range h2 {
+		groups[i] = x.(KindRun)
+	}
+	return &FileRun{file, groups}
+}
+
+// ----------------------------------------------------------------------------
+// PakRun
+
+// A PakRun describes a run of *FileRuns of a package.
+type PakRun struct {
+	Pak   *Pak
+	Files []*FileRun
+}
+
+// Sorting support for files within a PakRun.
+func (p *PakRun) Len() int           { return len(p.Files) }
+func (p *PakRun) Less(i, j int) bool { return p.Files[i].File.Name < p.Files[j].File.Name }
+func (p *PakRun) Swap(i, j int)      { p.Files[i], p.Files[j] = p.Files[j], p.Files[i] }
+
+// FileRuns are sorted by package for the reduction into PakRuns.
+func lessFileRun(x, y interface{}) bool {
+	return x.(*FileRun).File.Pak.less(y.(*FileRun).File.Pak)
+}
+
+// newPakRun allocates a new PakRun from the *FileRun run h.
+func newPakRun(h RunList) interface{} {
+	pak := h[0].(*FileRun).File.Pak
+	files := make([]*FileRun, len(h))
+	for i, x := range h {
+		files[i] = x.(*FileRun)
+	}
+	run := &PakRun{pak, files}
+	sort.Sort(run) // files were sorted by package; sort them by file now
+	return run
+}
+
+// ----------------------------------------------------------------------------
+// HitList
+
+// A HitList describes a list of PakRuns.
+type HitList []*PakRun
+
+// PakRuns are sorted by package.
+func lessPakRun(x, y interface{}) bool { return x.(*PakRun).Pak.less(y.(*PakRun).Pak) }
+
+func reduce(h0 RunList) HitList {
+	// reduce a list of Spots into a list of FileRuns
+	h1 := h0.reduce(lessSpot, newFileRun)
+	// reduce a list of FileRuns into a list of PakRuns
+	h2 := h1.reduce(lessFileRun, newPakRun)
+	// sort the list of PakRuns by package
+	h2.sort(lessPakRun)
+	// create a HitList
+	h := make(HitList, len(h2))
+	for i, p := range h2 {
+		h[i] = p.(*PakRun)
+	}
+	return h
+}
+
+// filter returns a new HitList created by filtering
+// all PakRuns from h that have a matching pakname.
+func (h HitList) filter(pakname string) HitList {
+	var hh HitList
+	for _, p := range h {
+		if p.Pak.Name == pakname {
+			hh = append(hh, p)
+		}
+	}
+	return hh
+}
+
+// ----------------------------------------------------------------------------
+// AltWords
+
+type wordPair struct {
+	canon string // canonical word spelling (all lowercase)
+	alt   string // alternative spelling
+}
+
+// An AltWords describes a list of alternative spellings for a
+// canonical (all lowercase) spelling of a word.
+type AltWords struct {
+	Canon string   // canonical word spelling (all lowercase)
+	Alts  []string // alternative spelling for the same word
+}
+
+// wordPairs are sorted by their canonical spelling.
+func lessWordPair(x, y interface{}) bool { return x.(*wordPair).canon < y.(*wordPair).canon }
+
+// newAltWords allocates a new AltWords from the *wordPair run h.
+func newAltWords(h RunList) interface{} {
+	canon := h[0].(*wordPair).canon
+	alts := make([]string, len(h))
+	for i, x := range h {
+		alts[i] = x.(*wordPair).alt
+	}
+	return &AltWords{canon, alts}
+}
+
+func (a *AltWords) filter(s string) *AltWords {
+	var alts []string
+	for _, w := range a.Alts {
+		if w != s {
+			alts = append(alts, w)
+		}
+	}
+	if len(alts) > 0 {
+		return &AltWords{a.Canon, alts}
+	}
+	return nil
+}
+
+// Ident stores information about external identifiers in order to create
+// links to package documentation.
+type Ident struct {
+	Path    string // e.g. "net/http"
+	Package string // e.g. "http"
+	Name    string // e.g. "NewRequest"
+	Doc     string // e.g. "NewRequest returns a new Request..."
+}
+
+// byImportCount sorts the given slice of Idents by the import
+// counts of the packages to which they belong.
+type byImportCount struct {
+	Idents      []Ident
+	ImportCount map[string]int
+}
+
+func (ic byImportCount) Len() int {
+	return len(ic.Idents)
+}
+
+func (ic byImportCount) Less(i, j int) bool {
+	ri := ic.ImportCount[ic.Idents[i].Path]
+	rj := ic.ImportCount[ic.Idents[j].Path]
+	if ri == rj {
+		return ic.Idents[i].Path < ic.Idents[j].Path
+	}
+	return ri > rj
+}
+
+func (ic byImportCount) Swap(i, j int) {
+	ic.Idents[i], ic.Idents[j] = ic.Idents[j], ic.Idents[i]
+}
+
+func (ic byImportCount) String() string {
+	buf := bytes.NewBuffer([]byte("["))
+	for _, v := range ic.Idents {
+		buf.WriteString(fmt.Sprintf("\n\t%s, %s (%d)", v.Path, v.Name, ic.ImportCount[v.Path]))
+	}
+	buf.WriteString("\n]")
+	return buf.String()
+}
+
+// filter creates a new Ident list where the results match the given
+// package name.
+func (ic byImportCount) filter(pakname string) []Ident {
+	if ic.Idents == nil {
+		return nil
+	}
+	var res []Ident
+	for _, i := range ic.Idents {
+		if i.Package == pakname {
+			res = append(res, i)
+		}
+	}
+	return res
+}
+
+// top returns the top n identifiers.
+func (ic byImportCount) top(n int) []Ident {
+	if len(ic.Idents) > n {
+		return ic.Idents[:n]
+	}
+	return ic.Idents
+}
+
+// ----------------------------------------------------------------------------
+// Indexer
+
+type IndexResult struct {
+	Decls  RunList // package-level declarations (with snippets)
+	Others RunList // all other occurrences
+}
+
+// Statistics provides statistics information for an index.
+type Statistics struct {
+	Bytes int // total size of indexed source files
+	Files int // number of indexed source files
+	Lines int // number of lines (all files)
+	Words int // number of different identifiers
+	Spots int // number of identifier occurrences
+}
+
+// An Indexer maintains the data structures and provides the machinery
+// for indexing .go files under a file tree. It implements the path.Visitor
+// interface for walking file trees, and the ast.Visitor interface for
+// walking Go ASTs.
+type Indexer struct {
+	c          *Corpus
+	fset       *token.FileSet // file set for all indexed files
+	fsOpenGate chan bool      // send pre fs.Open; receive on close
+
+	mu            sync.Mutex              // guards all the following
+	sources       bytes.Buffer            // concatenated sources
+	strings       map[string]string       // interned string
+	packages      map[Pak]*Pak            // interned *Paks
+	words         map[string]*IndexResult // RunLists of Spots
+	snippets      []*Snippet              // indices are stored in SpotInfos
+	current       *token.File             // last file added to file set
+	file          *File                   // AST for current file
+	decl          ast.Decl                // AST for current decl
+	stats         Statistics
+	throttle      *util.Throttle
+	importCount   map[string]int                 // package path ("net/http") => count
+	packagePath   map[string]map[string]bool     // "template" => "text/template" => true
+	exports       map[string]map[string]SpotKind // "net/http" => "ListenAndServe" => FuncDecl
+	curPkgExports map[string]SpotKind
+	idents        map[SpotKind]map[string][]Ident // kind => name => list of Idents
+}
+
+func (x *Indexer) intern(s string) string {
+	if s, ok := x.strings[s]; ok {
+		return s
+	}
+	x.strings[s] = s
+	return s
+}
+
+func (x *Indexer) lookupPackage(path, name string) *Pak {
+	// In the source directory tree, more than one package may
+	// live in the same directory. For the packages map, construct
+	// a key that includes both the directory path and the package
+	// name.
+	key := Pak{Path: x.intern(path), Name: x.intern(name)}
+	pak := x.packages[key]
+	if pak == nil {
+		pak = &key
+		x.packages[key] = pak
+	}
+	return pak
+}
+
+func (x *Indexer) addSnippet(s *Snippet) int {
+	index := len(x.snippets)
+	x.snippets = append(x.snippets, s)
+	return index
+}
+
+func (x *Indexer) visitIdent(kind SpotKind, id *ast.Ident) {
+	if id == nil {
+		return
+	}
+	name := x.intern(id.Name)
+
+	switch kind {
+	case TypeDecl, FuncDecl, ConstDecl, VarDecl:
+		x.curPkgExports[name] = kind
+	}
+
+	lists, found := x.words[name]
+	if !found {
+		lists = new(IndexResult)
+		x.words[name] = lists
+	}
+
+	if kind == Use || x.decl == nil {
+		if x.c.IndexGoCode {
+			// not a declaration or no snippet required
+			info := makeSpotInfo(kind, x.current.Line(id.Pos()), false)
+			lists.Others = append(lists.Others, Spot{x.file, info})
+		}
+	} else {
+		// a declaration with snippet
+		index := x.addSnippet(NewSnippet(x.fset, x.decl, id))
+		info := makeSpotInfo(kind, index, true)
+		lists.Decls = append(lists.Decls, Spot{x.file, info})
+	}
+
+	x.stats.Spots++
+}
+
+func (x *Indexer) visitFieldList(kind SpotKind, flist *ast.FieldList) {
+	for _, f := range flist.List {
+		x.decl = nil // no snippets for fields
+		for _, name := range f.Names {
+			x.visitIdent(kind, name)
+		}
+		ast.Walk(x, f.Type)
+		// ignore tag - not indexed at the moment
+	}
+}
+
+func (x *Indexer) visitSpec(kind SpotKind, spec ast.Spec) {
+	switch n := spec.(type) {
+	case *ast.ImportSpec:
+		x.visitIdent(ImportDecl, n.Name)
+		if n.Path != nil {
+			if imp, err := strconv.Unquote(n.Path.Value); err == nil {
+				x.importCount[x.intern(imp)]++
+			}
+		}
+
+	case *ast.ValueSpec:
+		for _, n := range n.Names {
+			x.visitIdent(kind, n)
+		}
+		ast.Walk(x, n.Type)
+		for _, v := range n.Values {
+			ast.Walk(x, v)
+		}
+
+	case *ast.TypeSpec:
+		x.visitIdent(TypeDecl, n.Name)
+		ast.Walk(x, n.Type)
+	}
+}
+
+func (x *Indexer) visitGenDecl(decl *ast.GenDecl) {
+	kind := VarDecl
+	if decl.Tok == token.CONST {
+		kind = ConstDecl
+	}
+	x.decl = decl
+	for _, s := range decl.Specs {
+		x.visitSpec(kind, s)
+	}
+}
+
+func (x *Indexer) Visit(node ast.Node) ast.Visitor {
+	switch n := node.(type) {
+	case nil:
+		// nothing to do
+
+	case *ast.Ident:
+		x.visitIdent(Use, n)
+
+	case *ast.FieldList:
+		x.visitFieldList(VarDecl, n)
+
+	case *ast.InterfaceType:
+		x.visitFieldList(MethodDecl, n.Methods)
+
+	case *ast.DeclStmt:
+		// local declarations should only be *ast.GenDecls;
+		// ignore incorrect ASTs
+		if decl, ok := n.Decl.(*ast.GenDecl); ok {
+			x.decl = nil // no snippets for local declarations
+			x.visitGenDecl(decl)
+		}
+
+	case *ast.GenDecl:
+		x.decl = n
+		x.visitGenDecl(n)
+
+	case *ast.FuncDecl:
+		kind := FuncDecl
+		if n.Recv != nil {
+			kind = MethodDecl
+			ast.Walk(x, n.Recv)
+		}
+		x.decl = n
+		x.visitIdent(kind, n.Name)
+		ast.Walk(x, n.Type)
+		if n.Body != nil {
+			ast.Walk(x, n.Body)
+		}
+
+	case *ast.File:
+		x.decl = nil
+		x.visitIdent(PackageClause, n.Name)
+		for _, d := range n.Decls {
+			ast.Walk(x, d)
+		}
+
+	default:
+		return x
+	}
+
+	return nil
+}
+
+// addFile adds a file to the index if possible and returns the file set file
+// and the file's AST if it was successfully parsed as a Go file. If addFile
+// failed (that is, if the file was not added), it returns file == nil.
+func (x *Indexer) addFile(f vfs.ReadSeekCloser, filename string, goFile bool) (file *token.File, ast *ast.File) {
+	defer f.Close()
+
+	// The file set's base offset and x.sources size must be in lock-step;
+	// this permits the direct mapping of suffix array lookup results to
+	// to corresponding Pos values.
+	//
+	// When a file is added to the file set, its offset base increases by
+	// the size of the file + 1; and the initial base offset is 1. Add an
+	// extra byte to the sources here.
+	x.sources.WriteByte(0)
+
+	// If the sources length doesn't match the file set base at this point
+	// the file set implementation changed or we have another error.
+	base := x.fset.Base()
+	if x.sources.Len() != base {
+		panic("internal error: file base incorrect")
+	}
+
+	// append file contents (src) to x.sources
+	if _, err := x.sources.ReadFrom(f); err == nil {
+		src := x.sources.Bytes()[base:]
+
+		if goFile {
+			// parse the file and in the process add it to the file set
+			if ast, err = parser.ParseFile(x.fset, filename, src, parser.ParseComments); err == nil {
+				file = x.fset.File(ast.Pos()) // ast.Pos() is inside the file
+				return
+			}
+			// file has parse errors, and the AST may be incorrect -
+			// set lines information explicitly and index as ordinary
+			// text file (cannot fall through to the text case below
+			// because the file has already been added to the file set
+			// by the parser)
+			file = x.fset.File(token.Pos(base)) // token.Pos(base) is inside the file
+			file.SetLinesForContent(src)
+			ast = nil
+			return
+		}
+
+		if util.IsText(src) {
+			// only add the file to the file set (for the full text index)
+			file = x.fset.AddFile(filename, x.fset.Base(), len(src))
+			file.SetLinesForContent(src)
+			return
+		}
+	}
+
+	// discard possibly added data
+	x.sources.Truncate(base - 1) // -1 to remove added byte 0 since no file was added
+	return
+}
+
+// Design note: Using an explicit white list of permitted files for indexing
+// makes sure that the important files are included and massively reduces the
+// number of files to index. The advantage over a blacklist is that unexpected
+// (non-blacklisted) files won't suddenly explode the index.
+
+// Files are whitelisted if they have a file name or extension
+// present as key in whitelisted.
+var whitelisted = map[string]bool{
+	".bash":        true,
+	".c":           true,
+	".cc":          true,
+	".cpp":         true,
+	".cxx":         true,
+	".css":         true,
+	".go":          true,
+	".goc":         true,
+	".h":           true,
+	".hh":          true,
+	".hpp":         true,
+	".hxx":         true,
+	".html":        true,
+	".js":          true,
+	".out":         true,
+	".py":          true,
+	".s":           true,
+	".sh":          true,
+	".txt":         true,
+	".xml":         true,
+	"AUTHORS":      true,
+	"CONTRIBUTORS": true,
+	"LICENSE":      true,
+	"Makefile":     true,
+	"PATENTS":      true,
+	"README":       true,
+}
+
+// isWhitelisted returns true if a file is on the list
+// of "permitted" files for indexing. The filename must
+// be the directory-local name of the file.
+func isWhitelisted(filename string) bool {
+	key := pathpkg.Ext(filename)
+	if key == "" {
+		// file has no extension - use entire filename
+		key = filename
+	}
+	return whitelisted[key]
+}
+
+func (x *Indexer) indexDocs(dirname string, filename string, astFile *ast.File) {
+	pkgName := x.intern(astFile.Name.Name)
+	if pkgName == "main" {
+		return
+	}
+	pkgPath := x.intern(strings.TrimPrefix(strings.TrimPrefix(dirname, "/src/"), "pkg/"))
+	astPkg := ast.Package{
+		Name: pkgName,
+		Files: map[string]*ast.File{
+			filename: astFile,
+		},
+	}
+	var m doc.Mode
+	docPkg := doc.New(&astPkg, dirname, m)
+	addIdent := func(sk SpotKind, name string, docstr string) {
+		if x.idents[sk] == nil {
+			x.idents[sk] = make(map[string][]Ident)
+		}
+		name = x.intern(name)
+		x.idents[sk][name] = append(x.idents[sk][name], Ident{
+			Path:    pkgPath,
+			Package: pkgName,
+			Name:    name,
+			Doc:     doc.Synopsis(docstr),
+		})
+	}
+
+	if x.idents[PackageClause] == nil {
+		x.idents[PackageClause] = make(map[string][]Ident)
+	}
+	// List of words under which the package identifier will be stored.
+	// This includes the package name and the components of the directory
+	// in which it resides.
+	words := strings.Split(pathpkg.Dir(pkgPath), "/")
+	if words[0] == "." {
+		words = []string{}
+	}
+	name := x.intern(docPkg.Name)
+	synopsis := doc.Synopsis(docPkg.Doc)
+	words = append(words, name)
+	pkgIdent := Ident{
+		Path:    pkgPath,
+		Package: pkgName,
+		Name:    name,
+		Doc:     synopsis,
+	}
+	for _, word := range words {
+		word = x.intern(word)
+		found := false
+		pkgs := x.idents[PackageClause][word]
+		for i, p := range pkgs {
+			if p.Path == pkgPath {
+				if docPkg.Doc != "" {
+					p.Doc = synopsis
+					pkgs[i] = p
+				}
+				found = true
+				break
+			}
+		}
+		if !found {
+			x.idents[PackageClause][word] = append(x.idents[PackageClause][word], pkgIdent)
+		}
+	}
+
+	for _, c := range docPkg.Consts {
+		for _, name := range c.Names {
+			addIdent(ConstDecl, name, c.Doc)
+		}
+	}
+	for _, t := range docPkg.Types {
+		addIdent(TypeDecl, t.Name, t.Doc)
+		for _, c := range t.Consts {
+			for _, name := range c.Names {
+				addIdent(ConstDecl, name, c.Doc)
+			}
+		}
+		for _, v := range t.Vars {
+			for _, name := range v.Names {
+				addIdent(VarDecl, name, v.Doc)
+			}
+		}
+		for _, f := range t.Funcs {
+			addIdent(FuncDecl, f.Name, f.Doc)
+		}
+		for _, f := range t.Methods {
+			addIdent(MethodDecl, f.Name, f.Doc)
+			// Change the name of methods to be "<typename>.<methodname>".
+			// They will still be indexed as <methodname>.
+			idents := x.idents[MethodDecl][f.Name]
+			idents[len(idents)-1].Name = x.intern(t.Name + "." + f.Name)
+		}
+	}
+	for _, v := range docPkg.Vars {
+		for _, name := range v.Names {
+			addIdent(VarDecl, name, v.Doc)
+		}
+	}
+	for _, f := range docPkg.Funcs {
+		addIdent(FuncDecl, f.Name, f.Doc)
+	}
+}
+
+func (x *Indexer) indexGoFile(dirname string, filename string, file *token.File, astFile *ast.File) {
+	pkgName := astFile.Name.Name
+
+	if x.c.IndexGoCode {
+		x.current = file
+		pak := x.lookupPackage(dirname, pkgName)
+		x.file = &File{filename, pak}
+		ast.Walk(x, astFile)
+	}
+
+	if x.c.IndexDocs {
+		// Test files are already filtered out in visitFile if IndexGoCode and
+		// IndexFullText are false.  Otherwise, check here.
+		isTestFile := (x.c.IndexGoCode || x.c.IndexFullText) &&
+			(strings.HasSuffix(filename, "_test.go") || strings.HasPrefix(dirname, "/test/"))
+		if !isTestFile {
+			x.indexDocs(dirname, filename, astFile)
+		}
+	}
+
+	ppKey := x.intern(pkgName)
+	if _, ok := x.packagePath[ppKey]; !ok {
+		x.packagePath[ppKey] = make(map[string]bool)
+	}
+	pkgPath := x.intern(strings.TrimPrefix(strings.TrimPrefix(dirname, "/src/"), "pkg/"))
+	x.packagePath[ppKey][pkgPath] = true
+
+	// Merge in exported symbols found walking this file into
+	// the map for that package.
+	if len(x.curPkgExports) > 0 {
+		dest, ok := x.exports[pkgPath]
+		if !ok {
+			dest = make(map[string]SpotKind)
+			x.exports[pkgPath] = dest
+		}
+		for k, v := range x.curPkgExports {
+			dest[k] = v
+		}
+	}
+}
+
+func (x *Indexer) visitFile(dirname string, fi os.FileInfo) {
+	if fi.IsDir() || !x.c.IndexEnabled {
+		return
+	}
+
+	filename := pathpkg.Join(dirname, fi.Name())
+	goFile := isGoFile(fi)
+
+	switch {
+	case x.c.IndexFullText:
+		if !isWhitelisted(fi.Name()) {
+			return
+		}
+	case x.c.IndexGoCode:
+		if !goFile {
+			return
+		}
+	case x.c.IndexDocs:
+		if !goFile ||
+			strings.HasSuffix(fi.Name(), "_test.go") ||
+			strings.HasPrefix(dirname, "/test/") {
+			return
+		}
+	default:
+		// No indexing turned on.
+		return
+	}
+
+	x.fsOpenGate <- true
+	defer func() { <-x.fsOpenGate }()
+
+	// open file
+	f, err := x.c.fs.Open(filename)
+	if err != nil {
+		return
+	}
+
+	x.mu.Lock()
+	defer x.mu.Unlock()
+
+	x.throttle.Throttle()
+
+	x.curPkgExports = make(map[string]SpotKind)
+	file, fast := x.addFile(f, filename, goFile)
+	if file == nil {
+		return // addFile failed
+	}
+
+	if fast != nil {
+		x.indexGoFile(dirname, fi.Name(), file, fast)
+	}
+
+	// update statistics
+	x.stats.Bytes += file.Size()
+	x.stats.Files++
+	x.stats.Lines += file.LineCount()
+}
+
+// indexOptions contains information that affects the contents of an index.
+type indexOptions struct {
+	// Docs provides documentation search results.
+	// It is only consulted if IndexEnabled is true.
+	// The default values is true.
+	Docs bool
+
+	// GoCode provides Go source code search results.
+	// It is only consulted if IndexEnabled is true.
+	// The default values is true.
+	GoCode bool
+
+	// FullText provides search results from all files.
+	// It is only consulted if IndexEnabled is true.
+	// The default values is true.
+	FullText bool
+
+	// MaxResults optionally specifies the maximum results for indexing.
+	// The default is 1000.
+	MaxResults int
+}
+
+// ----------------------------------------------------------------------------
+// Index
+
+type LookupResult struct {
+	Decls  HitList // package-level declarations (with snippets)
+	Others HitList // all other occurrences
+}
+
+type Index struct {
+	fset        *token.FileSet           // file set used during indexing; nil if no textindex
+	suffixes    *suffixarray.Index       // suffixes for concatenated sources; nil if no textindex
+	words       map[string]*LookupResult // maps words to hit lists
+	alts        map[string]*AltWords     // maps canonical(words) to lists of alternative spellings
+	snippets    []*Snippet               // all snippets, indexed by snippet index
+	stats       Statistics
+	importCount map[string]int                 // package path ("net/http") => count
+	packagePath map[string]map[string]bool     // "template" => "text/template" => true
+	exports     map[string]map[string]SpotKind // "net/http" => "ListenAndServe" => FuncDecl
+	idents      map[SpotKind]map[string][]Ident
+	opts        indexOptions
+}
+
+func canonical(w string) string { return strings.ToLower(w) }
+
+// Somewhat arbitrary, but I figure low enough to not hurt disk-based filesystems
+// consuming file descriptors, where some systems have low 256 or 512 limits.
+// Go should have a built-in way to cap fd usage under the ulimit.
+const (
+	maxOpenFiles = 200
+	maxOpenDirs  = 50
+)
+
+func (c *Corpus) throttle() float64 {
+	if c.IndexThrottle <= 0 {
+		return 0.9
+	}
+	if c.IndexThrottle > 1.0 {
+		return 1.0
+	}
+	return c.IndexThrottle
+}
+
+// NewIndex creates a new index for the .go files provided by the corpus.
+func (c *Corpus) NewIndex() *Index {
+	// initialize Indexer
+	// (use some reasonably sized maps to start)
+	x := &Indexer{
+		c:           c,
+		fset:        token.NewFileSet(),
+		fsOpenGate:  make(chan bool, maxOpenFiles),
+		strings:     make(map[string]string),
+		packages:    make(map[Pak]*Pak, 256),
+		words:       make(map[string]*IndexResult, 8192),
+		throttle:    util.NewThrottle(c.throttle(), 100*time.Millisecond), // run at least 0.1s at a time
+		importCount: make(map[string]int),
+		packagePath: make(map[string]map[string]bool),
+		exports:     make(map[string]map[string]SpotKind),
+		idents:      make(map[SpotKind]map[string][]Ident, 4),
+	}
+
+	// index all files in the directories given by dirnames
+	var wg sync.WaitGroup // outstanding ReadDir + visitFile
+	dirGate := make(chan bool, maxOpenDirs)
+	for dirname := range c.fsDirnames() {
+		if c.IndexDirectory != nil && !c.IndexDirectory(dirname) {
+			continue
+		}
+		dirGate <- true
+		wg.Add(1)
+		go func(dirname string) {
+			defer func() { <-dirGate }()
+			defer wg.Done()
+
+			list, err := c.fs.ReadDir(dirname)
+			if err != nil {
+				log.Printf("ReadDir(%q): %v; skipping directory", dirname, err)
+				return // ignore this directory
+			}
+			for _, fi := range list {
+				wg.Add(1)
+				go func(fi os.FileInfo) {
+					defer wg.Done()
+					x.visitFile(dirname, fi)
+				}(fi)
+			}
+		}(dirname)
+	}
+	wg.Wait()
+
+	if !c.IndexFullText {
+		// the file set, the current file, and the sources are
+		// not needed after indexing if no text index is built -
+		// help GC and clear them
+		x.fset = nil
+		x.sources.Reset()
+		x.current = nil // contains reference to fset!
+	}
+
+	// for each word, reduce the RunLists into a LookupResult;
+	// also collect the word with its canonical spelling in a
+	// word list for later computation of alternative spellings
+	words := make(map[string]*LookupResult)
+	var wlist RunList
+	for w, h := range x.words {
+		decls := reduce(h.Decls)
+		others := reduce(h.Others)
+		words[w] = &LookupResult{
+			Decls:  decls,
+			Others: others,
+		}
+		wlist = append(wlist, &wordPair{canonical(w), w})
+		x.throttle.Throttle()
+	}
+	x.stats.Words = len(words)
+
+	// reduce the word list {canonical(w), w} into
+	// a list of AltWords runs {canonical(w), {w}}
+	alist := wlist.reduce(lessWordPair, newAltWords)
+
+	// convert alist into a map of alternative spellings
+	alts := make(map[string]*AltWords)
+	for i := 0; i < len(alist); i++ {
+		a := alist[i].(*AltWords)
+		alts[a.Canon] = a
+	}
+
+	// create text index
+	var suffixes *suffixarray.Index
+	if c.IndexFullText {
+		suffixes = suffixarray.New(x.sources.Bytes())
+	}
+
+	// sort idents by the number of imports of their respective packages
+	for _, idMap := range x.idents {
+		for _, ir := range idMap {
+			sort.Sort(byImportCount{ir, x.importCount})
+		}
+	}
+
+	return &Index{
+		fset:        x.fset,
+		suffixes:    suffixes,
+		words:       words,
+		alts:        alts,
+		snippets:    x.snippets,
+		stats:       x.stats,
+		importCount: x.importCount,
+		packagePath: x.packagePath,
+		exports:     x.exports,
+		idents:      x.idents,
+		opts: indexOptions{
+			Docs:       x.c.IndexDocs,
+			GoCode:     x.c.IndexGoCode,
+			FullText:   x.c.IndexFullText,
+			MaxResults: x.c.MaxResults,
+		},
+	}
+}
+
+var ErrFileIndexVersion = errors.New("file index version out of date")
+
+const fileIndexVersion = 3
+
+// fileIndex is the subset of Index that's gob-encoded for use by
+// Index.Write and Index.Read.
+type fileIndex struct {
+	Version     int
+	Words       map[string]*LookupResult
+	Alts        map[string]*AltWords
+	Snippets    []*Snippet
+	Fulltext    bool
+	Stats       Statistics
+	ImportCount map[string]int
+	PackagePath map[string]map[string]bool
+	Exports     map[string]map[string]SpotKind
+	Idents      map[SpotKind]map[string][]Ident
+	Opts        indexOptions
+}
+
+func (x *fileIndex) Write(w io.Writer) error {
+	return gob.NewEncoder(w).Encode(x)
+}
+
+func (x *fileIndex) Read(r io.Reader) error {
+	return gob.NewDecoder(r).Decode(x)
+}
+
+// WriteTo writes the index x to w.
+func (x *Index) WriteTo(w io.Writer) (n int64, err error) {
+	w = countingWriter{&n, w}
+	fulltext := false
+	if x.suffixes != nil {
+		fulltext = true
+	}
+	fx := fileIndex{
+		Version:     fileIndexVersion,
+		Words:       x.words,
+		Alts:        x.alts,
+		Snippets:    x.snippets,
+		Fulltext:    fulltext,
+		Stats:       x.stats,
+		ImportCount: x.importCount,
+		PackagePath: x.packagePath,
+		Exports:     x.exports,
+		Idents:      x.idents,
+		Opts:        x.opts,
+	}
+	if err := fx.Write(w); err != nil {
+		return 0, err
+	}
+	if fulltext {
+		encode := func(x interface{}) error {
+			return gob.NewEncoder(w).Encode(x)
+		}
+		if err := x.fset.Write(encode); err != nil {
+			return 0, err
+		}
+		if err := x.suffixes.Write(w); err != nil {
+			return 0, err
+		}
+	}
+	return n, nil
+}
+
+// ReadFrom reads the index from r into x; x must not be nil.
+// If r does not also implement io.ByteReader, it will be wrapped in a bufio.Reader.
+// If the index is from an old version, the error is ErrFileIndexVersion.
+func (x *Index) ReadFrom(r io.Reader) (n int64, err error) {
+	// We use the ability to read bytes as a plausible surrogate for buffering.
+	if _, ok := r.(io.ByteReader); !ok {
+		r = bufio.NewReader(r)
+	}
+	r = countingReader{&n, r.(byteReader)}
+	var fx fileIndex
+	if err := fx.Read(r); err != nil {
+		return n, err
+	}
+	if fx.Version != fileIndexVersion {
+		return 0, ErrFileIndexVersion
+	}
+	x.words = fx.Words
+	x.alts = fx.Alts
+	x.snippets = fx.Snippets
+	x.stats = fx.Stats
+	x.importCount = fx.ImportCount
+	x.packagePath = fx.PackagePath
+	x.exports = fx.Exports
+	x.idents = fx.Idents
+	x.opts = fx.Opts
+	if fx.Fulltext {
+		x.fset = token.NewFileSet()
+		decode := func(x interface{}) error {
+			return gob.NewDecoder(r).Decode(x)
+		}
+		if err := x.fset.Read(decode); err != nil {
+			return n, err
+		}
+		x.suffixes = new(suffixarray.Index)
+		if err := x.suffixes.Read(r); err != nil {
+			return n, err
+		}
+	}
+	return n, nil
+}
+
+// Stats returns index statistics.
+func (x *Index) Stats() Statistics {
+	return x.stats
+}
+
+// ImportCount returns a map from import paths to how many times they were seen.
+func (x *Index) ImportCount() map[string]int {
+	return x.importCount
+}
+
+// PackagePath returns a map from short package name to a set
+// of full package path names that use that short package name.
+func (x *Index) PackagePath() map[string]map[string]bool {
+	return x.packagePath
+}
+
+// Exports returns a map from full package path to exported
+// symbol name to its type.
+func (x *Index) Exports() map[string]map[string]SpotKind {
+	return x.exports
+}
+
+// Idents returns a map from identifier type to exported
+// symbol name to the list of identifiers matching that name.
+func (x *Index) Idents() map[SpotKind]map[string][]Ident {
+	return x.idents
+}
+
+func (x *Index) lookupWord(w string) (match *LookupResult, alt *AltWords) {
+	match = x.words[w]
+	alt = x.alts[canonical(w)]
+	// remove current spelling from alternatives
+	// (if there is no match, the alternatives do
+	// not contain the current spelling)
+	if match != nil && alt != nil {
+		alt = alt.filter(w)
+	}
+	return
+}
+
+// isIdentifier reports whether s is a Go identifier.
+func isIdentifier(s string) bool {
+	for i, ch := range s {
+		if unicode.IsLetter(ch) || ch == '_' || i > 0 && unicode.IsDigit(ch) {
+			continue
+		}
+		return false
+	}
+	return len(s) > 0
+}
+
+// For a given query, which is either a single identifier or a qualified
+// identifier, Lookup returns a SearchResult containing packages, a LookupResult, a
+// list of alternative spellings, and identifiers, if any. Any and all results
+// may be nil.  If the query syntax is wrong, an error is reported.
+func (x *Index) Lookup(query string) (*SearchResult, error) {
+	ss := strings.Split(query, ".")
+
+	// check query syntax
+	for _, s := range ss {
+		if !isIdentifier(s) {
+			return nil, errors.New("all query parts must be identifiers")
+		}
+	}
+	rslt := &SearchResult{
+		Query:  query,
+		Idents: make(map[SpotKind][]Ident, 5),
+	}
+	// handle simple and qualified identifiers
+	switch len(ss) {
+	case 1:
+		ident := ss[0]
+		rslt.Hit, rslt.Alt = x.lookupWord(ident)
+		if rslt.Hit != nil {
+			// found a match - filter packages with same name
+			// for the list of packages called ident, if any
+			rslt.Pak = rslt.Hit.Others.filter(ident)
+		}
+		for k, v := range x.idents {
+			const rsltLimit = 50
+			ids := byImportCount{v[ident], x.importCount}
+			rslt.Idents[k] = ids.top(rsltLimit)
+		}
+
+	case 2:
+		pakname, ident := ss[0], ss[1]
+		rslt.Hit, rslt.Alt = x.lookupWord(ident)
+		if rslt.Hit != nil {
+			// found a match - filter by package name
+			// (no paks - package names are not qualified)
+			decls := rslt.Hit.Decls.filter(pakname)
+			others := rslt.Hit.Others.filter(pakname)
+			rslt.Hit = &LookupResult{decls, others}
+		}
+		for k, v := range x.idents {
+			ids := byImportCount{v[ident], x.importCount}
+			rslt.Idents[k] = ids.filter(pakname)
+		}
+
+	default:
+		return nil, errors.New("query is not a (qualified) identifier")
+	}
+
+	return rslt, nil
+}
+
+func (x *Index) Snippet(i int) *Snippet {
+	// handle illegal snippet indices gracefully
+	if 0 <= i && i < len(x.snippets) {
+		return x.snippets[i]
+	}
+	return nil
+}
+
+type positionList []struct {
+	filename string
+	line     int
+}
+
+func (list positionList) Len() int           { return len(list) }
+func (list positionList) Less(i, j int) bool { return list[i].filename < list[j].filename }
+func (list positionList) Swap(i, j int)      { list[i], list[j] = list[j], list[i] }
+
+// unique returns the list sorted and with duplicate entries removed
+func unique(list []int) []int {
+	sort.Ints(list)
+	var last int
+	i := 0
+	for _, x := range list {
+		if i == 0 || x != last {
+			last = x
+			list[i] = x
+			i++
+		}
+	}
+	return list[0:i]
+}
+
+// A FileLines value specifies a file and line numbers within that file.
+type FileLines struct {
+	Filename string
+	Lines    []int
+}
+
+// LookupRegexp returns the number of matches and the matches where a regular
+// expression r is found in the full text index. At most n matches are
+// returned (thus found <= n).
+//
+func (x *Index) LookupRegexp(r *regexp.Regexp, n int) (found int, result []FileLines) {
+	if x.suffixes == nil || n <= 0 {
+		return
+	}
+	// n > 0
+
+	var list positionList
+	// FindAllIndex may returns matches that span across file boundaries.
+	// Such matches are unlikely, buf after eliminating them we may end up
+	// with fewer than n matches. If we don't have enough at the end, redo
+	// the search with an increased value n1, but only if FindAllIndex
+	// returned all the requested matches in the first place (if it
+	// returned fewer than that there cannot be more).
+	for n1 := n; found < n; n1 += n - found {
+		found = 0
+		matches := x.suffixes.FindAllIndex(r, n1)
+		// compute files, exclude matches that span file boundaries,
+		// and map offsets to file-local offsets
+		list = make(positionList, len(matches))
+		for _, m := range matches {
+			// by construction, an offset corresponds to the Pos value
+			// for the file set - use it to get the file and line
+			p := token.Pos(m[0])
+			if file := x.fset.File(p); file != nil {
+				if base := file.Base(); base <= m[1] && m[1] <= base+file.Size() {
+					// match [m[0], m[1]) is within the file boundaries
+					list[found].filename = file.Name()
+					list[found].line = file.Line(p)
+					found++
+				}
+			}
+		}
+		if found == n || len(matches) < n1 {
+			// found all matches or there's no chance to find more
+			break
+		}
+	}
+	list = list[0:found]
+	sort.Sort(list) // sort by filename
+
+	// collect matches belonging to the same file
+	var last string
+	var lines []int
+	addLines := func() {
+		if len(lines) > 0 {
+			// remove duplicate lines
+			result = append(result, FileLines{last, unique(lines)})
+			lines = nil
+		}
+	}
+	for _, m := range list {
+		if m.filename != last {
+			addLines()
+			last = m.filename
+		}
+		lines = append(lines, m.line)
+	}
+	addLines()
+
+	return
+}
+
+// InvalidateIndex should be called whenever any of the file systems
+// under godoc's observation change so that the indexer is kicked on.
+func (c *Corpus) invalidateIndex() {
+	c.fsModified.Set(nil)
+	c.refreshMetadata()
+}
+
+// feedDirnames feeds the directory names of all directories
+// under the file system given by root to channel c.
+//
+func (c *Corpus) feedDirnames(ch chan<- string) {
+	if dir, _ := c.fsTree.Get(); dir != nil {
+		for d := range dir.(*Directory).iter(false) {
+			ch <- d.Path
+		}
+	}
+}
+
+// fsDirnames() returns a channel sending all directory names
+// of all the file systems under godoc's observation.
+//
+func (c *Corpus) fsDirnames() <-chan string {
+	ch := make(chan string, 256) // buffered for fewer context switches
+	go func() {
+		c.feedDirnames(ch)
+		close(ch)
+	}()
+	return ch
+}
+
+// CompatibleWith reports whether the Index x is compatible with the corpus
+// indexing options set in c.
+func (x *Index) CompatibleWith(c *Corpus) bool {
+	return x.opts.Docs == c.IndexDocs &&
+		x.opts.GoCode == c.IndexGoCode &&
+		x.opts.FullText == c.IndexFullText &&
+		x.opts.MaxResults == c.MaxResults
+}
+
+func (c *Corpus) readIndex(filenames string) error {
+	matches, err := filepath.Glob(filenames)
+	if err != nil {
+		return err
+	} else if matches == nil {
+		return fmt.Errorf("no index files match %q", filenames)
+	}
+	sort.Strings(matches) // make sure files are in the right order
+	files := make([]io.Reader, 0, len(matches))
+	for _, filename := range matches {
+		f, err := os.Open(filename)
+		if err != nil {
+			return err
+		}
+		defer f.Close()
+		files = append(files, f)
+	}
+	return c.ReadIndexFrom(io.MultiReader(files...))
+}
+
+// ReadIndexFrom sets the current index from the serialized version found in r.
+func (c *Corpus) ReadIndexFrom(r io.Reader) error {
+	x := new(Index)
+	if _, err := x.ReadFrom(r); err != nil {
+		return err
+	}
+	if !x.CompatibleWith(c) {
+		return fmt.Errorf("index file options are incompatible: %v", x.opts)
+	}
+	c.searchIndex.Set(x)
+	return nil
+}
+
+func (c *Corpus) UpdateIndex() {
+	if c.Verbose {
+		log.Printf("updating index...")
+	}
+	start := time.Now()
+	index := c.NewIndex()
+	stop := time.Now()
+	c.searchIndex.Set(index)
+	if c.Verbose {
+		secs := stop.Sub(start).Seconds()
+		stats := index.Stats()
+		log.Printf("index updated (%gs, %d bytes of source, %d files, %d lines, %d unique words, %d spots)",
+			secs, stats.Bytes, stats.Files, stats.Lines, stats.Words, stats.Spots)
+	}
+	memstats := new(runtime.MemStats)
+	runtime.ReadMemStats(memstats)
+	if c.Verbose {
+		log.Printf("before GC: bytes = %d footprint = %d", memstats.HeapAlloc, memstats.Sys)
+	}
+	runtime.GC()
+	runtime.ReadMemStats(memstats)
+	if c.Verbose {
+		log.Printf("after  GC: bytes = %d footprint = %d", memstats.HeapAlloc, memstats.Sys)
+	}
+}
+
+// RunIndexer runs forever, indexing.
+func (c *Corpus) RunIndexer() {
+	// initialize the index from disk if possible
+	if c.IndexFiles != "" {
+		c.initFSTree()
+		if err := c.readIndex(c.IndexFiles); err != nil {
+			log.Printf("error reading index from file %s: %v", c.IndexFiles, err)
+		}
+		return
+	}
+
+	// Repeatedly update the package directory tree and index.
+	for {
+		c.initFSTree()
+		c.UpdateIndex()
+		if c.IndexInterval < 0 {
+			return
+		}
+		delay := 5 * time.Minute // by default, reindex every 5 minutes
+		if c.IndexInterval > 0 {
+			delay = c.IndexInterval
+		}
+		time.Sleep(delay)
+	}
+}
+
+type countingWriter struct {
+	n *int64
+	w io.Writer
+}
+
+func (c countingWriter) Write(p []byte) (n int, err error) {
+	n, err = c.w.Write(p)
+	*c.n += int64(n)
+	return
+}
+
+type byteReader interface {
+	io.Reader
+	io.ByteReader
+}
+
+type countingReader struct {
+	n *int64
+	r byteReader
+}
+
+func (c countingReader) Read(p []byte) (n int, err error) {
+	n, err = c.r.Read(p)
+	*c.n += int64(n)
+	return
+}
+
+func (c countingReader) ReadByte() (b byte, err error) {
+	b, err = c.r.ReadByte()
+	*c.n += 1
+	return
+}
diff --git a/internal/godoc/index_test.go b/internal/godoc/index_test.go
new file mode 100644
index 0000000..3718699
--- /dev/null
+++ b/internal/godoc/index_test.go
@@ -0,0 +1,323 @@
+// 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.
+
+package godoc
+
+import (
+	"bytes"
+	"reflect"
+	"sort"
+	"strings"
+	"testing"
+
+	"golang.org/x/website/internal/godoc/vfs/mapfs"
+)
+
+func newCorpus(t *testing.T) *Corpus {
+	c := NewCorpus(mapfs.New(map[string]string{
+		"src/foo/foo.go": `// Package foo is an example.
+package foo
+
+import "bar"
+
+const Pi = 3.1415
+
+var Foos []Foo
+
+// Foo is stuff.
+type Foo struct{}
+
+func New() *Foo {
+   return new(Foo)
+}
+`,
+		"src/bar/bar.go": `// Package bar is another example to test races.
+package bar
+`,
+		"src/other/bar/bar.go": `// Package bar is another bar package.
+package bar
+func X() {}
+`,
+		"src/skip/skip.go": `// Package skip should be skipped.
+package skip
+func Skip() {}
+`,
+		"src/bar/readme.txt": `Whitelisted text file.
+`,
+		"src/bar/baz.zzz": `Text file not whitelisted.
+`,
+	}))
+	c.IndexEnabled = true
+	c.IndexDirectory = func(dir string) bool {
+		return !strings.Contains(dir, "skip")
+	}
+
+	if err := c.Init(); err != nil {
+		t.Fatal(err)
+	}
+	return c
+}
+
+func TestIndex(t *testing.T) {
+	for _, docs := range []bool{true, false} {
+		for _, goCode := range []bool{true, false} {
+			for _, fullText := range []bool{true, false} {
+				c := newCorpus(t)
+				c.IndexDocs = docs
+				c.IndexGoCode = goCode
+				c.IndexFullText = fullText
+				c.UpdateIndex()
+				ix, _ := c.CurrentIndex()
+				if ix == nil {
+					t.Fatal("no index")
+				}
+				t.Logf("docs, goCode, fullText = %v,%v,%v", docs, goCode, fullText)
+				testIndex(t, c, ix)
+			}
+		}
+	}
+}
+
+func TestIndexWriteRead(t *testing.T) {
+	type key struct {
+		docs, goCode, fullText bool
+	}
+	type val struct {
+		buf *bytes.Buffer
+		c   *Corpus
+	}
+	m := map[key]val{}
+
+	for _, docs := range []bool{true, false} {
+		for _, goCode := range []bool{true, false} {
+			for _, fullText := range []bool{true, false} {
+				k := key{docs, goCode, fullText}
+				c := newCorpus(t)
+				c.IndexDocs = docs
+				c.IndexGoCode = goCode
+				c.IndexFullText = fullText
+				c.UpdateIndex()
+				ix, _ := c.CurrentIndex()
+				if ix == nil {
+					t.Fatal("no index")
+				}
+				var buf bytes.Buffer
+				nw, err := ix.WriteTo(&buf)
+				if err != nil {
+					t.Fatalf("Index.WriteTo: %v", err)
+				}
+				m[k] = val{bytes.NewBuffer(buf.Bytes()), c}
+				ix2 := new(Index)
+				nr, err := ix2.ReadFrom(&buf)
+				if err != nil {
+					t.Fatalf("Index.ReadFrom: %v", err)
+				}
+				if nr != nw {
+					t.Errorf("Wrote %d bytes to index but read %d", nw, nr)
+				}
+				testIndex(t, c, ix)
+			}
+		}
+	}
+	// Test CompatibleWith
+	for k1, v1 := range m {
+		ix := new(Index)
+		if _, err := ix.ReadFrom(v1.buf); err != nil {
+			t.Fatalf("Index.ReadFrom: %v", err)
+		}
+		for k2, v2 := range m {
+			if got, want := ix.CompatibleWith(v2.c), k1 == k2; got != want {
+				t.Errorf("CompatibleWith = %v; want %v for %v, %v", got, want, k1, k2)
+			}
+		}
+	}
+}
+
+func testIndex(t *testing.T, c *Corpus, ix *Index) {
+	if _, ok := ix.words["Skip"]; ok {
+		t.Errorf("the word Skip was found; expected it to be skipped")
+	}
+	checkStats(t, c, ix)
+	checkImportCount(t, c, ix)
+	checkPackagePath(t, c, ix)
+	checkExports(t, c, ix)
+	checkIdents(t, c, ix)
+}
+
+// checkStats checks the Index's statistics.
+// Some statistics are only set when we're indexing Go code.
+func checkStats(t *testing.T, c *Corpus, ix *Index) {
+	want := Statistics{}
+	if c.IndexFullText {
+		want.Bytes = 314
+		want.Files = 4
+		want.Lines = 21
+	} else if c.IndexDocs || c.IndexGoCode {
+		want.Bytes = 291
+		want.Files = 3
+		want.Lines = 20
+	}
+	if c.IndexGoCode {
+		want.Words = 8
+		want.Spots = 12
+	}
+	if got := ix.Stats(); !reflect.DeepEqual(got, want) {
+		t.Errorf("Stats = %#v; want %#v", got, want)
+	}
+}
+
+// checkImportCount checks the Index's import count map.
+// It is only set when we're indexing Go code.
+func checkImportCount(t *testing.T, c *Corpus, ix *Index) {
+	want := map[string]int{}
+	if c.IndexGoCode {
+		want = map[string]int{
+			"bar": 1,
+		}
+	}
+	if got := ix.ImportCount(); !reflect.DeepEqual(got, want) {
+		t.Errorf("ImportCount = %v; want %v", got, want)
+	}
+}
+
+// checkPackagePath checks the Index's package path map.
+// It is set if at least one of the indexing options is enabled.
+func checkPackagePath(t *testing.T, c *Corpus, ix *Index) {
+	want := map[string]map[string]bool{}
+	if c.IndexDocs || c.IndexGoCode || c.IndexFullText {
+		want = map[string]map[string]bool{
+			"foo": {
+				"foo": true,
+			},
+			"bar": {
+				"bar":       true,
+				"other/bar": true,
+			},
+		}
+	}
+	if got := ix.PackagePath(); !reflect.DeepEqual(got, want) {
+		t.Errorf("PackagePath = %v; want %v", got, want)
+	}
+}
+
+// checkExports checks the Index's exports map.
+// It is only set when we're indexing Go code.
+func checkExports(t *testing.T, c *Corpus, ix *Index) {
+	want := map[string]map[string]SpotKind{}
+	if c.IndexGoCode {
+		want = map[string]map[string]SpotKind{
+			"foo": {
+				"Pi":   ConstDecl,
+				"Foos": VarDecl,
+				"Foo":  TypeDecl,
+				"New":  FuncDecl,
+			},
+			"other/bar": {
+				"X": FuncDecl,
+			},
+		}
+	}
+	if got := ix.Exports(); !reflect.DeepEqual(got, want) {
+		t.Errorf("Exports = %v; want %v", got, want)
+	}
+}
+
+// checkIdents checks the Index's indents map.
+// It is only set when we're indexing documentation.
+func checkIdents(t *testing.T, c *Corpus, ix *Index) {
+	want := map[SpotKind]map[string][]Ident{}
+	if c.IndexDocs {
+		want = map[SpotKind]map[string][]Ident{
+			PackageClause: {
+				"bar": {
+					{"bar", "bar", "bar", "Package bar is another example to test races."},
+					{"other/bar", "bar", "bar", "Package bar is another bar package."},
+				},
+				"foo":   {{"foo", "foo", "foo", "Package foo is an example."}},
+				"other": {{"other/bar", "bar", "bar", "Package bar is another bar package."}},
+			},
+			ConstDecl: {
+				"Pi": {{"foo", "foo", "Pi", ""}},
+			},
+			VarDecl: {
+				"Foos": {{"foo", "foo", "Foos", ""}},
+			},
+			TypeDecl: {
+				"Foo": {{"foo", "foo", "Foo", "Foo is stuff."}},
+			},
+			FuncDecl: {
+				"New": {{"foo", "foo", "New", ""}},
+				"X":   {{"other/bar", "bar", "X", ""}},
+			},
+		}
+	}
+	if got := ix.Idents(); !reflect.DeepEqual(got, want) {
+		t.Errorf("Idents = %v; want %v", got, want)
+	}
+}
+
+func TestIdentResultSort(t *testing.T) {
+	ic := map[string]int{
+		"/a/b/pkg1": 10,
+		"/a/b/pkg2": 2,
+		"/b/d/pkg3": 20,
+	}
+	for _, tc := range []struct {
+		ir  []Ident
+		exp []Ident
+	}{
+		{
+			ir: []Ident{
+				{"/a/b/pkg2", "pkg2", "MyFunc2", ""},
+				{"/b/d/pkg3", "pkg3", "MyFunc3", ""},
+				{"/a/b/pkg1", "pkg1", "MyFunc1", ""},
+			},
+			exp: []Ident{
+				{"/b/d/pkg3", "pkg3", "MyFunc3", ""},
+				{"/a/b/pkg1", "pkg1", "MyFunc1", ""},
+				{"/a/b/pkg2", "pkg2", "MyFunc2", ""},
+			},
+		},
+		{
+			ir: []Ident{
+				{"/a/a/pkg1", "pkg1", "MyFunc1", ""},
+				{"/a/b/pkg1", "pkg1", "MyFunc1", ""},
+			},
+			exp: []Ident{
+				{"/a/b/pkg1", "pkg1", "MyFunc1", ""},
+				{"/a/a/pkg1", "pkg1", "MyFunc1", ""},
+			},
+		},
+	} {
+		if sort.Sort(byImportCount{tc.ir, ic}); !reflect.DeepEqual(tc.ir, tc.exp) {
+			t.Errorf("got: %v, want %v", tc.ir, tc.exp)
+		}
+	}
+}
+
+func TestIdentFilter(t *testing.T) {
+	ic := map[string]int{}
+	for _, tc := range []struct {
+		ir  []Ident
+		pak string
+		exp []Ident
+	}{
+		{
+			ir: []Ident{
+				{"/a/b/pkg2", "pkg2", "MyFunc2", ""},
+				{"/b/d/pkg3", "pkg3", "MyFunc3", ""},
+				{"/a/b/pkg1", "pkg1", "MyFunc1", ""},
+			},
+			pak: "pkg2",
+			exp: []Ident{
+				{"/a/b/pkg2", "pkg2", "MyFunc2", ""},
+			},
+		},
+	} {
+		res := byImportCount{tc.ir, ic}.filter(tc.pak)
+		if !reflect.DeepEqual(res, tc.exp) {
+			t.Errorf("got: %v, want %v", res, tc.exp)
+		}
+	}
+}
diff --git a/internal/godoc/linkify.go b/internal/godoc/linkify.go
new file mode 100644
index 0000000..e4add22
--- /dev/null
+++ b/internal/godoc/linkify.go
@@ -0,0 +1,195 @@
+// 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.
+
+// This file implements LinkifyText which introduces
+// links for identifiers pointing to their declarations.
+// The approach does not cover all cases because godoc
+// doesn't have complete type information, but it's
+// reasonably good for browsing.
+
+package godoc
+
+import (
+	"fmt"
+	"go/ast"
+	"go/doc"
+	"go/token"
+	"io"
+	"strconv"
+)
+
+// LinkifyText HTML-escapes source text and writes it to w.
+// Identifiers that are in a "use" position (i.e., that are
+// not being declared), are wrapped with HTML links pointing
+// to the respective declaration, if possible. Comments are
+// formatted the same way as with FormatText.
+//
+func LinkifyText(w io.Writer, text []byte, n ast.Node) {
+	links := linksFor(n)
+
+	i := 0     // links index
+	prev := "" // prev HTML tag
+	linkWriter := func(w io.Writer, _ int, start bool) {
+		// end tag
+		if !start {
+			if prev != "" {
+				fmt.Fprintf(w, `</%s>`, prev)
+				prev = ""
+			}
+			return
+		}
+
+		// start tag
+		prev = ""
+		if i < len(links) {
+			switch info := links[i]; {
+			case info.path != "" && info.name == "":
+				// package path
+				fmt.Fprintf(w, `<a href="/pkg/%s/">`, info.path)
+				prev = "a"
+			case info.path != "" && info.name != "":
+				// qualified identifier
+				fmt.Fprintf(w, `<a href="/pkg/%s/#%s">`, info.path, info.name)
+				prev = "a"
+			case info.path == "" && info.name != "":
+				// local identifier
+				if info.isVal {
+					fmt.Fprintf(w, `<span id="%s">`, info.name)
+					prev = "span"
+				} else if ast.IsExported(info.name) {
+					fmt.Fprintf(w, `<a href="#%s">`, info.name)
+					prev = "a"
+				}
+			}
+			i++
+		}
+	}
+
+	idents := tokenSelection(text, token.IDENT)
+	comments := tokenSelection(text, token.COMMENT)
+	FormatSelections(w, text, linkWriter, idents, selectionTag, comments)
+}
+
+// A link describes the (HTML) link information for an identifier.
+// The zero value of a link represents "no link".
+//
+type link struct {
+	path, name string // package path, identifier name
+	isVal      bool   // identifier is defined in a const or var declaration
+}
+
+// linksFor returns the list of links for the identifiers used
+// by node in the same order as they appear in the source.
+//
+func linksFor(node ast.Node) (links []link) {
+	// linkMap tracks link information for each ast.Ident node. Entries may
+	// be created out of source order (for example, when we visit a parent
+	// definition node). These links are appended to the returned slice when
+	// their ast.Ident nodes are visited.
+	linkMap := make(map[*ast.Ident]link)
+
+	ast.Inspect(node, func(node ast.Node) bool {
+		switch n := node.(type) {
+		case *ast.Field:
+			for _, n := range n.Names {
+				linkMap[n] = link{}
+			}
+		case *ast.ImportSpec:
+			if name := n.Name; name != nil {
+				linkMap[name] = link{}
+			}
+		case *ast.ValueSpec:
+			for _, n := range n.Names {
+				linkMap[n] = link{name: n.Name, isVal: true}
+			}
+		case *ast.FuncDecl:
+			linkMap[n.Name] = link{}
+		case *ast.TypeSpec:
+			linkMap[n.Name] = link{}
+		case *ast.AssignStmt:
+			// Short variable declarations only show up if we apply
+			// this code to all source code (as opposed to exported
+			// declarations only).
+			if n.Tok == token.DEFINE {
+				// Some of the lhs variables may be re-declared,
+				// so technically they are not defs. We don't
+				// care for now.
+				for _, x := range n.Lhs {
+					// Each lhs expression should be an
+					// ident, but we are conservative and check.
+					if n, _ := x.(*ast.Ident); n != nil {
+						linkMap[n] = link{isVal: true}
+					}
+				}
+			}
+		case *ast.SelectorExpr:
+			// Detect qualified identifiers of the form pkg.ident.
+			// If anything fails we return true and collect individual
+			// identifiers instead.
+			if x, _ := n.X.(*ast.Ident); x != nil {
+				// Create links only if x is a qualified identifier.
+				if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
+					if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
+						// spec.Path.Value is the import path
+						if path, err := strconv.Unquote(spec.Path.Value); err == nil {
+							// Register two links, one for the package
+							// and one for the qualified identifier.
+							linkMap[x] = link{path: path}
+							linkMap[n.Sel] = link{path: path, name: n.Sel.Name}
+						}
+					}
+				}
+			}
+		case *ast.CompositeLit:
+			// Detect field names within composite literals. These links should
+			// be prefixed by the type name.
+			fieldPath := ""
+			prefix := ""
+			switch typ := n.Type.(type) {
+			case *ast.Ident:
+				prefix = typ.Name + "."
+			case *ast.SelectorExpr:
+				if x, _ := typ.X.(*ast.Ident); x != nil {
+					// Create links only if x is a qualified identifier.
+					if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
+						if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
+							// spec.Path.Value is the import path
+							if path, err := strconv.Unquote(spec.Path.Value); err == nil {
+								// Register two links, one for the package
+								// and one for the qualified identifier.
+								linkMap[x] = link{path: path}
+								linkMap[typ.Sel] = link{path: path, name: typ.Sel.Name}
+								fieldPath = path
+								prefix = typ.Sel.Name + "."
+							}
+						}
+					}
+				}
+			}
+			for _, e := range n.Elts {
+				if kv, ok := e.(*ast.KeyValueExpr); ok {
+					if k, ok := kv.Key.(*ast.Ident); ok {
+						// Note: there is some syntactic ambiguity here. We cannot determine
+						// if this is a struct literal or a map literal without type
+						// information. We assume struct literal.
+						name := prefix + k.Name
+						linkMap[k] = link{path: fieldPath, name: name}
+					}
+				}
+			}
+		case *ast.Ident:
+			if l, ok := linkMap[n]; ok {
+				links = append(links, l)
+			} else {
+				l := link{name: n.Name}
+				if n.Obj == nil && doc.IsPredeclared(n.Name) {
+					l.path = builtinPkgPath
+				}
+				links = append(links, l)
+			}
+		}
+		return true
+	})
+	return
+}
diff --git a/internal/godoc/markdown.go b/internal/godoc/markdown.go
new file mode 100644
index 0000000..fd61aa5
--- /dev/null
+++ b/internal/godoc/markdown.go
@@ -0,0 +1,31 @@
+// Copyright 2020 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.
+
+package godoc
+
+import (
+	"bytes"
+
+	"github.com/yuin/goldmark"
+	"github.com/yuin/goldmark/parser"
+	"github.com/yuin/goldmark/renderer/html"
+)
+
+// renderMarkdown converts a limited and opinionated flavor of Markdown (compliant with
+// CommonMark 0.29) to HTML for the purposes of Go websites.
+//
+// The Markdown source may contain raw HTML,
+// but Go templates have already been processed.
+func renderMarkdown(src []byte) ([]byte, error) {
+	// parser.WithHeadingAttribute allows custom ids on headings.
+	// html.WithUnsafe allows use of raw HTML, which we need for tables.
+	md := goldmark.New(
+		goldmark.WithParserOptions(parser.WithHeadingAttribute()),
+		goldmark.WithRendererOptions(html.WithUnsafe()))
+	var buf bytes.Buffer
+	if err := md.Convert(src, &buf); err != nil {
+		return nil, err
+	}
+	return buf.Bytes(), nil
+}
diff --git a/internal/godoc/meta.go b/internal/godoc/meta.go
new file mode 100644
index 0000000..c180254
--- /dev/null
+++ b/internal/godoc/meta.go
@@ -0,0 +1,161 @@
+// Copyright 2009 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.
+
+package godoc
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"log"
+	"os"
+	pathpkg "path"
+	"strings"
+	"time"
+
+	"golang.org/x/website/internal/godoc/vfs"
+)
+
+var (
+	doctype   = []byte("<!DOCTYPE ")
+	jsonStart = []byte("<!--{")
+	jsonEnd   = []byte("}-->")
+)
+
+// ----------------------------------------------------------------------------
+// Documentation Metadata
+
+type Metadata struct {
+	// These fields can be set in the JSON header at the top of a doc.
+	Title    string
+	Subtitle string
+	Template bool     // execute as template
+	Path     string   // canonical path for this page
+	AltPaths []string // redirect these other paths to this page
+
+	// These are internal to the implementation.
+	filePath string // filesystem path relative to goroot
+}
+
+func (m *Metadata) FilePath() string { return m.filePath }
+
+// extractMetadata extracts the Metadata from a byte slice.
+// It returns the Metadata value and the remaining data.
+// If no metadata is present the original byte slice is returned.
+//
+func extractMetadata(b []byte) (meta Metadata, tail []byte, err error) {
+	tail = b
+	if !bytes.HasPrefix(b, jsonStart) {
+		return
+	}
+	end := bytes.Index(b, jsonEnd)
+	if end < 0 {
+		return
+	}
+	b = b[len(jsonStart)-1 : end+1] // drop leading <!-- and include trailing }
+	if err = json.Unmarshal(b, &meta); err != nil {
+		return
+	}
+	tail = tail[end+len(jsonEnd):]
+	return
+}
+
+// UpdateMetadata scans $GOROOT/doc for HTML and Markdown files, reads their metadata,
+// and updates the DocMetadata map.
+func (c *Corpus) updateMetadata() {
+	metadata := make(map[string]*Metadata)
+	var scan func(string) // scan is recursive
+	scan = func(dir string) {
+		fis, err := c.fs.ReadDir(dir)
+		if err != nil {
+			if dir == "/doc" && errors.Is(err, os.ErrNotExist) {
+				// Be quiet during tests that don't have a /doc tree.
+				return
+			}
+			log.Printf("updateMetadata %s: %v", dir, err)
+			return
+		}
+		for _, fi := range fis {
+			name := pathpkg.Join(dir, fi.Name())
+			if fi.IsDir() {
+				scan(name) // recurse
+				continue
+			}
+			if !strings.HasSuffix(name, ".html") && !strings.HasSuffix(name, ".md") {
+				continue
+			}
+			// Extract metadata from the file.
+			b, err := vfs.ReadFile(c.fs, name)
+			if err != nil {
+				log.Printf("updateMetadata %s: %v", name, err)
+				continue
+			}
+			meta, _, err := extractMetadata(b)
+			if err != nil {
+				log.Printf("updateMetadata: %s: %v", name, err)
+				continue
+			}
+			// Present all .md as if they were .html,
+			// so that it doesn't matter which one a page is written in.
+			if strings.HasSuffix(name, ".md") {
+				name = strings.TrimSuffix(name, ".md") + ".html"
+			}
+			// Store relative filesystem path in Metadata.
+			meta.filePath = name
+			if meta.Path == "" {
+				// If no Path, canonical path is actual path with .html removed.
+				meta.Path = strings.TrimSuffix(name, ".html")
+			}
+			// Store under both paths.
+			metadata[meta.Path] = &meta
+			metadata[meta.filePath] = &meta
+			for _, path := range meta.AltPaths {
+				metadata[path] = &meta
+			}
+		}
+	}
+	scan("/doc")
+	c.docMetadata.Set(metadata)
+}
+
+// MetadataFor returns the *Metadata for a given relative path or nil if none
+// exists.
+//
+func (c *Corpus) MetadataFor(relpath string) *Metadata {
+	if m, _ := c.docMetadata.Get(); m != nil {
+		meta := m.(map[string]*Metadata)
+		// If metadata for this relpath exists, return it.
+		if p := meta[relpath]; p != nil {
+			return p
+		}
+		// Try with or without trailing slash.
+		if strings.HasSuffix(relpath, "/") {
+			relpath = relpath[:len(relpath)-1]
+		} else {
+			relpath = relpath + "/"
+		}
+		return meta[relpath]
+	}
+	return nil
+}
+
+// refreshMetadata sends a signal to update DocMetadata. If a refresh is in
+// progress the metadata will be refreshed again afterward.
+//
+func (c *Corpus) refreshMetadata() {
+	select {
+	case c.refreshMetadataSignal <- true:
+	default:
+	}
+}
+
+// RefreshMetadataLoop runs forever, updating DocMetadata when the underlying
+// file system changes. It should be launched in a goroutine.
+func (c *Corpus) refreshMetadataLoop() {
+	for {
+		<-c.refreshMetadataSignal
+		c.updateMetadata()
+		time.Sleep(10 * time.Second) // at most once every 10 seconds
+	}
+}
diff --git a/internal/godoc/page.go b/internal/godoc/page.go
new file mode 100644
index 0000000..5833a89
--- /dev/null
+++ b/internal/godoc/page.go
@@ -0,0 +1,82 @@
+// Copyright 2009 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.
+
+package godoc
+
+import (
+	"net/http"
+	"os"
+	"path/filepath"
+	"runtime"
+	"strings"
+
+	"golang.org/x/website/internal/godoc/golangorgenv"
+)
+
+// Page describes the contents of the top-level godoc webpage.
+type Page struct {
+	Title    string
+	Tabtitle string
+	Subtitle string
+	SrcPath  string
+	Query    string
+	Body     []byte
+	GoogleCN bool // page is being served from golang.google.cn
+	TreeView bool // page needs to contain treeview related js and css
+
+	// filled in by ServePage
+	SearchBox       bool
+	Playground      bool
+	Version         string
+	GoogleAnalytics string
+}
+
+func (p *Presentation) ServePage(w http.ResponseWriter, page Page) {
+	if page.Tabtitle == "" {
+		page.Tabtitle = page.Title
+	}
+	page.SearchBox = p.Corpus.IndexEnabled
+	page.Playground = p.ShowPlayground
+	page.Version = runtime.Version()
+	page.GoogleAnalytics = p.GoogleAnalytics
+	applyTemplateToResponseWriter(w, p.GodocHTML, page)
+}
+
+func (p *Presentation) ServeError(w http.ResponseWriter, r *http.Request, relpath string, err error) {
+	w.WriteHeader(http.StatusNotFound)
+	if perr, ok := err.(*os.PathError); ok {
+		rel, err := filepath.Rel(runtime.GOROOT(), perr.Path)
+		if err != nil {
+			perr.Path = "REDACTED"
+		} else {
+			perr.Path = filepath.Join("$GOROOT", rel)
+		}
+	}
+	p.ServePage(w, Page{
+		Title:           "File " + relpath,
+		Subtitle:        relpath,
+		Body:            applyTemplate(p.ErrorHTML, "errorHTML", err),
+		GoogleCN:        googleCN(r),
+		GoogleAnalytics: p.GoogleAnalytics,
+	})
+}
+
+// googleCN reports whether request r is considered
+// to be served from golang.google.cn.
+func googleCN(r *http.Request) bool {
+	if r.FormValue("googlecn") != "" {
+		return true
+	}
+	if strings.HasSuffix(r.Host, ".cn") {
+		return true
+	}
+	if !golangorgenv.CheckCountry() {
+		return false
+	}
+	switch r.Header.Get("X-Appengine-Country") {
+	case "", "ZZ", "CN":
+		return true
+	}
+	return false
+}
diff --git a/internal/godoc/parser.go b/internal/godoc/parser.go
new file mode 100644
index 0000000..8712918
--- /dev/null
+++ b/internal/godoc/parser.go
@@ -0,0 +1,74 @@
+// Copyright 2011 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.
+
+// This file contains support functions for parsing .go files
+// accessed via godoc's file system fs.
+
+package godoc
+
+import (
+	"bytes"
+	"go/ast"
+	"go/parser"
+	"go/token"
+	pathpkg "path"
+
+	"golang.org/x/website/internal/godoc/vfs"
+)
+
+var linePrefix = []byte("//line ")
+
+// This function replaces source lines starting with "//line " with a blank line.
+// It does this irrespective of whether the line is truly a line comment or not;
+// e.g., the line may be inside a string, or a /*-style comment; however that is
+// rather unlikely (proper testing would require a full Go scan which we want to
+// avoid for performance).
+func replaceLinePrefixCommentsWithBlankLine(src []byte) {
+	for {
+		i := bytes.Index(src, linePrefix)
+		if i < 0 {
+			break // we're done
+		}
+		// 0 <= i && i+len(linePrefix) <= len(src)
+		if i == 0 || src[i-1] == '\n' {
+			// at beginning of line: blank out line
+			for i < len(src) && src[i] != '\n' {
+				src[i] = ' '
+				i++
+			}
+		} else {
+			// not at beginning of line: skip over prefix
+			i += len(linePrefix)
+		}
+		// i <= len(src)
+		src = src[i:]
+	}
+}
+
+func (c *Corpus) parseFile(fset *token.FileSet, filename string, mode parser.Mode) (*ast.File, error) {
+	src, err := vfs.ReadFile(c.fs, filename)
+	if err != nil {
+		return nil, err
+	}
+
+	// Temporary ad-hoc fix for issue 5247.
+	// TODO(gri,dmitshur) Remove this in favor of a better fix, eventually (see issue 32092).
+	replaceLinePrefixCommentsWithBlankLine(src)
+
+	return parser.ParseFile(fset, filename, src, mode)
+}
+
+func (c *Corpus) parseFiles(fset *token.FileSet, relpath string, abspath string, localnames []string) (map[string]*ast.File, error) {
+	files := make(map[string]*ast.File)
+	for _, f := range localnames {
+		absname := pathpkg.Join(abspath, f)
+		file, err := c.parseFile(fset, absname, parser.ParseComments)
+		if err != nil {
+			return nil, err
+		}
+		files[pathpkg.Join(relpath, f)] = file
+	}
+
+	return files, nil
+}
diff --git a/internal/godoc/pres.go b/internal/godoc/pres.go
new file mode 100644
index 0000000..fb17a42
--- /dev/null
+++ b/internal/godoc/pres.go
@@ -0,0 +1,164 @@
+// 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.
+
+package godoc
+
+import (
+	"net/http"
+	"regexp"
+	"sync"
+	"text/template"
+
+	"golang.org/x/website/internal/godoc/vfs/httpfs"
+)
+
+// SearchResultFunc functions return an HTML body for displaying search results.
+type SearchResultFunc func(p *Presentation, result SearchResult) []byte
+
+// Presentation generates output from a corpus.
+type Presentation struct {
+	Corpus *Corpus
+
+	mux        *http.ServeMux
+	fileServer http.Handler
+	cmdHandler handlerServer
+	pkgHandler handlerServer
+
+	CallGraphHTML,
+	DirlistHTML,
+	ErrorHTML,
+	ExampleHTML,
+	GodocHTML,
+	ImplementsHTML,
+	MethodSetHTML,
+	PackageHTML,
+	PackageRootHTML,
+	SearchHTML,
+	SearchDocHTML,
+	SearchCodeHTML,
+	SearchTxtHTML,
+	SearchDescXML *template.Template // If not nil, register a /opensearch.xml handler with this template.
+
+	// TabWidth optionally specifies the tab width.
+	TabWidth int
+
+	ShowTimestamps bool
+	ShowPlayground bool
+	DeclLinks      bool
+
+	// NotesRx optionally specifies a regexp to match
+	// notes to render in the output.
+	NotesRx *regexp.Regexp
+
+	// AdjustPageInfoMode optionally specifies a function to
+	// modify the PageInfoMode of a request. The default chosen
+	// value is provided.
+	AdjustPageInfoMode func(req *http.Request, mode PageInfoMode) PageInfoMode
+
+	// URLForSrc optionally specifies a function that takes a source file and
+	// returns a URL for it.
+	// The source file argument has the form /src/<path>/<filename>.
+	URLForSrc func(src string) string
+
+	// URLForSrcPos optionally specifies a function to create a URL given a
+	// source file, a line from the source file (1-based), and low & high offset
+	// positions (0-based, bytes from beginning of file). Ideally, the returned
+	// URL will be for the specified line of the file, while the high & low
+	// positions will be used to highlight a section of the file.
+	// The source file argument has the form /src/<path>/<filename>.
+	URLForSrcPos func(src string, line, low, high int) string
+
+	// URLForSrcQuery optionally specifies a function to create a URL given a
+	// source file, a query string, and a line from the source file (1-based).
+	// The source file argument has the form /src/<path>/<filename>.
+	// The query argument will be escaped for the purposes of embedding in a URL
+	// query parameter.
+	// Ideally, the returned URL will be for the specified line of the file with
+	// the query string highlighted.
+	URLForSrcQuery func(src, query string, line int) string
+
+	// SearchResults optionally specifies a list of functions returning an HTML
+	// body for displaying search results.
+	SearchResults []SearchResultFunc
+
+	// GoogleAnalytics optionally adds Google Analytics via the provided
+	// tracking ID to each page.
+	GoogleAnalytics string
+
+	initFuncMapOnce sync.Once
+	funcMap         template.FuncMap
+	templateFuncs   template.FuncMap
+}
+
+// NewPresentation returns a new Presentation from a corpus.
+// It sets SearchResults to:
+// [SearchResultDoc SearchResultCode SearchResultTxt].
+func NewPresentation(c *Corpus) *Presentation {
+	if c == nil {
+		panic("nil Corpus")
+	}
+	p := &Presentation{
+		Corpus:     c,
+		mux:        http.NewServeMux(),
+		fileServer: http.FileServer(httpfs.New(c.fs)),
+
+		TabWidth:  4,
+		DeclLinks: true,
+		SearchResults: []SearchResultFunc{
+			(*Presentation).SearchResultDoc,
+			(*Presentation).SearchResultCode,
+			(*Presentation).SearchResultTxt,
+		},
+	}
+	p.cmdHandler = handlerServer{
+		p:       p,
+		c:       c,
+		pattern: "/cmd/",
+		fsRoot:  "/src",
+	}
+	p.pkgHandler = handlerServer{
+		p:           p,
+		c:           c,
+		pattern:     "/pkg/",
+		stripPrefix: "pkg/",
+		fsRoot:      "/src",
+		exclude:     []string{"/src/cmd"},
+	}
+	p.cmdHandler.registerWithMux(p.mux)
+	p.pkgHandler.registerWithMux(p.mux)
+	p.mux.HandleFunc("/", p.ServeFile)
+	p.mux.HandleFunc("/search", p.HandleSearch)
+	if p.SearchDescXML != nil {
+		p.mux.HandleFunc("/opensearch.xml", p.serveSearchDesc)
+	}
+	return p
+}
+
+func (p *Presentation) FileServer() http.Handler {
+	return p.fileServer
+}
+
+func (p *Presentation) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	p.mux.ServeHTTP(w, r)
+}
+
+func (p *Presentation) PkgFSRoot() string {
+	return p.pkgHandler.fsRoot
+}
+
+func (p *Presentation) CmdFSRoot() string {
+	return p.cmdHandler.fsRoot
+}
+
+// TODO(bradfitz): move this to be a method on Corpus. Just moving code around for now,
+// but this doesn't feel right.
+func (p *Presentation) GetPkgPageInfo(abspath, relpath string, mode PageInfoMode) *PageInfo {
+	return p.pkgHandler.GetPageInfo(abspath, relpath, mode, "", "")
+}
+
+// TODO(bradfitz): move this to be a method on Corpus. Just moving code around for now,
+// but this doesn't feel right.
+func (p *Presentation) GetCmdPageInfo(abspath, relpath string, mode PageInfoMode) *PageInfo {
+	return p.cmdHandler.GetPageInfo(abspath, relpath, mode, "", "")
+}
diff --git a/internal/godoc/redirect/hash.go b/internal/godoc/redirect/hash.go
new file mode 100644
index 0000000..d5a1e3e
--- /dev/null
+++ b/internal/godoc/redirect/hash.go
@@ -0,0 +1,138 @@
+// Copyright 2014 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.
+
+// This file provides a compact encoding of
+// a map of Mercurial hashes to Git hashes.
+
+package redirect
+
+import (
+	"encoding/binary"
+	"fmt"
+	"io"
+	"os"
+	"sort"
+	"strconv"
+	"strings"
+)
+
+// hashMap is a map of Mercurial hashes to Git hashes.
+type hashMap struct {
+	file    *os.File
+	entries int
+}
+
+// newHashMap takes a file handle that contains a map of Mercurial to Git
+// hashes. The file should be a sequence of pairs of little-endian encoded
+// uint32s, representing a hgHash and a gitHash respectively.
+// The sequence must be sorted by hgHash.
+// The file must remain open for as long as the returned hashMap is used.
+func newHashMap(f *os.File) (*hashMap, error) {
+	fi, err := f.Stat()
+	if err != nil {
+		return nil, err
+	}
+	return &hashMap{file: f, entries: int(fi.Size() / 8)}, nil
+}
+
+// Lookup finds an hgHash in the map that matches the given prefix, and returns
+// its corresponding gitHash. The prefix must be at least 8 characters long.
+func (m *hashMap) Lookup(s string) gitHash {
+	if m == nil {
+		return 0
+	}
+	hg, err := hgHashFromString(s)
+	if err != nil {
+		return 0
+	}
+	var git gitHash
+	b := make([]byte, 8)
+	sort.Search(m.entries, func(i int) bool {
+		n, err := m.file.ReadAt(b, int64(i*8))
+		if err != nil {
+			panic(err)
+		}
+		if n != 8 {
+			panic(io.ErrUnexpectedEOF)
+		}
+		v := hgHash(binary.LittleEndian.Uint32(b[:4]))
+		if v == hg {
+			git = gitHash(binary.LittleEndian.Uint32(b[4:]))
+		}
+		return v >= hg
+	})
+	return git
+}
+
+// hgHash represents the lower (leftmost) 32 bits of a Mercurial hash.
+type hgHash uint32
+
+func (h hgHash) String() string {
+	return intToHash(int64(h))
+}
+
+func hgHashFromString(s string) (hgHash, error) {
+	if len(s) < 8 {
+		return 0, fmt.Errorf("string too small: len(s) = %d", len(s))
+	}
+	hash := s[:8]
+	i, err := strconv.ParseInt(hash, 16, 64)
+	if err != nil {
+		return 0, err
+	}
+	return hgHash(i), nil
+}
+
+// gitHash represents the leftmost 28 bits of a Git hash in its upper 28 bits,
+// and it encodes hash's repository in the lower 4  bits.
+type gitHash uint32
+
+func (h gitHash) Hash() string {
+	return intToHash(int64(h))[:7]
+}
+
+func (h gitHash) Repo() string {
+	return repo(h & 0xF).String()
+}
+
+func intToHash(i int64) string {
+	s := strconv.FormatInt(i, 16)
+	if len(s) < 8 {
+		s = strings.Repeat("0", 8-len(s)) + s
+	}
+	return s
+}
+
+// repo represents a Go Git repository.
+type repo byte
+
+const (
+	repoGo repo = iota
+	repoBlog
+	repoCrypto
+	repoExp
+	repoImage
+	repoMobile
+	repoNet
+	repoSys
+	repoTalks
+	repoText
+	repoTools
+)
+
+func (r repo) String() string {
+	return map[repo]string{
+		repoGo:     "go",
+		repoBlog:   "blog",
+		repoCrypto: "crypto",
+		repoExp:    "exp",
+		repoImage:  "image",
+		repoMobile: "mobile",
+		repoNet:    "net",
+		repoSys:    "sys",
+		repoTalks:  "talks",
+		repoText:   "text",
+		repoTools:  "tools",
+	}[r]
+}
diff --git a/internal/godoc/redirect/redirect.go b/internal/godoc/redirect/redirect.go
new file mode 100644
index 0000000..059d264
--- /dev/null
+++ b/internal/godoc/redirect/redirect.go
@@ -0,0 +1,324 @@
+// 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.
+
+// Package redirect provides hooks to register HTTP handlers that redirect old
+// godoc paths to their new equivalents and assist in accessing the issue
+// tracker, wiki, code review system, etc.
+package redirect // import "golang.org/x/website/internal/godoc/redirect"
+
+import (
+	"context"
+	"fmt"
+	"html/template"
+	"net/http"
+	"os"
+	"regexp"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	"golang.org/x/net/context/ctxhttp"
+)
+
+// Register registers HTTP handlers that redirect old godoc paths to their new
+// equivalents and assist in accessing the issue tracker, wiki, code review
+// system, etc. If mux is nil it uses http.DefaultServeMux.
+func Register(mux *http.ServeMux) {
+	if mux == nil {
+		mux = http.DefaultServeMux
+	}
+	handlePathRedirects(mux, pkgRedirects, "/pkg/")
+	handlePathRedirects(mux, cmdRedirects, "/cmd/")
+	for prefix, redirect := range prefixHelpers {
+		p := "/" + prefix + "/"
+		mux.Handle(p, PrefixHandler(p, redirect))
+	}
+	for path, redirect := range redirects {
+		mux.Handle(path, Handler(redirect))
+	}
+	// NB: /src/pkg (sans trailing slash) is the index of packages.
+	mux.HandleFunc("/src/pkg/", srcPkgHandler)
+	mux.HandleFunc("/cl/", clHandler)
+	mux.HandleFunc("/change/", changeHandler)
+	mux.HandleFunc("/design/", designHandler)
+}
+
+func handlePathRedirects(mux *http.ServeMux, redirects map[string]string, prefix string) {
+	for source, target := range redirects {
+		h := Handler(prefix + target + "/")
+		p := prefix + source
+		mux.Handle(p, h)
+		mux.Handle(p+"/", h)
+	}
+}
+
+// Packages that were renamed between r60 and go1.
+var pkgRedirects = map[string]string{
+	"asn1":              "encoding/asn1",
+	"big":               "math/big",
+	"cmath":             "math/cmplx",
+	"csv":               "encoding/csv",
+	"exec":              "os/exec",
+	"exp/template/html": "html/template",
+	"gob":               "encoding/gob",
+	"http":              "net/http",
+	"http/cgi":          "net/http/cgi",
+	"http/fcgi":         "net/http/fcgi",
+	"http/httptest":     "net/http/httptest",
+	"http/pprof":        "net/http/pprof",
+	"json":              "encoding/json",
+	"mail":              "net/mail",
+	"rand":              "math/rand",
+	"rpc":               "net/rpc",
+	"rpc/jsonrpc":       "net/rpc/jsonrpc",
+	"scanner":           "text/scanner",
+	"smtp":              "net/smtp",
+	"tabwriter":         "text/tabwriter",
+	"template":          "text/template",
+	"template/parse":    "text/template/parse",
+	"url":               "net/url",
+	"utf16":             "unicode/utf16",
+	"utf8":              "unicode/utf8",
+	"xml":               "encoding/xml",
+}
+
+// Commands that were renamed between r60 and go1.
+var cmdRedirects = map[string]string{
+	"gofix":     "fix",
+	"goinstall": "go",
+	"gopack":    "pack",
+	"gotest":    "go",
+	"govet":     "vet",
+	"goyacc":    "yacc",
+}
+
+var redirects = map[string]string{
+	"/blog":       "/blog/",
+	"/build":      "http://build.golang.org",
+	"/change":     "https://go.googlesource.com/go",
+	"/cl":         "https://go-review.googlesource.com",
+	"/cmd/godoc/": "https://pkg.go.dev/golang.org/x/tools/cmd/godoc",
+	"/issue":      "https://github.com/golang/go/issues",
+	"/issue/new":  "https://github.com/golang/go/issues/new",
+	"/issues":     "https://github.com/golang/go/issues",
+	"/issues/new": "https://github.com/golang/go/issues/new",
+	"/play":       "http://play.golang.org",
+	"/design":     "https://go.googlesource.com/proposal/+/master/design",
+
+	// In Go 1.2 the references page is part of /doc/.
+	"/ref": "/doc/#references",
+	// This next rule clobbers /ref/spec and /ref/mem.
+	// TODO(adg): figure out what to do here, if anything.
+	// "/ref/": "/doc/#references",
+
+	// Be nice to people who are looking in the wrong place.
+	"/doc/mem":  "/ref/mem",
+	"/doc/spec": "/ref/spec",
+
+	"/talks": "http://talks.golang.org",
+	"/tour":  "http://tour.golang.org",
+	"/wiki":  "https://github.com/golang/go/wiki",
+
+	"/doc/articles/c_go_cgo.html":                    "/blog/c-go-cgo",
+	"/doc/articles/concurrency_patterns.html":        "/blog/go-concurrency-patterns-timing-out-and",
+	"/doc/articles/defer_panic_recover.html":         "/blog/defer-panic-and-recover",
+	"/doc/articles/error_handling.html":              "/blog/error-handling-and-go",
+	"/doc/articles/gobs_of_data.html":                "/blog/gobs-of-data",
+	"/doc/articles/godoc_documenting_go_code.html":   "/blog/godoc-documenting-go-code",
+	"/doc/articles/gos_declaration_syntax.html":      "/blog/gos-declaration-syntax",
+	"/doc/articles/image_draw.html":                  "/blog/go-imagedraw-package",
+	"/doc/articles/image_package.html":               "/blog/go-image-package",
+	"/doc/articles/json_and_go.html":                 "/blog/json-and-go",
+	"/doc/articles/json_rpc_tale_of_interfaces.html": "/blog/json-rpc-tale-of-interfaces",
+	"/doc/articles/laws_of_reflection.html":          "/blog/laws-of-reflection",
+	"/doc/articles/slices_usage_and_internals.html":  "/blog/go-slices-usage-and-internals",
+	"/doc/go_for_cpp_programmers.html":               "/wiki/GoForCPPProgrammers",
+	"/doc/go_tutorial.html":                          "http://tour.golang.org/",
+}
+
+var prefixHelpers = map[string]string{
+	"issue":  "https://github.com/golang/go/issues/",
+	"issues": "https://github.com/golang/go/issues/",
+	"play":   "http://play.golang.org/",
+	"talks":  "http://talks.golang.org/",
+	"wiki":   "https://github.com/golang/go/wiki/",
+}
+
+func Handler(target string) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		url := target
+		if qs := r.URL.RawQuery; qs != "" {
+			url += "?" + qs
+		}
+		http.Redirect(w, r, url, http.StatusMovedPermanently)
+	})
+}
+
+var validID = regexp.MustCompile(`^[A-Za-z0-9-]*/?$`)
+
+func PrefixHandler(prefix, baseURL string) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		if p := r.URL.Path; p == prefix {
+			// redirect /prefix/ to /prefix
+			http.Redirect(w, r, p[:len(p)-1], http.StatusFound)
+			return
+		}
+		id := r.URL.Path[len(prefix):]
+		if !validID.MatchString(id) {
+			http.Error(w, "Not found", http.StatusNotFound)
+			return
+		}
+		target := baseURL + id
+		http.Redirect(w, r, target, http.StatusFound)
+	})
+}
+
+// Redirect requests from the old "/src/pkg/foo" to the new "/src/foo".
+// See http://golang.org/s/go14nopkg
+func srcPkgHandler(w http.ResponseWriter, r *http.Request) {
+	r.URL.Path = "/src/" + r.URL.Path[len("/src/pkg/"):]
+	http.Redirect(w, r, r.URL.String(), http.StatusMovedPermanently)
+}
+
+func clHandler(w http.ResponseWriter, r *http.Request) {
+	const prefix = "/cl/"
+	if p := r.URL.Path; p == prefix {
+		// redirect /prefix/ to /prefix
+		http.Redirect(w, r, p[:len(p)-1], http.StatusFound)
+		return
+	}
+	id := r.URL.Path[len(prefix):]
+	// support /cl/152700045/, which is used in commit 0edafefc36.
+	id = strings.TrimSuffix(id, "/")
+	if !validID.MatchString(id) {
+		http.Error(w, "Not found", http.StatusNotFound)
+		return
+	}
+	target := ""
+
+	if n, err := strconv.Atoi(id); err == nil && isRietveldCL(n) {
+		// Issue 28836: if this Rietveld CL happens to
+		// also be a Gerrit CL, render a disambiguation HTML
+		// page with two links instead. We need to make a
+		// Gerrit API call to figure that out, but we cache
+		// known Gerrit CLs so it's done at most once per CL.
+		if ok, err := isGerritCL(r.Context(), n); err == nil && ok {
+			w.Header().Set("Content-Type", "text/html; charset=utf-8")
+			clDisambiguationHTML.Execute(w, n)
+			return
+		}
+
+		target = "https://codereview.appspot.com/" + id
+	} else {
+		target = "https://go-review.googlesource.com/" + id
+	}
+	http.Redirect(w, r, target, http.StatusFound)
+}
+
+var clDisambiguationHTML = template.Must(template.New("").Parse(`<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>Go CL {{.}} Disambiguation</title>
+		<meta name="viewport" content="width=device-width">
+	</head>
+	<body>
+		CL number {{.}} exists in both Gerrit (the current code review system)
+		and Rietveld (the previous code review system). Please make a choice:
+
+		<ul>
+			<li><a href="https://go-review.googlesource.com/{{.}}">Gerrit CL {{.}}</a></li>
+			<li><a href="https://codereview.appspot.com/{{.}}">Rietveld CL {{.}}</a></li>
+		</ul>
+	</body>
+</html>`))
+
+// isGerritCL reports whether a Gerrit CL with the specified numeric change ID (e.g., "4247")
+// is known to exist by querying the Gerrit API at https://go-review.googlesource.com.
+// isGerritCL uses gerritCLCache as a cache of Gerrit CL IDs that exist.
+func isGerritCL(ctx context.Context, id int) (bool, error) {
+	// Check cache first.
+	gerritCLCache.Lock()
+	ok := gerritCLCache.exist[id]
+	gerritCLCache.Unlock()
+	if ok {
+		return true, nil
+	}
+
+	// Query the Gerrit API Get Change endpoint, as documented at
+	// https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-change.
+	ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
+	defer cancel()
+	resp, err := ctxhttp.Get(ctx, nil, fmt.Sprintf("https://go-review.googlesource.com/changes/%d", id))
+	if err != nil {
+		return false, err
+	}
+	resp.Body.Close()
+	switch resp.StatusCode {
+	case http.StatusOK:
+		// A Gerrit CL with this ID exists. Add it to cache.
+		gerritCLCache.Lock()
+		gerritCLCache.exist[id] = true
+		gerritCLCache.Unlock()
+		return true, nil
+	case http.StatusNotFound:
+		// A Gerrit CL with this ID doesn't exist. It may get created in the future.
+		return false, nil
+	default:
+		return false, fmt.Errorf("unexpected status code: %v", resp.Status)
+	}
+}
+
+var gerritCLCache = struct {
+	sync.Mutex
+	exist map[int]bool // exist is a set of Gerrit CL IDs that are known to exist.
+}{exist: make(map[int]bool)}
+
+var changeMap *hashMap
+
+// LoadChangeMap loads the specified map of Mercurial to Git revisions,
+// which is used by the /change/ handler to intelligently map old hg
+// revisions to their new git equivalents.
+// It should be called before calling Register.
+// The file should remain open as long as the process is running.
+// See the implementation of this package for details.
+func LoadChangeMap(filename string) error {
+	f, err := os.Open(filename)
+	if err != nil {
+		return err
+	}
+	m, err := newHashMap(f)
+	if err != nil {
+		return err
+	}
+	changeMap = m
+	return nil
+}
+
+func changeHandler(w http.ResponseWriter, r *http.Request) {
+	const prefix = "/change/"
+	if p := r.URL.Path; p == prefix {
+		// redirect /prefix/ to /prefix
+		http.Redirect(w, r, p[:len(p)-1], http.StatusFound)
+		return
+	}
+	hash := r.URL.Path[len(prefix):]
+	target := "https://go.googlesource.com/go/+/" + hash
+	if git := changeMap.Lookup(hash); git > 0 {
+		target = fmt.Sprintf("https://go.googlesource.com/%v/+/%v", git.Repo(), git.Hash())
+	}
+	http.Redirect(w, r, target, http.StatusFound)
+}
+
+func designHandler(w http.ResponseWriter, r *http.Request) {
+	const prefix = "/design/"
+	if p := r.URL.Path; p == prefix {
+		// redirect /prefix/ to /prefix
+		http.Redirect(w, r, p[:len(p)-1], http.StatusFound)
+		return
+	}
+	name := r.URL.Path[len(prefix):]
+	target := "https://go.googlesource.com/proposal/+/master/design/" + name + ".md"
+	http.Redirect(w, r, target, http.StatusFound)
+}
diff --git a/internal/godoc/redirect/redirect_test.go b/internal/godoc/redirect/redirect_test.go
new file mode 100644
index 0000000..756c0d0
--- /dev/null
+++ b/internal/godoc/redirect/redirect_test.go
@@ -0,0 +1,113 @@
+// Copyright 2015 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.
+
+package redirect
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+)
+
+type redirectResult struct {
+	status int
+	path   string
+}
+
+func errorResult(status int) redirectResult {
+	return redirectResult{status, ""}
+}
+
+func TestRedirects(t *testing.T) {
+	var tests = map[string]redirectResult{
+		"/build":    {301, "http://build.golang.org"},
+		"/ref":      {301, "/doc/#references"},
+		"/doc/mem":  {301, "/ref/mem"},
+		"/doc/spec": {301, "/ref/spec"},
+		"/tour":     {301, "http://tour.golang.org"},
+		"/foo":      errorResult(404),
+
+		"/pkg/asn1":           {301, "/pkg/encoding/asn1/"},
+		"/pkg/template/parse": {301, "/pkg/text/template/parse/"},
+
+		"/src/pkg/foo": {301, "/src/foo"},
+
+		"/cmd/gofix": {301, "/cmd/fix/"},
+
+		// git commits (/change)
+		// TODO: mercurial tags and LoadChangeMap.
+		"/change":   {301, "https://go.googlesource.com/go"},
+		"/change/a": {302, "https://go.googlesource.com/go/+/a"},
+
+		"/issue":                    {301, "https://github.com/golang/go/issues"},
+		"/issue?":                   {301, "https://github.com/golang/go/issues"},
+		"/issue/1":                  {302, "https://github.com/golang/go/issues/1"},
+		"/issue/new":                {301, "https://github.com/golang/go/issues/new"},
+		"/issue/new?a=b&c=d%20&e=f": {301, "https://github.com/golang/go/issues/new?a=b&c=d%20&e=f"},
+		"/issues":                   {301, "https://github.com/golang/go/issues"},
+		"/issues/1":                 {302, "https://github.com/golang/go/issues/1"},
+		"/issues/new":               {301, "https://github.com/golang/go/issues/new"},
+		"/issues/1/2/3":             errorResult(404),
+
+		"/wiki/foo":  {302, "https://github.com/golang/go/wiki/foo"},
+		"/wiki/foo/": {302, "https://github.com/golang/go/wiki/foo/"},
+
+		"/design":              {301, "https://go.googlesource.com/proposal/+/master/design"},
+		"/design/":             {302, "/design"},
+		"/design/123-foo":      {302, "https://go.googlesource.com/proposal/+/master/design/123-foo.md"},
+		"/design/text/123-foo": {302, "https://go.googlesource.com/proposal/+/master/design/text/123-foo.md"},
+
+		"/cl/1":          {302, "https://go-review.googlesource.com/1"},
+		"/cl/1/":         {302, "https://go-review.googlesource.com/1"},
+		"/cl/267120043":  {302, "https://codereview.appspot.com/267120043"},
+		"/cl/267120043/": {302, "https://codereview.appspot.com/267120043"},
+
+		// Verify that we're using the Rietveld CL table:
+		"/cl/152046": {302, "https://codereview.appspot.com/152046"},
+		"/cl/152047": {302, "https://go-review.googlesource.com/152047"},
+		"/cl/152048": {302, "https://codereview.appspot.com/152048"},
+
+		// And verify we're using the "bigEnoughAssumeRietveld" value:
+		"/cl/299999": {302, "https://go-review.googlesource.com/299999"},
+		"/cl/300000": {302, "https://codereview.appspot.com/300000"},
+	}
+
+	mux := http.NewServeMux()
+	Register(mux)
+	ts := httptest.NewServer(mux)
+	defer ts.Close()
+
+	for path, want := range tests {
+		if want.path != "" && want.path[0] == '/' {
+			// All redirects are absolute.
+			want.path = ts.URL + want.path
+		}
+
+		req, err := http.NewRequest("GET", ts.URL+path, nil)
+		if err != nil {
+			t.Errorf("(path: %q) unexpected error: %v", path, err)
+			continue
+		}
+
+		resp, err := http.DefaultTransport.RoundTrip(req)
+		if err != nil {
+			t.Errorf("(path: %q) unexpected error: %v", path, err)
+			continue
+		}
+
+		if resp.StatusCode != want.status {
+			t.Errorf("(path: %q) got status %d, want %d", path, resp.StatusCode, want.status)
+		}
+
+		if want.status != 301 && want.status != 302 {
+			// Not a redirect. Just check status.
+			continue
+		}
+
+		out, _ := resp.Location()
+		if got := out.String(); got != want.path {
+			t.Errorf("(path: %q) got %s, want %s", path, got, want.path)
+		}
+	}
+}
diff --git a/internal/godoc/redirect/rietveld.go b/internal/godoc/redirect/rietveld.go
new file mode 100644
index 0000000..81b1094
--- /dev/null
+++ b/internal/godoc/redirect/rietveld.go
@@ -0,0 +1,1093 @@
+// Copyright 2018 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.
+
+package redirect
+
+// bigEnoughAssumeRietveld is the value where CLs equal or great are
+// assumed to be on Rietveld. By including this threshold we shrink
+// the size of the table below. When Go amasses 150,000 more CLs, we'll
+// need to bump this number and regenerate the list below.
+const bigEnoughAssumeRietveld = 300000
+
+// isRietveldCL reports whether cl was a Rietveld CL number.
+func isRietveldCL(cl int) bool {
+	return cl >= bigEnoughAssumeRietveld || lowRietveldCL[cl]
+}
+
+// lowRietveldCLs are the old CL numbers assigned by Rietveld code
+// review system as used by Go prior to Gerrit which are less than
+// bigEnoughAssumeRietveld.
+//
+// This list of numbers is registered with the /cl/NNNN redirect
+// handler to disambiguate which code review system a particular
+// number corresponds to. In some rare cases there may be duplicates,
+// in which case we might render an HTML choice for the user.
+//
+// To re-generate this list, run:
+//
+// $ cd $GOROOT
+// $ git log 7d7c6a9..94151eb | grep "^    https://golang.org/cl/" | perl -ne 's,^\s+https://golang.org/cl/(\d+).*$,$1,; chomp; print "$_: true,\n" if $_ < 300000' | sort -n | uniq
+//
+// Note that we ignore the x/* repos because we didn't start using
+// "subrepos" until the Rietveld CLs numbers were already 4,000,000+,
+// well above bigEnoughAssumeRietveld.
+var lowRietveldCL = map[int]bool{
+	152046: true,
+	152048: true,
+	152049: true,
+	152050: true,
+	152051: true,
+	152052: true,
+	152055: true,
+	152056: true,
+	152057: true,
+	152072: true,
+	152073: true,
+	152075: true,
+	152076: true,
+	152077: true,
+	152078: true,
+	152079: true,
+	152080: true,
+	152082: true,
+	152084: true,
+	152085: true,
+	152086: true,
+	152088: true,
+	152089: true,
+	152091: true,
+	152098: true,
+	152101: true,
+	152102: true,
+	152105: true,
+	152106: true,
+	152107: true,
+	152108: true,
+	152109: true,
+	152110: true,
+	152114: true,
+	152117: true,
+	152118: true,
+	152120: true,
+	152123: true,
+	152124: true,
+	152128: true,
+	152130: true,
+	152131: true,
+	152138: true,
+	152141: true,
+	152142: true,
+	153048: true,
+	153049: true,
+	153050: true,
+	153051: true,
+	153055: true,
+	153056: true,
+	153057: true,
+	154043: true,
+	154044: true,
+	154045: true,
+	154049: true,
+	154055: true,
+	154057: true,
+	154058: true,
+	154059: true,
+	154061: true,
+	154064: true,
+	154065: true,
+	154067: true,
+	154068: true,
+	154069: true,
+	154071: true,
+	154072: true,
+	154073: true,
+	154076: true,
+	154079: true,
+	154096: true,
+	154097: true,
+	154099: true,
+	154100: true,
+	154101: true,
+	154102: true,
+	154108: true,
+	154118: true,
+	154121: true,
+	154122: true,
+	154123: true,
+	154125: true,
+	154126: true,
+	154128: true,
+	154136: true,
+	154138: true,
+	154139: true,
+	154140: true,
+	154141: true,
+	154142: true,
+	154143: true,
+	154144: true,
+	154145: true,
+	154146: true,
+	154152: true,
+	154153: true,
+	154156: true,
+	154159: true,
+	154161: true,
+	154166: true,
+	154167: true,
+	154169: true,
+	154171: true,
+	154172: true,
+	154173: true,
+	154174: true,
+	154175: true,
+	154176: true,
+	154177: true,
+	154178: true,
+	154179: true,
+	154180: true,
+	155041: true,
+	155042: true,
+	155045: true,
+	155047: true,
+	155048: true,
+	155049: true,
+	155050: true,
+	155054: true,
+	155055: true,
+	155056: true,
+	155057: true,
+	155058: true,
+	155059: true,
+	155061: true,
+	155062: true,
+	155063: true,
+	155065: true,
+	155067: true,
+	155069: true,
+	155072: true,
+	155074: true,
+	155075: true,
+	155077: true,
+	155078: true,
+	155079: true,
+	156041: true,
+	156044: true,
+	156045: true,
+	156046: true,
+	156047: true,
+	156051: true,
+	156052: true,
+	156054: true,
+	156055: true,
+	156056: true,
+	156058: true,
+	156059: true,
+	156060: true,
+	156061: true,
+	156062: true,
+	156063: true,
+	156066: true,
+	156067: true,
+	156070: true,
+	156071: true,
+	156073: true,
+	156075: true,
+	156077: true,
+	156079: true,
+	156080: true,
+	156081: true,
+	156083: true,
+	156084: true,
+	156085: true,
+	156086: true,
+	156089: true,
+	156091: true,
+	156092: true,
+	156093: true,
+	156094: true,
+	156097: true,
+	156099: true,
+	156100: true,
+	156102: true,
+	156103: true,
+	156104: true,
+	156106: true,
+	156107: true,
+	156108: true,
+	156109: true,
+	156110: true,
+	156113: true,
+	156115: true,
+	156116: true,
+	157041: true,
+	157042: true,
+	157043: true,
+	157044: true,
+	157046: true,
+	157053: true,
+	157055: true,
+	157056: true,
+	157058: true,
+	157060: true,
+	157061: true,
+	157062: true,
+	157065: true,
+	157066: true,
+	157067: true,
+	157068: true,
+	157069: true,
+	157071: true,
+	157072: true,
+	157073: true,
+	157074: true,
+	157075: true,
+	157076: true,
+	157077: true,
+	157082: true,
+	157084: true,
+	157085: true,
+	157087: true,
+	157088: true,
+	157091: true,
+	157095: true,
+	157096: true,
+	157099: true,
+	157100: true,
+	157101: true,
+	157102: true,
+	157103: true,
+	157104: true,
+	157106: true,
+	157110: true,
+	157111: true,
+	157112: true,
+	157114: true,
+	157116: true,
+	157119: true,
+	157140: true,
+	157142: true,
+	157143: true,
+	157144: true,
+	157146: true,
+	157147: true,
+	157149: true,
+	157151: true,
+	157152: true,
+	157153: true,
+	157154: true,
+	157156: true,
+	157157: true,
+	157158: true,
+	157159: true,
+	157160: true,
+	157162: true,
+	157166: true,
+	157167: true,
+	157168: true,
+	157170: true,
+	158041: true,
+	159044: true,
+	159049: true,
+	159050: true,
+	159051: true,
+	160043: true,
+	160044: true,
+	160045: true,
+	160046: true,
+	160047: true,
+	160054: true,
+	160056: true,
+	160057: true,
+	160059: true,
+	160060: true,
+	160061: true,
+	160064: true,
+	160065: true,
+	160069: true,
+	160070: true,
+	161049: true,
+	161050: true,
+	161056: true,
+	161058: true,
+	161060: true,
+	161061: true,
+	161069: true,
+	161070: true,
+	161073: true,
+	161075: true,
+	162041: true,
+	162044: true,
+	162046: true,
+	162053: true,
+	162054: true,
+	162055: true,
+	162056: true,
+	162057: true,
+	162058: true,
+	162059: true,
+	162061: true,
+	162062: true,
+	163042: true,
+	163044: true,
+	163049: true,
+	163050: true,
+	163051: true,
+	163052: true,
+	163053: true,
+	163055: true,
+	163058: true,
+	163061: true,
+	163062: true,
+	163064: true,
+	163067: true,
+	163068: true,
+	163069: true,
+	163070: true,
+	163071: true,
+	163072: true,
+	163082: true,
+	163083: true,
+	163085: true,
+	163088: true,
+	163091: true,
+	163092: true,
+	163097: true,
+	163098: true,
+	164043: true,
+	164047: true,
+	164049: true,
+	164052: true,
+	164053: true,
+	164056: true,
+	164059: true,
+	164060: true,
+	164062: true,
+	164068: true,
+	164069: true,
+	164071: true,
+	164073: true,
+	164074: true,
+	164075: true,
+	164078: true,
+	164079: true,
+	164081: true,
+	164082: true,
+	164083: true,
+	164085: true,
+	164086: true,
+	164088: true,
+	164090: true,
+	164091: true,
+	164092: true,
+	164093: true,
+	164094: true,
+	164095: true,
+	165042: true,
+	165044: true,
+	165045: true,
+	165048: true,
+	165049: true,
+	165050: true,
+	165051: true,
+	165055: true,
+	165057: true,
+	165058: true,
+	165059: true,
+	165061: true,
+	165062: true,
+	165063: true,
+	165064: true,
+	165065: true,
+	165068: true,
+	165070: true,
+	165076: true,
+	165078: true,
+	165080: true,
+	165083: true,
+	165086: true,
+	165097: true,
+	165100: true,
+	165101: true,
+	166041: true,
+	166043: true,
+	166044: true,
+	166047: true,
+	166049: true,
+	166052: true,
+	166053: true,
+	166055: true,
+	166058: true,
+	166059: true,
+	166060: true,
+	166064: true,
+	166066: true,
+	166067: true,
+	166068: true,
+	166070: true,
+	166071: true,
+	166072: true,
+	166073: true,
+	166074: true,
+	166076: true,
+	166077: true,
+	166078: true,
+	166080: true,
+	167043: true,
+	167044: true,
+	167047: true,
+	167050: true,
+	167055: true,
+	167057: true,
+	167058: true,
+	168041: true,
+	168045: true,
+	170042: true,
+	170043: true,
+	170044: true,
+	170046: true,
+	170047: true,
+	170048: true,
+	170049: true,
+	171044: true,
+	171046: true,
+	171047: true,
+	171048: true,
+	171051: true,
+	172041: true,
+	172042: true,
+	172043: true,
+	172045: true,
+	172049: true,
+	173041: true,
+	173044: true,
+	173045: true,
+	174042: true,
+	174047: true,
+	174048: true,
+	174050: true,
+	174051: true,
+	174052: true,
+	174053: true,
+	174063: true,
+	174064: true,
+	174072: true,
+	174076: true,
+	174077: true,
+	174078: true,
+	174082: true,
+	174083: true,
+	174087: true,
+	175045: true,
+	175046: true,
+	175047: true,
+	175048: true,
+	176056: true,
+	176057: true,
+	176058: true,
+	176061: true,
+	176062: true,
+	176063: true,
+	176064: true,
+	176066: true,
+	176067: true,
+	176070: true,
+	176071: true,
+	176076: true,
+	178043: true,
+	178044: true,
+	178046: true,
+	178048: true,
+	179047: true,
+	179055: true,
+	179061: true,
+	179062: true,
+	179063: true,
+	179067: true,
+	179069: true,
+	179070: true,
+	179072: true,
+	179079: true,
+	179088: true,
+	179095: true,
+	179096: true,
+	179097: true,
+	179099: true,
+	179105: true,
+	179106: true,
+	179108: true,
+	179118: true,
+	179120: true,
+	179125: true,
+	179126: true,
+	179128: true,
+	179129: true,
+	179130: true,
+	180044: true,
+	180045: true,
+	180046: true,
+	180047: true,
+	180048: true,
+	180049: true,
+	180050: true,
+	180052: true,
+	180053: true,
+	180054: true,
+	180055: true,
+	180056: true,
+	180057: true,
+	180059: true,
+	180061: true,
+	180064: true,
+	180065: true,
+	180068: true,
+	180069: true,
+	180070: true,
+	180074: true,
+	180075: true,
+	180081: true,
+	180082: true,
+	180085: true,
+	180092: true,
+	180099: true,
+	180105: true,
+	180108: true,
+	180112: true,
+	180118: true,
+	181041: true,
+	181043: true,
+	181044: true,
+	181045: true,
+	181049: true,
+	181050: true,
+	181055: true,
+	181057: true,
+	181058: true,
+	181059: true,
+	181063: true,
+	181071: true,
+	181073: true,
+	181075: true,
+	181077: true,
+	181080: true,
+	181083: true,
+	181084: true,
+	181085: true,
+	181086: true,
+	181087: true,
+	181089: true,
+	181097: true,
+	181099: true,
+	181102: true,
+	181111: true,
+	181130: true,
+	181135: true,
+	181137: true,
+	181138: true,
+	181139: true,
+	181151: true,
+	181152: true,
+	181153: true,
+	181155: true,
+	181156: true,
+	181157: true,
+	181158: true,
+	181160: true,
+	181161: true,
+	181163: true,
+	181164: true,
+	181171: true,
+	181179: true,
+	181183: true,
+	181184: true,
+	181186: true,
+	182041: true,
+	182043: true,
+	182044: true,
+	183042: true,
+	183043: true,
+	183044: true,
+	183047: true,
+	183049: true,
+	183065: true,
+	183066: true,
+	183073: true,
+	183074: true,
+	183075: true,
+	183083: true,
+	183084: true,
+	183087: true,
+	183088: true,
+	183090: true,
+	183095: true,
+	183104: true,
+	183107: true,
+	183109: true,
+	183111: true,
+	183112: true,
+	183113: true,
+	183116: true,
+	183123: true,
+	183124: true,
+	183125: true,
+	183126: true,
+	183132: true,
+	183133: true,
+	183135: true,
+	183136: true,
+	183137: true,
+	183138: true,
+	183139: true,
+	183140: true,
+	183141: true,
+	183142: true,
+	183153: true,
+	183155: true,
+	183156: true,
+	183157: true,
+	183160: true,
+	184043: true,
+	184055: true,
+	184058: true,
+	184059: true,
+	184068: true,
+	184069: true,
+	184079: true,
+	184080: true,
+	184081: true,
+	185043: true,
+	185045: true,
+	186042: true,
+	186043: true,
+	186073: true,
+	186076: true,
+	186077: true,
+	186078: true,
+	186079: true,
+	186081: true,
+	186095: true,
+	186108: true,
+	186113: true,
+	186115: true,
+	186116: true,
+	186118: true,
+	186119: true,
+	186132: true,
+	186137: true,
+	186138: true,
+	186139: true,
+	186143: true,
+	186144: true,
+	186145: true,
+	186146: true,
+	186147: true,
+	186148: true,
+	186159: true,
+	186160: true,
+	186161: true,
+	186165: true,
+	186169: true,
+	186173: true,
+	186180: true,
+	186210: true,
+	186211: true,
+	186212: true,
+	186213: true,
+	186214: true,
+	186215: true,
+	186216: true,
+	186228: true,
+	186229: true,
+	186230: true,
+	186232: true,
+	186234: true,
+	186255: true,
+	186263: true,
+	186276: true,
+	186279: true,
+	186282: true,
+	186283: true,
+	188043: true,
+	189042: true,
+	189057: true,
+	189059: true,
+	189062: true,
+	189078: true,
+	189080: true,
+	189083: true,
+	189088: true,
+	189093: true,
+	189095: true,
+	189096: true,
+	189098: true,
+	189100: true,
+	190041: true,
+	190042: true,
+	190043: true,
+	190044: true,
+	190059: true,
+	190062: true,
+	190068: true,
+	190074: true,
+	190076: true,
+	190077: true,
+	190079: true,
+	190085: true,
+	190088: true,
+	190103: true,
+	190104: true,
+	193055: true,
+	193066: true,
+	193067: true,
+	193070: true,
+	193075: true,
+	193079: true,
+	193080: true,
+	193081: true,
+	193091: true,
+	193092: true,
+	193095: true,
+	193101: true,
+	193104: true,
+	194043: true,
+	194045: true,
+	194046: true,
+	194050: true,
+	194051: true,
+	194052: true,
+	194053: true,
+	194064: true,
+	194066: true,
+	194069: true,
+	194071: true,
+	194072: true,
+	194073: true,
+	194074: true,
+	194076: true,
+	194077: true,
+	194078: true,
+	194082: true,
+	194084: true,
+	194085: true,
+	194090: true,
+	194091: true,
+	194092: true,
+	194094: true,
+	194097: true,
+	194098: true,
+	194099: true,
+	194100: true,
+	194114: true,
+	194116: true,
+	194118: true,
+	194119: true,
+	194120: true,
+	194121: true,
+	194122: true,
+	194126: true,
+	194129: true,
+	194131: true,
+	194132: true,
+	194133: true,
+	194134: true,
+	194146: true,
+	194151: true,
+	194156: true,
+	194157: true,
+	194159: true,
+	194161: true,
+	194165: true,
+	195041: true,
+	195044: true,
+	195050: true,
+	195051: true,
+	195052: true,
+	195068: true,
+	195075: true,
+	195076: true,
+	195079: true,
+	195080: true,
+	195081: true,
+	196042: true,
+	196044: true,
+	196050: true,
+	196051: true,
+	196055: true,
+	196056: true,
+	196061: true,
+	196063: true,
+	196065: true,
+	196070: true,
+	196071: true,
+	196075: true,
+	196077: true,
+	196079: true,
+	196087: true,
+	196088: true,
+	196090: true,
+	196091: true,
+	197041: true,
+	197042: true,
+	197043: true,
+	197044: true,
+	198044: true,
+	198045: true,
+	198046: true,
+	198048: true,
+	198049: true,
+	198050: true,
+	198053: true,
+	198057: true,
+	198058: true,
+	198066: true,
+	198071: true,
+	198074: true,
+	198081: true,
+	198084: true,
+	198085: true,
+	198102: true,
+	199042: true,
+	199044: true,
+	199045: true,
+	199046: true,
+	199047: true,
+	199052: true,
+	199054: true,
+	199057: true,
+	199066: true,
+	199070: true,
+	199082: true,
+	199091: true,
+	199094: true,
+	199096: true,
+	201041: true,
+	201042: true,
+	201043: true,
+	201047: true,
+	201048: true,
+	201049: true,
+	201058: true,
+	201061: true,
+	201064: true,
+	201065: true,
+	201068: true,
+	202042: true,
+	202043: true,
+	202044: true,
+	202051: true,
+	202054: true,
+	202055: true,
+	203043: true,
+	203050: true,
+	203051: true,
+	203053: true,
+	203060: true,
+	203062: true,
+	204042: true,
+	204044: true,
+	204048: true,
+	204052: true,
+	204053: true,
+	204061: true,
+	204062: true,
+	204064: true,
+	204065: true,
+	204067: true,
+	204068: true,
+	204069: true,
+	205042: true,
+	205044: true,
+	206043: true,
+	206044: true,
+	206047: true,
+	206050: true,
+	206051: true,
+	206052: true,
+	206053: true,
+	206054: true,
+	206055: true,
+	206058: true,
+	206059: true,
+	206060: true,
+	206067: true,
+	206069: true,
+	206077: true,
+	206078: true,
+	206079: true,
+	206084: true,
+	206089: true,
+	206101: true,
+	206107: true,
+	206109: true,
+	207043: true,
+	207044: true,
+	207049: true,
+	207050: true,
+	207051: true,
+	207052: true,
+	207053: true,
+	207054: true,
+	207055: true,
+	207061: true,
+	207062: true,
+	207069: true,
+	207071: true,
+	207085: true,
+	207086: true,
+	207087: true,
+	207088: true,
+	207095: true,
+	207096: true,
+	207102: true,
+	207103: true,
+	207106: true,
+	207108: true,
+	207110: true,
+	207111: true,
+	207112: true,
+	209041: true,
+	209042: true,
+	209043: true,
+	209044: true,
+	210042: true,
+	210043: true,
+	210044: true,
+	210047: true,
+	211041: true,
+	212041: true,
+	212045: true,
+	212046: true,
+	212047: true,
+	213041: true,
+	213042: true,
+	214042: true,
+	214046: true,
+	214049: true,
+	214050: true,
+	215042: true,
+	215048: true,
+	215050: true,
+	216043: true,
+	216046: true,
+	216047: true,
+	216052: true,
+	216053: true,
+	216054: true,
+	216059: true,
+	216068: true,
+	217041: true,
+	217044: true,
+	217047: true,
+	217048: true,
+	217049: true,
+	217056: true,
+	217058: true,
+	217059: true,
+	217060: true,
+	217061: true,
+	217064: true,
+	217066: true,
+	217069: true,
+	217071: true,
+	217085: true,
+	217086: true,
+	217088: true,
+	217093: true,
+	217094: true,
+	217108: true,
+	217109: true,
+	217111: true,
+	217115: true,
+	217116: true,
+	218042: true,
+	218044: true,
+	218046: true,
+	218050: true,
+	218060: true,
+	218061: true,
+	218063: true,
+	218064: true,
+	218065: true,
+	218070: true,
+	218071: true,
+	218072: true,
+	218074: true,
+	218076: true,
+	222041: true,
+	223041: true,
+	223043: true,
+	223044: true,
+	223050: true,
+	223052: true,
+	223054: true,
+	223058: true,
+	223059: true,
+	223061: true,
+	223068: true,
+	223069: true,
+	223070: true,
+	223071: true,
+	223073: true,
+	223075: true,
+	223076: true,
+	223083: true,
+	223087: true,
+	223094: true,
+	223096: true,
+	223101: true,
+	223106: true,
+	223108: true,
+	224041: true,
+	224042: true,
+	224043: true,
+	224045: true,
+	224051: true,
+	224053: true,
+	224057: true,
+	224060: true,
+	224061: true,
+	224062: true,
+	224063: true,
+	224068: true,
+	224069: true,
+	224081: true,
+	224084: true,
+	224087: true,
+	224090: true,
+	224096: true,
+	224105: true,
+	225042: true,
+	227041: true,
+	229045: true,
+	229046: true,
+	229048: true,
+	229049: true,
+	229050: true,
+	231042: true,
+	236041: true,
+	237041: true,
+	238041: true,
+	238042: true,
+	240041: true,
+	240042: true,
+	240043: true,
+	241041: true,
+	243041: true,
+	244041: true,
+	245041: true,
+	247041: true,
+	250041: true,
+	252041: true,
+	253041: true,
+	253045: true,
+	254043: true,
+	255042: true,
+	255043: true,
+	257041: true,
+	257042: true,
+	258041: true,
+	261041: true,
+	264041: true,
+	294042: true,
+	296042: true,
+}
diff --git a/internal/godoc/search.go b/internal/godoc/search.go
new file mode 100644
index 0000000..0e7509a
--- /dev/null
+++ b/internal/godoc/search.go
@@ -0,0 +1,186 @@
+// Copyright 2009 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.
+
+package godoc
+
+import (
+	"bytes"
+	"fmt"
+	"net/http"
+	"regexp"
+	"strings"
+)
+
+type SearchResult struct {
+	Query string
+	Alert string // error or warning message
+
+	// identifier matches
+	Pak HitList       // packages matching Query
+	Hit *LookupResult // identifier matches of Query
+	Alt *AltWords     // alternative identifiers to look for
+
+	// textual matches
+	Found    int         // number of textual occurrences found
+	Textual  []FileLines // textual matches of Query
+	Complete bool        // true if all textual occurrences of Query are reported
+	Idents   map[SpotKind][]Ident
+}
+
+func (c *Corpus) Lookup(query string) SearchResult {
+	result := &SearchResult{Query: query}
+
+	index, timestamp := c.CurrentIndex()
+	if index != nil {
+		// identifier search
+		if r, err := index.Lookup(query); err == nil {
+			result = r
+		} else if err != nil && !c.IndexFullText {
+			// ignore the error if full text search is enabled
+			// since the query may be a valid regular expression
+			result.Alert = "Error in query string: " + err.Error()
+			return *result
+		}
+
+		// full text search
+		if c.IndexFullText && query != "" {
+			rx, err := regexp.Compile(query)
+			if err != nil {
+				result.Alert = "Error in query regular expression: " + err.Error()
+				return *result
+			}
+			// If we get maxResults+1 results we know that there are more than
+			// maxResults results and thus the result may be incomplete (to be
+			// precise, we should remove one result from the result set, but
+			// nobody is going to count the results on the result page).
+			result.Found, result.Textual = index.LookupRegexp(rx, c.MaxResults+1)
+			result.Complete = result.Found <= c.MaxResults
+			if !result.Complete {
+				result.Found-- // since we looked for maxResults+1
+			}
+		}
+	}
+
+	// is the result accurate?
+	if c.IndexEnabled {
+		if ts := c.FSModifiedTime(); timestamp.Before(ts) {
+			// The index is older than the latest file system change under godoc's observation.
+			result.Alert = "Indexing in progress: result may be inaccurate"
+		}
+	} else {
+		result.Alert = "Search index disabled: no results available"
+	}
+
+	return *result
+}
+
+// SearchResultDoc optionally specifies a function returning an HTML body
+// displaying search results matching godoc documentation.
+func (p *Presentation) SearchResultDoc(result SearchResult) []byte {
+	return applyTemplate(p.SearchDocHTML, "searchDocHTML", result)
+}
+
+// SearchResultCode optionally specifies a function returning an HTML body
+// displaying search results matching source code.
+func (p *Presentation) SearchResultCode(result SearchResult) []byte {
+	return applyTemplate(p.SearchCodeHTML, "searchCodeHTML", result)
+}
+
+// SearchResultTxt optionally specifies a function returning an HTML body
+// displaying search results of textual matches.
+func (p *Presentation) SearchResultTxt(result SearchResult) []byte {
+	return applyTemplate(p.SearchTxtHTML, "searchTxtHTML", result)
+}
+
+// HandleSearch obtains results for the requested search and returns a page
+// to display them.
+func (p *Presentation) HandleSearch(w http.ResponseWriter, r *http.Request) {
+	query := strings.TrimSpace(r.FormValue("q"))
+	result := p.Corpus.Lookup(query)
+
+	var contents bytes.Buffer
+	for _, f := range p.SearchResults {
+		contents.Write(f(p, result))
+	}
+
+	var title string
+	if haveResults := contents.Len() > 0; haveResults {
+		title = fmt.Sprintf(`Results for query: %v`, query)
+		if !p.Corpus.IndexEnabled {
+			result.Alert = ""
+		}
+	} else {
+		title = fmt.Sprintf(`No results found for query %q`, query)
+	}
+
+	body := bytes.NewBuffer(applyTemplate(p.SearchHTML, "searchHTML", result))
+	body.Write(contents.Bytes())
+
+	p.ServePage(w, Page{
+		Title:    title,
+		Tabtitle: query,
+		Query:    query,
+		Body:     body.Bytes(),
+		GoogleCN: googleCN(r),
+	})
+}
+
+func (p *Presentation) serveSearchDesc(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/opensearchdescription+xml")
+	data := map[string]interface{}{
+		"BaseURL": fmt.Sprintf("http://%s", r.Host),
+	}
+	applyTemplateToResponseWriter(w, p.SearchDescXML, &data)
+}
+
+// tocColCount returns the no. of columns
+// to split the toc table to.
+func tocColCount(result SearchResult) int {
+	tocLen := tocLen(result)
+	colCount := 0
+	// Simple heuristic based on visual aesthetic in manual testing.
+	switch {
+	case tocLen <= 10:
+		colCount = 1
+	case tocLen <= 20:
+		colCount = 2
+	case tocLen <= 80:
+		colCount = 3
+	default:
+		colCount = 4
+	}
+	return colCount
+}
+
+// tocLen calculates the no. of items in the toc table
+// by going through various fields in the SearchResult
+// that is rendered in the UI.
+func tocLen(result SearchResult) int {
+	tocLen := 0
+	for _, val := range result.Idents {
+		if len(val) != 0 {
+			tocLen++
+		}
+	}
+	// If no identifiers, then just one item for the header text "Package <result.Query>".
+	// See searchcode.html for further details.
+	if len(result.Idents) == 0 {
+		tocLen++
+	}
+	if result.Hit != nil {
+		if len(result.Hit.Decls) > 0 {
+			tocLen += len(result.Hit.Decls)
+			// We need one extra item for the header text "Package-level declarations".
+			tocLen++
+		}
+		if len(result.Hit.Others) > 0 {
+			tocLen += len(result.Hit.Others)
+			// We need one extra item for the header text "Local declarations and uses".
+			tocLen++
+		}
+	}
+	// For "textual occurrences".
+	tocLen++
+	return tocLen
+}
diff --git a/internal/godoc/server.go b/internal/godoc/server.go
new file mode 100644
index 0000000..a8e0e8d
--- /dev/null
+++ b/internal/godoc/server.go
@@ -0,0 +1,858 @@
+// 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.
+
+package godoc
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"go/ast"
+	"go/build"
+	"go/doc"
+	"go/token"
+	htmlpkg "html"
+	htmltemplate "html/template"
+	"io"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	pathpkg "path"
+	"path/filepath"
+	"sort"
+	"strings"
+	"text/template"
+	"time"
+
+	"golang.org/x/website/internal/godoc/analysis"
+	"golang.org/x/website/internal/godoc/util"
+	"golang.org/x/website/internal/godoc/vfs"
+)
+
+// handlerServer is a migration from an old godoc http Handler type.
+// This should probably merge into something else.
+type handlerServer struct {
+	p           *Presentation
+	c           *Corpus  // copy of p.Corpus
+	pattern     string   // url pattern; e.g. "/pkg/"
+	stripPrefix string   // prefix to strip from import path; e.g. "pkg/"
+	fsRoot      string   // file system root to which the pattern is mapped; e.g. "/src"
+	exclude     []string // file system paths to exclude; e.g. "/src/cmd"
+}
+
+func (s *handlerServer) registerWithMux(mux *http.ServeMux) {
+	mux.Handle(s.pattern, s)
+}
+
+// GetPageInfo returns the PageInfo for a package directory abspath. If the
+// parameter genAST is set, an AST containing only the package exports is
+// computed (PageInfo.PAst), otherwise package documentation (PageInfo.Doc)
+// is extracted from the AST. If there is no corresponding package in the
+// directory, PageInfo.PAst and PageInfo.PDoc are nil. If there are no sub-
+// directories, PageInfo.Dirs is nil. If an error occurred, PageInfo.Err is
+// set to the respective error but the error is not logged.
+//
+func (h *handlerServer) GetPageInfo(abspath, relpath string, mode PageInfoMode, goos, goarch string) *PageInfo {
+	info := &PageInfo{Dirname: abspath, Mode: mode}
+
+	// Restrict to the package files that would be used when building
+	// the package on this system.  This makes sure that if there are
+	// separate implementations for, say, Windows vs Unix, we don't
+	// jumble them all together.
+	// Note: If goos/goarch aren't set, the current binary's GOOS/GOARCH
+	// are used.
+	ctxt := build.Default
+	ctxt.IsAbsPath = pathpkg.IsAbs
+	ctxt.IsDir = func(path string) bool {
+		fi, err := h.c.fs.Stat(filepath.ToSlash(path))
+		return err == nil && fi.IsDir()
+	}
+	ctxt.ReadDir = func(dir string) ([]os.FileInfo, error) {
+		f, err := h.c.fs.ReadDir(filepath.ToSlash(dir))
+		filtered := make([]os.FileInfo, 0, len(f))
+		for _, i := range f {
+			if mode&NoFiltering != 0 || i.Name() != "internal" {
+				filtered = append(filtered, i)
+			}
+		}
+		return filtered, err
+	}
+	ctxt.OpenFile = func(name string) (r io.ReadCloser, err error) {
+		data, err := vfs.ReadFile(h.c.fs, filepath.ToSlash(name))
+		if err != nil {
+			return nil, err
+		}
+		return ioutil.NopCloser(bytes.NewReader(data)), nil
+	}
+
+	// Make the syscall/js package always visible by default.
+	// It defaults to the host's GOOS/GOARCH, and golang.org's
+	// linux/amd64 means the wasm syscall/js package was blank.
+	// And you can't run godoc on js/wasm anyway, so host defaults
+	// don't make sense here.
+	if goos == "" && goarch == "" && relpath == "syscall/js" {
+		goos, goarch = "js", "wasm"
+	}
+	if goos != "" {
+		ctxt.GOOS = goos
+	}
+	if goarch != "" {
+		ctxt.GOARCH = goarch
+	}
+
+	pkginfo, err := ctxt.ImportDir(abspath, 0)
+	// continue if there are no Go source files; we still want the directory info
+	if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
+		info.Err = err
+		return info
+	}
+
+	// collect package files
+	pkgname := pkginfo.Name
+	pkgfiles := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
+	if len(pkgfiles) == 0 {
+		// Commands written in C have no .go files in the build.
+		// Instead, documentation may be found in an ignored file.
+		// The file may be ignored via an explicit +build ignore
+		// constraint (recommended), or by defining the package
+		// documentation (historic).
+		pkgname = "main" // assume package main since pkginfo.Name == ""
+		pkgfiles = pkginfo.IgnoredGoFiles
+	}
+
+	// get package information, if any
+	if len(pkgfiles) > 0 {
+		// build package AST
+		fset := token.NewFileSet()
+		files, err := h.c.parseFiles(fset, relpath, abspath, pkgfiles)
+		if err != nil {
+			info.Err = err
+			return info
+		}
+
+		// ignore any errors - they are due to unresolved identifiers
+		pkg, _ := ast.NewPackage(fset, files, poorMansImporter, nil)
+
+		// extract package documentation
+		info.FSet = fset
+		if mode&ShowSource == 0 {
+			// show extracted documentation
+			var m doc.Mode
+			if mode&NoFiltering != 0 {
+				m |= doc.AllDecls
+			}
+			if mode&AllMethods != 0 {
+				m |= doc.AllMethods
+			}
+			info.PDoc = doc.New(pkg, pathpkg.Clean(relpath), m) // no trailing '/' in importpath
+			if mode&NoTypeAssoc != 0 {
+				for _, t := range info.PDoc.Types {
+					info.PDoc.Consts = append(info.PDoc.Consts, t.Consts...)
+					info.PDoc.Vars = append(info.PDoc.Vars, t.Vars...)
+					info.PDoc.Funcs = append(info.PDoc.Funcs, t.Funcs...)
+					t.Consts = nil
+					t.Vars = nil
+					t.Funcs = nil
+				}
+				// for now we cannot easily sort consts and vars since
+				// go/doc.Value doesn't export the order information
+				sort.Sort(funcsByName(info.PDoc.Funcs))
+			}
+
+			// collect examples
+			testfiles := append(pkginfo.TestGoFiles, pkginfo.XTestGoFiles...)
+			files, err = h.c.parseFiles(fset, relpath, abspath, testfiles)
+			if err != nil {
+				log.Println("parsing examples:", err)
+			}
+			info.Examples = collectExamples(h.c, pkg, files)
+
+			// collect any notes that we want to show
+			if info.PDoc.Notes != nil {
+				// could regexp.Compile only once per godoc, but probably not worth it
+				if rx := h.p.NotesRx; rx != nil {
+					for m, n := range info.PDoc.Notes {
+						if rx.MatchString(m) {
+							if info.Notes == nil {
+								info.Notes = make(map[string][]*doc.Note)
+							}
+							info.Notes[m] = n
+						}
+					}
+				}
+			}
+
+		} else {
+			// show source code
+			// TODO(gri) Consider eliminating export filtering in this mode,
+			//           or perhaps eliminating the mode altogether.
+			if mode&NoFiltering == 0 {
+				packageExports(fset, pkg)
+			}
+			info.PAst = files
+		}
+		info.IsMain = pkgname == "main"
+	}
+
+	// get directory information, if any
+	var dir *Directory
+	var timestamp time.Time
+	if tree, ts := h.c.fsTree.Get(); tree != nil && tree.(*Directory) != nil {
+		// directory tree is present; lookup respective directory
+		// (may still fail if the file system was updated and the
+		// new directory tree has not yet been computed)
+		dir = tree.(*Directory).lookup(abspath)
+		timestamp = ts
+	}
+	if dir == nil {
+		// TODO(agnivade): handle this case better, now since there is no CLI mode.
+		// no directory tree present (happens in command-line mode);
+		// compute 2 levels for this page. The second level is to
+		// get the synopses of sub-directories.
+		// note: cannot use path filter here because in general
+		// it doesn't contain the FSTree path
+		dir = h.c.newDirectory(abspath, 2)
+		timestamp = time.Now()
+	}
+	info.Dirs = dir.listing(true, func(path string) bool { return h.includePath(path, mode) })
+
+	info.DirTime = timestamp
+	info.DirFlat = mode&FlatDir != 0
+
+	return info
+}
+
+func (h *handlerServer) includePath(path string, mode PageInfoMode) (r bool) {
+	// if the path is under one of the exclusion paths, don't list.
+	for _, e := range h.exclude {
+		if strings.HasPrefix(path, e) {
+			return false
+		}
+	}
+
+	// if the path includes 'internal', don't list unless we are in the NoFiltering mode.
+	if mode&NoFiltering != 0 {
+		return true
+	}
+	if strings.Contains(path, "internal") || strings.Contains(path, "vendor") {
+		for _, c := range strings.Split(filepath.Clean(path), string(os.PathSeparator)) {
+			if c == "internal" || c == "vendor" {
+				return false
+			}
+		}
+	}
+	return true
+}
+
+type funcsByName []*doc.Func
+
+func (s funcsByName) Len() int           { return len(s) }
+func (s funcsByName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
+func (s funcsByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
+
+func (h *handlerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	if redirect(w, r) {
+		return
+	}
+
+	relpath := pathpkg.Clean(r.URL.Path[len(h.stripPrefix)+1:])
+
+	if !h.corpusInitialized() {
+		h.p.ServeError(w, r, relpath, errors.New("Scan is not yet complete. Please retry after a few moments"))
+		return
+	}
+
+	abspath := pathpkg.Join(h.fsRoot, relpath)
+	mode := h.p.GetPageInfoMode(r)
+	if relpath == builtinPkgPath {
+		// The fake built-in package contains unexported identifiers,
+		// but we want to show them. Also, disable type association,
+		// since it's not helpful for this fake package (see issue 6645).
+		mode |= NoFiltering | NoTypeAssoc
+	}
+	info := h.GetPageInfo(abspath, relpath, mode, r.FormValue("GOOS"), r.FormValue("GOARCH"))
+	if info.Err != nil {
+		log.Print(info.Err)
+		h.p.ServeError(w, r, relpath, info.Err)
+		return
+	}
+
+	var tabtitle, title, subtitle string
+	switch {
+	case info.PAst != nil:
+		for _, ast := range info.PAst {
+			tabtitle = ast.Name.Name
+			break
+		}
+	case info.PDoc != nil:
+		tabtitle = info.PDoc.Name
+	default:
+		tabtitle = info.Dirname
+		title = "Directory "
+		if h.p.ShowTimestamps {
+			subtitle = "Last update: " + info.DirTime.String()
+		}
+	}
+	if title == "" {
+		if info.IsMain {
+			// assume that the directory name is the command name
+			_, tabtitle = pathpkg.Split(relpath)
+			title = "Command "
+		} else {
+			title = "Package "
+		}
+	}
+	title += tabtitle
+
+	// special cases for top-level package/command directories
+	switch tabtitle {
+	case "/src":
+		title = "Packages"
+		tabtitle = "Packages"
+	case "/src/cmd":
+		title = "Commands"
+		tabtitle = "Commands"
+	}
+
+	// Emit JSON array for type information.
+	pi := h.c.Analysis.PackageInfo(relpath)
+	hasTreeView := len(pi.CallGraph) != 0
+	info.CallGraphIndex = pi.CallGraphIndex
+	info.CallGraph = htmltemplate.JS(marshalJSON(pi.CallGraph))
+	info.AnalysisData = htmltemplate.JS(marshalJSON(pi.Types))
+	info.TypeInfoIndex = make(map[string]int)
+	for i, ti := range pi.Types {
+		info.TypeInfoIndex[ti.Name] = i
+	}
+
+	info.GoogleCN = googleCN(r)
+	var body []byte
+	if info.Dirname == "/src" {
+		body = applyTemplate(h.p.PackageRootHTML, "packageRootHTML", info)
+	} else {
+		body = applyTemplate(h.p.PackageHTML, "packageHTML", info)
+	}
+	h.p.ServePage(w, Page{
+		Title:    title,
+		Tabtitle: tabtitle,
+		Subtitle: subtitle,
+		Body:     body,
+		GoogleCN: info.GoogleCN,
+		TreeView: hasTreeView,
+	})
+}
+
+func (h *handlerServer) corpusInitialized() bool {
+	h.c.initMu.RLock()
+	defer h.c.initMu.RUnlock()
+	return h.c.initDone
+}
+
+type PageInfoMode uint
+
+const (
+	PageInfoModeQueryString = "m" // query string where PageInfoMode is stored
+
+	NoFiltering PageInfoMode = 1 << iota // do not filter exports
+	AllMethods                           // show all embedded methods
+	ShowSource                           // show source code, do not extract documentation
+	FlatDir                              // show directory in a flat (non-indented) manner
+	NoTypeAssoc                          // don't associate consts, vars, and factory functions with types (not exposed via ?m= query parameter, used for package builtin, see issue 6645)
+)
+
+// modeNames defines names for each PageInfoMode flag.
+var modeNames = map[string]PageInfoMode{
+	"all":     NoFiltering,
+	"methods": AllMethods,
+	"src":     ShowSource,
+	"flat":    FlatDir,
+}
+
+// generate a query string for persisting PageInfoMode between pages.
+func modeQueryString(mode PageInfoMode) string {
+	if modeNames := mode.names(); len(modeNames) > 0 {
+		return "?m=" + strings.Join(modeNames, ",")
+	}
+	return ""
+}
+
+// alphabetically sorted names of active flags for a PageInfoMode.
+func (m PageInfoMode) names() []string {
+	var names []string
+	for name, mode := range modeNames {
+		if m&mode != 0 {
+			names = append(names, name)
+		}
+	}
+	sort.Strings(names)
+	return names
+}
+
+// GetPageInfoMode computes the PageInfoMode flags by analyzing the request
+// URL form value "m". It is value is a comma-separated list of mode names
+// as defined by modeNames (e.g.: m=src,text).
+func (p *Presentation) GetPageInfoMode(r *http.Request) PageInfoMode {
+	var mode PageInfoMode
+	for _, k := range strings.Split(r.FormValue(PageInfoModeQueryString), ",") {
+		if m, found := modeNames[strings.TrimSpace(k)]; found {
+			mode |= m
+		}
+	}
+	if p.AdjustPageInfoMode != nil {
+		mode = p.AdjustPageInfoMode(r, mode)
+	}
+	return mode
+}
+
+// poorMansImporter returns a (dummy) package object named
+// by the last path component of the provided package path
+// (as is the convention for packages). This is sufficient
+// to resolve package identifiers without doing an actual
+// import. It never returns an error.
+//
+func poorMansImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) {
+	pkg := imports[path]
+	if pkg == nil {
+		// note that strings.LastIndex returns -1 if there is no "/"
+		pkg = ast.NewObj(ast.Pkg, path[strings.LastIndex(path, "/")+1:])
+		pkg.Data = ast.NewScope(nil) // required by ast.NewPackage for dot-import
+		imports[path] = pkg
+	}
+	return pkg, nil
+}
+
+// globalNames returns a set of the names declared by all package-level
+// declarations. Method names are returned in the form Receiver_Method.
+func globalNames(pkg *ast.Package) map[string]bool {
+	names := make(map[string]bool)
+	for _, file := range pkg.Files {
+		for _, decl := range file.Decls {
+			addNames(names, decl)
+		}
+	}
+	return names
+}
+
+// collectExamples collects examples for pkg from testfiles.
+func collectExamples(c *Corpus, pkg *ast.Package, testfiles map[string]*ast.File) []*doc.Example {
+	var files []*ast.File
+	for _, f := range testfiles {
+		files = append(files, f)
+	}
+
+	var examples []*doc.Example
+	globals := globalNames(pkg)
+	for _, e := range doc.Examples(files...) {
+		name := stripExampleSuffix(e.Name)
+		if name == "" || globals[name] {
+			examples = append(examples, e)
+		} else if c.Verbose {
+			log.Printf("skipping example 'Example%s' because '%s' is not a known function or type", e.Name, e.Name)
+		}
+	}
+
+	return examples
+}
+
+// addNames adds the names declared by decl to the names set.
+// Method names are added in the form ReceiverTypeName_Method.
+func addNames(names map[string]bool, decl ast.Decl) {
+	switch d := decl.(type) {
+	case *ast.FuncDecl:
+		name := d.Name.Name
+		if d.Recv != nil {
+			var typeName string
+			switch r := d.Recv.List[0].Type.(type) {
+			case *ast.StarExpr:
+				typeName = r.X.(*ast.Ident).Name
+			case *ast.Ident:
+				typeName = r.Name
+			}
+			name = typeName + "_" + name
+		}
+		names[name] = true
+	case *ast.GenDecl:
+		for _, spec := range d.Specs {
+			switch s := spec.(type) {
+			case *ast.TypeSpec:
+				names[s.Name.Name] = true
+			case *ast.ValueSpec:
+				for _, id := range s.Names {
+					names[id.Name] = true
+				}
+			}
+		}
+	}
+}
+
+// packageExports is a local implementation of ast.PackageExports
+// which correctly updates each package file's comment list.
+// (The ast.PackageExports signature is frozen, hence the local
+// implementation).
+//
+func packageExports(fset *token.FileSet, pkg *ast.Package) {
+	for _, src := range pkg.Files {
+		cmap := ast.NewCommentMap(fset, src, src.Comments)
+		ast.FileExports(src)
+		src.Comments = cmap.Filter(src).Comments()
+	}
+}
+
+func applyTemplate(t *template.Template, name string, data interface{}) []byte {
+	var buf bytes.Buffer
+	if err := t.Execute(&buf, data); err != nil {
+		log.Printf("%s.Execute: %s", name, err)
+	}
+	return buf.Bytes()
+}
+
+type writerCapturesErr struct {
+	w   io.Writer
+	err error
+}
+
+func (w *writerCapturesErr) Write(p []byte) (int, error) {
+	n, err := w.w.Write(p)
+	if err != nil {
+		w.err = err
+	}
+	return n, err
+}
+
+// applyTemplateToResponseWriter uses an http.ResponseWriter as the io.Writer
+// for the call to template.Execute.  It uses an io.Writer wrapper to capture
+// errors from the underlying http.ResponseWriter.  Errors are logged only when
+// they come from the template processing and not the Writer; this avoid
+// polluting log files with error messages due to networking issues, such as
+// client disconnects and http HEAD protocol violations.
+func applyTemplateToResponseWriter(rw http.ResponseWriter, t *template.Template, data interface{}) {
+	w := &writerCapturesErr{w: rw}
+	err := t.Execute(w, data)
+	// There are some cases where template.Execute does not return an error when
+	// rw returns an error, and some where it does.  So check w.err first.
+	if w.err == nil && err != nil {
+		// Log template errors.
+		log.Printf("%s.Execute: %s", t.Name(), err)
+	}
+}
+
+func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) {
+	canonical := pathpkg.Clean(r.URL.Path)
+	if !strings.HasSuffix(canonical, "/") {
+		canonical += "/"
+	}
+	if r.URL.Path != canonical {
+		url := *r.URL
+		url.Path = canonical
+		http.Redirect(w, r, url.String(), http.StatusMovedPermanently)
+		redirected = true
+	}
+	return
+}
+
+func redirectFile(w http.ResponseWriter, r *http.Request) (redirected bool) {
+	c := pathpkg.Clean(r.URL.Path)
+	c = strings.TrimRight(c, "/")
+	if r.URL.Path != c {
+		url := *r.URL
+		url.Path = c
+		http.Redirect(w, r, url.String(), http.StatusMovedPermanently)
+		redirected = true
+	}
+	return
+}
+
+func (p *Presentation) serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, title string) {
+	src, err := vfs.ReadFile(p.Corpus.fs, abspath)
+	if err != nil {
+		log.Printf("ReadFile: %s", err)
+		p.ServeError(w, r, relpath, err)
+		return
+	}
+
+	if r.FormValue(PageInfoModeQueryString) == "text" {
+		p.ServeText(w, src)
+		return
+	}
+
+	h := r.FormValue("h")
+	s := RangeSelection(r.FormValue("s"))
+
+	var buf bytes.Buffer
+	if pathpkg.Ext(abspath) == ".go" {
+		// Find markup links for this file (e.g. "/src/fmt/print.go").
+		fi := p.Corpus.Analysis.FileInfo(abspath)
+		buf.WriteString("<script type='text/javascript'>document.ANALYSIS_DATA = ")
+		buf.Write(marshalJSON(fi.Data))
+		buf.WriteString(";</script>\n")
+
+		if status := p.Corpus.Analysis.Status(); status != "" {
+			buf.WriteString("<a href='/lib/godoc/analysis/help.html'>Static analysis features</a> ")
+			// TODO(adonovan): show analysis status at per-file granularity.
+			fmt.Fprintf(&buf, "<span style='color: grey'>[%s]</span><br/>", htmlpkg.EscapeString(status))
+		}
+
+		buf.WriteString("<pre>")
+		formatGoSource(&buf, src, fi.Links, h, s)
+		buf.WriteString("</pre>")
+	} else {
+		buf.WriteString("<pre>")
+		FormatText(&buf, src, 1, false, h, s)
+		buf.WriteString("</pre>")
+	}
+	fmt.Fprintf(&buf, `<p><a href="/%s?m=text">View as plain text</a></p>`, htmlpkg.EscapeString(relpath))
+
+	p.ServePage(w, Page{
+		Title:    title,
+		SrcPath:  relpath,
+		Tabtitle: relpath,
+		Body:     buf.Bytes(),
+		GoogleCN: googleCN(r),
+	})
+}
+
+// formatGoSource HTML-escapes Go source text and writes it to w,
+// decorating it with the specified analysis links.
+//
+func formatGoSource(buf *bytes.Buffer, text []byte, links []analysis.Link, pattern string, selection Selection) {
+	// Emit to a temp buffer so that we can add line anchors at the end.
+	saved, buf := buf, new(bytes.Buffer)
+
+	var i int
+	var link analysis.Link // shared state of the two funcs below
+	segmentIter := func() (seg Segment) {
+		if i < len(links) {
+			link = links[i]
+			i++
+			seg = Segment{link.Start(), link.End()}
+		}
+		return
+	}
+	linkWriter := func(w io.Writer, offs int, start bool) {
+		link.Write(w, offs, start)
+	}
+
+	comments := tokenSelection(text, token.COMMENT)
+	var highlights Selection
+	if pattern != "" {
+		highlights = regexpSelection(text, pattern)
+	}
+
+	FormatSelections(buf, text, linkWriter, segmentIter, selectionTag, comments, highlights, selection)
+
+	// Now copy buf to saved, adding line anchors.
+
+	// The lineSelection mechanism can't be composed with our
+	// linkWriter, so we have to add line spans as another pass.
+	n := 1
+	for _, line := range bytes.Split(buf.Bytes(), []byte("\n")) {
+		// The line numbers are inserted into the document via a CSS ::before
+		// pseudo-element. This prevents them from being copied when users
+		// highlight and copy text.
+		// ::before is supported in 98% of browsers: https://caniuse.com/#feat=css-gencontent
+		// This is also the trick Github uses to hide line numbers.
+		//
+		// The first tab for the code snippet needs to start in column 9, so
+		// it indents a full 8 spaces, hence the two nbsp's. Otherwise the tab
+		// character only indents a short amount.
+		//
+		// Due to rounding and font width Firefox might not treat 8 rendered
+		// characters as 8 characters wide, and subsequently may treat the tab
+		// character in the 9th position as moving the width from (7.5 or so) up
+		// to 8. See
+		// https://github.com/webcompat/web-bugs/issues/17530#issuecomment-402675091
+		// for a fuller explanation. The solution is to add a CSS class to
+		// explicitly declare the width to be 8 characters.
+		fmt.Fprintf(saved, `<span id="L%d" class="ln">%6d&nbsp;&nbsp;</span>`, n, n)
+		n++
+		saved.Write(line)
+		saved.WriteByte('\n')
+	}
+}
+
+func (p *Presentation) serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
+	if redirect(w, r) {
+		return
+	}
+
+	list, err := p.Corpus.fs.ReadDir(abspath)
+	if err != nil {
+		p.ServeError(w, r, relpath, err)
+		return
+	}
+
+	p.ServePage(w, Page{
+		Title:    "Directory",
+		SrcPath:  relpath,
+		Tabtitle: relpath,
+		Body:     applyTemplate(p.DirlistHTML, "dirlistHTML", list),
+		GoogleCN: googleCN(r),
+	})
+}
+
+func (p *Presentation) ServeHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
+	// get HTML body contents
+	isMarkdown := false
+	src, err := vfs.ReadFile(p.Corpus.fs, abspath)
+	if err != nil && strings.HasSuffix(abspath, ".html") {
+		if md, errMD := vfs.ReadFile(p.Corpus.fs, strings.TrimSuffix(abspath, ".html")+".md"); errMD == nil {
+			src = md
+			isMarkdown = true
+			err = nil
+		}
+	}
+	if err != nil {
+		log.Printf("ReadFile: %s", err)
+		p.ServeError(w, r, relpath, err)
+		return
+	}
+
+	// if it begins with "<!DOCTYPE " assume it is standalone
+	// html that doesn't need the template wrapping.
+	if bytes.HasPrefix(src, doctype) {
+		w.Write(src)
+		return
+	}
+
+	// if it begins with a JSON blob, read in the metadata.
+	meta, src, err := extractMetadata(src)
+	if err != nil {
+		log.Printf("decoding metadata %s: %v", relpath, err)
+	}
+
+	page := Page{
+		Title:    meta.Title,
+		Subtitle: meta.Subtitle,
+		GoogleCN: googleCN(r),
+	}
+
+	// evaluate as template if indicated
+	if meta.Template {
+		tmpl, err := template.New("main").Funcs(p.TemplateFuncs()).Parse(string(src))
+		if err != nil {
+			log.Printf("parsing template %s: %v", relpath, err)
+			p.ServeError(w, r, relpath, err)
+			return
+		}
+		var buf bytes.Buffer
+		if err := tmpl.Execute(&buf, page); err != nil {
+			log.Printf("executing template %s: %v", relpath, err)
+			p.ServeError(w, r, relpath, err)
+			return
+		}
+		src = buf.Bytes()
+	}
+
+	// Apply markdown as indicated.
+	// (Note template applies before Markdown.)
+	if isMarkdown {
+		html, err := renderMarkdown(src)
+		if err != nil {
+			log.Printf("executing markdown %s: %v", relpath, err)
+			p.ServeError(w, r, relpath, err)
+			return
+		}
+		src = html
+	}
+
+	// if it's the language spec, add tags to EBNF productions
+	if strings.HasSuffix(abspath, "go_spec.html") {
+		var buf bytes.Buffer
+		Linkify(&buf, src)
+		src = buf.Bytes()
+	}
+
+	page.Body = src
+	p.ServePage(w, page)
+}
+
+func (p *Presentation) ServeFile(w http.ResponseWriter, r *http.Request) {
+	p.serveFile(w, r)
+}
+
+func (p *Presentation) serveFile(w http.ResponseWriter, r *http.Request) {
+	if strings.HasSuffix(r.URL.Path, "/index.html") {
+		// We'll show index.html for the directory.
+		// Use the dir/ version as canonical instead of dir/index.html.
+		http.Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len("index.html")], http.StatusMovedPermanently)
+		return
+	}
+
+	// Check to see if we need to redirect or serve another file.
+	relpath := r.URL.Path
+	if m := p.Corpus.MetadataFor(relpath); m != nil {
+		if m.Path != relpath {
+			// Redirect to canonical path.
+			http.Redirect(w, r, m.Path, http.StatusMovedPermanently)
+			return
+		}
+		// Serve from the actual filesystem path.
+		relpath = m.filePath
+	}
+
+	abspath := relpath
+	relpath = relpath[1:] // strip leading slash
+
+	switch pathpkg.Ext(relpath) {
+	case ".html":
+		p.ServeHTMLDoc(w, r, abspath, relpath)
+		return
+
+	case ".go":
+		p.serveTextFile(w, r, abspath, relpath, "Source file")
+		return
+	}
+
+	dir, err := p.Corpus.fs.Lstat(abspath)
+	if err != nil {
+		log.Print(err)
+		p.ServeError(w, r, relpath, err)
+		return
+	}
+
+	if dir != nil && dir.IsDir() {
+		if redirect(w, r) {
+			return
+		}
+		index := pathpkg.Join(abspath, "index.html")
+		if util.IsTextFile(p.Corpus.fs, index) || util.IsTextFile(p.Corpus.fs, pathpkg.Join(abspath, "index.md")) {
+			p.ServeHTMLDoc(w, r, index, index)
+			return
+		}
+		p.serveDirectory(w, r, abspath, relpath)
+		return
+	}
+
+	if util.IsTextFile(p.Corpus.fs, abspath) {
+		if redirectFile(w, r) {
+			return
+		}
+		p.serveTextFile(w, r, abspath, relpath, "Text file")
+		return
+	}
+
+	p.fileServer.ServeHTTP(w, r)
+}
+
+func (p *Presentation) ServeText(w http.ResponseWriter, text []byte) {
+	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
+	w.Write(text)
+}
+
+func marshalJSON(x interface{}) []byte {
+	var data []byte
+	var err error
+	const indentJSON = false // for easier debugging
+	if indentJSON {
+		data, err = json.MarshalIndent(x, "", "    ")
+	} else {
+		data, err = json.Marshal(x)
+	}
+	if err != nil {
+		panic(fmt.Sprintf("json.Marshal failed: %s", err))
+	}
+	return data
+}
diff --git a/internal/godoc/server_test.go b/internal/godoc/server_test.go
new file mode 100644
index 0000000..7a446f9
--- /dev/null
+++ b/internal/godoc/server_test.go
@@ -0,0 +1,130 @@
+// Copyright 2018 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.
+
+package godoc
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"net/url"
+	"strings"
+	"testing"
+	"text/template"
+
+	"golang.org/x/website/internal/godoc/vfs/mapfs"
+)
+
+// TestIgnoredGoFiles tests the scenario where a folder has no .go or .c files,
+// but has an ignored go file.
+func TestIgnoredGoFiles(t *testing.T) {
+	packagePath := "github.com/package"
+	packageComment := "main is documented in an ignored .go file"
+
+	c := NewCorpus(mapfs.New(map[string]string{
+		"src/" + packagePath + "/ignored.go": `// +build ignore
+
+// ` + packageComment + `
+package main`}))
+	srv := &handlerServer{
+		p: &Presentation{
+			Corpus: c,
+		},
+		c: c,
+	}
+	pInfo := srv.GetPageInfo("/src/"+packagePath, packagePath, NoFiltering, "linux", "amd64")
+
+	if pInfo.PDoc == nil {
+		t.Error("pInfo.PDoc = nil; want non-nil.")
+	} else {
+		if got, want := pInfo.PDoc.Doc, packageComment+"\n"; got != want {
+			t.Errorf("pInfo.PDoc.Doc = %q; want %q.", got, want)
+		}
+		if got, want := pInfo.PDoc.Name, "main"; got != want {
+			t.Errorf("pInfo.PDoc.Name = %q; want %q.", got, want)
+		}
+		if got, want := pInfo.PDoc.ImportPath, packagePath; got != want {
+			t.Errorf("pInfo.PDoc.ImportPath = %q; want %q.", got, want)
+		}
+	}
+	if pInfo.FSet == nil {
+		t.Error("pInfo.FSet = nil; want non-nil.")
+	}
+}
+
+func TestIssue5247(t *testing.T) {
+	const packagePath = "example.com/p"
+	c := NewCorpus(mapfs.New(map[string]string{
+		"src/" + packagePath + "/p.go": `package p
+
+//line notgen.go:3
+// F doc //line 1 should appear
+// line 2 should appear
+func F()
+//line foo.go:100`})) // No newline at end to check corner cases.
+
+	srv := &handlerServer{
+		p: &Presentation{Corpus: c},
+		c: c,
+	}
+	pInfo := srv.GetPageInfo("/src/"+packagePath, packagePath, 0, "linux", "amd64")
+	if got, want := pInfo.PDoc.Funcs[0].Doc, "F doc //line 1 should appear\nline 2 should appear\n"; got != want {
+		t.Errorf("pInfo.PDoc.Funcs[0].Doc = %q; want %q", got, want)
+	}
+}
+
+func testServeBody(t *testing.T, p *Presentation, path, body string) {
+	t.Helper()
+	r := &http.Request{URL: &url.URL{Path: path}}
+	rw := httptest.NewRecorder()
+	p.ServeFile(rw, r)
+	if rw.Code != 200 || !strings.Contains(rw.Body.String(), body) {
+		t.Fatalf("GET %s: expected 200 w/ %q: got %d w/ body:\n%s",
+			path, body, rw.Code, rw.Body)
+	}
+}
+
+func TestRedirectAndMetadata(t *testing.T) {
+	c := NewCorpus(mapfs.New(map[string]string{
+		"doc/y/index.html": "Hello, y.",
+		"doc/x/index.html": `<!--{
+		"Path": "/doc/x/"
+}-->
+
+Hello, x.
+`}))
+	c.updateMetadata()
+	p := &Presentation{
+		Corpus:    c,
+		GodocHTML: template.Must(template.New("").Parse(`{{printf "%s" .Body}}`)),
+	}
+
+	// Test that redirect is sent back correctly.
+	// Used to panic. See golang.org/issue/40665.
+	for _, elem := range []string{"x", "y"} {
+		dir := "/doc/" + elem + "/"
+
+		r := &http.Request{URL: &url.URL{Path: dir + "index.html"}}
+		rw := httptest.NewRecorder()
+		p.ServeFile(rw, r)
+		loc := rw.Result().Header.Get("Location")
+		if rw.Code != 301 || loc != dir {
+			t.Errorf("GET %s: expected 301 -> %q, got %d -> %q", r.URL.Path, dir, rw.Code, loc)
+		}
+
+		testServeBody(t, p, dir, "Hello, "+elem)
+	}
+}
+
+func TestMarkdown(t *testing.T) {
+	p := &Presentation{
+		Corpus: NewCorpus(mapfs.New(map[string]string{
+			"doc/test.md":  "**bold**",
+			"doc/test2.md": `{{"*template*"}}`,
+		})),
+		GodocHTML: template.Must(template.New("").Parse(`{{printf "%s" .Body}}`)),
+	}
+
+	testServeBody(t, p, "/doc/test.html", "<strong>bold</strong>")
+	testServeBody(t, p, "/doc/test2.html", "<em>template</em>")
+}
diff --git a/internal/godoc/snippet.go b/internal/godoc/snippet.go
new file mode 100644
index 0000000..1750478
--- /dev/null
+++ b/internal/godoc/snippet.go
@@ -0,0 +1,123 @@
+// Copyright 2009 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.
+
+// This file contains the infrastructure to create a code
+// snippet for search results.
+//
+// Note: At the moment, this only creates HTML snippets.
+
+package godoc
+
+import (
+	"bytes"
+	"fmt"
+	"go/ast"
+	"go/token"
+)
+
+type Snippet struct {
+	Line int
+	Text string // HTML-escaped
+}
+
+func (p *Presentation) newSnippet(fset *token.FileSet, decl ast.Decl, id *ast.Ident) *Snippet {
+	// TODO instead of pretty-printing the node, should use the original source instead
+	var buf1 bytes.Buffer
+	p.writeNode(&buf1, nil, fset, decl)
+	// wrap text with <pre> tag
+	var buf2 bytes.Buffer
+	buf2.WriteString("<pre>")
+	FormatText(&buf2, buf1.Bytes(), -1, true, id.Name, nil)
+	buf2.WriteString("</pre>")
+	return &Snippet{fset.Position(id.Pos()).Line, buf2.String()}
+}
+
+func findSpec(list []ast.Spec, id *ast.Ident) ast.Spec {
+	for _, spec := range list {
+		switch s := spec.(type) {
+		case *ast.ImportSpec:
+			if s.Name == id {
+				return s
+			}
+		case *ast.ValueSpec:
+			for _, n := range s.Names {
+				if n == id {
+					return s
+				}
+			}
+		case *ast.TypeSpec:
+			if s.Name == id {
+				return s
+			}
+		}
+	}
+	return nil
+}
+
+func (p *Presentation) genSnippet(fset *token.FileSet, d *ast.GenDecl, id *ast.Ident) *Snippet {
+	s := findSpec(d.Specs, id)
+	if s == nil {
+		return nil //  declaration doesn't contain id - exit gracefully
+	}
+
+	// only use the spec containing the id for the snippet
+	dd := &ast.GenDecl{
+		Doc:    d.Doc,
+		TokPos: d.Pos(),
+		Tok:    d.Tok,
+		Lparen: d.Lparen,
+		Specs:  []ast.Spec{s},
+		Rparen: d.Rparen,
+	}
+
+	return p.newSnippet(fset, dd, id)
+}
+
+func (p *Presentation) funcSnippet(fset *token.FileSet, d *ast.FuncDecl, id *ast.Ident) *Snippet {
+	if d.Name != id {
+		return nil //  declaration doesn't contain id - exit gracefully
+	}
+
+	// only use the function signature for the snippet
+	dd := &ast.FuncDecl{
+		Doc:  d.Doc,
+		Recv: d.Recv,
+		Name: d.Name,
+		Type: d.Type,
+	}
+
+	return p.newSnippet(fset, dd, id)
+}
+
+// NewSnippet creates a text snippet from a declaration decl containing an
+// identifier id. Parts of the declaration not containing the identifier
+// may be removed for a more compact snippet.
+func NewSnippet(fset *token.FileSet, decl ast.Decl, id *ast.Ident) *Snippet {
+	// TODO(bradfitz, adg): remove this function.  But it's used by indexer, which
+	// doesn't have a *Presentation, and NewSnippet needs a TabWidth.
+	var p Presentation
+	p.TabWidth = 4
+	return p.NewSnippet(fset, decl, id)
+}
+
+// NewSnippet creates a text snippet from a declaration decl containing an
+// identifier id. Parts of the declaration not containing the identifier
+// may be removed for a more compact snippet.
+func (p *Presentation) NewSnippet(fset *token.FileSet, decl ast.Decl, id *ast.Ident) *Snippet {
+	var s *Snippet
+	switch d := decl.(type) {
+	case *ast.GenDecl:
+		s = p.genSnippet(fset, d, id)
+	case *ast.FuncDecl:
+		s = p.funcSnippet(fset, d, id)
+	}
+
+	// handle failure gracefully
+	if s == nil {
+		var buf bytes.Buffer
+		fmt.Fprintf(&buf, `<span class="alert">could not generate a snippet for <span class="highlight">%s</span></span>`, id.Name)
+		s = &Snippet{fset.Position(id.Pos()).Line, buf.String()}
+	}
+	return s
+}
diff --git a/internal/godoc/spec.go b/internal/godoc/spec.go
new file mode 100644
index 0000000..9ec9427
--- /dev/null
+++ b/internal/godoc/spec.go
@@ -0,0 +1,179 @@
+// Copyright 2009 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.
+
+package godoc
+
+// This file contains the mechanism to "linkify" html source
+// text containing EBNF sections (as found in go_spec.html).
+// The result is the input source text with the EBNF sections
+// modified such that identifiers are linked to the respective
+// definitions.
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"text/scanner"
+)
+
+type ebnfParser struct {
+	out     io.Writer // parser output
+	src     []byte    // parser input
+	scanner scanner.Scanner
+	prev    int    // offset of previous token
+	pos     int    // offset of current token
+	tok     rune   // one token look-ahead
+	lit     string // token literal
+}
+
+func (p *ebnfParser) flush() {
+	p.out.Write(p.src[p.prev:p.pos])
+	p.prev = p.pos
+}
+
+func (p *ebnfParser) next() {
+	p.tok = p.scanner.Scan()
+	p.pos = p.scanner.Position.Offset
+	p.lit = p.scanner.TokenText()
+}
+
+func (p *ebnfParser) printf(format string, args ...interface{}) {
+	p.flush()
+	fmt.Fprintf(p.out, format, args...)
+}
+
+func (p *ebnfParser) errorExpected(msg string) {
+	p.printf(`<span class="highlight">error: expected %s, found %s</span>`, msg, scanner.TokenString(p.tok))
+}
+
+func (p *ebnfParser) expect(tok rune) {
+	if p.tok != tok {
+		p.errorExpected(scanner.TokenString(tok))
+	}
+	p.next() // make progress in any case
+}
+
+func (p *ebnfParser) parseIdentifier(def bool) {
+	if p.tok == scanner.Ident {
+		name := p.lit
+		if def {
+			p.printf(`<a id="%s">%s</a>`, name, name)
+		} else {
+			p.printf(`<a href="#%s" class="noline">%s</a>`, name, name)
+		}
+		p.prev += len(name) // skip identifier when printing next time
+		p.next()
+	} else {
+		p.expect(scanner.Ident)
+	}
+}
+
+func (p *ebnfParser) parseTerm() bool {
+	switch p.tok {
+	case scanner.Ident:
+		p.parseIdentifier(false)
+
+	case scanner.String, scanner.RawString:
+		p.next()
+		const ellipsis = '…' // U+2026, the horizontal ellipsis character
+		if p.tok == ellipsis {
+			p.next()
+			p.expect(scanner.String)
+		}
+
+	case '(':
+		p.next()
+		p.parseExpression()
+		p.expect(')')
+
+	case '[':
+		p.next()
+		p.parseExpression()
+		p.expect(']')
+
+	case '{':
+		p.next()
+		p.parseExpression()
+		p.expect('}')
+
+	default:
+		return false // no term found
+	}
+
+	return true
+}
+
+func (p *ebnfParser) parseSequence() {
+	if !p.parseTerm() {
+		p.errorExpected("term")
+	}
+	for p.parseTerm() {
+	}
+}
+
+func (p *ebnfParser) parseExpression() {
+	for {
+		p.parseSequence()
+		if p.tok != '|' {
+			break
+		}
+		p.next()
+	}
+}
+
+func (p *ebnfParser) parseProduction() {
+	p.parseIdentifier(true)
+	p.expect('=')
+	if p.tok != '.' {
+		p.parseExpression()
+	}
+	p.expect('.')
+}
+
+func (p *ebnfParser) parse(out io.Writer, src []byte) {
+	// initialize ebnfParser
+	p.out = out
+	p.src = src
+	p.scanner.Init(bytes.NewBuffer(src))
+	p.next() // initializes pos, tok, lit
+
+	// process source
+	for p.tok != scanner.EOF {
+		p.parseProduction()
+	}
+	p.flush()
+}
+
+// Markers around EBNF sections
+var (
+	openTag  = []byte(`<pre class="ebnf">`)
+	closeTag = []byte(`</pre>`)
+)
+
+func Linkify(out io.Writer, src []byte) {
+	for len(src) > 0 {
+		// i: beginning of EBNF text (or end of source)
+		i := bytes.Index(src, openTag)
+		if i < 0 {
+			i = len(src) - len(openTag)
+		}
+		i += len(openTag)
+
+		// j: end of EBNF text (or end of source)
+		j := bytes.Index(src[i:], closeTag) // close marker
+		if j < 0 {
+			j = len(src) - i
+		}
+		j += i
+
+		// write text before EBNF
+		out.Write(src[0:i])
+		// process EBNF
+		var p ebnfParser
+		p.parse(out, src[i:j])
+
+		// advance
+		src = src[j:]
+	}
+}
diff --git a/internal/godoc/spec_test.go b/internal/godoc/spec_test.go
new file mode 100644
index 0000000..c016516
--- /dev/null
+++ b/internal/godoc/spec_test.go
@@ -0,0 +1,22 @@
+// Copyright 2018 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.
+
+package godoc
+
+import (
+	"bytes"
+	"strings"
+	"testing"
+)
+
+func TestParseEBNFString(t *testing.T) {
+	var p ebnfParser
+	var buf bytes.Buffer
+	src := []byte("octal_byte_value = `\\` octal_digit octal_digit octal_digit .")
+	p.parse(&buf, src)
+
+	if strings.Contains(buf.String(), "error") {
+		t.Error(buf.String())
+	}
+}
diff --git a/internal/godoc/spot.go b/internal/godoc/spot.go
new file mode 100644
index 0000000..95ffa4b
--- /dev/null
+++ b/internal/godoc/spot.go
@@ -0,0 +1,83 @@
+// 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.
+
+package godoc
+
+// ----------------------------------------------------------------------------
+// SpotInfo
+
+// A SpotInfo value describes a particular identifier spot in a given file;
+// It encodes three values: the SpotKind (declaration or use), a line or
+// snippet index "lori", and whether it's a line or index.
+//
+// The following encoding is used:
+//
+//   bits    32   4    1       0
+//   value    [lori|kind|isIndex]
+//
+type SpotInfo uint32
+
+// SpotKind describes whether an identifier is declared (and what kind of
+// declaration) or used.
+type SpotKind uint32
+
+const (
+	PackageClause SpotKind = iota
+	ImportDecl
+	ConstDecl
+	TypeDecl
+	VarDecl
+	FuncDecl
+	MethodDecl
+	Use
+	nKinds
+)
+
+var (
+	// These must match the SpotKind values above.
+	name = []string{
+		"Packages",
+		"Imports",
+		"Constants",
+		"Types",
+		"Variables",
+		"Functions",
+		"Methods",
+		"Uses",
+		"Unknown",
+	}
+)
+
+func (x SpotKind) Name() string { return name[x] }
+
+func init() {
+	// sanity check: if nKinds is too large, the SpotInfo
+	// accessor functions may need to be updated
+	if nKinds > 8 {
+		panic("internal error: nKinds > 8")
+	}
+}
+
+// makeSpotInfo makes a SpotInfo.
+func makeSpotInfo(kind SpotKind, lori int, isIndex bool) SpotInfo {
+	// encode lori: bits [4..32)
+	x := SpotInfo(lori) << 4
+	if int(x>>4) != lori {
+		// lori value doesn't fit - since snippet indices are
+		// most certainly always smaller then 1<<28, this can
+		// only happen for line numbers; give it no line number (= 0)
+		x = 0
+	}
+	// encode kind: bits [1..4)
+	x |= SpotInfo(kind) << 1
+	// encode isIndex: bit 0
+	if isIndex {
+		x |= 1
+	}
+	return x
+}
+
+func (x SpotInfo) Kind() SpotKind { return SpotKind(x >> 1 & 7) }
+func (x SpotInfo) Lori() int      { return int(x >> 4) }
+func (x SpotInfo) IsIndex() bool  { return x&1 != 0 }
diff --git a/internal/godoc/static/analysis/call-eg.png b/internal/godoc/static/analysis/call-eg.png
new file mode 100644
index 0000000..c48bf4d
--- /dev/null
+++ b/internal/godoc/static/analysis/call-eg.png
Binary files differ
diff --git a/internal/godoc/static/analysis/call3.png b/internal/godoc/static/analysis/call3.png
new file mode 100644
index 0000000..387a38c
--- /dev/null
+++ b/internal/godoc/static/analysis/call3.png
Binary files differ
diff --git a/internal/godoc/static/analysis/callers1.png b/internal/godoc/static/analysis/callers1.png
new file mode 100644
index 0000000..80fbc62
--- /dev/null
+++ b/internal/godoc/static/analysis/callers1.png
Binary files differ
diff --git a/internal/godoc/static/analysis/callers2.png b/internal/godoc/static/analysis/callers2.png
new file mode 100644
index 0000000..59a84ed
--- /dev/null
+++ b/internal/godoc/static/analysis/callers2.png
Binary files differ
diff --git a/internal/godoc/static/analysis/chan1.png b/internal/godoc/static/analysis/chan1.png
new file mode 100644
index 0000000..5eb2811
--- /dev/null
+++ b/internal/godoc/static/analysis/chan1.png
Binary files differ
diff --git a/internal/godoc/static/analysis/chan2a.png b/internal/godoc/static/analysis/chan2a.png
new file mode 100644
index 0000000..b757504
--- /dev/null
+++ b/internal/godoc/static/analysis/chan2a.png
Binary files differ
diff --git a/internal/godoc/static/analysis/chan2b.png b/internal/godoc/static/analysis/chan2b.png
new file mode 100644
index 0000000..d197862
--- /dev/null
+++ b/internal/godoc/static/analysis/chan2b.png
Binary files differ
diff --git a/internal/godoc/static/analysis/error1.png b/internal/godoc/static/analysis/error1.png
new file mode 100644
index 0000000..69550b9
--- /dev/null
+++ b/internal/godoc/static/analysis/error1.png
Binary files differ
diff --git a/internal/godoc/static/analysis/help.html b/internal/godoc/static/analysis/help.html
new file mode 100644
index 0000000..023c07d
--- /dev/null
+++ b/internal/godoc/static/analysis/help.html
@@ -0,0 +1,254 @@
+<!--{
+        "Title": "Static analysis features of godoc"
+}-->
+
+<style>
+  span.err { 'font-size:120%; color:darkred; background-color: yellow; }
+  img.ss { margin-left: 1in; } /* screenshot */
+  img.dotted { border: thin dotted; }
+</style>
+
+<!-- Images were grabbed from Chrome/Linux at 150% zoom, and are
+     displayed at 66% of natural size.  This allows users to zoom a
+     little before seeing pixels. -->
+
+<p>
+  When invoked with the <code>-analysis</code> flag, godoc performs
+  static analysis on the Go packages it indexes and displays the
+  results in the source and package views.  This document provides a
+  brief tour of these features.
+</p>
+
+<h2>Type analysis features</h2>
+<p>
+  <code>godoc -analysis=type</code> performs static checking similar
+  to that done by a compiler: it detects ill-formed programs, resolves
+  each identifier to the entity it denotes, computes the type of each
+  expression and the method set of each type, and determines which
+  types are assignable to each interface type.
+
+  <b>Type analysis</b> is relatively quick, requiring about 10 seconds for
+  the &gt;200 packages of the standard library, for example.
+</p>
+
+<h3>Compiler errors</h3>
+<p>
+  If any source file contains a compilation error, the source view
+  will highlight the errant location in red.  Hovering over it
+  displays the error message.
+</p>
+<img class="ss" width='811' src='error1.png'><br/>
+
+<h3>Identifier resolution</h3>
+<p>
+  In the source view, every referring identifier is annotated with
+  information about the language entity it refers to: a package,
+  constant, variable, type, function or statement label.
+
+  Hovering over the identifier reveals the entity's kind and type
+  (e.g. <code>var x int</code> or <code>func f
+  func(int) string</code>).
+</p>
+<img class="ss" width='652' src='ident-field.png'><br/>
+<br/>
+<img class="ss" width='652' src='ident-func.png'>
+<p>
+  Clicking the link takes you to the entity's definition.
+</p>
+<img class="ss" width='652' src='ident-def.png'><br/>
+
+<h3>Type information: size/alignment, method set, interfaces</h3>
+<p>
+  Clicking on the identifier that defines a named type causes a panel
+  to appear, displaying information about the named type, including
+  its size and alignment in bytes, its
+  <a href='http://golang.org/ref/spec#Method_sets'>method set</a>, and its
+  <i>implements</i> relation: the set of types T that are assignable to
+  or from this type U where at least one of T or U is an interface.
+
+  This example shows information about <code>net/rpc.methodType</code>.
+</p>
+<img class="ss" width='470' src='typeinfo-src.png'>
+<p>
+  The method set includes not only the declared methods of the type,
+  but also any methods "promoted" from anonymous fields of structs,
+  such as <code>sync.Mutex</code> in this example.
+
+  In addition, the receiver type is displayed as <code>*T</code> or
+  <code>T</code> depending on whether it requires the address or just
+  a copy of the receiver value.
+</p>
+<p>
+  The method set and <i>implements</i> relation are also available
+  via the package view.
+</p>
+<img class="ss dotted" width='716' src='typeinfo-pkg.png'>
+
+<h2>Pointer analysis features</h2>
+<p>
+  <code>godoc -analysis=pointer</code> additionally performs a precise
+  whole-program <b>pointer analysis</b>.  In other words, it
+  approximates the set of memory locations to which each
+  reference&mdash;not just vars of kind <code>*T</code>, but also
+  <code>[]T</code>, <code>func</code>, <code>map</code>,
+  <code>chan</code>, and <code>interface</code>&mdash;may refer.  This
+  information reveals the possible destinations of each dynamic call
+  (via a <code>func</code> variable or interface method), and the
+  relationship between send and receive operations on the same
+  channel.
+</p>
+<p>
+  Compared to type analysis, pointer analysis requires more time and
+  memory, and is impractical for code bases exceeding a million lines.
+</p>
+
+<h3>Call graph navigation</h3>
+<p>
+  When pointer analysis is complete, the source view annotates the
+  code with <b>callers</b> and <b>callees</b> information: callers
+  information is associated with the <code>func</code> keyword that
+  declares a function, and callees information is associated with the
+  open paren '<span style="color: dark-blue"><code>(</code></span>' of
+  a function call.
+</p>
+<p>
+  In this example, hovering over the declaration of the
+  <code>rot13</code> function (defined in strings/strings_test.go)
+  reveals that it is called in exactly one place.
+</p>
+<img class="ss" width='612' src='callers1.png'>
+<p>
+  Clicking the link navigates to the sole caller.  (If there were
+  multiple callers, a list of choices would be displayed first.)
+</p>
+<img class="ss" width='680' src='callers2.png'>
+<p>
+  Notice that hovering over this call reveals that there are 19
+  possible callees at this site, of which our <code>rot13</code>
+  function was just one: this is a dynamic call through a variable of
+  type <code>func(rune) rune</code>.
+
+  Clicking on the call brings up the list of all 19 potential callees,
+  shown truncated.  Many of them are anonymous functions.
+</p>
+<img class="ss" width='564' src='call3.png'>
+<p>
+  Pointer analysis gives a very precise approximation of the call
+  graph compared to type-based techniques.
+
+  As a case in point, the next example shows the dynamic call inside
+  the <code>testing</code> package responsible for calling all
+  user-defined functions named <code>Example<i>XYZ</i></code>.
+</p>
+<img class="ss" width='361' src='call-eg.png'>
+<p>
+  Recall that all such functions have type <code>func()</code>,
+  i.e. no arguments and no results.  A type-based approximation could
+  only conclude that this call might dispatch to any function matching
+  that type&mdash;and these are very numerous in most
+  programs&mdash;but pointer analysis can track the flow of specific
+  <code>func</code> values through the testing package.
+
+  As an indication of its precision, the result contains only
+  functions whose name starts with <code>Example</code>.
+</p>
+
+<h3>Intra-package call graph</h3>
+<p>
+  The same call graph information is presented in a very different way
+  in the package view.  For each package, an interactive tree view
+  allows exploration of the call graph as it relates to just that
+  package; all functions from other packages are elided.
+
+  The roots of the tree are the external entry points of the package:
+  not only its exported functions, but also any unexported or
+  anonymous functions that are called (dynamically) from outside the
+  package.
+</p>
+<p>
+  This example shows the entry points of the
+  <code>path/filepath</code> package, with the call graph for
+  <code>Glob</code> expanded several levels
+</p>
+<img class="ss dotted" width='501' src='ipcg-pkg.png'>
+<p>
+  Notice that the nodes for Glob and Join appear multiple times: the
+  tree is a partial unrolling of a cyclic graph; the full unrolling
+  is in general infinite.
+</p>
+<p>
+  For each function documented in the package view, another
+  interactive tree view allows exploration of the same graph starting
+  at that function.
+
+  This is a portion of the internal graph of
+  <code>net/http.ListenAndServe</code>.
+</p>
+<img class="ss dotted" width='455' src='ipcg-func.png'>
+
+<h3>Channel peers (send ↔ receive)</h3>
+<p>
+  Because concurrent Go programs use channels to pass not just values
+  but also control between different goroutines, it is natural when
+  reading Go code to want to navigate from a channel send to the
+  corresponding receive so as to understand the sequence of events.
+</p>
+<p>
+  Godoc annotates every channel operation&mdash;make, send, range,
+  receive, close&mdash;with a link to a panel displaying information
+  about other operations that might alias the same channel.
+</p>
+<p>
+  This example, from the tests of <code>net/http</code>, shows a send
+  operation on a <code>chan bool</code>.
+</p>
+<img class="ss" width='811' src='chan1.png'>
+<p>
+  Clicking on the <code>&lt;-</code> send operator reveals that this
+  channel is made at a unique location (line 332) and that there are
+  three receive operations that might read this value.
+
+  It hardly needs pointing out that some channel element types are
+  very widely used (e.g. struct{}, bool, int, interface{}) and that a
+  typical Go program might contain dozens of receive operations on a
+  value of type <code>chan bool</code>; yet the pointer analysis is
+  able to distinguish operations on channels at a much finer precision
+  than based on their type alone.
+</p>
+<p>
+  Notice also that the send occurs in a different (anonymous) function
+  from the outer one containing the <code>make</code> and the receive
+  operations.
+</p>
+<p>
+  Here's another example of send on a different <code>chan
+  bool</code>, also in package <code>net/http</code>:
+</p>
+<img class="ss" width='774' src='chan2a.png'>
+<p>
+  The analysis finds just one receive operation that might receive
+  from this channel, in the test for this feature.
+</p>
+<img class="ss" width='737' src='chan2b.png'>
+
+<h2>Known issues</h2>
+<p>
+  All analysis results pertain to exactly
+  one configuration (e.g. amd64 linux).  Files that are conditionally
+  compiled based on different platforms or build tags are not visible
+  to the analysis.
+</p>
+<p>
+  Files that <code>import "C"</code> require
+  preprocessing by the cgo tool.  The file offsets after preprocessing
+  do not align with the unpreprocessed file, so markup is misaligned.
+</p>
+<p>
+  Files are not periodically re-analyzed.
+  If the files change underneath the running server, the displayed
+  markup is misaligned.
+</p>
+<p>
+  Additional issues are listed at
+  <a href='https://go.googlesource.com/tools/+/master/godoc/analysis/README'>tools/godoc/analysis/README</a>.
+</p>
diff --git a/internal/godoc/static/analysis/ident-def.png b/internal/godoc/static/analysis/ident-def.png
new file mode 100644
index 0000000..b0d9e55
--- /dev/null
+++ b/internal/godoc/static/analysis/ident-def.png
Binary files differ
diff --git a/internal/godoc/static/analysis/ident-field.png b/internal/godoc/static/analysis/ident-field.png
new file mode 100644
index 0000000..76cbe5a
--- /dev/null
+++ b/internal/godoc/static/analysis/ident-field.png
Binary files differ
diff --git a/internal/godoc/static/analysis/ident-func.png b/internal/godoc/static/analysis/ident-func.png
new file mode 100644
index 0000000..69670fa
--- /dev/null
+++ b/internal/godoc/static/analysis/ident-func.png
Binary files differ
diff --git a/internal/godoc/static/analysis/ipcg-func.png b/internal/godoc/static/analysis/ipcg-func.png
new file mode 100644
index 0000000..523318d
--- /dev/null
+++ b/internal/godoc/static/analysis/ipcg-func.png
Binary files differ
diff --git a/internal/godoc/static/analysis/ipcg-pkg.png b/internal/godoc/static/analysis/ipcg-pkg.png
new file mode 100644
index 0000000..e029068
--- /dev/null
+++ b/internal/godoc/static/analysis/ipcg-pkg.png
Binary files differ
diff --git a/internal/godoc/static/analysis/typeinfo-pkg.png b/internal/godoc/static/analysis/typeinfo-pkg.png
new file mode 100644
index 0000000..91bd5f7
--- /dev/null
+++ b/internal/godoc/static/analysis/typeinfo-pkg.png
Binary files differ
diff --git a/internal/godoc/static/analysis/typeinfo-src.png b/internal/godoc/static/analysis/typeinfo-src.png
new file mode 100644
index 0000000..6e5b147
--- /dev/null
+++ b/internal/godoc/static/analysis/typeinfo-src.png
Binary files differ
diff --git a/internal/godoc/static/callgraph.html b/internal/godoc/static/callgraph.html
new file mode 100644
index 0000000..c56b2ef
--- /dev/null
+++ b/internal/godoc/static/callgraph.html
@@ -0,0 +1,15 @@
+<div class="toggle" style="display: none">
+	<div class="collapsed">
+		<p class="exampleHeading toggleButton">▹ <span class="text">Internal call graph</span></p>
+	</div>
+	<div class="expanded">
+		<p class="exampleHeading toggleButton">▾ <span class="text">Internal call graph</span></p>
+		<p>
+		  This viewer shows the portion of the internal call
+		  graph of this package that is reachable from this function.
+		  See the <a href='#pkg-callgraph'>package's call
+		  graph</a> for more information.
+		</p>
+		<ul style="margin-left: 0.5in" id="callgraph-{{.Index}}" class="treeview"></ul>
+	</div>
+</div>
diff --git a/internal/godoc/static/dirlist.html b/internal/godoc/static/dirlist.html
new file mode 100644
index 0000000..a3e1a2f
--- /dev/null
+++ b/internal/godoc/static/dirlist.html
@@ -0,0 +1,31 @@
+<!--
+	Copyright 2009 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.
+-->
+
+<p>
+<table class="layout">
+<tr>
+	<th align="left">File</th>
+	<td width="25">&nbsp;</td>
+	<th align="right">Bytes</th>
+	<td width="25">&nbsp;</td>
+	<th align="left">Modified</th>
+</tr>
+<tr>
+	<td><a href="..">..</a></td>
+</tr>
+{{range .}}
+<tr>
+	{{$name_html := fileInfoName . | html}}
+	<td align="left"><a href="{{$name_html}}">{{$name_html}}</a></td>
+	<td></td>
+	<td align="right">{{html .Size}}</td>
+	<td></td>
+	<td align="left">{{fileInfoTime . | html}}</td>
+</tr>
+{{end}}
+
+</table>
+</p>
diff --git a/internal/godoc/static/doc.go b/internal/godoc/static/doc.go
new file mode 100644
index 0000000..ee0c923
--- /dev/null
+++ b/internal/godoc/static/doc.go
@@ -0,0 +1,8 @@
+// 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.
+
+// Package static exports a map of static file content that supports the godoc
+// user interface. The map should be used with the mapfs package, see
+// golang.org/x/website/internal/godoc/vfs/mapfs.
+package static // import "golang.org/x/website/internal/godoc/static"
diff --git a/internal/godoc/static/error.html b/internal/godoc/static/error.html
new file mode 100644
index 0000000..7573aa2
--- /dev/null
+++ b/internal/godoc/static/error.html
@@ -0,0 +1,9 @@
+<!--
+	Copyright 2009 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.
+-->
+
+<p>
+<span class="alert" style="font-size:120%">{{html .}}</span>
+</p>
diff --git a/internal/godoc/static/example.html b/internal/godoc/static/example.html
new file mode 100644
index 0000000..1e86b25
--- /dev/null
+++ b/internal/godoc/static/example.html
@@ -0,0 +1,30 @@
+<div id="example_{{.Name}}" class="toggle">
+	<div class="collapsed">
+		<p class="exampleHeading toggleButton">▹ <span class="text">Example{{example_suffix .Name}}</span></p>
+	</div>
+	<div class="expanded">
+		<p class="exampleHeading toggleButton">▾ <span class="text">Example{{example_suffix .Name}}</span></p>
+		{{with .Doc}}<p>{{html .}}</p>{{end}}
+		{{$output := .Output}}
+		{{with .Play}}
+			<div class="play">
+				<div class="input"><textarea class="code" spellcheck="false">{{html .}}</textarea></div>
+				<div class="output"><pre>{{html $output}}</pre></div>
+				<div class="buttons">
+					<a class="run" title="Run this code [shift-enter]">Run</a>
+					<a class="fmt" title="Format this code">Format</a>
+					{{if not $.GoogleCN}}
+					<a class="share" title="Share this code">Share</a>
+					{{end}}
+				</div>
+			</div>
+		{{else}}
+			<p>Code:</p>
+			<pre class="code">{{.Code}}</pre>
+			{{with .Output}}
+			<p>Output:</p>
+			<pre class="output">{{html .}}</pre>
+			{{end}}
+		{{end}}
+	</div>
+</div>
diff --git a/internal/godoc/static/gen.go b/internal/godoc/static/gen.go
new file mode 100644
index 0000000..85c6714
--- /dev/null
+++ b/internal/godoc/static/gen.go
@@ -0,0 +1,105 @@
+// Copyright 2014 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.
+
+package static
+
+//go:generate go run makestatic.go
+
+import (
+	"bytes"
+	"fmt"
+	"go/format"
+	"io/ioutil"
+	"unicode"
+)
+
+var files = []string{
+	"analysis/call3.png",
+	"analysis/call-eg.png",
+	"analysis/callers1.png",
+	"analysis/callers2.png",
+	"analysis/chan1.png",
+	"analysis/chan2a.png",
+	"analysis/chan2b.png",
+	"analysis/error1.png",
+	"analysis/help.html",
+	"analysis/ident-def.png",
+	"analysis/ident-field.png",
+	"analysis/ident-func.png",
+	"analysis/ipcg-func.png",
+	"analysis/ipcg-pkg.png",
+	"analysis/typeinfo-pkg.png",
+	"analysis/typeinfo-src.png",
+	"callgraph.html",
+	"dirlist.html",
+	"error.html",
+	"example.html",
+	"godoc.html",
+	"godocs.js",
+	"images/minus.gif",
+	"images/plus.gif",
+	"images/treeview-black-line.gif",
+	"images/treeview-black.gif",
+	"images/treeview-default-line.gif",
+	"images/treeview-default.gif",
+	"images/treeview-gray-line.gif",
+	"images/treeview-gray.gif",
+	"implements.html",
+	"jquery.js",
+	"jquery.treeview.css",
+	"jquery.treeview.edit.js",
+	"jquery.treeview.js",
+	"methodset.html",
+	"package.html",
+	"packageroot.html",
+	"play.js",
+	"playground.js",
+	"search.html",
+	"searchcode.html",
+	"searchdoc.html",
+	"searchtxt.html",
+	"style.css",
+}
+
+// Generate reads a set of files and returns a file buffer that declares
+// a map of string constants containing contents of the input files.
+func Generate() ([]byte, error) {
+	buf := new(bytes.Buffer)
+	fmt.Fprintf(buf, "%v\n\n%v\n\npackage static\n\n", license, warning)
+	fmt.Fprintf(buf, "var Files = map[string]string{\n")
+	for _, fn := range files {
+		b, err := ioutil.ReadFile(fn)
+		if err != nil {
+			return b, err
+		}
+		fmt.Fprintf(buf, "\t%q: ", fn)
+		appendQuote(buf, b)
+		fmt.Fprintf(buf, ",\n\n")
+	}
+	fmt.Fprintln(buf, "}")
+	return format.Source(buf.Bytes())
+}
+
+// appendQuote is like strconv.AppendQuote, but we avoid the latter
+// because it changes when Unicode evolves, breaking gen_test.go.
+func appendQuote(out *bytes.Buffer, data []byte) {
+	out.WriteByte('"')
+	for _, b := range data {
+		if b == '\\' || b == '"' {
+			out.WriteByte('\\')
+			out.WriteByte(b)
+		} else if b <= unicode.MaxASCII && unicode.IsPrint(rune(b)) && !unicode.IsSpace(rune(b)) {
+			out.WriteByte(b)
+		} else {
+			fmt.Fprintf(out, "\\x%02x", b)
+		}
+	}
+	out.WriteByte('"')
+}
+
+const warning = `// Code generated by "makestatic"; DO NOT EDIT.`
+
+const license = `// 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.`
diff --git a/internal/godoc/static/gen_test.go b/internal/godoc/static/gen_test.go
new file mode 100644
index 0000000..e5a9f81
--- /dev/null
+++ b/internal/godoc/static/gen_test.go
@@ -0,0 +1,53 @@
+// Copyright 2018 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.
+
+package static
+
+import (
+	"bytes"
+	"io/ioutil"
+	"runtime"
+	"strconv"
+	"testing"
+	"unicode"
+)
+
+func TestStaticIsUpToDate(t *testing.T) {
+	if runtime.GOOS == "android" {
+		t.Skip("files not available on android")
+	}
+	oldBuf, err := ioutil.ReadFile("static.go")
+	if err != nil {
+		t.Errorf("error while reading static.go: %v\n", err)
+	}
+
+	newBuf, err := Generate()
+	if err != nil {
+		t.Errorf("error while generating static.go: %v\n", err)
+	}
+
+	if !bytes.Equal(oldBuf, newBuf) {
+		t.Error(`static.go is stale.  Run:
+  $ go generate golang.org/x/website/internal/godoc/static
+  $ git diff
+to see the differences.`)
+
+	}
+}
+
+// TestAppendQuote ensures that AppendQuote produces a valid literal.
+func TestAppendQuote(t *testing.T) {
+	var in, out bytes.Buffer
+	for r := rune(0); r < unicode.MaxRune; r++ {
+		in.WriteRune(r)
+	}
+	appendQuote(&out, in.Bytes())
+	in2, err := strconv.Unquote(out.String())
+	if err != nil {
+		t.Fatalf("AppendQuote produced invalid string literal: %v", err)
+	}
+	if got, want := in2, in.String(); got != want {
+		t.Fatal("AppendQuote modified string") // no point printing got/want: huge
+	}
+}
diff --git a/internal/godoc/static/godoc.html b/internal/godoc/static/godoc.html
new file mode 100644
index 0000000..86cacb7
--- /dev/null
+++ b/internal/godoc/static/godoc.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta name="theme-color" content="#375EAB">
+{{with .Tabtitle}}
+  <title>{{html .}} - Go Documentation Server</title>
+{{else}}
+  <title>Go Documentation Server</title>
+{{end}}
+<link type="text/css" rel="stylesheet" href="/lib/godoc/style.css">
+{{if .TreeView}}
+<link rel="stylesheet" href="/lib/godoc/jquery.treeview.css">
+{{end}}
+<script>window.initFuncs = [];</script>
+<script src="/lib/godoc/jquery.js" defer></script>
+{{if .TreeView}}
+<script src="/lib/godoc/jquery.treeview.js" defer></script>
+<script src="/lib/godoc/jquery.treeview.edit.js" defer></script>
+{{end}}
+
+{{if .Playground}}
+<script src="/lib/godoc/playground.js" defer></script>
+{{end}}
+{{with .Version}}<script>var goVersion = {{printf "%q" .}};</script>{{end}}
+<script src="/lib/godoc/godocs.js" defer></script>
+</head>
+<body>
+
+<div id='lowframe' style="position: fixed; bottom: 0; left: 0; height: 0; width: 100%; border-top: thin solid grey; background-color: white; overflow: auto;">
+...
+</div><!-- #lowframe -->
+
+<div id="topbar"{{if .Title}} class="wide"{{end}}><div class="container">
+<div class="top-heading" id="heading-wide"><a href="/pkg/">Go Documentation Server</a></div>
+<div class="top-heading" id="heading-narrow"><a href="/pkg/">GoDoc</a></div>
+<a href="#" id="menu-button"><span id="menu-button-arrow">&#9661;</span></a>
+<form method="GET" action="/search">
+<div id="menu">
+{{if (and .Playground .Title)}}
+<a id="playgroundButton" href="http://play.golang.org/" title="Show Go Playground">Play</a>
+{{end}}
+<span class="search-box"><input type="search" id="search" name="q" placeholder="Search" aria-label="Search" required><button type="submit"><span><!-- magnifying glass: --><svg width="24" height="24" viewBox="0 0 24 24"><title>submit search</title><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/><path d="M0 0h24v24H0z" fill="none"/></svg></span></button></span>
+</div>
+</form>
+
+</div></div>
+
+{{if .Playground}}
+<div id="playground" class="play">
+	<div class="input"><textarea class="code" spellcheck="false">package main
+
+import "fmt"
+
+func main() {
+	fmt.Println("Hello, 世界")
+}</textarea></div>
+	<div class="output"></div>
+	<div class="buttons">
+		<a class="run" title="Run this code [shift-enter]">Run</a>
+		<a class="fmt" title="Format this code">Format</a>
+		{{if not $.GoogleCN}}
+		<a class="share" title="Share this code">Share</a>
+		{{end}}
+	</div>
+</div>
+{{end}}
+
+<div id="page"{{if .Title}} class="wide"{{end}}>
+<div class="container">
+
+{{if or .Title .SrcPath}}
+  <h1>
+    {{html .Title}}
+    {{html .SrcPath | srcBreadcrumb}}
+  </h1>
+{{end}}
+
+{{with .Subtitle}}
+  <h2>{{html .}}</h2>
+{{end}}
+
+{{with .SrcPath}}
+  <h2>
+    Documentation: {{html . | srcToPkgLink}}
+  </h2>
+{{end}}
+
+{{/* The Table of Contents is automatically inserted in this <div>.
+     Do not delete this <div>. */}}
+<div id="nav"></div>
+
+{{/* Body is HTML-escaped elsewhere */}}
+{{printf "%s" .Body}}
+
+<div id="footer">
+Build version {{html .Version}}.<br>
+Except as <a href="https://developers.google.com/site-policies#restrictions">noted</a>,
+the content of this page is licensed under the
+Creative Commons Attribution 3.0 License,
+and code is licensed under a <a href="/LICENSE">BSD license</a>.<br>
+<a href="/doc/tos.html">Terms of Service</a> |
+<a href="http://www.google.com/intl/en/policies/privacy/">Privacy Policy</a>
+</div>
+
+</div><!-- .container -->
+</div><!-- #page -->
+</body>
+</html>
diff --git a/internal/godoc/static/godocs.js b/internal/godoc/static/godocs.js
new file mode 100644
index 0000000..7f02ba2
--- /dev/null
+++ b/internal/godoc/static/godocs.js
@@ -0,0 +1,688 @@
+// Copyright 2012 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.
+
+/* A little code to ease navigation of these documents.
+ *
+ * On window load we:
+ *  + Generate a table of contents (generateTOC)
+ *  + Bind foldable sections (bindToggles)
+ *  + Bind links to foldable sections (bindToggleLinks)
+ */
+
+(function() {
+  'use strict';
+
+  // Mobile-friendly topbar menu
+  $(function() {
+    var menu = $('#menu');
+    var menuButton = $('#menu-button');
+    var menuButtonArrow = $('#menu-button-arrow');
+    menuButton.click(function(event) {
+      menu.toggleClass('menu-visible');
+      menuButtonArrow.toggleClass('vertical-flip');
+      event.preventDefault();
+      return false;
+    });
+  });
+
+  /* Generates a table of contents: looks for h2 and h3 elements and generates
+   * links. "Decorates" the element with id=="nav" with this table of contents.
+   */
+  function generateTOC() {
+    if ($('#manual-nav').length > 0) {
+      return;
+    }
+
+    // For search, we send the toc precomputed from server-side.
+    // TODO: Ideally, this should always be precomputed for all pages, but then
+    // we need to do HTML parsing on the server-side.
+    if (location.pathname === '/search') {
+      return;
+    }
+
+    var nav = $('#nav');
+    if (nav.length === 0) {
+      return;
+    }
+
+    var toc_items = [];
+    $(nav)
+      .nextAll('h2, h3')
+      .each(function() {
+        var node = this;
+        if (node.id == '') node.id = 'tmp_' + toc_items.length;
+        var link = $('<a/>')
+          .attr('href', '#' + node.id)
+          .text($(node).text());
+        var item;
+        if ($(node).is('h2')) {
+          item = $('<dt/>');
+        } else {
+          // h3
+          item = $('<dd class="indent"/>');
+        }
+        item.append(link);
+        toc_items.push(item);
+      });
+    if (toc_items.length <= 1) {
+      return;
+    }
+    var dl1 = $('<dl/>');
+    var dl2 = $('<dl/>');
+
+    var split_index = toc_items.length / 2 + 1;
+    if (split_index < 8) {
+      split_index = toc_items.length;
+    }
+    for (var i = 0; i < split_index; i++) {
+      dl1.append(toc_items[i]);
+    }
+    for (; /* keep using i */ i < toc_items.length; i++) {
+      dl2.append(toc_items[i]);
+    }
+
+    var tocTable = $('<table class="unruled"/>').appendTo(nav);
+    var tocBody = $('<tbody/>').appendTo(tocTable);
+    var tocRow = $('<tr/>').appendTo(tocBody);
+
+    // 1st column
+    $('<td class="first"/>')
+      .appendTo(tocRow)
+      .append(dl1);
+    // 2nd column
+    $('<td/>')
+      .appendTo(tocRow)
+      .append(dl2);
+  }
+
+  function bindToggle(el) {
+    $('.toggleButton', el).click(function() {
+      if ($(this).closest('.toggle, .toggleVisible')[0] != el) {
+        // Only trigger the closest toggle header.
+        return;
+      }
+
+      if ($(el).is('.toggle')) {
+        $(el)
+          .addClass('toggleVisible')
+          .removeClass('toggle');
+      } else {
+        $(el)
+          .addClass('toggle')
+          .removeClass('toggleVisible');
+      }
+    });
+  }
+
+  function bindToggles(selector) {
+    $(selector).each(function(i, el) {
+      bindToggle(el);
+    });
+  }
+
+  function bindToggleLink(el, prefix) {
+    $(el).click(function() {
+      var href = $(el).attr('href');
+      var i = href.indexOf('#' + prefix);
+      if (i < 0) {
+        return;
+      }
+      var id = '#' + prefix + href.slice(i + 1 + prefix.length);
+      if ($(id).is('.toggle')) {
+        $(id)
+          .find('.toggleButton')
+          .first()
+          .click();
+      }
+    });
+  }
+  function bindToggleLinks(selector, prefix) {
+    $(selector).each(function(i, el) {
+      bindToggleLink(el, prefix);
+    });
+  }
+
+  function setupDropdownPlayground() {
+    if (!$('#page').is('.wide')) {
+      return; // don't show on front page
+    }
+    var button = $('#playgroundButton');
+    var div = $('#playground');
+    var setup = false;
+    button.toggle(
+      function() {
+        button.addClass('active');
+        div.show();
+        if (setup) {
+          return;
+        }
+        setup = true;
+        playground({
+          codeEl: $('.code', div),
+          outputEl: $('.output', div),
+          runEl: $('.run', div),
+          fmtEl: $('.fmt', div),
+          shareEl: $('.share', div),
+          shareRedirect: '//play.golang.org/p/',
+        });
+      },
+      function() {
+        button.removeClass('active');
+        div.hide();
+      }
+    );
+    $('#menu').css('min-width', '+=60');
+
+    // Hide inline playground if we click somewhere on the page.
+    // This is needed in mobile devices, where the "Play" button
+    // is not clickable once the playground opens up.
+    $('#page').click(function() {
+      if (button.hasClass('active')) {
+        button.click();
+      }
+    });
+  }
+
+  function setupInlinePlayground() {
+    'use strict';
+    // Set up playground when each element is toggled.
+    $('div.play').each(function(i, el) {
+      // Set up playground for this example.
+      var setup = function() {
+        var code = $('.code', el);
+        playground({
+          codeEl: code,
+          outputEl: $('.output', el),
+          runEl: $('.run', el),
+          fmtEl: $('.fmt', el),
+          shareEl: $('.share', el),
+          shareRedirect: '//play.golang.org/p/',
+        });
+
+        // Make the code textarea resize to fit content.
+        var resize = function() {
+          code.height(0);
+          var h = code[0].scrollHeight;
+          code.height(h + 20); // minimize bouncing.
+          code.closest('.input').height(h);
+        };
+        code.on('keydown', resize);
+        code.on('keyup', resize);
+        code.keyup(); // resize now.
+      };
+
+      // If example already visible, set up playground now.
+      if ($(el).is(':visible')) {
+        setup();
+        return;
+      }
+
+      // Otherwise, set up playground when example is expanded.
+      var built = false;
+      $(el)
+        .closest('.toggle')
+        .click(function() {
+          // Only set up once.
+          if (!built) {
+            setup();
+            built = true;
+          }
+        });
+    });
+  }
+
+  // fixFocus tries to put focus to div#page so that keyboard navigation works.
+  function fixFocus() {
+    var page = $('div#page');
+    var topbar = $('div#topbar');
+    page.css('outline', 0); // disable outline when focused
+    page.attr('tabindex', -1); // and set tabindex so that it is focusable
+    $(window)
+      .resize(function(evt) {
+        // only focus page when the topbar is at fixed position (that is, it's in
+        // front of page, and keyboard event will go to the former by default.)
+        // by focusing page, keyboard event will go to page so that up/down arrow,
+        // space, etc. will work as expected.
+        if (topbar.css('position') == 'fixed') page.focus();
+      })
+      .resize();
+  }
+
+  function toggleHash() {
+    var id = window.location.hash.substring(1);
+    // Open all of the toggles for a particular hash.
+    var els = $(
+      document.getElementById(id),
+      $('a[name]').filter(function() {
+        return $(this).attr('name') == id;
+      })
+    );
+
+    while (els.length) {
+      for (var i = 0; i < els.length; i++) {
+        var el = $(els[i]);
+        if (el.is('.toggle')) {
+          el.find('.toggleButton')
+            .first()
+            .click();
+        }
+      }
+      els = el.parent();
+    }
+  }
+
+  function personalizeInstallInstructions() {
+    var prefix = '?download=';
+    var s = window.location.search;
+    if (s.indexOf(prefix) != 0) {
+      // No 'download' query string; detect "test" instructions from User Agent.
+      if (navigator.platform.indexOf('Win') != -1) {
+        $('.testUnix').hide();
+        $('.testWindows').show();
+      } else {
+        $('.testUnix').show();
+        $('.testWindows').hide();
+      }
+      return;
+    }
+
+    var filename = s.substr(prefix.length);
+    var filenameRE = /^go1\.\d+(\.\d+)?([a-z0-9]+)?\.([a-z0-9]+)(-[a-z0-9]+)?(-osx10\.[68])?\.([a-z.]+)$/;
+    var m = filenameRE.exec(filename);
+    if (!m) {
+      // Can't interpret file name; bail.
+      return;
+    }
+    $('.downloadFilename').text(filename);
+    $('.hideFromDownload').hide();
+
+    var os = m[3];
+    var ext = m[6];
+    if (ext != 'tar.gz') {
+      $('#tarballInstructions').hide();
+    }
+    if (os != 'darwin' || ext != 'pkg') {
+      $('#darwinPackageInstructions').hide();
+    }
+    if (os != 'windows') {
+      $('#windowsInstructions').hide();
+      $('.testUnix').show();
+      $('.testWindows').hide();
+    } else {
+      if (ext != 'msi') {
+        $('#windowsInstallerInstructions').hide();
+      }
+      if (ext != 'zip') {
+        $('#windowsZipInstructions').hide();
+      }
+      $('.testUnix').hide();
+      $('.testWindows').show();
+    }
+
+    var download = 'https://dl.google.com/go/' + filename;
+
+    var message = $(
+      '<p class="downloading">' +
+        'Your download should begin shortly. ' +
+        'If it does not, click <a>this link</a>.</p>'
+    );
+    message.find('a').attr('href', download);
+    message.insertAfter('#nav');
+
+    window.location = download;
+  }
+
+  function updateVersionTags() {
+    var v = window.goVersion;
+    if (/^go[0-9.]+$/.test(v)) {
+      $('.versionTag')
+        .empty()
+        .text(v);
+      $('.whereTag').hide();
+    }
+  }
+
+  function addPermalinks() {
+    function addPermalink(source, parent) {
+      var id = source.attr('id');
+      if (id == '' || id.indexOf('tmp_') === 0) {
+        // Auto-generated permalink.
+        return;
+      }
+      if (parent.find('> .permalink').length) {
+        // Already attached.
+        return;
+      }
+      parent
+        .append(' ')
+        .append($("<a class='permalink'>&#xb6;</a>").attr('href', '#' + id));
+    }
+
+    $('#page .container')
+      .find('h2[id], h3[id]')
+      .each(function() {
+        var el = $(this);
+        addPermalink(el, el);
+      });
+
+    $('#page .container')
+      .find('dl[id]')
+      .each(function() {
+        var el = $(this);
+        // Add the anchor to the "dt" element.
+        addPermalink(el, el.find('> dt').first());
+      });
+  }
+
+  $('.js-expandAll').click(function() {
+    if ($(this).hasClass('collapsed')) {
+      toggleExamples('toggle');
+      $(this).text('(Collapse All)');
+    } else {
+      toggleExamples('toggleVisible');
+      $(this).text('(Expand All)');
+    }
+    $(this).toggleClass('collapsed');
+  });
+
+  function toggleExamples(className) {
+    // We need to explicitly iterate through divs starting with "example_"
+    // to avoid toggling Overview and Index collapsibles.
+    $("[id^='example_']").each(function() {
+      // Check for state and click it only if required.
+      if ($(this).hasClass(className)) {
+        $(this)
+          .find('.toggleButton')
+          .first()
+          .click();
+      }
+    });
+  }
+
+  $(document).ready(function() {
+    generateTOC();
+    addPermalinks();
+    bindToggles('.toggle');
+    bindToggles('.toggleVisible');
+    bindToggleLinks('.exampleLink', 'example_');
+    bindToggleLinks('.overviewLink', '');
+    bindToggleLinks('.examplesLink', '');
+    bindToggleLinks('.indexLink', '');
+    setupDropdownPlayground();
+    setupInlinePlayground();
+    fixFocus();
+    setupTypeInfo();
+    setupCallgraphs();
+    toggleHash();
+    personalizeInstallInstructions();
+    updateVersionTags();
+
+    // godoc.html defines window.initFuncs in the <head> tag, and root.html and
+    // codewalk.js push their on-page-ready functions to the list.
+    // We execute those functions here, to avoid loading jQuery until the page
+    // content is loaded.
+    for (var i = 0; i < window.initFuncs.length; i++) window.initFuncs[i]();
+  });
+
+  // -- analysis ---------------------------------------------------------
+
+  // escapeHTML returns HTML for s, with metacharacters quoted.
+  // It is safe for use in both elements and attributes
+  // (unlike the "set innerText, read innerHTML" trick).
+  function escapeHTML(s) {
+    return s
+      .replace(/&/g, '&amp;')
+      .replace(/\"/g, '&quot;')
+      .replace(/\'/g, '&#39;')
+      .replace(/</g, '&lt;')
+      .replace(/>/g, '&gt;');
+  }
+
+  // makeAnchor returns HTML for an <a> element, given an anchorJSON object.
+  function makeAnchor(json) {
+    var html = escapeHTML(json.Text);
+    if (json.Href != '') {
+      html = "<a href='" + escapeHTML(json.Href) + "'>" + html + '</a>';
+    }
+    return html;
+  }
+
+  function showLowFrame(html) {
+    var lowframe = document.getElementById('lowframe');
+    lowframe.style.height = '200px';
+    lowframe.innerHTML =
+      "<p style='text-align: left;'>" +
+      html +
+      '</p>\n' +
+      "<div onclick='hideLowFrame()' style='position: absolute; top: 0; right: 0; cursor: pointer;'>✘</div>";
+  }
+
+  document.hideLowFrame = function() {
+    var lowframe = document.getElementById('lowframe');
+    lowframe.style.height = '0px';
+  };
+
+  // onClickCallers is the onclick action for the 'func' tokens of a
+  // function declaration.
+  document.onClickCallers = function(index) {
+    var data = document.ANALYSIS_DATA[index];
+    if (data.Callers.length == 1 && data.Callers[0].Sites.length == 1) {
+      document.location = data.Callers[0].Sites[0].Href; // jump to sole caller
+      return;
+    }
+
+    var html =
+      'Callers of <code>' + escapeHTML(data.Callee) + '</code>:<br/>\n';
+    for (var i = 0; i < data.Callers.length; i++) {
+      var caller = data.Callers[i];
+      html += '<code>' + escapeHTML(caller.Func) + '</code>';
+      var sites = caller.Sites;
+      if (sites != null && sites.length > 0) {
+        html += ' at line ';
+        for (var j = 0; j < sites.length; j++) {
+          if (j > 0) {
+            html += ', ';
+          }
+          html += '<code>' + makeAnchor(sites[j]) + '</code>';
+        }
+      }
+      html += '<br/>\n';
+    }
+    showLowFrame(html);
+  };
+
+  // onClickCallees is the onclick action for the '(' token of a function call.
+  document.onClickCallees = function(index) {
+    var data = document.ANALYSIS_DATA[index];
+    if (data.Callees.length == 1) {
+      document.location = data.Callees[0].Href; // jump to sole callee
+      return;
+    }
+
+    var html = 'Callees of this ' + escapeHTML(data.Descr) + ':<br/>\n';
+    for (var i = 0; i < data.Callees.length; i++) {
+      html += '<code>' + makeAnchor(data.Callees[i]) + '</code><br/>\n';
+    }
+    showLowFrame(html);
+  };
+
+  // onClickTypeInfo is the onclick action for identifiers declaring a named type.
+  document.onClickTypeInfo = function(index) {
+    var data = document.ANALYSIS_DATA[index];
+    var html =
+      'Type <code>' +
+      data.Name +
+      '</code>: ' +
+      '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<small>(size=' +
+      data.Size +
+      ', align=' +
+      data.Align +
+      ')</small><br/>\n';
+    html += implementsHTML(data);
+    html += methodsetHTML(data);
+    showLowFrame(html);
+  };
+
+  // implementsHTML returns HTML for the implements relation of the
+  // specified TypeInfoJSON value.
+  function implementsHTML(info) {
+    var html = '';
+    if (info.ImplGroups != null) {
+      for (var i = 0; i < info.ImplGroups.length; i++) {
+        var group = info.ImplGroups[i];
+        var x = '<code>' + escapeHTML(group.Descr) + '</code> ';
+        for (var j = 0; j < group.Facts.length; j++) {
+          var fact = group.Facts[j];
+          var y = '<code>' + makeAnchor(fact.Other) + '</code>';
+          if (fact.ByKind != null) {
+            html += escapeHTML(fact.ByKind) + ' type ' + y + ' implements ' + x;
+          } else {
+            html += x + ' implements ' + y;
+          }
+          html += '<br/>\n';
+        }
+      }
+    }
+    return html;
+  }
+
+  // methodsetHTML returns HTML for the methodset of the specified
+  // TypeInfoJSON value.
+  function methodsetHTML(info) {
+    var html = '';
+    if (info.Methods != null) {
+      for (var i = 0; i < info.Methods.length; i++) {
+        html += '<code>' + makeAnchor(info.Methods[i]) + '</code><br/>\n';
+      }
+    }
+    return html;
+  }
+
+  // onClickComm is the onclick action for channel "make" and "<-"
+  // send/receive tokens.
+  document.onClickComm = function(index) {
+    var ops = document.ANALYSIS_DATA[index].Ops;
+    if (ops.length == 1) {
+      document.location = ops[0].Op.Href; // jump to sole element
+      return;
+    }
+
+    var html = 'Operations on this channel:<br/>\n';
+    for (var i = 0; i < ops.length; i++) {
+      html +=
+        makeAnchor(ops[i].Op) +
+        ' by <code>' +
+        escapeHTML(ops[i].Fn) +
+        '</code><br/>\n';
+    }
+    if (ops.length == 0) {
+      html += '(none)<br/>\n';
+    }
+    showLowFrame(html);
+  };
+
+  $(window).load(function() {
+    // Scroll window so that first selection is visible.
+    // (This means we don't need to emit id='L%d' spans for each line.)
+    // TODO(adonovan): ideally, scroll it so that it's under the pointer,
+    // but I don't know how to get the pointer y coordinate.
+    var elts = document.getElementsByClassName('selection');
+    if (elts.length > 0) {
+      elts[0].scrollIntoView();
+    }
+  });
+
+  // setupTypeInfo populates the "Implements" and "Method set" toggle for
+  // each type in the package doc.
+  function setupTypeInfo() {
+    for (var i in document.ANALYSIS_DATA) {
+      var data = document.ANALYSIS_DATA[i];
+
+      var el = document.getElementById('implements-' + i);
+      if (el != null) {
+        // el != null => data is TypeInfoJSON.
+        if (data.ImplGroups != null) {
+          el.innerHTML = implementsHTML(data);
+          el.parentNode.parentNode.style.display = 'block';
+        }
+      }
+
+      var el = document.getElementById('methodset-' + i);
+      if (el != null) {
+        // el != null => data is TypeInfoJSON.
+        if (data.Methods != null) {
+          el.innerHTML = methodsetHTML(data);
+          el.parentNode.parentNode.style.display = 'block';
+        }
+      }
+    }
+  }
+
+  function setupCallgraphs() {
+    if (document.CALLGRAPH == null) {
+      return;
+    }
+    document.getElementById('pkg-callgraph').style.display = 'block';
+
+    var treeviews = document.getElementsByClassName('treeview');
+    for (var i = 0; i < treeviews.length; i++) {
+      var tree = treeviews[i];
+      if (tree.id == null || tree.id.indexOf('callgraph-') != 0) {
+        continue;
+      }
+      var id = tree.id.substring('callgraph-'.length);
+      $(tree).treeview({ collapsed: true, animated: 'fast' });
+      document.cgAddChildren(tree, tree, [id]);
+      tree.parentNode.parentNode.style.display = 'block';
+    }
+  }
+
+  document.cgAddChildren = function(tree, ul, indices) {
+    if (indices != null) {
+      for (var i = 0; i < indices.length; i++) {
+        var li = cgAddChild(tree, ul, document.CALLGRAPH[indices[i]]);
+        if (i == indices.length - 1) {
+          $(li).addClass('last');
+        }
+      }
+    }
+    $(tree).treeview({ animated: 'fast', add: ul });
+  };
+
+  // cgAddChild adds an <li> element for document.CALLGRAPH node cgn to
+  // the parent <ul> element ul. tree is the tree's root <ul> element.
+  function cgAddChild(tree, ul, cgn) {
+    var li = document.createElement('li');
+    ul.appendChild(li);
+    li.className = 'closed';
+
+    var code = document.createElement('code');
+
+    if (cgn.Callees != null) {
+      $(li).addClass('expandable');
+
+      // Event handlers and innerHTML updates don't play nicely together,
+      // hence all this explicit DOM manipulation.
+      var hitarea = document.createElement('div');
+      hitarea.className = 'hitarea expandable-hitarea';
+      li.appendChild(hitarea);
+
+      li.appendChild(code);
+
+      var childUL = document.createElement('ul');
+      li.appendChild(childUL);
+      childUL.setAttribute('style', 'display: none;');
+
+      var onClick = function() {
+        document.cgAddChildren(tree, childUL, cgn.Callees);
+        hitarea.removeEventListener('click', onClick);
+      };
+      hitarea.addEventListener('click', onClick);
+    } else {
+      li.appendChild(code);
+    }
+    code.innerHTML += '&nbsp;' + makeAnchor(cgn.Func);
+    return li;
+  }
+})();
diff --git a/internal/godoc/static/images/minus.gif b/internal/godoc/static/images/minus.gif
new file mode 100644
index 0000000..b15d7e1
--- /dev/null
+++ b/internal/godoc/static/images/minus.gif
Binary files differ
diff --git a/internal/godoc/static/images/plus.gif b/internal/godoc/static/images/plus.gif
new file mode 100644
index 0000000..c1222e9
--- /dev/null
+++ b/internal/godoc/static/images/plus.gif
Binary files differ
diff --git a/internal/godoc/static/images/treeview-black-line.gif b/internal/godoc/static/images/treeview-black-line.gif
new file mode 100644
index 0000000..03cb5d7
--- /dev/null
+++ b/internal/godoc/static/images/treeview-black-line.gif
Binary files differ
diff --git a/internal/godoc/static/images/treeview-black.gif b/internal/godoc/static/images/treeview-black.gif
new file mode 100644
index 0000000..fa277e6
--- /dev/null
+++ b/internal/godoc/static/images/treeview-black.gif
Binary files differ
diff --git a/internal/godoc/static/images/treeview-default-line.gif b/internal/godoc/static/images/treeview-default-line.gif
new file mode 100644
index 0000000..e88b0d4
--- /dev/null
+++ b/internal/godoc/static/images/treeview-default-line.gif
Binary files differ
diff --git a/internal/godoc/static/images/treeview-default.gif b/internal/godoc/static/images/treeview-default.gif
new file mode 100644
index 0000000..a3387e7
--- /dev/null
+++ b/internal/godoc/static/images/treeview-default.gif
Binary files differ
diff --git a/internal/godoc/static/images/treeview-gray-line.gif b/internal/godoc/static/images/treeview-gray-line.gif
new file mode 100644
index 0000000..170382c
--- /dev/null
+++ b/internal/godoc/static/images/treeview-gray-line.gif
Binary files differ
diff --git a/internal/godoc/static/images/treeview-gray.gif b/internal/godoc/static/images/treeview-gray.gif
new file mode 100644
index 0000000..222c369
--- /dev/null
+++ b/internal/godoc/static/images/treeview-gray.gif
Binary files differ
diff --git a/internal/godoc/static/implements.html b/internal/godoc/static/implements.html
new file mode 100644
index 0000000..5f65b86
--- /dev/null
+++ b/internal/godoc/static/implements.html
@@ -0,0 +1,9 @@
+<div class="toggle" style="display: none">
+	<div class="collapsed">
+		<p class="exampleHeading toggleButton">▹ <span class="text">Implements</span></p>
+	</div>
+	<div class="expanded">
+		<p class="exampleHeading toggleButton">▾ <span class="text">Implements</span></p>
+		<div style="margin-left: 1in" id='implements-{{.Index}}'>...</div>
+	</div>
+</div>
diff --git a/internal/godoc/static/jquery.js b/internal/godoc/static/jquery.js
new file mode 100644
index 0000000..bc3fbc8
--- /dev/null
+++ b/internal/godoc/static/jquery.js
@@ -0,0 +1,2 @@
+/*! jQuery v1.8.2 jquery.com | jquery.org/license */
+(function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d<e;d++)p.event.add(b,c,h[c][d])}g.data&&(g.data=p.extend({},g.data))}function bE(a,b){var c;if(b.nodeType!==1)return;b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?(b.parentNode&&(b.outerHTML=a.outerHTML),p.support.html5Clone&&a.innerHTML&&!p.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):c==="input"&&bv.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaultValue:c==="script"&&b.text!==a.text&&(b.text=a.text),b.removeAttribute(p.expando)}function bF(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bG(a){bv.test(a.type)&&(a.defaultChecked=a.checked)}function bY(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=bW.length;while(e--){b=bW[e]+c;if(b in a)return b}return d}function bZ(a,b){return a=b||a,p.css(a,"display")==="none"||!p.contains(a.ownerDocument,a)}function b$(a,b){var c,d,e=[],f=0,g=a.length;for(;f<g;f++){c=a[f];if(!c.style)continue;e[f]=p._data(c,"olddisplay"),b?(!e[f]&&c.style.display==="none"&&(c.style.display=""),c.style.display===""&&bZ(c)&&(e[f]=p._data(c,"olddisplay",cc(c.nodeName)))):(d=bH(c,"display"),!e[f]&&d!=="none"&&p._data(c,"olddisplay",d))}for(f=0;f<g;f++){c=a[f];if(!c.style)continue;if(!b||c.style.display==="none"||c.style.display==="")c.style.display=b?e[f]||"":"none"}return a}function b_(a,b,c){var d=bP.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function ca(a,b,c,d){var e=c===(d?"border":"content")?4:b==="width"?1:0,f=0;for(;e<4;e+=2)c==="margin"&&(f+=p.css(a,c+bV[e],!0)),d?(c==="content"&&(f-=parseFloat(bH(a,"padding"+bV[e]))||0),c!=="margin"&&(f-=parseFloat(bH(a,"border"+bV[e]+"Width"))||0)):(f+=parseFloat(bH(a,"padding"+bV[e]))||0,c!=="padding"&&(f+=parseFloat(bH(a,"border"+bV[e]+"Width"))||0));return f}function cb(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=!0,f=p.support.boxSizing&&p.css(a,"boxSizing")==="border-box";if(d<=0||d==null){d=bH(a,b);if(d<0||d==null)d=a.style[b];if(bQ.test(d))return d;e=f&&(p.support.boxSizingReliable||d===a.style[b]),d=parseFloat(d)||0}return d+ca(a,b,c||(f?"border":"content"),e)+"px"}function cc(a){if(bS[a])return bS[a];var b=p("<"+a+">").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write("<!doctype html><html><body>"),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bS[a]=c,c}function ci(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||ce.test(a)?d(a,e):ci(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ci(a+"["+e+"]",b[e],c,d);else d(a,b)}function cz(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h<i;h++)d=g[h],f=/^\+/.test(d),f&&(d=d.substr(1)||"*"),e=a[d]=a[d]||[],e[f?"unshift":"push"](c)}}function cA(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h,i=a[f],j=0,k=i?i.length:0,l=a===cv;for(;j<k&&(l||!h);j++)h=i[j](c,d,e),typeof h=="string"&&(!l||g[h]?h=b:(c.dataTypes.unshift(h),h=cA(a,c,d,e,h,g)));return(l||!h)&&!g["*"]&&(h=cA(a,c,d,e,"*",g)),h}function cB(a,c){var d,e,f=p.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((f[d]?a:e||(e={}))[d]=c[d]);e&&p.extend(!0,a,e)}function cC(a,c,d){var e,f,g,h,i=a.contents,j=a.dataTypes,k=a.responseFields;for(f in k)f in d&&(c[k[f]]=d[f]);while(j[0]==="*")j.shift(),e===b&&(e=a.mimeType||c.getResponseHeader("content-type"));if(e)for(f in i)if(i[f]&&i[f].test(e)){j.unshift(f);break}if(j[0]in d)g=j[0];else{for(f in d){if(!j[0]||a.converters[f+" "+j[0]]){g=f;break}h||(h=f)}g=g||h}if(g)return g!==j[0]&&j.unshift(g),d[g]}function cD(a,b){var c,d,e,f,g=a.dataTypes.slice(),h=g[0],i={},j=0;a.dataFilter&&(b=a.dataFilter(b,a.dataType));if(g[1])for(c in a.converters)i[c.toLowerCase()]=a.converters[c];for(;e=g[++j];)if(e!=="*"){if(h!=="*"&&h!==e){c=i[h+" "+e]||i["* "+e];if(!c)for(d in i){f=d.split(" ");if(f[1]===e){c=i[h+" "+f[0]]||i["* "+f[0]];if(c){c===!0?c=i[d]:i[d]!==!0&&(e=f[0],g.splice(j--,0,e));break}}}if(c!==!0)if(c&&a["throws"])b=c(b);else try{b=c(b)}catch(k){return{state:"parsererror",error:c?k:"No conversion from "+h+" to "+e}}}h=e}return{state:"success",data:b}}function cL(){try{return new a.XMLHttpRequest}catch(b){}}function cM(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function cU(){return setTimeout(function(){cN=b},0),cN=p.now()}function cV(a,b){p.each(b,function(b,c){var d=(cT[b]||[]).concat(cT["*"]),e=0,f=d.length;for(;e<f;e++)if(d[e].call(a,b,c))return})}function cW(a,b,c){var d,e=0,f=0,g=cS.length,h=p.Deferred().always(function(){delete i.elem}),i=function(){var b=cN||cU(),c=Math.max(0,j.startTime+j.duration-b),d=1-(c/j.duration||0),e=0,f=j.tweens.length;for(;e<f;e++)j.tweens[e].run(d);return h.notifyWith(a,[j,d,c]),d<1&&f?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:p.extend({},b),opts:p.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:cN||cU(),duration:c.duration,tweens:[],createTween:function(b,c,d){var e=p.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(e),e},stop:function(b){var c=0,d=b?j.tweens.length:0;for(;c<d;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;cX(k,j.opts.specialEasing);for(;e<g;e++){d=cS[e].call(j,a,k,j.opts);if(d)return d}return cV(j,k),p.isFunction(j.opts.start)&&j.opts.start.call(a,j),p.fx.timer(p.extend(i,{anim:j,queue:j.opts.queue,elem:a})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}function cX(a,b){var c,d,e,f,g;for(c in a){d=p.camelCase(c),e=b[d],f=a[c],p.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=p.cssHooks[d];if(g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}}function cY(a,b,c){var d,e,f,g,h,i,j,k,l=this,m=a.style,n={},o=[],q=a.nodeType&&bZ(a);c.queue||(j=p._queueHooks(a,"fx"),j.unqueued==null&&(j.unqueued=0,k=j.empty.fire,j.empty.fire=function(){j.unqueued||k()}),j.unqueued++,l.always(function(){l.always(function(){j.unqueued--,p.queue(a,"fx").length||j.empty.fire()})})),a.nodeType===1&&("height"in b||"width"in b)&&(c.overflow=[m.overflow,m.overflowX,m.overflowY],p.css(a,"display")==="inline"&&p.css(a,"float")==="none"&&(!p.support.inlineBlockNeedsLayout||cc(a.nodeName)==="inline"?m.display="inline-block":m.zoom=1)),c.overflow&&(m.overflow="hidden",p.support.shrinkWrapBlocks||l.done(function(){m.overflow=c.overflow[0],m.overflowX=c.overflow[1],m.overflowY=c.overflow[2]}));for(d in b){f=b[d];if(cP.exec(f)){delete b[d];if(f===(q?"hide":"show"))continue;o.push(d)}}g=o.length;if(g){h=p._data(a,"fxshow")||p._data(a,"fxshow",{}),q?p(a).show():l.done(function(){p(a).hide()}),l.done(function(){var b;p.removeData(a,"fxshow",!0);for(b in n)p.style(a,b,n[b])});for(d=0;d<g;d++)e=o[d],i=l.createTween(e,q?h[e]:0),n[e]=h[e]||p.style(a,e),e in h||(h[e]=i.start,q&&(i.end=i.start,i.start=e==="width"||e==="height"?1:0))}}function cZ(a,b,c,d,e){return new cZ.prototype.init(a,b,c,d,e)}function c$(a,b){var c,d={height:a},e=0;b=b?1:0;for(;e<4;e+=2-b)c=bV[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function da(a){return p.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}var c,d,e=a.document,f=a.location,g=a.navigator,h=a.jQuery,i=a.$,j=Array.prototype.push,k=Array.prototype.slice,l=Array.prototype.indexOf,m=Object.prototype.toString,n=Object.prototype.hasOwnProperty,o=String.prototype.trim,p=function(a,b){return new p.fn.init(a,b,c)},q=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,r=/\S/,s=/\s+/,t=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,u=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEvent("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.2",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j===i&&(h=this,--i);for(;i<j;i++)if((a=arguments[i])!=null)for(c in a){d=h[c],e=a[c];if(h===e)continue;k&&e&&(p.isPlainObject(e)||(f=p.isArray(e)))?(f?(f=!1,g=d&&p.isArray(d)?d:[]):g=d&&p.isPlainObject(d)?d:{},h[c]=p.extend(k,g,e)):e!==b&&(h[c]=e)}return h},p.extend({noConflict:function(b){return a.$===p&&(a.$=i),b&&a.jQuery===p&&(a.jQuery=h),p},isReady:!1,readyWait:1,holdReady:function(a){a?p.readyWait++:p.ready(!0)},ready:function(a){if(a===!0?--p.readyWait:p.isReady)return;if(!e.body)return setTimeout(p.ready,1);p.isReady=!0;if(a!==!0&&--p.readyWait>0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"||a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f<g;)if(c.apply(a[f++],d)===!1)break}else if(h){for(e in a)if(c.call(a[e],e,a[e])===!1)break}else for(;f<g;)if(c.call(a[f],f,a[f++])===!1)break;return a},trim:o&&!o.call(" ")?function(a){return a==null?"":o.call(a)}:function(a){return a==null?"":(a+"").replace(t,"")},makeArray:function(a,b){var c,d=b||[];return a!=null&&(c=p.type(a),a.length==null||c==="string"||c==="function"||c==="regexp"||p.isWindow(a)?j.call(d,a):p.merge(d,a)),d},inArray:function(a,b,c){var d;if(b){if(l)return l.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=c.length,e=a.length,f=0;if(typeof d=="number")for(;f<d;f++)a[e++]=c[f];else while(c[f]!==b)a[e++]=c[f++];return a.length=e,a},grep:function(a,b,c){var d,e=[],f=0,g=a.length;c=!!c;for(;f<g;f++)d=!!b(a[f],f),c!==d&&e.push(a[f]);return e},map:function(a,c,d){var e,f,g=[],h=0,i=a.length,j=a instanceof p||i!==b&&typeof i=="number"&&(i>0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h<i;h++)e=c(a[h],h,d),e!=null&&(g[g.length]=e);else for(f in a)e=c(a[f],f,d),e!=null&&(g[g.length]=e);return g.concat.apply([],g)},guid:1,proxy:function(a,c){var d,e,f;return typeof c=="string"&&(d=a[c],c=a,a=d),p.isFunction(a)?(e=k.call(arguments,2),f=function(){return a.apply(c,e.concat(k.call(arguments)))},f.guid=a.guid=a.guid||p.guid++,f):b},access:function(a,c,d,e,f,g,h){var i,j=d==null,k=0,l=a.length;if(d&&typeof d=="object"){for(k in d)p.access(a,c,k,d[k],1,g,e);f=1}else if(e!==b){i=h===b&&p.isFunction(e),j&&(i?(i=c,c=function(a,b,c){return i.call(p(a),c)}):(c.call(a,e),c=null));if(c)for(;k<l;k++)c(a[k],d,i?e.call(a[k],k,c(a[k],d)):e,h);f=1}return f?a:j?c.call(a):l?c(a[0],d):g},now:function(){return(new Date).getTime()}}),p.ready.promise=function(b){if(!d){d=p.Deferred();if(e.readyState==="complete")setTimeout(p.ready,1);else if(e.addEventListener)e.addEventListener("DOMContentLoaded",D,!1),a.addEventListener("load",p.ready,!1);else{e.attachEvent("onreadystatechange",D),a.attachEvent("onload",p.ready);var c=!1;try{c=a.frameElement==null&&e.documentElement}catch(f){}c&&c.doScroll&&function g(){if(!p.isReady){try{c.doScroll("left")}catch(a){return setTimeout(g,50)}p.ready()}}()}}return d.promise(b)},p.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){E["[object "+b+"]"]=b.toLowerCase()}),c=p(e);var F={};p.Callbacks=function(a){a=typeof a=="string"?F[a]||G(a):p.extend({},a);var c,d,e,f,g,h,i=[],j=!a.once&&[],k=function(b){c=a.memory&&b,d=!0,h=f||0,f=0,g=i.length,e=!0;for(;i&&h<g;h++)if(i[h].apply(b[0],b[1])===!1&&a.stopOnFalse){c=!1;break}e=!1,i&&(j?j.length&&k(j.shift()):c?i=[]:l.disable())},l={add:function(){if(i){var b=i.length;(function d(b){p.each(b,function(b,c){var e=p.type(c);e==="function"&&(!a.unique||!l.has(c))?i.push(c):c&&c.length&&e!=="string"&&d(c)})})(arguments),e?g=i.length:c&&(f=b,k(c))}return this},remove:function(){return i&&p.each(arguments,function(a,b){var c;while((c=p.inArray(b,i,c))>-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return a!=null?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promise)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b<d;b++)c[b]&&p.isFunction(c[b].promise)?c[b].promise().done(g(b,j,c)).fail(f.reject).progress(g(b,i,h)):--e}return e||f.resolveWith(j,c),f.promise()}}),p.support=function(){var b,c,d,f,g,h,i,j,k,l,m,n=e.createElement("div");n.setAttribute("className","t"),n.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="<div></div>",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||p.guid++:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e<f;e++)delete d[b[e]];if(!(c?K:p.isEmptyObject)(d))return}}if(!c){delete h[i].data;if(!K(h[i]))return}g?p.cleanData([a],!0):p.support.deleteExpando||h!=h.window?delete h[i]:h[i]=null},_data:function(a,b,c){return p.data(a,b,c,!0)},acceptData:function(a){var b=a.nodeName&&p.noData[a.nodeName.toLowerCase()];return!b||b!==!0&&a.getAttribute("classid")===b}}),p.fn.extend({data:function(a,c){var d,e,f,g,h,i=this[0],j=0,k=null;if(a===b){if(this.length){k=p.data(i);if(i.nodeType===1&&!p._data(i,"parsedAttrs")){f=i.attributes;for(h=f.length;j<h;j++)g=f[j].name,g.indexOf("data-")||(g=p.camelCase(g.substring(5)),J(i,g,k[g]));p._data(i,"parsedAttrs",!0)}}return k}return typeof a=="object"?this.each(function(){p.data(this,a)}):(d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!",p.access(this,function(c){if(c===b)return k=this.triggerHandler("getData"+e,[d[0]]),k===b&&i&&(k=p.data(i,a),k=J(i,a,k)),k===b&&d[1]?this.data(d[0]):k;d[1]=c,this.each(function(){var b=p(this);b.triggerHandler("setData"+e,d),p.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.length,e=c.shift(),f=p._queueHooks(a,b),g=function(){p.dequeue(a,b)};e==="inprogress"&&(e=c.shift(),d--),e&&(b==="fx"&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length<d?p.queue(this[0],a):c===b?this:this.each(function(){var b=p.queue(this,a,c);p._queueHooks(this,a),a==="fx"&&b[0]!=="inprogress"&&p.dequeue(this,a)})},dequeue:function(a){return this.each(function(){p.dequeue(this,a)})},delay:function(a,b){return a=p.fx?p.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){var d,e=1,f=p.Deferred(),g=this,h=this.length,i=function(){--e||f.resolveWith(g,[g])};typeof a!="string"&&(c=a,a=b),a=a||"fx";while(h--)d=p._data(g[h],a+"queueHooks"),d&&d.empty&&(e++,d.empty.add(i));return i(),f.promise(c)}});var L,M,N,O=/[\t\r\n]/g,P=/\r/g,Q=/^(?:button|input)$/i,R=/^(?:button|input|object|select|textarea)$/i,S=/^a(?:rea|)$/i,T=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,U=p.support.getSetAttribute;p.fn.extend({attr:function(a,b){return p.access(this,p.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{f=" "+e.className+" ";for(g=0,h=b.length;g<h;g++)f.indexOf(" "+b[g]+" ")<0&&(f+=b[g]+" ");e.className=p.trim(f)}}}return this},removeClass:function(a){var c,d,e,f,g,h,i;if(p.isFunction(a))return this.each(function(b){p(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(s);for(h=0,i=this.length;h<i;h++){e=this[h];if(e.nodeType===1&&e.className){d=(" "+e.className+" ").replace(O," ");for(f=0,g=c.length;f<g;f++)while(d.indexOf(" "+c[f]+" ")>=0)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(O," ").indexOf(b)>=0)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c<d;c++){e=h[c];if(e.selected&&(p.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!p.nodeName(e.parentNode,"optgroup"))){b=p(e).val();if(i)return b;g.push(b)}}return i&&!g.length&&h.length?p(h[f]).val():g},set:function(a,b){var c=p.makeArray(b);return p(a).find("option").each(function(){this.selected=p.inArray(p(this).val(),c)>=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,d+""),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g<d.length;g++)e=d[g],e&&(c=p.propFix[e]||e,f=T.test(e),f||p.attr(a,e,""),a.removeAttribute(U?e:c),f&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(Q.test(a.nodeName)&&a.parentNode)p.error("type property can't be changed");else if(!p.support.radioValue&&b==="radio"&&p.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}},value:{get:function(a,b){return L&&p.nodeName(a,"button")?L.get(a,b):b in a?a.value:null},set:function(a,b,c){if(L&&p.nodeName(a,"button"))return L.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,f,g,h=a.nodeType;if(!a||h===3||h===8||h===2)return;return g=h!==1||!p.isXMLDoc(a),g&&(c=p.propFix[c]||c,f=p.propHooks[c]),d!==b?f&&"set"in f&&(e=f.set(a,d,c))!==b?e:a[c]=d:f&&"get"in f&&(e=f.get(a,c))!==null?e:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):R.test(a.nodeName)||S.test(a.nodeName)&&a.href?0:b}}}}),M={get:function(a,c){var d,e=p.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;return b===!1?p.removeAttr(a,c):(d=p.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase())),c}},U||(N={name:!0,id:!0,coords:!0},L=p.valHooks.button={get:function(a,c){var d;return d=a.getAttributeNode(c),d&&(N[c]?d.value!=="":d.specified)?d.value:b},set:function(a,b,c){var d=a.getAttributeNode(c);return d||(d=e.createAttribute(c),a.setAttributeNode(d)),d.value=b+""}},p.each(["width","height"],function(a,b){p.attrHooks[b]=p.extend(p.attrHooks[b],{set:function(a,c){if(c==="")return a.setAttribute(b,"auto"),c}})}),p.attrHooks.contenteditable={get:L.get,set:function(a,b,c){b===""&&(b="false"),L.set(a,b,c)}}),p.support.hrefNormalized||p.each(["href","src","width","height"],function(a,c){p.attrHooks[c]=p.extend(p.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),p.support.style||(p.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=b+""}}),p.support.optSelected||(p.propHooks.selected=p.extend(p.propHooks.selected,{get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}})),p.support.enctype||(p.propFix.enctype="encoding"),p.support.checkOn||p.each(["radio","checkbox"],function(){p.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),p.each(["radio","checkbox"],function(){p.valHooks[this]=p.extend(p.valHooks[this],{set:function(a,b){if(p.isArray(b))return a.checked=p.inArray(p(a).val(),b)>=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j<c.length;j++){k=W.exec(c[j])||[],l=k[1],m=(k[2]||"").split(".").sort(),r=p.event.special[l]||{},l=(f?r.delegateType:r.bindType)||l,r=p.event.special[l]||{},n=p.extend({type:l,origType:k[1],data:e,handler:d,guid:d.guid,selector:f,needsContext:f&&p.expr.match.needsContext.test(f),namespace:m.join(".")},o),q=i[l];if(!q){q=i[l]=[],q.delegateCount=0;if(!r.setup||r.setup.call(a,e,m,h)===!1)a.addEventListener?a.addEventListener(l,h,!1):a.attachEvent&&a.attachEvent("on"+l,h)}r.add&&(r.add.call(a,n),n.handler.guid||(n.handler.guid=d.guid)),f?q.splice(q.delegateCount++,0,n):q.push(n),p.event.global[l]=!0}a=null},global:{},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,q,r=p.hasData(a)&&p._data(a);if(!r||!(m=r.events))return;b=p.trim(_(b||"")).split(" ");for(f=0;f<b.length;f++){g=W.exec(b[f])||[],h=i=g[1],j=g[2];if(!h){for(h in m)p.event.remove(a,h+b[f],c,d,!0);continue}n=p.event.special[h]||{},h=(d?n.delegateType:n.bindType)||h,o=m[h]||[],k=o.length,j=j?new RegExp("(^|\\.)"+j.split(".").sort().join("\\.(?:.*\\.|)")+"(\\.|$)"):null;for(l=0;l<o.length;l++)q=o[l],(e||i===q.origType)&&(!c||c.guid===q.guid)&&(!j||j.test(q.namespace))&&(!d||d===q.selector||d==="**"&&q.selector)&&(o.splice(l--,1),q.selector&&o.delegateCount--,n.remove&&n.remove.call(a,q));o.length===0&&k!==o.length&&((!n.teardown||n.teardown.call(a,j,r.handle)===!1)&&p.removeEvent(a,h,r.handle),delete m[h])}p.isEmptyObject(m)&&(delete r.handle,p.removeData(a,"events",!0))},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,f,g){if(!f||f.nodeType!==3&&f.nodeType!==8){var h,i,j,k,l,m,n,o,q,r,s=c.type||c,t=[];if($.test(s+p.event.triggered))return;s.indexOf("!")>=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j<q.length&&!c.isPropagationStopped();j++)k=q[j][0],c.type=q[j][1],o=(p._data(k,"events")||{})[c.type]&&p._data(k,"handle"),o&&o.apply(k,d),o=m&&k[m],o&&p.acceptData(k)&&o.apply&&o.apply(k,d)===!1&&c.preventDefault();return c.type=s,!g&&!c.isDefaultPrevented()&&(!n._default||n._default.apply(f.ownerDocument,d)===!1)&&(s!=="click"||!p.nodeName(f,"a"))&&p.acceptData(f)&&m&&f[s]&&(s!=="focus"&&s!=="blur"||c.target.offsetWidth!==0)&&!p.isWindow(f)&&(l=f[m],l&&(f[m]=null),p.event.triggered=s,f[s](),p.event.triggered=b,l&&(f[m]=l)),c.result}return},dispatch:function(c){c=p.event.fix(c||a.event);var d,e,f,g,h,i,j,l,m,n,o=(p._data(this,"events")||{})[c.type]||[],q=o.delegateCount,r=k.call(arguments),s=!c.exclusive&&!c.namespace,t=p.event.special[c.type]||{},u=[];r[0]=c,c.delegateTarget=this;if(t.preDispatch&&t.preDispatch.call(this,c)===!1)return;if(q&&(!c.button||c.type!=="click"))for(f=c.target;f!=this;f=f.parentNode||this)if(f.disabled!==!0||c.type!=="click"){h={},j=[];for(d=0;d<q;d++)l=o[d],m=l.selector,h[m]===b&&(h[m]=l.needsContext?p(m,this).index(f)>=0:p.find(m,this,null,[f]).length),h[m]&&j.push(l);j.length&&u.push({elem:f,matches:j})}o.length>q&&u.push({elem:this,matches:o.slice(q)});for(d=0;d<u.length&&!c.isPropagationStopped();d++){i=u[d],c.currentTarget=i.elem;for(e=0;e<i.matches.length&&!c.isImmediatePropagationStopped();e++){l=i.matches[e];if(s||!c.namespace&&!l.namespace||c.namespace_re&&c.namespace_re.test(l.namespace))c.data=l.data,c.handleObj=l,g=((p.event.special[l.origType]||{}).handle||l.handler).apply(i.elem,r),g!==b&&(c.result=g,g===!1&&(c.preventDefault(),c.stopPropagation()))}}return t.postDispatch&&t.postDispatch.call(this,c),c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,c){var d,f,g,h=c.button,i=c.fromElement;return a.pageX==null&&c.clientX!=null&&(d=a.target.ownerDocument||e,f=d.documentElement,g=d.body,a.pageX=c.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=c.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?c.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0),a}},fix:function(a){if(a[p.expando])return a;var b,c,d=a,f=p.event.fixHooks[a.type]||{},g=f.props?this.props.concat(f.props):this.props;a=p.Event(d);for(b=g.length;b;)c=g[--b],a[c]=d[c];return a.target||(a.target=d.srcElement||e),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,f.filter?f.filter(a,d):a},special:{load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){p.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=p.extend(new p.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?p.event.trigger(e,null,b):p.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},p.event.handle=p.event.dispatch,p.removeEvent=e.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]=="undefined"&&(a[d]=null),a.detachEvent(d,c))},p.Event=function(a,b){if(this instanceof p.Event)a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?bb:ba):this.type=a,b&&p.extend(this,b),this.timeStamp=a&&a.timeStamp||p.now(),this[p.expando]=!0;else return new p.Event(a,b)},p.Event.prototype={preventDefault:function(){this.isDefaultPrevented=bb;var a=this.originalEvent;if(!a)return;a.preventDefault?a.preventDefault():a.returnValue=!1},stopPropagation:function(){this.isPropagationStopped=bb;var a=this.originalEvent;if(!a)return;a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=bb,this.stopPropagation()},isDefaultPrevented:ba,isPropagationStopped:ba,isImmediatePropagationStopped:ba},p.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){p.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj,g=f.selector;if(!e||e!==d&&!p.contains(d,e))a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b;return c}}}),p.support.submitBubbles||(p.event.special.submit={setup:function(){if(p.nodeName(this,"form"))return!1;p.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=p.nodeName(c,"input")||p.nodeName(c,"button")?c.form:b;d&&!p._data(d,"_submit_attached")&&(p.event.add(d,"submit._submit",function(a){a._submit_bubble=!0}),p._data(d,"_submit_attached",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&p.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(p.nodeName(this,"form"))return!1;p.event.remove(this,"._submit")}}),p.support.changeBubbles||(p.event.special.change={setup:function(){if(V.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")p.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),p.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),p.event.simulate("change",this,a,!0)});return!1}p.event.add(this,"beforeactivate._change",function(a){var b=a.target;V.test(b.nodeName)&&!p._data(b,"_change_attached")&&(p.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&p.event.simulate("change",this.parentNode,a,!0)}),p._data(b,"_change_attached",!0))})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){return p.event.remove(this,"._change"),!V.test(this.nodeName)}}),p.support.focusinBubbles||p.each({focus:"focusin",blur:"focusout"},function(a,b){var c=0,d=function(a){p.event.simulate(b,a.target,p.event.fix(a),!0)};p.event.special[b]={setup:function(){c++===0&&e.addEventListener(a,d,!0)},teardown:function(){--c===0&&e.removeEventListener(a,d,!0)}}}),p.fn.extend({on:function(a,c,d,e,f){var g,h;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(h in a)this.on(h,c,d,a[h],f);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=ba;else if(!e)return this;return f===1&&(g=e,e=function(a){return p().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=p.guid++)),this.each(function(){p.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){var e,f;if(a&&a.preventDefault&&a.handleObj)return e=a.handleObj,p(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler),this;if(typeof a=="object"){for(f in a)this.off(f,c,a[f]);return this}if(c===!1||typeof c=="function")d=c,c=b;return d===!1&&(d=ba),this.each(function(){p.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){return p(this.context).on(a,this.selector,b,c),this},die:function(a,b){return p(this.context).off(a,this.selector||"**",b),this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length===1?this.off(a,"**"):this.off(b,a||"**",c)},trigger:function(a,b){return this.each(function(){p.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return p.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||p.guid++,d=0,e=function(c){var e=(p._data(this,"lastToggle"+a.guid)||0)%d;return p._data(this,"lastToggle"+a.guid,e+1),c.preventDefault(),b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),p.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){p.fn[b]=function(a,c){return c==null&&(c=a,a=null),arguments.length>0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function bc(a,b,c,d){c=c||[],b=b||r;var e,f,i,j,k=b.nodeType;if(!a||typeof a!="string")return c;if(k!==1&&k!==9)return[];i=g(b);if(!i&&!d)if(e=P.exec(a))if(j=e[1]){if(k===9){f=b.getElementById(j);if(!f||!f.parentNode)return c;if(f.id===j)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(j))&&h(b,f)&&f.id===j)return c.push(f),c}else{if(e[2])return w.apply(c,x.call(b.getElementsByTagName(a),0)),c;if((j=e[3])&&_&&b.getElementsByClassName)return w.apply(c,x.call(b.getElementsByClassName(j),0)),c}return bp(a.replace(L,"$1"),b,c,d,i)}function bd(a){return function(b){var c=b.nodeName.toLowerCase();return c==="input"&&b.type===a}}function be(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}}function bf(a){return z(function(b){return b=+b,z(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function bg(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}function bh(a,b){var c,d,f,g,h,i,j,k=C[o][a];if(k)return b?0:k.slice(0);h=a,i=[],j=e.preFilter;while(h){if(!c||(d=M.exec(h)))d&&(h=h.slice(d[0].length)),i.push(f=[]);c=!1;if(d=N.exec(h))f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=d[0].replace(L," ");for(g in e.filter)(d=W[g].exec(h))&&(!j[g]||(d=j[g](d,r,!0)))&&(f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=g,c.matches=d);if(!c)break}return b?h.length:h?bc.error(a):C(a,i).slice(0)}function bi(a,b,d){var e=b.dir,f=d&&b.dir==="parentNode",g=u++;return b.first?function(b,c,d){while(b=b[e])if(f||b.nodeType===1)return a(b,c,d)}:function(b,d,h){if(!h){var i,j=t+" "+g+" ",k=j+c;while(b=b[e])if(f||b.nodeType===1){if((i=b[o])===k)return b.sizset;if(typeof i=="string"&&i.indexOf(j)===0){if(b.sizset)return b}else{b[o]=k;if(a(b,d,h))return b.sizset=!0,b;b.sizset=!1}}}else while(b=b[e])if(f||b.nodeType===1)if(a(b,d,h))return b}}function bj(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function bk(a,b,c,d,e){var f,g=[],h=0,i=a.length,j=b!=null;for(;h<i;h++)if(f=a[h])if(!c||c(f,d,e))g.push(f),j&&b.push(h);return g}function bl(a,b,c,d,e,f){return d&&!d[o]&&(d=bl(d)),e&&!e[o]&&(e=bl(e,f)),z(function(f,g,h,i){if(f&&e)return;var j,k,l,m=[],n=[],o=g.length,p=f||bo(b||"*",h.nodeType?[h]:h,[],f),q=a&&(f||!b)?bk(p,m,a,h,i):p,r=c?e||(f?a:o||d)?[]:g:q;c&&c(q,r,h,i);if(d){l=bk(r,n),d(l,[],h,i),j=l.length;while(j--)if(k=l[j])r[n[j]]=!(q[n[j]]=k)}if(f){j=a&&r.length;while(j--)if(k=r[j])f[m[j]]=!(g[m[j]]=k)}else r=bk(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):w.apply(g,r)})}function bm(a){var b,c,d,f=a.length,g=e.relative[a[0].type],h=g||e.relative[" "],i=g?1:0,j=bi(function(a){return a===b},h,!0),k=bi(function(a){return y.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==l)||((b=c).nodeType?j(a,c,d):k(a,c,d))}];for(;i<f;i++)if(c=e.relative[a[i].type])m=[bi(bj(m),c)];else{c=e.filter[a[i].type].apply(null,a[i].matches);if(c[o]){d=++i;for(;d<f;d++)if(e.relative[a[d].type])break;return bl(i>1&&bj(m),i>1&&a.slice(0,i-1).join("").replace(L,"$1"),c,i<d&&bm(a.slice(i,d)),d<f&&bm(a=a.slice(d)),d<f&&a.join(""))}m.push(c)}return bj(m)}function bn(a,b){var d=b.length>0,f=a.length>0,g=function(h,i,j,k,m){var n,o,p,q=[],s=0,u="0",x=h&&[],y=m!=null,z=l,A=h||f&&e.find.TAG("*",m&&i.parentNode||i),B=t+=z==null?1:Math.E;y&&(l=i!==r&&i,c=g.el);for(;(n=A[u])!=null;u++){if(f&&n){for(o=0;p=a[o];o++)if(p(n,i,j)){k.push(n);break}y&&(t=B,c=++g.el)}d&&((n=!p&&n)&&s--,h&&x.push(n))}s+=u;if(d&&u!==s){for(o=0;p=b[o];o++)p(x,q,i,j);if(h){if(s>0)while(u--)!x[u]&&!q[u]&&(q[u]=v.call(k));q=bk(q)}w.apply(k,q),y&&!h&&q.length>0&&s+b.length>1&&bc.uniqueSort(k)}return y&&(t=B,l=z),x};return g.el=0,d?z(g):g}function bo(a,b,c,d){var e=0,f=b.length;for(;e<f;e++)bc(a,b[e],c,d);return c}function bp(a,b,c,d,f){var g,h,j,k,l,m=bh(a),n=m.length;if(!d&&m.length===1){h=m[0]=m[0].slice(0);if(h.length>2&&(j=h[0]).type==="ID"&&b.nodeType===9&&!f&&e.relative[h[1].type]){b=e.find.ID(j.matches[0].replace(V,""),b,f)[0];if(!b)return c;a=a.slice(h.shift().length)}for(g=W.POS.test(a)?-1:h.length-1;g>=0;g--){j=h[g];if(e.relative[k=j.type])break;if(l=e.find[k])if(d=l(j.matches[0].replace(V,""),R.test(h[0].type)&&b.parentNode||b,f)){h.splice(g,1),a=d.length&&h.join("");if(!a)return w.apply(c,x.call(d,0)),c;break}}}return i(a,m)(d,b,f,c,R.test(a)),c}function bq(){}var c,d,e,f,g,h,i,j,k,l,m=!0,n="undefined",o=("sizcache"+Math.random()).replace(".",""),q=String,r=a.document,s=r.documentElement,t=0,u=0,v=[].pop,w=[].push,x=[].slice,y=[].indexOf||function(a){var b=0,c=this.length;for(;b<c;b++)if(this[b]===a)return b;return-1},z=function(a,b){return a[o]=b==null||b,a},A=function(){var a={},b=[];return z(function(c,d){return b.push(c)>e.cacheLength&&delete a[b.shift()],a[c]=d},a)},B=A(),C=A(),D=A(),E="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",G=F.replace("w","w#"),H="([*^$|!~]?=)",I="\\["+E+"*("+F+")"+E+"*(?:"+H+E+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+G+")|)|)"+E+"*\\]",J=":("+F+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+I+")|[^:]|\\\\.)*|.*))\\)|)",K=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+E+"*((?:-\\d)?\\d*)"+E+"*\\)|)(?=[^-]|$)",L=new RegExp("^"+E+"+|((?:^|[^\\\\])(?:\\\\.)*)"+E+"+$","g"),M=new RegExp("^"+E+"*,"+E+"*"),N=new RegExp("^"+E+"*([\\x20\\t\\r\\n\\f>+~])"+E+"*"),O=new RegExp(J),P=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,Q=/^:not/,R=/[\x20\t\r\n\f]*[+~]/,S=/:not\($/,T=/h\d/i,U=/input|select|textarea|button/i,V=/\\(?!\\)/g,W={ID:new RegExp("^#("+F+")"),CLASS:new RegExp("^\\.("+F+")"),NAME:new RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:new RegExp("^("+F.replace("w","w*")+")"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+J),POS:new RegExp(K,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+E+"*(even|odd|(([+-]|)(\\d*)n|)"+E+"*(?:([+-]|)"+E+"*(\\d+)|))"+E+"*\\)|)","i"),needsContext:new RegExp("^"+E+"*[>+~]|"+K,"i")},X=function(a){var b=r.createElement("div");try{return a(b)}catch(c){return!1}finally{b=null}},Y=X(function(a){return a.appendChild(r.createComment("")),!a.getElementsByTagName("*").length}),Z=X(function(a){return a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!==n&&a.firstChild.getAttribute("href")==="#"}),$=X(function(a){a.innerHTML="<select></select>";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),_=X(function(a){return a.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",!a.getElementsByClassName||!a.getElementsByClassName("e").length?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length===2)}),ba=X(function(a){a.id=o+0,a.innerHTML="<a name='"+o+"'></a><div name='"+o+"'></div>",s.insertBefore(a,s.firstChild);var b=r.getElementsByName&&r.getElementsByName(o).length===2+r.getElementsByName(o+0).length;return d=!r.getElementById(o),s.removeChild(a),b});try{x.call(s.childNodes,0)[0].nodeType}catch(bb){x=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}bc.matches=function(a,b){return bc(a,null,null,b)},bc.matchesSelector=function(a,b){return bc(b,null,null,[a]).length>0},f=bc.getText=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=f(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=f(b);return c},g=bc.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},h=bc.contains=s.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b&&b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:s.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},bc.attr=function(a,b){var c,d=g(a);return d||(b=b.toLowerCase()),(c=e.attrHandle[b])?c(a):d||$?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},e=bc.selectors={cacheLength:50,createPseudo:z,match:W,attrHandle:Z?{}:{href:function(a){return a.getAttribute("href",2)},type:function(a){return a.getAttribute("type")}},find:{ID:d?function(a,b,c){if(typeof b.getElementById!==n&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==n&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==n&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:Y?function(a,b){if(typeof b.getElementsByTagName!==n)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c},NAME:ba&&function(a,b){if(typeof b.getElementsByName!==n)return b.getElementsByName(name)},CLASS:_&&function(a,b,c){if(typeof b.getElementsByClassName!==n&&!c)return b.getElementsByClassName(a)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(V,""),a[3]=(a[4]||a[5]||"").replace(V,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||bc.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&bc.error(a[0]),a},PSEUDO:function(a){var b,c;if(W.CHILD.test(a[0]))return null;if(a[3])a[2]=a[3];else if(b=a[4])O.test(b)&&(c=bh(b,!0))&&(c=b.indexOf(")",b.length-c)-b.length)&&(b=b.slice(0,c),a[0]=a[0].slice(0,c)),a[2]=b;return a.slice(0,3)}},filter:{ID:d?function(a){return a=a.replace(V,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(V,""),function(b){var c=typeof b.getAttributeNode!==n&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(V,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=B[o][a];return b||(b=B(a,new RegExp("(^|"+E+")"+a+"("+E+"|$)"))),function(a){return b.test(a.className||typeof a.getAttribute!==n&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return function(d,e){var f=bc.attr(d,a);return f==null?b==="!=":b?(f+="",b==="="?f===c:b==="!="?f!==c:b==="^="?c&&f.indexOf(c)===0:b==="*="?c&&f.indexOf(c)>-1:b==="$="?c&&f.substr(f.length-c.length)===c:b==="~="?(" "+f+" ").indexOf(c)>-1:b==="|="?f===c||f.substr(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d){return a==="nth"?function(a){var b,e,f=a.parentNode;if(c===1&&d===0)return!0;if(f){e=0;for(b=f.firstChild;b;b=b.nextSibling)if(b.nodeType===1){e++;if(a===b)break}}return e-=d,e===c||e%c===0&&e/c>=0}:function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b){var c,d=e.pseudos[a]||e.setFilters[a.toLowerCase()]||bc.error("unsupported pseudo: "+a);return d[o]?d(b):d.length>1?(c=[a,a,"",b],e.setFilters.hasOwnProperty(a.toLowerCase())?z(function(a,c){var e,f=d(a,b),g=f.length;while(g--)e=y.call(a,f[g]),a[e]=!(c[e]=f[g])}):function(a){return d(a,0,c)}):d}},pseudos:{not:z(function(a){var b=[],c=[],d=i(a.replace(L,"$1"));return d[o]?z(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)if(f=g[h])a[h]=!(b[h]=f)}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:z(function(a){return function(b){return bc(a,b).length>0}}),contains:z(function(a){return function(b){return(b.textContent||b.innerText||f(b)).indexOf(a)>-1}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!e.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},header:function(a){return T.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:bd("radio"),checkbox:bd("checkbox"),file:bd("file"),password:bd("password"),image:bd("image"),submit:be("submit"),reset:be("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return U.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement},first:bf(function(a,b,c){return[0]}),last:bf(function(a,b,c){return[b-1]}),eq:bf(function(a,b,c){return[c<0?c+b:c]}),even:bf(function(a,b,c){for(var d=0;d<b;d+=2)a.push(d);return a}),odd:bf(function(a,b,c){for(var d=1;d<b;d+=2)a.push(d);return a}),lt:bf(function(a,b,c){for(var d=c<0?c+b:c;--d>=0;)a.push(d);return a}),gt:bf(function(a,b,c){for(var d=c<0?c+b:c;++d<b;)a.push(d);return a})}},j=s.compareDocumentPosition?function(a,b){return a===b?(k=!0,0):(!a.compareDocumentPosition||!b.compareDocumentPosition?a.compareDocumentPosition:a.compareDocumentPosition(b)&4)?-1:1}:function(a,b){if(a===b)return k=!0,0;if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,h=b.parentNode,i=g;if(g===h)return bg(a,b);if(!g)return-1;if(!h)return 1;while(i)e.unshift(i),i=i.parentNode;i=h;while(i)f.unshift(i),i=i.parentNode;c=e.length,d=f.length;for(var j=0;j<c&&j<d;j++)if(e[j]!==f[j])return bg(e[j],f[j]);return j===c?bg(a,f[j],-1):bg(e[j],b,1)},[0,0].sort(j),m=!k,bc.uniqueSort=function(a){var b,c=1;k=m,a.sort(j);if(k)for(;b=a[c];c++)b===a[c-1]&&a.splice(c--,1);return a},bc.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},i=bc.compile=function(a,b){var c,d=[],e=[],f=D[o][a];if(!f){b||(b=bh(a)),c=b.length;while(c--)f=bm(b[c]),f[o]?d.push(f):e.push(f);f=D(a,bn(e,d))}return f},r.querySelectorAll&&function(){var a,b=bp,c=/'|\\/g,d=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,e=[":focus"],f=[":active",":focus"],h=s.matchesSelector||s.mozMatchesSelector||s.webkitMatchesSelector||s.oMatchesSelector||s.msMatchesSelector;X(function(a){a.innerHTML="<select><option selected=''></option></select>",a.querySelectorAll("[selected]").length||e.push("\\["+E+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),X(function(a){a.innerHTML="<p test=''></p>",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+E+"*(?:\"\"|'')"),a.innerHTML="<input type='hidden'/>",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=new RegExp(e.join("|")),bp=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a))){var i,j,k=!0,l=o,m=d,n=d.nodeType===9&&a;if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){i=bh(a),(k=d.getAttribute("id"))?l=k.replace(c,"\\$&"):d.setAttribute("id",l),l="[id='"+l+"'] ",j=i.length;while(j--)i[j]=l+i[j].join("");m=R.test(a)&&d.parentNode||d,n=i.join(",")}if(n)try{return w.apply(f,x.call(m.querySelectorAll(n),0)),f}catch(p){}finally{k||d.removeAttribute("id")}}return b(a,d,f,g,h)},h&&(X(function(b){a=h.call(b,"div");try{h.call(b,"[test!='']:sizzle"),f.push("!=",J)}catch(c){}}),f=new RegExp(f.join("|")),bc.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!g(b)&&!f.test(c)&&(!e||!e.test(c)))try{var i=h.call(b,c);if(i||a||b.document&&b.document.nodeType!==11)return i}catch(j){}return bc(c,null,null,[b]).length>0})}(),e.pseudos.nth=e.pseudos.eq,e.filters=bq.prototype=e.pseudos,e.setFilters=new bq,bc.attr=p.attr,p.find=bc,p.expr=bc.selectors,p.expr[":"]=p.expr.pseudos,p.unique=bc.uniqueSort,p.text=bc.getText,p.isXMLDoc=bc.isXML,p.contains=bc.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b<c;b++)if(p.contains(h[b],this))return!0});g=this.pushStack("","find",a);for(b=0,c=this.length;b<c;b++){d=g.length,p.find(a,this[b],g);if(b>0)for(e=d;e<g.length;e++)for(f=0;f<d;f++)if(g[f]===g[e]){g.splice(e--,1);break}}return g},has:function(a){var b,c=p(a,this),d=c.length;return this.filter(function(){for(b=0;b<d;b++)if(p.contains(this,c[b]))return!0})},not:function(a){return this.pushStack(bj(this,a,!1),"not",a)},filter:function(a){return this.pushStack(bj(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?bf.test(a)?p(a,this.context).index(this[0])>=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d<e;d++){c=this[d];while(c&&c.ownerDocument&&c!==b&&c.nodeType!==11){if(g?g.index(c)>-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/<tbody/i,br=/<|&#?\w+;/,bs=/<(?:script|style|link)/i,bt=/<(?:script|object|embed|option|style)/i,bu=new RegExp("<(?:"+bl+")[\\s/>]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,bz={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X<div>","</div>"]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(f){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){return bh(this[0])?this.length?this.pushStack(p(p.isFunction(a)?a():a),"replaceWith",a):this:p.isFunction(a)?this.each(function(b){var c=p(this),d=c.html();c.replaceWith(a.call(this,b,d))}):(typeof a!="string"&&(a=p(a).detach()),this.each(function(){var b=this.nextSibling,c=this.parentNode;p(this).remove(),b?p(b).before(a):p(c).append(a)}))},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){a=[].concat.apply([],a);var e,f,g,h,i=0,j=a[0],k=[],l=this.length;if(!p.support.checkClone&&l>1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i<l;i++)d.call(c&&p.nodeName(this[i],"table")?bC(this[i],"tbody"):this[i],i===h?g:p.clone(g,!0,!0))}g=f=null,k.length&&p.each(k,function(a,b){b.src?p.ajax?p.ajax({url:b.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):p.error("no ajax"):p.globalEval((b.text||b.textContent||b.innerHTML||"").replace(by,"")),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),p.buildFragment=function(a,c,d){var f,g,h,i=a[0];return c=c||e,c=!c.nodeType&&c[0]||c,c=c.ownerDocument||c,a.length===1&&typeof i=="string"&&i.length<512&&c===e&&i.charAt(0)==="<"&&!bt.test(i)&&(p.support.checkClone||!bw.test(i))&&(p.support.html5Clone||!bu.test(i))&&(g=!0,f=p.fragments[i],h=f!==b),f||(f=c.createDocumentFragment(),p.clean(a,c,f,d),g&&(p.fragments[i]=h&&f)),{fragment:f,cacheable:g}},p.fragments={},p.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){p.fn[a]=function(c){var d,e=0,f=[],g=p(c),h=g.length,i=this.length===1&&this[0].parentNode;if((i==null||i&&i.nodeType===11&&i.childNodes.length===1)&&h===1)return g[b](this[0]),this;for(;e<h;e++)d=(e>0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1></$2>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]==="<table>"&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tbody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getComputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,cs=/([?&])_=[^&]*/,ct=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,cu=p.fn.load,cv={},cw={},cx=["*/"]+["*"];try{ck=f.href}catch(cy){ck=e.createElement("a"),ck.href="",ck=ck.href}cj=ct.exec(ck.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&cu)return cu.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):c&&typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("<div>").append(a.replace(cr,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cB(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cB(a,b),a},ajaxSettings:{url:ck,isLocal:cn.test(cj[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cx},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cz(cv),ajaxTransport:cz(cw),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cC(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cD(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=(c||y)+"",k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cm.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(cl,"").replace(cp,cj[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=ct.exec(l.url.toLowerCase())||!1,l.crossDomain=i&&i.join(":")+(i[3]?"":i[1]==="http:"?80:443)!==cj.join(":")+(cj[3]?"":cj[1]==="http:"?80:443)),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cA(cv,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!co.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cq.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cs,"$1_="+z);l.url=A+(A===l.url?(cq.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cx+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cA(cw,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cE=[],cF=/\?/,cG=/(=)\?(?=&|$)|\?\?/,cH=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cE.pop()||p.expando+"_"+cH++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cG.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cG.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cG,"$1"+f):m?c.data=i.replace(cG,"$1"+f):k&&(c.url+=(cF.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cE.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cI,cJ=a.ActiveXObject?function(){for(var a in cI)cI[a](0,1)}:!1,cK=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cL()||cM()}:cL,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cJ&&delete cI[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cK,cJ&&(cI||(cI={},p(a).unload(cJ)),cI[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cN,cO,cP=/^(?:toggle|show|hide)$/,cQ=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cR=/queueHooks$/,cS=[cY],cT={"*":[function(a,b){var c,d,e=this.createTween(a,b),f=cQ.exec(b),g=e.cur(),h=+g||0,i=1,j=20;if(f){c=+f[2],d=f[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&h){h=p.css(e.elem,a,!0)||c||1;do i=i||".5",h=h/i,p.style(e.elem,a,h+d);while(i!==(i=e.cur()/g)&&i!==1&&--j)}e.unit=d,e.start=h,e.end=f[1]?h+(f[1]+1)*c:c}return e}]};p.Animation=p.extend(cW,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d<e;d++)c=a[d],cT[c]=cT[c]||[],cT[c].unshift(b)},prefilter:function(a,b){b?cS.unshift(a):cS.push(a)}}),p.Tween=cZ,cZ.prototype={constructor:cZ,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(p.cssNumber[c]?"":"px")},cur:function(){var a=cZ.propHooks[this.prop];return a&&a.get?a.get(this):cZ.propHooks._default.get(this)},run:function(a){var b,c=cZ.propHooks[this.prop];return this.options.duration?this.pos=b=p.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):cZ.propHooks._default.set(this),this}},cZ.prototype.init.prototype=cZ.prototype,cZ.propHooks={_default:{get:function(a){var b;return a.elem[a.prop]==null||!!a.elem.style&&a.elem.style[a.prop]!=null?(b=p.css(a.elem,a.prop,!1,""),!b||b==="auto"?0:b):a.elem[a.prop]},set:function(a){p.fx.step[a.prop]?p.fx.step[a.prop](a):a.elem.style&&(a.elem.style[p.cssProps[a.prop]]!=null||p.cssHooks[a.prop])?p.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},cZ.propHooks.scrollTop=cZ.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},p.each(["toggle","show","hide"],function(a,b){var c=p.fn[b];p.fn[b]=function(d,e,f){return d==null||typeof d=="boolean"||!a&&p.isFunction(d)&&p.isFunction(e)?c.apply(this,arguments):this.animate(c$(b,!0),d,e,f)}}),p.fn.extend({fadeTo:function(a,b,c,d){return this.filter(bZ).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=p.isEmptyObject(a),f=p.speed(b,c,d),g=function(){var b=cW(this,p.extend({},a),f);e&&b.stop(!0)};return e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,c,d){var e=function(a){var b=a.stop;delete a.stop,b(d)};return typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,c=a!=null&&a+"queueHooks",f=p.timers,g=p._data(this);if(c)g[c]&&g[c].stop&&e(g[c]);else for(c in g)g[c]&&g[c].stop&&cR.test(c)&&e(g[c]);for(c=f.length;c--;)f[c].elem===this&&(a==null||f[c].queue===a)&&(f[c].anim.stop(d),b=!1,f.splice(c,1));(b||!d)&&p.dequeue(this,a)})}}),p.each({slideDown:c$("show"),slideUp:c$("hide"),slideToggle:c$("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){p.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),p.speed=function(a,b,c){var d=a&&typeof a=="object"?p.extend({},a):{complete:c||!c&&b||p.isFunction(a)&&a,duration:a,easing:c&&b||b&&!p.isFunction(b)&&b};d.duration=p.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in p.fx.speeds?p.fx.speeds[d.duration]:p.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";return d.old=d.complete,d.complete=function(){p.isFunction(d.old)&&d.old.call(this),d.queue&&p.dequeue(this,d.queue)},d},p.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},p.timers=[],p.fx=cZ.prototype.init,p.fx.tick=function(){var a,b=p.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||p.fx.stop()},p.fx.timer=function(a){a()&&p.timers.push(a)&&!cO&&(cO=setInterval(p.fx.tick,p.fx.interval))},p.fx.interval=13,p.fx.stop=function(){clearInterval(cO),cO=null},p.fx.speeds={slow:600,fast:200,_default:400},p.fx.step={},p.expr&&p.expr.filters&&(p.expr.filters.animated=function(a){return p.grep(p.timers,function(b){return a===b.elem}).length});var c_=/^(?:body|html)$/i;p.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){p.offset.setOffset(this,a,b)});var c,d,e,f,g,h,i,j={top:0,left:0},k=this[0],l=k&&k.ownerDocument;if(!l)return;return(d=l.body)===k?p.offset.bodyOffset(k):(c=l.documentElement,p.contains(c,k)?(typeof k.getBoundingClientRect!="undefined"&&(j=k.getBoundingClientRect()),e=da(l),f=c.clientTop||d.clientTop||0,g=c.clientLeft||d.clientLeft||0,h=e.pageYOffset||c.scrollTop,i=e.pageXOffset||c.scrollLeft,{top:j.top+h-f,left:j.left+i-g}):j)},p.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;return p.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(p.css(a,"marginTop"))||0,c+=parseFloat(p.css(a,"marginLeft"))||0),{top:b,left:c}},setOffset:function(a,b,c){var d=p.css(a,"position");d==="static"&&(a.style.position="relative");var e=p(a),f=e.offset(),g=p.css(a,"top"),h=p.css(a,"left"),i=(d==="absolute"||d==="fixed")&&p.inArray("auto",[g,h])>-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c_.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c_.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=da(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g,null)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window);
\ No newline at end of file
diff --git a/internal/godoc/static/jquery.treeview.css b/internal/godoc/static/jquery.treeview.css
new file mode 100644
index 0000000..cf3cc08
--- /dev/null
+++ b/internal/godoc/static/jquery.treeview.css
@@ -0,0 +1,76 @@
+/* https://github.com/jzaefferer/jquery-treeview/blob/1.4.2/jquery.treeview.css */
+/* License: MIT. */
+.treeview, .treeview ul {
+	padding: 0;
+	margin: 0;
+	list-style: none;
+}
+
+.treeview ul {
+	background-color: white;
+	margin-top: 4px;
+}
+
+.treeview .hitarea {
+	background: url(images/treeview-default.gif) -64px -25px no-repeat;
+	height: 16px;
+	width: 16px;
+	margin-left: -16px;
+	float: left;
+	cursor: pointer;
+}
+/* fix for IE6 */
+* html .hitarea {
+	display: inline;
+	float:none;
+}
+
+.treeview li {
+	margin: 0;
+	padding: 3px 0pt 3px 16px;
+}
+
+.treeview a.selected {
+	background-color: #eee;
+}
+
+#treecontrol { margin: 1em 0; display: none; }
+
+.treeview .hover { color: red; cursor: pointer; }
+
+.treeview li { background: url(images/treeview-default-line.gif) 0 0 no-repeat; }
+.treeview li.collapsable, .treeview li.expandable { background-position: 0 -176px; }
+
+.treeview .expandable-hitarea { background-position: -80px -3px; }
+
+.treeview li.last { background-position: 0 -1766px }
+.treeview li.lastCollapsable, .treeview li.lastExpandable { background-image: url(images/treeview-default.gif); }
+.treeview li.lastCollapsable { background-position: 0 -111px }
+.treeview li.lastExpandable { background-position: -32px -67px }
+
+.treeview div.lastCollapsable-hitarea, .treeview div.lastExpandable-hitarea { background-position: 0; }
+
+.treeview-red li { background-image: url(images/treeview-red-line.gif); }
+.treeview-red .hitarea, .treeview-red li.lastCollapsable, .treeview-red li.lastExpandable { background-image: url(images/treeview-red.gif); }
+
+.treeview-black li { background-image: url(images/treeview-black-line.gif); }
+.treeview-black .hitarea, .treeview-black li.lastCollapsable, .treeview-black li.lastExpandable { background-image: url(images/treeview-black.gif); }
+
+.treeview-gray li { background-image: url(images/treeview-gray-line.gif); }
+.treeview-gray .hitarea, .treeview-gray li.lastCollapsable, .treeview-gray li.lastExpandable { background-image: url(images/treeview-gray.gif); }
+
+.treeview-famfamfam li { background-image: url(images/treeview-famfamfam-line.gif); }
+.treeview-famfamfam .hitarea, .treeview-famfamfam li.lastCollapsable, .treeview-famfamfam li.lastExpandable { background-image: url(images/treeview-famfamfam.gif); }
+
+.treeview .placeholder {
+	background: url(images/ajax-loader.gif) 0 0 no-repeat;
+	height: 16px;
+	width: 16px;
+	display: block;
+}
+
+.filetree li { padding: 3px 0 2px 16px; }
+.filetree span.folder, .filetree span.file { padding: 1px 0 1px 16px; display: block; }
+.filetree span.folder { background: url(images/folder.gif) 0 0 no-repeat; }
+.filetree li.expandable span.folder { background: url(images/folder-closed.gif) 0 0 no-repeat; }
+.filetree span.file { background: url(images/file.gif) 0 0 no-repeat; }
diff --git a/internal/godoc/static/jquery.treeview.edit.js b/internal/godoc/static/jquery.treeview.edit.js
new file mode 100644
index 0000000..4d0f15e
--- /dev/null
+++ b/internal/godoc/static/jquery.treeview.edit.js
@@ -0,0 +1,39 @@
+/* https://github.com/jzaefferer/jquery-treeview/blob/1.4.2/jquery.treeview.edit.js */
+/* License: MIT. */
+(function($) {
+	var CLASSES = $.treeview.classes;
+	var proxied = $.fn.treeview;
+	$.fn.treeview = function(settings) {
+		settings = $.extend({}, settings);
+		if (settings.add) {
+			return this.trigger("add", [settings.add]);
+		}
+		if (settings.remove) {
+			return this.trigger("remove", [settings.remove]);
+		}
+		return proxied.apply(this, arguments).bind("add", function(event, branches) {
+			$(branches).prev()
+				.removeClass(CLASSES.last)
+				.removeClass(CLASSES.lastCollapsable)
+				.removeClass(CLASSES.lastExpandable)
+			.find(">.hitarea")
+				.removeClass(CLASSES.lastCollapsableHitarea)
+				.removeClass(CLASSES.lastExpandableHitarea);
+			$(branches).find("li").andSelf().prepareBranches(settings).applyClasses(settings, $(this).data("toggler"));
+		}).bind("remove", function(event, branches) {
+			var prev = $(branches).prev();
+			var parent = $(branches).parent();
+			$(branches).remove();
+			prev.filter(":last-child").addClass(CLASSES.last)
+				.filter("." + CLASSES.expandable).replaceClass(CLASSES.last, CLASSES.lastExpandable).end()
+				.find(">.hitarea").replaceClass(CLASSES.expandableHitarea, CLASSES.lastExpandableHitarea).end()
+				.filter("." + CLASSES.collapsable).replaceClass(CLASSES.last, CLASSES.lastCollapsable).end()
+				.find(">.hitarea").replaceClass(CLASSES.collapsableHitarea, CLASSES.lastCollapsableHitarea);
+			if (parent.is(":not(:has(>))") && parent[0] != this) {
+				parent.parent().removeClass(CLASSES.collapsable).removeClass(CLASSES.expandable)
+				parent.siblings(".hitarea").andSelf().remove();
+			}
+		});
+	};
+
+})(jQuery);
diff --git a/internal/godoc/static/jquery.treeview.js b/internal/godoc/static/jquery.treeview.js
new file mode 100644
index 0000000..27cee3f
--- /dev/null
+++ b/internal/godoc/static/jquery.treeview.js
@@ -0,0 +1,250 @@
+/*
+ * Treeview 1.4.2 - jQuery plugin to hide and show branches of a tree
+ *
+ * http://bassistance.de/jquery-plugins/jquery-plugin-treeview/
+ *
+ * Copyright Jörn Zaefferer
+ * Released under the MIT license:
+ *   http://www.opensource.org/licenses/mit-license.php
+ */
+
+;(function($) {
+
+	// TODO rewrite as a widget, removing all the extra plugins
+	$.extend($.fn, {
+		swapClass: function(c1, c2) {
+			var c1Elements = this.filter('.' + c1);
+			this.filter('.' + c2).removeClass(c2).addClass(c1);
+			c1Elements.removeClass(c1).addClass(c2);
+			return this;
+		},
+		replaceClass: function(c1, c2) {
+			return this.filter('.' + c1).removeClass(c1).addClass(c2).end();
+		},
+		hoverClass: function(className) {
+			className = className || "hover";
+			return this.hover(function() {
+				$(this).addClass(className);
+			}, function() {
+				$(this).removeClass(className);
+			});
+		},
+		heightToggle: function(animated, callback) {
+			animated ?
+				this.animate({ height: "toggle" }, animated, callback) :
+				this.each(function(){
+					jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ]();
+					if(callback)
+						callback.apply(this, arguments);
+				});
+		},
+		heightHide: function(animated, callback) {
+			if (animated) {
+				this.animate({ height: "hide" }, animated, callback);
+			} else {
+				this.hide();
+				if (callback)
+					this.each(callback);
+			}
+		},
+		prepareBranches: function(settings) {
+			if (!settings.prerendered) {
+				// mark last tree items
+				this.filter(":last-child:not(ul)").addClass(CLASSES.last);
+				// collapse whole tree, or only those marked as closed, anyway except those marked as open
+				this.filter((settings.collapsed ? "" : "." + CLASSES.closed) + ":not(." + CLASSES.open + ")").find(">ul").hide();
+			}
+			// return all items with sublists
+			return this.filter(":has(>ul)");
+		},
+		applyClasses: function(settings, toggler) {
+			// TODO use event delegation
+			this.filter(":has(>ul):not(:has(>a))").find(">span").unbind("click.treeview").bind("click.treeview", function(event) {
+				// don't handle click events on children, eg. checkboxes
+				if ( this == event.target )
+					toggler.apply($(this).next());
+			}).add( $("a", this) ).hoverClass();
+
+			if (!settings.prerendered) {
+				// handle closed ones first
+				this.filter(":has(>ul:hidden)")
+						.addClass(CLASSES.expandable)
+						.replaceClass(CLASSES.last, CLASSES.lastExpandable);
+
+				// handle open ones
+				this.not(":has(>ul:hidden)")
+						.addClass(CLASSES.collapsable)
+						.replaceClass(CLASSES.last, CLASSES.lastCollapsable);
+
+	            // create hitarea if not present
+				var hitarea = this.find("div." + CLASSES.hitarea);
+				if (!hitarea.length)
+					hitarea = this.prepend("<div class=\"" + CLASSES.hitarea + "\"/>").find("div." + CLASSES.hitarea);
+				hitarea.removeClass().addClass(CLASSES.hitarea).each(function() {
+					var classes = "";
+					$.each($(this).parent().attr("class").split(" "), function() {
+						classes += this + "-hitarea ";
+					});
+					$(this).addClass( classes );
+				})
+			}
+
+			// apply event to hitarea
+			this.find("div." + CLASSES.hitarea).click( toggler );
+		},
+		treeview: function(settings) {
+
+			settings = $.extend({
+				cookieId: "treeview"
+			}, settings);
+
+			if ( settings.toggle ) {
+				var callback = settings.toggle;
+				settings.toggle = function() {
+					return callback.apply($(this).parent()[0], arguments);
+				};
+			}
+
+			// factory for treecontroller
+			function treeController(tree, control) {
+				// factory for click handlers
+				function handler(filter) {
+					return function() {
+						// reuse toggle event handler, applying the elements to toggle
+						// start searching for all hitareas
+						toggler.apply( $("div." + CLASSES.hitarea, tree).filter(function() {
+							// for plain toggle, no filter is provided, otherwise we need to check the parent element
+							return filter ? $(this).parent("." + filter).length : true;
+						}) );
+						return false;
+					};
+				}
+				// click on first element to collapse tree
+				$("a:eq(0)", control).click( handler(CLASSES.collapsable) );
+				// click on second to expand tree
+				$("a:eq(1)", control).click( handler(CLASSES.expandable) );
+				// click on third to toggle tree
+				$("a:eq(2)", control).click( handler() );
+			}
+
+			// handle toggle event
+			function toggler() {
+				$(this)
+					.parent()
+					// swap classes for hitarea
+					.find(">.hitarea")
+						.swapClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
+						.swapClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea )
+					.end()
+					// swap classes for parent li
+					.swapClass( CLASSES.collapsable, CLASSES.expandable )
+					.swapClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
+					// find child lists
+					.find( ">ul" )
+					// toggle them
+					.heightToggle( settings.animated, settings.toggle );
+				if ( settings.unique ) {
+					$(this).parent()
+						.siblings()
+						// swap classes for hitarea
+						.find(">.hitarea")
+							.replaceClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
+							.replaceClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea )
+						.end()
+						.replaceClass( CLASSES.collapsable, CLASSES.expandable )
+						.replaceClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
+						.find( ">ul" )
+						.heightHide( settings.animated, settings.toggle );
+				}
+			}
+			this.data("toggler", toggler);
+
+			function serialize() {
+				function binary(arg) {
+					return arg ? 1 : 0;
+				}
+				var data = [];
+				branches.each(function(i, e) {
+					data[i] = $(e).is(":has(>ul:visible)") ? 1 : 0;
+				});
+				$.cookie(settings.cookieId, data.join(""), settings.cookieOptions );
+			}
+
+			function deserialize() {
+				var stored = $.cookie(settings.cookieId);
+				if ( stored ) {
+					var data = stored.split("");
+					branches.each(function(i, e) {
+						$(e).find(">ul")[ parseInt(data[i]) ? "show" : "hide" ]();
+					});
+				}
+			}
+
+			// add treeview class to activate styles
+			this.addClass("treeview");
+
+			// prepare branches and find all tree items with child lists
+			var branches = this.find("li").prepareBranches(settings);
+
+			switch(settings.persist) {
+			case "cookie":
+				var toggleCallback = settings.toggle;
+				settings.toggle = function() {
+					serialize();
+					if (toggleCallback) {
+						toggleCallback.apply(this, arguments);
+					}
+				};
+				deserialize();
+				break;
+			case "location":
+				var current = this.find("a").filter(function() {
+					return location.href.toLowerCase().indexOf(this.href.toLowerCase()) == 0;
+				});
+				if ( current.length ) {
+					// TODO update the open/closed classes
+					var items = current.addClass("selected").parents("ul, li").add( current.next() ).show();
+					if (settings.prerendered) {
+						// if prerendered is on, replicate the basic class swapping
+						items.filter("li")
+							.swapClass( CLASSES.collapsable, CLASSES.expandable )
+							.swapClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
+							.find(">.hitarea")
+								.swapClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
+								.swapClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea );
+					}
+				}
+				break;
+			}
+
+			branches.applyClasses(settings, toggler);
+
+			// if control option is set, create the treecontroller and show it
+			if ( settings.control ) {
+				treeController(this, settings.control);
+				$(settings.control).show();
+			}
+
+			return this;
+		}
+	});
+
+	// classes used by the plugin
+	// need to be styled via external stylesheet, see first example
+	$.treeview = {};
+	var CLASSES = ($.treeview.classes = {
+		open: "open",
+		closed: "closed",
+		expandable: "expandable",
+		expandableHitarea: "expandable-hitarea",
+		lastExpandableHitarea: "lastExpandable-hitarea",
+		collapsable: "collapsable",
+		collapsableHitarea: "collapsable-hitarea",
+		lastCollapsableHitarea: "lastCollapsable-hitarea",
+		lastCollapsable: "lastCollapsable",
+		lastExpandable: "lastExpandable",
+		last: "last",
+		hitarea: "hitarea"
+	});
+
+})(jQuery);
diff --git a/internal/godoc/static/makestatic.go b/internal/godoc/static/makestatic.go
new file mode 100644
index 0000000..ca6d287
--- /dev/null
+++ b/internal/godoc/static/makestatic.go
@@ -0,0 +1,37 @@
+//go:build ignore
+// +build ignore
+
+// 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.
+
+// Command makestatic writes the generated file buffer to "static.go".
+// It is intended to be invoked via "go generate" (directive in "gen.go").
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+
+	"golang.org/x/website/internal/godoc/static"
+)
+
+func main() {
+	if err := makestatic(); err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+}
+
+func makestatic() error {
+	buf, err := static.Generate()
+	if err != nil {
+		return fmt.Errorf("error while generating static.go: %v\n", err)
+	}
+	err = ioutil.WriteFile("static.go", buf, 0666)
+	if err != nil {
+		return fmt.Errorf("error while writing static.go: %v\n", err)
+	}
+	return nil
+}
diff --git a/internal/godoc/static/methodset.html b/internal/godoc/static/methodset.html
new file mode 100644
index 0000000..1b339e3
--- /dev/null
+++ b/internal/godoc/static/methodset.html
@@ -0,0 +1,9 @@
+<div class="toggle" style="display: none">
+	<div class="collapsed">
+		<p class="exampleHeading toggleButton">▹ <span class="text">Method set</span></p>
+	</div>
+	<div class="expanded">
+		<p class="exampleHeading toggleButton">▾ <span class="text">Method set</span></p>
+		<div style="margin-left: 1in" id='methodset-{{.Index}}'>...</div>
+	</div>
+</div>
diff --git a/internal/godoc/static/package.html b/internal/godoc/static/package.html
new file mode 100644
index 0000000..86445df
--- /dev/null
+++ b/internal/godoc/static/package.html
@@ -0,0 +1,292 @@
+<!--
+	Copyright 2009 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.
+-->
+<!--
+	Note: Static (i.e., not template-generated) href and id
+	attributes start with "pkg-" to make it impossible for
+	them to conflict with generated attributes (some of which
+	correspond to Go identifiers).
+-->
+{{with .PDoc}}
+	<script>
+	document.ANALYSIS_DATA = {{$.AnalysisData}};
+	document.CALLGRAPH = {{$.CallGraph}};
+	</script>
+
+	{{if $.IsMain}}
+		{{/* command documentation */}}
+		{{comment_html .Doc}}
+	{{else}}
+		{{/* package documentation */}}
+		<div id="short-nav">
+			<dl>
+			<dd><code>import "{{html .ImportPath}}"</code></dd>
+			</dl>
+			<dl>
+			<dd><a href="#pkg-overview" class="overviewLink">Overview</a></dd>
+			<dd><a href="#pkg-index" class="indexLink">Index</a></dd>
+			{{if $.Examples}}
+				<dd><a href="#pkg-examples" class="examplesLink">Examples</a></dd>
+			{{end}}
+			{{if $.Dirs}}
+				<dd><a href="#pkg-subdirectories">Subdirectories</a></dd>
+			{{end}}
+			</dl>
+		</div>
+		<!-- The package's Name is printed as title by the top-level template -->
+		<div id="pkg-overview" class="toggleVisible">
+			<div class="collapsed">
+				<h2 class="toggleButton" title="Click to show Overview section">Overview ▹</h2>
+			</div>
+			<div class="expanded">
+				<h2 class="toggleButton" title="Click to hide Overview section">Overview ▾</h2>
+				{{comment_html .Doc}}
+				{{example_html $ ""}}
+			</div>
+		</div>
+
+		<div id="pkg-index" class="toggleVisible">
+		<div class="collapsed">
+			<h2 class="toggleButton" title="Click to show Index section">Index ▹</h2>
+		</div>
+		<div class="expanded">
+			<h2 class="toggleButton" title="Click to hide Index section">Index ▾</h2>
+
+		<!-- Table of contents for API; must be named manual-nav to turn off auto nav. -->
+			<div id="manual-nav">
+			<dl>
+			{{if .Consts}}
+				<dd><a href="#pkg-constants">Constants</a></dd>
+			{{end}}
+			{{if .Vars}}
+				<dd><a href="#pkg-variables">Variables</a></dd>
+			{{end}}
+			{{range .Funcs}}
+				{{$name_html := html .Name}}
+				<dd><a href="#{{$name_html}}">{{node_html $ .Decl false | sanitize}}</a></dd>
+			{{end}}
+			{{range .Types}}
+				{{$tname_html := html .Name}}
+				<dd><a href="#{{$tname_html}}">type {{$tname_html}}</a></dd>
+				{{range .Funcs}}
+					{{$name_html := html .Name}}
+					<dd>&nbsp; &nbsp; <a href="#{{$name_html}}">{{node_html $ .Decl false | sanitize}}</a></dd>
+				{{end}}
+				{{range .Methods}}
+					{{$name_html := html .Name}}
+					<dd>&nbsp; &nbsp; <a href="#{{$tname_html}}.{{$name_html}}">{{node_html $ .Decl false | sanitize}}</a></dd>
+				{{end}}
+			{{end}}
+			{{if $.Notes}}
+				{{range $marker, $item := $.Notes}}
+				<dd><a href="#pkg-note-{{$marker}}">{{noteTitle $marker | html}}s</a></dd>
+				{{end}}
+			{{end}}
+			</dl>
+			</div><!-- #manual-nav -->
+
+		{{if $.Examples}}
+		<div id="pkg-examples">
+			<h3>Examples</h3>
+			<div class="js-expandAll expandAll collapsed">(Expand All)</div>
+			<dl>
+			{{range $.Examples}}
+			<dd><a class="exampleLink" href="#example_{{.Name}}">{{example_name .Name}}</a></dd>
+			{{end}}
+			</dl>
+		</div>
+		{{end}}
+
+		{{with .Filenames}}
+			<h3>Package files</h3>
+			<p>
+			<span style="font-size:90%">
+			{{range .}}
+				<a href="{{.|srcLink|html}}">{{.|filename|html}}</a>
+			{{end}}
+			</span>
+			</p>
+		{{end}}
+		</div><!-- .expanded -->
+		</div><!-- #pkg-index -->
+
+		{{if ne $.CallGraph "null"}}
+		<div id="pkg-callgraph" class="toggle" style="display: none">
+		<div class="collapsed">
+			<h2 class="toggleButton" title="Click to show Internal Call Graph section">Internal call graph ▹</h2>
+		</div> <!-- .expanded -->
+		<div class="expanded">
+			<h2 class="toggleButton" title="Click to hide Internal Call Graph section">Internal call graph ▾</h2>
+			<p>
+			  In the call graph viewer below, each node
+			  is a function belonging to this package
+			  and its children are the functions it
+			  calls&mdash;perhaps dynamically.
+			</p>
+			<p>
+			  The root nodes are the entry points of the
+			  package: functions that may be called from
+			  outside the package.
+			  There may be non-exported or anonymous
+			  functions among them if they are called
+			  dynamically from another package.
+			</p>
+			<p>
+			  Click a node to visit that function's source code.
+			  From there you can visit its callers by
+			  clicking its declaring <code>func</code>
+			  token.
+			</p>
+			<p>
+			  Functions may be omitted if they were
+			  determined to be unreachable in the
+			  particular programs or tests that were
+			  analyzed.
+			</p>
+			<!-- Zero means show all package entry points. -->
+			<ul style="margin-left: 0.5in" id="callgraph-0" class="treeview"></ul>
+		</div>
+		</div> <!-- #pkg-callgraph -->
+		{{end}}
+
+		{{with .Consts}}
+			<h2 id="pkg-constants">Constants</h2>
+			{{range .}}
+				{{comment_html .Doc}}
+				<pre>{{node_html $ .Decl true}}</pre>
+			{{end}}
+		{{end}}
+		{{with .Vars}}
+			<h2 id="pkg-variables">Variables</h2>
+			{{range .}}
+				{{comment_html .Doc}}
+				<pre>{{node_html $ .Decl true}}</pre>
+			{{end}}
+		{{end}}
+		{{range .Funcs}}
+			{{/* Name is a string - no need for FSet */}}
+			{{$name_html := html .Name}}
+			<h2 id="{{$name_html}}">func <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a>
+				<a class="permalink" href="#{{$name_html}}">&#xb6;</a>
+				{{$since := since "func" "" .Name $.PDoc.ImportPath}}
+				{{if $since}}<span title="Added in Go {{$since}}">{{$since}}</span>{{end}}
+			</h2>
+			<pre>{{node_html $ .Decl true}}</pre>
+			{{comment_html .Doc}}
+			{{example_html $ .Name}}
+			{{callgraph_html $ "" .Name}}
+
+		{{end}}
+		{{range .Types}}
+			{{$tname := .Name}}
+			{{$tname_html := html .Name}}
+			<h2 id="{{$tname_html}}">type <a href="{{posLink_url $ .Decl}}">{{$tname_html}}</a>
+				<a class="permalink" href="#{{$tname_html}}">&#xb6;</a>
+				{{$since := since "type" "" .Name $.PDoc.ImportPath}}
+				{{if $since}}<span title="Added in Go {{$since}}">{{$since}}</span>{{end}}
+			</h2>
+			{{comment_html .Doc}}
+			<pre>{{node_html $ .Decl true}}</pre>
+
+			{{range .Consts}}
+				{{comment_html .Doc}}
+				<pre>{{node_html $ .Decl true}}</pre>
+			{{end}}
+
+			{{range .Vars}}
+				{{comment_html .Doc}}
+				<pre>{{node_html $ .Decl true}}</pre>
+			{{end}}
+
+			{{example_html $ $tname}}
+			{{implements_html $ $tname}}
+			{{methodset_html $ $tname}}
+
+			{{range .Funcs}}
+				{{$name_html := html .Name}}
+				<h3 id="{{$name_html}}">func <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a>
+					<a class="permalink" href="#{{$name_html}}">&#xb6;</a>
+					{{$since := since "func" "" .Name $.PDoc.ImportPath}}
+					{{if $since}}<span title="Added in Go {{$since}}">{{$since}}</span>{{end}}
+				</h3>
+				<pre>{{node_html $ .Decl true}}</pre>
+				{{comment_html .Doc}}
+				{{example_html $ .Name}}
+				{{callgraph_html $ "" .Name}}
+			{{end}}
+
+			{{range .Methods}}
+				{{$name_html := html .Name}}
+				<h3 id="{{$tname_html}}.{{$name_html}}">func ({{html .Recv}}) <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a>
+					<a class="permalink" href="#{{$tname_html}}.{{$name_html}}">&#xb6;</a>
+					{{$since := since "method" .Recv .Name $.PDoc.ImportPath}}
+					{{if $since}}<span title="Added in Go {{$since}}">{{$since}}</span>{{end}}
+				</h3>
+				<pre>{{node_html $ .Decl true}}</pre>
+				{{comment_html .Doc}}
+				{{$name := printf "%s_%s" $tname .Name}}
+				{{example_html $ $name}}
+				{{callgraph_html $ .Recv .Name}}
+			{{end}}
+		{{end}}
+	{{end}}
+
+	{{with $.Notes}}
+		{{range $marker, $content := .}}
+			<h2 id="pkg-note-{{$marker}}">{{noteTitle $marker | html}}s</h2>
+			<ul style="list-style: none; padding: 0;">
+			{{range .}}
+			<li><a href="{{posLink_url $ .}}" style="float: left;">&#x261e;</a> {{comment_html .Body}}</li>
+			{{end}}
+			</ul>
+		{{end}}
+	{{end}}
+{{end}}
+
+{{with .PAst}}
+	{{range $filename, $ast := .}}
+		<a href="{{$filename|srcLink|html}}">{{$filename|filename|html}}</a>:<pre>{{node_html $ $ast false}}</pre>
+	{{end}}
+{{end}}
+
+{{with .Dirs}}
+	{{/* DirList entries are numbers and strings - no need for FSet */}}
+	{{if $.PDoc}}
+		<h2 id="pkg-subdirectories">Subdirectories</h2>
+	{{end}}
+	<div class="pkg-dir">
+		<table>
+			<tr>
+				<th class="pkg-name">Name</th>
+				<th class="pkg-synopsis">Synopsis</th>
+			</tr>
+
+			{{if not (or (eq $.Dirname "/src/cmd") $.DirFlat)}}
+			<tr>
+				<td colspan="2"><a href="..">..</a></td>
+			</tr>
+			{{end}}
+
+			{{range .List}}
+				<tr>
+				{{if $.DirFlat}}
+					{{if .HasPkg}}
+						<td class="pkg-name">
+							<a href="{{html .Path}}/{{modeQueryString $.Mode | html}}">{{html .Path}}</a>
+						</td>
+					{{end}}
+				{{else}}
+					<td class="pkg-name" style="padding-left: {{multiply .Depth 20}}px;">
+						<a href="{{html .Path}}/{{modeQueryString $.Mode | html}}">{{html .Name}}</a>
+					</td>
+				{{end}}
+					<td class="pkg-synopsis">
+						{{html .Synopsis}}
+					</td>
+				</tr>
+			{{end}}
+		</table>
+	</div>
+{{end}}
diff --git a/internal/godoc/static/packageroot.html b/internal/godoc/static/packageroot.html
new file mode 100644
index 0000000..c246c79
--- /dev/null
+++ b/internal/godoc/static/packageroot.html
@@ -0,0 +1,150 @@
+<!--
+	Copyright 2018 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.
+-->
+<!--
+	Note: Static (i.e., not template-generated) href and id
+	attributes start with "pkg-" to make it impossible for
+	them to conflict with generated attributes (some of which
+	correspond to Go identifiers).
+-->
+{{with .PAst}}
+	{{range $filename, $ast := .}}
+		<a href="{{$filename|srcLink|html}}">{{$filename|filename|html}}</a>:<pre>{{node_html $ $ast false}}</pre>
+	{{end}}
+{{end}}
+
+{{with .Dirs}}
+	{{/* DirList entries are numbers and strings - no need for FSet */}}
+	{{if $.PDoc}}
+		<h2 id="pkg-subdirectories">Subdirectories</h2>
+	{{end}}
+		<div id="manual-nav">
+			<dl>
+				<dt><a href="#stdlib">Standard library</a></dt>
+				{{if hasThirdParty .List }}
+					<dt><a href="#thirdparty">Third party</a></dt>
+				{{end}}
+				<dt><a href="#other">Other packages</a></dt>
+				<dd><a href="#subrepo">Sub-repositories</a></dd>
+				<dd><a href="#community">Community</a></dd>
+			</dl>
+		</div>
+
+		<div id="stdlib" class="toggleVisible">
+			<div class="collapsed">
+				<h2 class="toggleButton" title="Click to show Standard library section">Standard library ▹</h2>
+			</div>
+			<div class="expanded">
+				<h2 class="toggleButton" title="Click to hide Standard library section">Standard library ▾</h2>
+				<img alt="" class="gopher" src="/doc/gopher/pkg.png"/>
+				<div class="pkg-dir">
+					<table>
+						<tr>
+							<th class="pkg-name">Name</th>
+							<th class="pkg-synopsis">Synopsis</th>
+						</tr>
+
+						{{range .List}}
+							<tr>
+							{{if eq .RootType "GOROOT"}}
+							{{if $.DirFlat}}
+								{{if .HasPkg}}
+										<td class="pkg-name">
+											<a href="{{html .Path}}/{{modeQueryString $.Mode | html}}">{{html .Path}}</a>
+										</td>
+								{{end}}
+							{{else}}
+									<td class="pkg-name" style="padding-left: {{multiply .Depth 20}}px;">
+										<a href="{{html .Path}}/{{modeQueryString $.Mode | html}}">{{html .Name}}</a>
+									</td>
+							{{end}}
+								<td class="pkg-synopsis">
+									{{html .Synopsis}}
+								</td>
+							{{end}}
+							</tr>
+						{{end}}
+					</table>
+				</div> <!-- .pkg-dir -->
+			</div> <!-- .expanded -->
+		</div> <!-- #stdlib .toggleVisible -->
+
+	{{if hasThirdParty .List }}
+		<div id="thirdparty" class="toggleVisible">
+			<div class="collapsed">
+				<h2 class="toggleButton" title="Click to show Third party section">Third party ▹</h2>
+			</div>
+			<div class="expanded">
+				<h2 class="toggleButton" title="Click to hide Third party section">Third party ▾</h2>
+				<div class="pkg-dir">
+					<table>
+						<tr>
+							<th class="pkg-name">Name</th>
+							<th class="pkg-synopsis">Synopsis</th>
+						</tr>
+
+						{{range .List}}
+							<tr>
+								{{if eq .RootType "GOPATH"}}
+								{{if $.DirFlat}}
+									{{if .HasPkg}}
+											<td class="pkg-name">
+												<a href="{{html .Path}}/{{modeQueryString $.Mode | html}}">{{html .Path}}</a>
+											</td>
+									{{end}}
+								{{else}}
+										<td class="pkg-name" style="padding-left: {{multiply .Depth 20}}px;">
+											<a href="{{html .Path}}/{{modeQueryString $.Mode | html}}">{{html .Name}}</a>
+										</td>
+								{{end}}
+									<td class="pkg-synopsis">
+										{{html .Synopsis}}
+									</td>
+								{{end}}
+							</tr>
+						{{end}}
+					</table>
+				</div> <!-- .pkg-dir -->
+			</div> <!-- .expanded -->
+		</div> <!-- #stdlib .toggleVisible -->
+	{{end}}
+
+	<h2 id="other">Other packages</h2>
+	<h3 id="subrepo">Sub-repositories</h3>
+	<p>
+	These packages are part of the Go Project but outside the main Go tree.
+	They are developed under looser <a href="/doc/go1compat">compatibility requirements</a> than the Go core.
+	Install them with "<a href="/cmd/go/#hdr-Download_and_install_packages_and_dependencies">go get</a>".
+	</p>
+	<ul>
+		<li><a href="//pkg.go.dev/golang.org/x/benchmarks">benchmarks</a> — benchmarks to measure Go as it is developed.</li>
+		<li><a href="//pkg.go.dev/golang.org/x/blog">blog</a> — <a href="//blog.golang.org">blog.golang.org</a>'s implementation.</li>
+		<li><a href="//pkg.go.dev/golang.org/x/build">build</a> — <a href="//build.golang.org">build.golang.org</a>'s implementation.</li>
+		<li><a href="//pkg.go.dev/golang.org/x/crypto">crypto</a> — additional cryptography packages.</li>
+		<li><a href="//pkg.go.dev/golang.org/x/debug">debug</a> — an experimental debugger for Go.</li>
+		<li><a href="//pkg.go.dev/golang.org/x/image">image</a> — additional imaging packages.</li>
+		<li><a href="//pkg.go.dev/golang.org/x/mobile">mobile</a> — experimental support for Go on mobile platforms.</li>
+		<li><a href="//pkg.go.dev/golang.org/x/net">net</a> — additional networking packages.</li>
+		<li><a href="//pkg.go.dev/golang.org/x/perf">perf</a> — packages and tools for performance measurement, storage, and analysis.</li>
+		<li><a href="//pkg.go.dev/golang.org/x/pkgsite">pkgsite</a> — home of the pkg.go.dev website.</li>
+		<li><a href="//pkg.go.dev/golang.org/x/review">review</a> — a tool for working with Gerrit code reviews.</li>
+		<li><a href="//pkg.go.dev/golang.org/x/sync">sync</a> — additional concurrency primitives.</li>
+		<li><a href="//pkg.go.dev/golang.org/x/sys">sys</a> — packages for making system calls.</li>
+		<li><a href="//pkg.go.dev/golang.org/x/text">text</a> — packages for working with text.</li>
+		<li><a href="//pkg.go.dev/golang.org/x/time">time</a> — additional time packages.</li>
+		<li><a href="//pkg.go.dev/golang.org/x/tools">tools</a> — godoc, goimports, gorename, and other tools.</li>
+		<li><a href="//pkg.go.dev/golang.org/x/tour">tour</a> — <a href="//tour.golang.org">tour.golang.org</a>'s implementation.</li>
+		<li><a href="//pkg.go.dev/golang.org/x/exp">exp</a> — experimental and deprecated packages (handle with care; may change without warning).</li>
+	</ul>
+
+	<h3 id="community">Community</h3>
+	<p>
+	These services can help you find Open Source packages provided by the community.
+	</p>
+	<ul>
+		<li><a href="//pkg.go.dev">Pkg.go.dev</a> - the Go package discovery site.</li>
+		<li><a href="/wiki/Projects">Projects at the Go Wiki</a> - a curated list of Go projects.</li>
+	</ul>
+{{end}}
diff --git a/internal/godoc/static/play.js b/internal/godoc/static/play.js
new file mode 100644
index 0000000..9cb1539
--- /dev/null
+++ b/internal/godoc/static/play.js
@@ -0,0 +1,114 @@
+// Copyright 2012 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.
+
+function initPlayground(transport) {
+  'use strict';
+
+  function text(node) {
+    var s = '';
+    for (var i = 0; i < node.childNodes.length; i++) {
+      var n = node.childNodes[i];
+      if (n.nodeType === 1) {
+        if (n.tagName === 'BUTTON') continue;
+        if (n.tagName === 'SPAN' && n.className === 'number') continue;
+        if (n.tagName === 'DIV' || n.tagName === 'BR' || n.tagName === 'PRE') {
+          s += '\n';
+        }
+        s += text(n);
+        continue;
+      }
+      if (n.nodeType === 3) {
+        s += n.nodeValue;
+      }
+    }
+    return s.replace('\xA0', ' '); // replace non-breaking spaces
+  }
+
+  // When presenter notes are enabled, the index passed
+  // here will identify the playground to be synced
+  function init(code, index) {
+    var output = document.createElement('div');
+    var outpre = document.createElement('pre');
+    var running;
+
+    if ($ && $(output).resizable) {
+      $(output).resizable({
+        handles: 'n,w,nw',
+        minHeight: 27,
+        minWidth: 135,
+        maxHeight: 608,
+        maxWidth: 990,
+      });
+    }
+
+    function onKill() {
+      if (running) running.Kill();
+      if (window.notesEnabled) updatePlayStorage('onKill', index);
+    }
+
+    function onRun(e) {
+      var sk = e.shiftKey || localStorage.getItem('play-shiftKey') === 'true';
+      if (running) running.Kill();
+      output.style.display = 'block';
+      outpre.textContent = '';
+      run1.style.display = 'none';
+      var options = { Race: sk };
+      running = transport.Run(text(code), PlaygroundOutput(outpre), options);
+      if (window.notesEnabled) updatePlayStorage('onRun', index, e);
+    }
+
+    function onClose() {
+      if (running) running.Kill();
+      output.style.display = 'none';
+      run1.style.display = 'inline-block';
+      if (window.notesEnabled) updatePlayStorage('onClose', index);
+    }
+
+    if (window.notesEnabled) {
+      playgroundHandlers.onRun.push(onRun);
+      playgroundHandlers.onClose.push(onClose);
+      playgroundHandlers.onKill.push(onKill);
+    }
+
+    var run1 = document.createElement('button');
+    run1.textContent = 'Run';
+    run1.className = 'run';
+    run1.addEventListener('click', onRun, false);
+    var run2 = document.createElement('button');
+    run2.className = 'run';
+    run2.textContent = 'Run';
+    run2.addEventListener('click', onRun, false);
+    var kill = document.createElement('button');
+    kill.className = 'kill';
+    kill.textContent = 'Kill';
+    kill.addEventListener('click', onKill, false);
+    var close = document.createElement('button');
+    close.className = 'close';
+    close.textContent = 'Close';
+    close.addEventListener('click', onClose, false);
+
+    var button = document.createElement('div');
+    button.classList.add('buttons');
+    button.appendChild(run1);
+    // Hack to simulate insertAfter
+    code.parentNode.insertBefore(button, code.nextSibling);
+
+    var buttons = document.createElement('div');
+    buttons.classList.add('buttons');
+    buttons.appendChild(run2);
+    buttons.appendChild(kill);
+    buttons.appendChild(close);
+
+    output.classList.add('output');
+    output.appendChild(buttons);
+    output.appendChild(outpre);
+    output.style.display = 'none';
+    code.parentNode.insertBefore(output, button.nextSibling);
+  }
+
+  var play = document.querySelectorAll('div.playground');
+  for (var i = 0; i < play.length; i++) {
+    init(play[i], i);
+  }
+}
diff --git a/internal/godoc/static/playground.js b/internal/godoc/static/playground.js
new file mode 100644
index 0000000..f5278a0
--- /dev/null
+++ b/internal/godoc/static/playground.js
@@ -0,0 +1,593 @@
+// Copyright 2012 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.
+
+/*
+In the absence of any formal way to specify interfaces in JavaScript,
+here's a skeleton implementation of a playground transport.
+
+        function Transport() {
+                // Set up any transport state (eg, make a websocket connection).
+                return {
+                        Run: function(body, output, options) {
+                                // Compile and run the program 'body' with 'options'.
+				// Call the 'output' callback to display program output.
+                                return {
+                                        Kill: function() {
+                                                // Kill the running program.
+                                        }
+                                };
+                        }
+                };
+        }
+
+	// The output callback is called multiple times, and each time it is
+	// passed an object of this form.
+        var write = {
+                Kind: 'string', // 'start', 'stdout', 'stderr', 'end'
+                Body: 'string'  // content of write or end status message
+        }
+
+	// The first call must be of Kind 'start' with no body.
+	// Subsequent calls may be of Kind 'stdout' or 'stderr'
+	// and must have a non-null Body string.
+	// The final call should be of Kind 'end' with an optional
+	// Body string, signifying a failure ("killed", for example).
+
+	// The output callback must be of this form.
+	// See PlaygroundOutput (below) for an implementation.
+        function outputCallback(write) {
+        }
+*/
+
+// HTTPTransport is the default transport.
+// enableVet enables running vet if a program was compiled and ran successfully.
+// If vet returned any errors, display them before the output of a program.
+function HTTPTransport(enableVet) {
+  'use strict';
+
+  function playback(output, data) {
+    // Backwards compatibility: default values do not affect the output.
+    var events = data.Events || [];
+    var errors = data.Errors || '';
+    var status = data.Status || 0;
+    var isTest = data.IsTest || false;
+    var testsFailed = data.TestsFailed || 0;
+
+    var timeout;
+    output({ Kind: 'start' });
+    function next() {
+      if (!events || events.length === 0) {
+        if (isTest) {
+          if (testsFailed > 0) {
+            output({
+              Kind: 'system',
+              Body:
+                '\n' +
+                testsFailed +
+                ' test' +
+                (testsFailed > 1 ? 's' : '') +
+                ' failed.',
+            });
+          } else {
+            output({ Kind: 'system', Body: '\nAll tests passed.' });
+          }
+        } else {
+          if (status > 0) {
+            output({ Kind: 'end', Body: 'status ' + status + '.' });
+          } else {
+            if (errors !== '') {
+              // errors are displayed only in the case of timeout.
+              output({ Kind: 'end', Body: errors + '.' });
+            } else {
+              output({ Kind: 'end' });
+            }
+          }
+        }
+        return;
+      }
+      var e = events.shift();
+      if (e.Delay === 0) {
+        output({ Kind: e.Kind, Body: e.Message });
+        next();
+        return;
+      }
+      timeout = setTimeout(function() {
+        output({ Kind: e.Kind, Body: e.Message });
+        next();
+      }, e.Delay / 1000000);
+    }
+    next();
+    return {
+      Stop: function() {
+        clearTimeout(timeout);
+      },
+    };
+  }
+
+  function error(output, msg) {
+    output({ Kind: 'start' });
+    output({ Kind: 'stderr', Body: msg });
+    output({ Kind: 'end' });
+  }
+
+  function buildFailed(output, msg) {
+    output({ Kind: 'start' });
+    output({ Kind: 'stderr', Body: msg });
+    output({ Kind: 'system', Body: '\nGo build failed.' });
+  }
+
+  var seq = 0;
+  return {
+    Run: function(body, output, options) {
+      seq++;
+      var cur = seq;
+      var playing;
+      $.ajax('/compile', {
+        type: 'POST',
+        data: { version: 2, body: body, withVet: enableVet },
+        dataType: 'json',
+        success: function(data) {
+          if (seq != cur) return;
+          if (!data) return;
+          if (playing != null) playing.Stop();
+          if (data.Errors) {
+            if (data.Errors === 'process took too long') {
+              // Playback the output that was captured before the timeout.
+              playing = playback(output, data);
+            } else {
+              buildFailed(output, data.Errors);
+            }
+            return;
+          }
+          if (!data.Events) {
+            data.Events = [];
+          }
+          if (data.VetErrors) {
+            // Inject errors from the vet as the first events in the output.
+            data.Events.unshift({
+              Message: 'Go vet exited.\n\n',
+              Kind: 'system',
+              Delay: 0,
+            });
+            data.Events.unshift({
+              Message: data.VetErrors,
+              Kind: 'stderr',
+              Delay: 0,
+            });
+          }
+
+          if (!enableVet || data.VetOK || data.VetErrors) {
+            playing = playback(output, data);
+            return;
+          }
+
+          // In case the server support doesn't support
+          // compile+vet in same request signaled by the
+          // 'withVet' parameter above, also try the old way.
+          // TODO: remove this when it falls out of use.
+          // It is 2019-05-13 now.
+          $.ajax('/vet', {
+            data: { body: body },
+            type: 'POST',
+            dataType: 'json',
+            success: function(dataVet) {
+              if (dataVet.Errors) {
+                // inject errors from the vet as the first events in the output
+                data.Events.unshift({
+                  Message: 'Go vet exited.\n\n',
+                  Kind: 'system',
+                  Delay: 0,
+                });
+                data.Events.unshift({
+                  Message: dataVet.Errors,
+                  Kind: 'stderr',
+                  Delay: 0,
+                });
+              }
+              playing = playback(output, data);
+            },
+            error: function() {
+              playing = playback(output, data);
+            },
+          });
+        },
+        error: function() {
+          error(output, 'Error communicating with remote server.');
+        },
+      });
+      return {
+        Kill: function() {
+          if (playing != null) playing.Stop();
+          output({ Kind: 'end', Body: 'killed' });
+        },
+      };
+    },
+  };
+}
+
+function SocketTransport() {
+  'use strict';
+
+  var id = 0;
+  var outputs = {};
+  var started = {};
+  var websocket;
+  if (window.location.protocol == 'http:') {
+    websocket = new WebSocket('ws://' + window.location.host + '/socket');
+  } else if (window.location.protocol == 'https:') {
+    websocket = new WebSocket('wss://' + window.location.host + '/socket');
+  }
+
+  websocket.onclose = function() {
+    console.log('websocket connection closed');
+  };
+
+  websocket.onmessage = function(e) {
+    var m = JSON.parse(e.data);
+    var output = outputs[m.Id];
+    if (output === null) return;
+    if (!started[m.Id]) {
+      output({ Kind: 'start' });
+      started[m.Id] = true;
+    }
+    output({ Kind: m.Kind, Body: m.Body });
+  };
+
+  function send(m) {
+    websocket.send(JSON.stringify(m));
+  }
+
+  return {
+    Run: function(body, output, options) {
+      var thisID = id + '';
+      id++;
+      outputs[thisID] = output;
+      send({ Id: thisID, Kind: 'run', Body: body, Options: options });
+      return {
+        Kill: function() {
+          send({ Id: thisID, Kind: 'kill' });
+        },
+      };
+    },
+  };
+}
+
+function PlaygroundOutput(el) {
+  'use strict';
+
+  return function(write) {
+    if (write.Kind == 'start') {
+      el.innerHTML = '';
+      return;
+    }
+
+    var cl = 'system';
+    if (write.Kind == 'stdout' || write.Kind == 'stderr') cl = write.Kind;
+
+    var m = write.Body;
+    if (write.Kind == 'end') {
+      m = '\nProgram exited' + (m ? ': ' + m : '.');
+    }
+
+    if (m.indexOf('IMAGE:') === 0) {
+      // TODO(adg): buffer all writes before creating image
+      var url = 'data:image/png;base64,' + m.substr(6);
+      var img = document.createElement('img');
+      img.src = url;
+      el.appendChild(img);
+      return;
+    }
+
+    // ^L clears the screen.
+    var s = m.split('\x0c');
+    if (s.length > 1) {
+      el.innerHTML = '';
+      m = s.pop();
+    }
+
+    m = m.replace(/&/g, '&amp;');
+    m = m.replace(/</g, '&lt;');
+    m = m.replace(/>/g, '&gt;');
+
+    var needScroll = el.scrollTop + el.offsetHeight == el.scrollHeight;
+
+    var span = document.createElement('span');
+    span.className = cl;
+    span.innerHTML = m;
+    el.appendChild(span);
+
+    if (needScroll) el.scrollTop = el.scrollHeight - el.offsetHeight;
+  };
+}
+
+(function() {
+  function lineHighlight(error) {
+    var regex = /prog.go:([0-9]+)/g;
+    var r = regex.exec(error);
+    while (r) {
+      $('.lines div')
+        .eq(r[1] - 1)
+        .addClass('lineerror');
+      r = regex.exec(error);
+    }
+  }
+  function highlightOutput(wrappedOutput) {
+    return function(write) {
+      if (write.Body) lineHighlight(write.Body);
+      wrappedOutput(write);
+    };
+  }
+  function lineClear() {
+    $('.lineerror').removeClass('lineerror');
+  }
+
+  // opts is an object with these keys
+  //  codeEl - code editor element
+  //  outputEl - program output element
+  //  runEl - run button element
+  //  fmtEl - fmt button element (optional)
+  //  fmtImportEl - fmt "imports" checkbox element (optional)
+  //  shareEl - share button element (optional)
+  //  shareURLEl - share URL text input element (optional)
+  //  shareRedirect - base URL to redirect to on share (optional)
+  //  toysEl - toys select element (optional)
+  //  enableHistory - enable using HTML5 history API (optional)
+  //  transport - playground transport to use (default is HTTPTransport)
+  //  enableShortcuts - whether to enable shortcuts (Ctrl+S/Cmd+S to save) (default is false)
+  //  enableVet - enable running vet and displaying its errors
+  function playground(opts) {
+    var code = $(opts.codeEl);
+    var transport = opts['transport'] || new HTTPTransport(opts['enableVet']);
+    var running;
+
+    // autoindent helpers.
+    function insertTabs(n) {
+      // find the selection start and end
+      var start = code[0].selectionStart;
+      var end = code[0].selectionEnd;
+      // split the textarea content into two, and insert n tabs
+      var v = code[0].value;
+      var u = v.substr(0, start);
+      for (var i = 0; i < n; i++) {
+        u += '\t';
+      }
+      u += v.substr(end);
+      // set revised content
+      code[0].value = u;
+      // reset caret position after inserted tabs
+      code[0].selectionStart = start + n;
+      code[0].selectionEnd = start + n;
+    }
+    function autoindent(el) {
+      var curpos = el.selectionStart;
+      var tabs = 0;
+      while (curpos > 0) {
+        curpos--;
+        if (el.value[curpos] == '\t') {
+          tabs++;
+        } else if (tabs > 0 || el.value[curpos] == '\n') {
+          break;
+        }
+      }
+      setTimeout(function() {
+        insertTabs(tabs);
+      }, 1);
+    }
+
+    // NOTE(cbro): e is a jQuery event, not a DOM event.
+    function handleSaveShortcut(e) {
+      if (e.isDefaultPrevented()) return false;
+      if (!e.metaKey && !e.ctrlKey) return false;
+      if (e.key != 'S' && e.key != 's') return false;
+
+      e.preventDefault();
+
+      // Share and save
+      share(function(url) {
+        window.location.href = url + '.go?download=true';
+      });
+
+      return true;
+    }
+
+    function keyHandler(e) {
+      if (opts.enableShortcuts && handleSaveShortcut(e)) return;
+
+      if (e.keyCode == 9 && !e.ctrlKey) {
+        // tab (but not ctrl-tab)
+        insertTabs(1);
+        e.preventDefault();
+        return false;
+      }
+      if (e.keyCode == 13) {
+        // enter
+        if (e.shiftKey) {
+          // +shift
+          run();
+          e.preventDefault();
+          return false;
+        }
+        if (e.ctrlKey) {
+          // +control
+          fmt();
+          e.preventDefault();
+        } else {
+          autoindent(e.target);
+        }
+      }
+      return true;
+    }
+    code.unbind('keydown').bind('keydown', keyHandler);
+    var outdiv = $(opts.outputEl).empty();
+    var output = $('<pre/>').appendTo(outdiv);
+
+    function body() {
+      return $(opts.codeEl).val();
+    }
+    function setBody(text) {
+      $(opts.codeEl).val(text);
+    }
+    function origin(href) {
+      return ('' + href)
+        .split('/')
+        .slice(0, 3)
+        .join('/');
+    }
+
+    var pushedEmpty = window.location.pathname == '/';
+    function inputChanged() {
+      if (pushedEmpty) {
+        return;
+      }
+      pushedEmpty = true;
+      $(opts.shareURLEl).hide();
+      window.history.pushState(null, '', '/');
+    }
+    function popState(e) {
+      if (e === null) {
+        return;
+      }
+      if (e && e.state && e.state.code) {
+        setBody(e.state.code);
+      }
+    }
+    var rewriteHistory = false;
+    if (
+      window.history &&
+      window.history.pushState &&
+      window.addEventListener &&
+      opts.enableHistory
+    ) {
+      rewriteHistory = true;
+      code[0].addEventListener('input', inputChanged);
+      window.addEventListener('popstate', popState);
+    }
+
+    function setError(error) {
+      if (running) running.Kill();
+      lineClear();
+      lineHighlight(error);
+      output
+        .empty()
+        .addClass('error')
+        .text(error);
+    }
+    function loading() {
+      lineClear();
+      if (running) running.Kill();
+      output.removeClass('error').text('Waiting for remote server...');
+    }
+    function run() {
+      loading();
+      running = transport.Run(
+        body(),
+        highlightOutput(PlaygroundOutput(output[0]))
+      );
+    }
+
+    function fmt() {
+      loading();
+      var data = { body: body() };
+      if ($(opts.fmtImportEl).is(':checked')) {
+        data['imports'] = 'true';
+      }
+      $.ajax('/fmt', {
+        data: data,
+        type: 'POST',
+        dataType: 'json',
+        success: function(data) {
+          if (data.Error) {
+            setError(data.Error);
+          } else {
+            setBody(data.Body);
+            setError('');
+          }
+        },
+      });
+    }
+
+    var shareURL; // jQuery element to show the shared URL.
+    var sharing = false; // true if there is a pending request.
+    var shareCallbacks = [];
+    function share(opt_callback) {
+      if (opt_callback) shareCallbacks.push(opt_callback);
+
+      if (sharing) return;
+      sharing = true;
+
+      var sharingData = body();
+      $.ajax('/share', {
+        processData: false,
+        data: sharingData,
+        type: 'POST',
+        contentType: 'text/plain; charset=utf-8',
+        complete: function(xhr) {
+          sharing = false;
+          if (xhr.status != 200) {
+            alert('Server error; try again.');
+            return;
+          }
+          if (opts.shareRedirect) {
+            window.location = opts.shareRedirect + xhr.responseText;
+          }
+          var path = '/p/' + xhr.responseText;
+          var url = origin(window.location) + path;
+
+          for (var i = 0; i < shareCallbacks.length; i++) {
+            shareCallbacks[i](url);
+          }
+          shareCallbacks = [];
+
+          if (shareURL) {
+            shareURL
+              .show()
+              .val(url)
+              .focus()
+              .select();
+
+            if (rewriteHistory) {
+              var historyData = { code: sharingData };
+              window.history.pushState(historyData, '', path);
+              pushedEmpty = false;
+            }
+          }
+        },
+      });
+    }
+
+    $(opts.runEl).click(run);
+    $(opts.fmtEl).click(fmt);
+
+    if (
+      opts.shareEl !== null &&
+      (opts.shareURLEl !== null || opts.shareRedirect !== null)
+    ) {
+      if (opts.shareURLEl) {
+        shareURL = $(opts.shareURLEl).hide();
+      }
+      $(opts.shareEl).click(function() {
+        share();
+      });
+    }
+
+    if (opts.toysEl !== null) {
+      $(opts.toysEl).bind('change', function() {
+        var toy = $(this).val();
+        $.ajax('/doc/play/' + toy, {
+          processData: false,
+          type: 'GET',
+          complete: function(xhr) {
+            if (xhr.status != 200) {
+              alert('Server error; try again.');
+              return;
+            }
+            setBody(xhr.responseText);
+          },
+        });
+      });
+    }
+  }
+
+  window.playground = playground;
+})();
diff --git a/internal/godoc/static/search.html b/internal/godoc/static/search.html
new file mode 100644
index 0000000..3714e1d
--- /dev/null
+++ b/internal/godoc/static/search.html
@@ -0,0 +1,66 @@
+<!--
+	Copyright 2009 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.
+-->
+
+{{ $colCount := tocColCount .}}
+{{/* Generate the TOC */}}
+<nav class="search-nav" style="column-count: {{$colCount}}" role="navigation">
+{{range $key, $val := .Idents}}
+	{{if $val}}
+		<a href="#{{$key.Name}}">{{$key.Name}}</a>
+		<br />
+	{{end}}
+{{end}}
+
+{{if not .Idents}}
+	{{with .Pak}}
+		<a href="#Packages">Package {{html $.Query}}</a>
+		<br />
+	{{end}}
+{{end}}
+
+{{with .Hit}}
+	{{with .Decls}}
+		<a href="#Global">Package-level declarations</a>
+		<br />
+		{{range .}}
+			{{$pkg_html := pkgLink .Pak.Path | html}}
+			<a href="#Global_{{$pkg_html}}" class="indent">package {{html .Pak.Name}}</a>
+			<br />
+		{{end}}
+	{{end}}
+	{{with .Others}}
+		<a href="#Local">Local declarations and uses</a>
+		<br />
+		{{range .}}
+			{{$pkg_html := pkgLink .Pak.Path | html}}
+			<a href="#Local_{{$pkg_html}}" class="indent">package {{html .Pak.Name}}</a>
+			<br />
+		{{end}}
+	{{end}}
+{{end}}
+
+{{with .Textual}}
+	{{if $.Complete}}
+		<a href="#Textual">{{html $.Found}} textual occurrences</a>
+	{{else}}
+		<a href="#Textual">More than {{html $.Found}} textual occurrences</a>
+	{{end}}
+{{end}}
+</nav>
+
+{{with .Alert}}
+	<p>
+	<span class="alert" style="font-size:120%">{{html .}}</span>
+	</p>
+{{end}}
+{{with .Alt}}
+	<p>
+	<span class="alert" style="font-size:120%">Did you mean: </span>
+	{{range .Alts}}
+		<a href="search?q={{urlquery .}}" style="font-size:120%">{{html .}}</a>
+	{{end}}
+	</p>
+{{end}}
diff --git a/internal/godoc/static/searchcode.html b/internal/godoc/static/searchcode.html
new file mode 100644
index 0000000..a032e64
--- /dev/null
+++ b/internal/godoc/static/searchcode.html
@@ -0,0 +1,64 @@
+<!--
+	Copyright 2009 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.
+-->
+{{$query_url := urlquery .Query}}
+{{if not .Idents}}
+	{{with .Pak}}
+		<h2 id="Packages">Package {{html $.Query}}</h2>
+		<p>
+		<table class="layout">
+		{{range .}}
+			{{$pkg_html := pkgLink .Pak.Path | html}}
+			<tr><td><a href="/{{$pkg_html}}">{{$pkg_html}}</a></td></tr>
+		{{end}}
+		</table>
+		</p>
+	{{end}}
+{{end}}
+{{with .Hit}}
+	{{with .Decls}}
+		<h2 id="Global">Package-level declarations</h2>
+		{{range .}}
+			{{$pkg_html := pkgLink .Pak.Path | html}}
+			<h3 id="Global_{{$pkg_html}}">package <a href="/{{$pkg_html}}">{{html .Pak.Name}}</a></h3>
+			{{range .Files}}
+				{{$file := .File.Path}}
+				{{range .Groups}}
+					{{range .}}
+						{{$line := infoLine .}}
+						<a href="{{queryLink $file $query_url $line | html}}">{{$file}}:{{$line}}</a>
+						{{infoSnippet_html .}}
+					{{end}}
+				{{end}}
+			{{end}}
+		{{end}}
+	{{end}}
+	{{with .Others}}
+		<h2 id="Local">Local declarations and uses</h2>
+		{{range .}}
+			{{$pkg_html := pkgLink .Pak.Path | html}}
+			<h3 id="Local_{{$pkg_html}}">package <a href="/{{$pkg_html}}">{{html .Pak.Name}}</a></h3>
+			{{range .Files}}
+				{{$file := .File.Path}}
+				<a href="{{queryLink $file $query_url 0 | html}}">{{$file}}</a>
+				<table class="layout">
+				{{range .Groups}}
+					<tr>
+					<td width="25"></td>
+					<th align="left" valign="top">{{index . 0 | infoKind_html}}</th>
+					<td align="left" width="4"></td>
+					<td>
+					{{range .}}
+						{{$line := infoLine .}}
+						<a href="{{queryLink $file $query_url $line | html}}">{{$line}}</a>
+					{{end}}
+					</td>
+					</tr>
+				{{end}}
+				</table>
+			{{end}}
+		{{end}}
+	{{end}}
+{{end}}
diff --git a/internal/godoc/static/searchdoc.html b/internal/godoc/static/searchdoc.html
new file mode 100644
index 0000000..679c02c
--- /dev/null
+++ b/internal/godoc/static/searchdoc.html
@@ -0,0 +1,24 @@
+<!--
+	Copyright 2009 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.
+-->
+{{range $key, $val := .Idents}}
+	{{if $val}}
+		<h2 id="{{$key.Name}}">{{$key.Name}}</h2>
+		{{range $val}}
+			{{$pkg_html := pkgLink .Path | html}}
+			{{if eq "Packages" $key.Name}}
+				<a href="/{{$pkg_html}}">{{html .Path}}</a>
+			{{else}}
+				{{$doc_html := docLink .Path .Name| html}}
+				<a href="/{{$pkg_html}}">{{html .Package}}</a>.<a href="{{$doc_html}}">{{.Name}}</a>
+			{{end}}
+			{{if .Doc}}
+				<p>{{comment_html .Doc}}</p>
+			{{else}}
+				<p><em>No documentation available</em></p>
+			{{end}}
+		{{end}}
+	{{end}}
+{{end}}
diff --git a/internal/godoc/static/searchtxt.html b/internal/godoc/static/searchtxt.html
new file mode 100644
index 0000000..7e4a978
--- /dev/null
+++ b/internal/godoc/static/searchtxt.html
@@ -0,0 +1,42 @@
+<!--
+	Copyright 2009 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.
+-->
+{{$query_url := urlquery .Query}}
+{{with .Textual}}
+	{{if $.Complete}}
+		<h2 id="Textual">{{html $.Found}} textual occurrences</h2>
+	{{else}}
+		<h2 id="Textual">More than {{html $.Found}} textual occurrences</h2>
+		<p>
+		<span class="alert" style="font-size:120%">Not all files or lines containing "{{html $.Query}}" are shown.</span>
+		</p>
+	{{end}}
+	<p>
+	<table class="layout">
+	{{range .}}
+		{{$file := .Filename}}
+		<tr>
+		<td align="left" valign="top">
+		<a href="{{queryLink $file $query_url 0}}">{{$file}}</a>:
+		</td>
+		<td align="left" width="4"></td>
+		<th align="left" valign="top">{{len .Lines}}</th>
+		<td align="left" width="4"></td>
+		<td align="left">
+		{{range .Lines}}
+			<a href="{{queryLink $file $query_url .}}">{{html .}}</a>
+		{{end}}
+		{{if not $.Complete}}
+			...
+		{{end}}
+		</td>
+		</tr>
+	{{end}}
+	{{if not $.Complete}}
+		<tr><td align="left">...</td></tr>
+	{{end}}
+	</table>
+	</p>
+{{end}}
diff --git a/internal/godoc/static/static.go b/internal/godoc/static/static.go
new file mode 100644
index 0000000..e2a6452
--- /dev/null
+++ b/internal/godoc/static/static.go
@@ -0,0 +1,99 @@
+// 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.
+
+// Code generated by "makestatic"; DO NOT EDIT.
+
+package static
+
+var Files = map[string]string{
+	"analysis/call3.png": "\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x03N\x00\x00\x01\xea\x08\x03\x00\x00\x00\x04l\xeeb\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x02\xfdPLTE\x00\x01\x00\x04\x06\x03\x06\x09\x05\x0e\x10\x0d\x16\x17\x15$\x18\x0f\x1e\x1d\x17!#\x20#$\"$%#&(%)(\"()'-+\x1f*+),-+-/,41&J-\x1202/241S/\x0e564,7M796<:.:<9!?p=?<B@4@B?DECLH9?JVHIGvD\x13JLIMOLVQ@QRPUWT_ZHY[X,`\xae7]\xaduWG8`\xaa\\^[A`\xa5:b\xab;b\xac<c\xad_a^=d\xae@f\xb0bdaRe\x99lfP\x90^8Ci\xb4egdMj\xafhjgFm\xb1Ol\xb2fj\x90Ip\xb4\xa7c\"}iflnkKr\xb7oqnNu\xba\xb3h\"{s[Xv\xb6tvs\x7fr\x7fZx\xb8ry\x81]{\xbb\\}\xb6y{x_}\xbeX\x80\xbf`\x80\xba\x96xpb\x80\xc0}\x7f|\x89\x80eb\x83\xbd\x7f\x81~\x9a{oe\x85\xc0\x82\x83\x81k\x85\xbb\xa7}fh\x88\xc3m\x88\xbdj\x8a\xc4\xbe~G\x87\x89\x86q\x8c\xc1\x8a\x8c\x89\xbe\x82Y\x96\x8cnt\x8e\xc4z\x8f\xc0\x8f\x91\x8ex\x92\xc8~\x93\xc3x\x95\xc4z\x94\xca\x92\x94\x91\x80\x95\xc5\xd3\x8aJz\x98\xc7\x82\x96\xc7\xa0\x96w|\x99\xc8\x95\x97\x94\x83\x98\xc9\x82\x9a\xc4\x97\x99\x96\x86\x9a\xcc\xe4\x8dA\x80\x9d\xcc\x85\x9d\xc7\x9a\x9c\x98\x83\xa0\xcf\x9c\x9e\x9b\xa8\x9e\x7f\x88\xa0\xcb\xf3\x915\x8a\xa2\xcc\x99\xa0\xb5\x9f\xa1\x9e\x8e\xa2\xc7\x8b\xa3\xce\xa1\xa3\xa0\xfa\x95/\xa2\xa4\xa1\x8e\xa6\xd0\xff\x952\x92\xa6\xcb\xb3\xa6\x82\xa5\xa7\xa4\x91\xa9\xd4\xa7\xa9\xa6\x94\xac\xd6\x99\xac\xd2\xaa\xac\xa8\xb9\xac\x88\x9e\xae\xce\x9b\xaf\xd4\xad\xaf\xac\x9d\xb1\xd6\xa2\xb1\xd1\xaf\xb1\xae\xa1\xb5\xda\xa5\xb5\xd5\xb3\xb5\xb2\xc2\xb5\x90\xa3\xb7\xdd\xb5\xb7\xb4\xac\xb7\xd2\xaa\xb9\xd9\xb7\xb9\xb6\xac\xbc\xdc\xba\xbc\xb9\xb1\xbd\xd7\xaf\xbe\xdf\xcb\xbe\x98\xbd\xbf\xbc\xb1\xc0\xe0\xba\xc1\xd6\xc0\xc2\xbe\xb7\xc2\xdd\xc2\xc4\xc1\xb9\xc5\xe0\xbd\xc5\xda\xc4\xc6\xc3\xbb\xc7\xe2\xd6\xc7\x9d\xbc\xc8\xe3\xbb\xca\xde\xc7\xc9\xc6\xbf\xca\xe5\xc6\xca\xda\xc0\xcc\xda\xca\xcc\xc8\xc1\xcc\xe7\xbf\xcf\xe2\xc2\xce\xe9\xcd\xcf\xcc\xc4\xd0\xde\xcb\xcf\xdf\xc8\xd0\xe5\xcf\xd1\xce\xdf\xd1\xa5\xca\xd1\xe7\xcc\xd3\xe9\xd2\xd4\xd1\xcc\xd5\xdd\xd3\xd3\xde\xce\xd6\xeb\xd6\xd8\xd5\xd0\xd8\xed\xe7\xd8\xac\xd2\xda\xe2\xcb\xdb\xee\xd5\xd9\xe9\xd9\xd9\xe4\xd7\xdb\xeb\xda\xdc\xd9\xd8\xdc\xec\xd2\xde\xec\xd9\xdd\xed\xdc\xdd\xe7\xdd\xdf\xdc\xd4\xe0\xef\xdb\xdf\xef\xf0\xe0\xb3\xdc\xe1\xe4\xdd\xe1\xf1\xe0\xe2\xdf\xd9\xe5\xf4\xe0\xe5\xe8\xe0\xe4\xf4\xe3\xe5\xe2\xde\xe6\xef\xe7\xe5\xe9\xe5\xe7\xe4\xe6\xe7\xf1\xe0\xe9\xf1\xe4\xe9\xeb\xe7\xe9\xe6\xe3\xec\xf4\xeb\xed\xea\xe6\xee\xf7\xec\xed\xf7\xe8\xf0\xf9\xf0\xf0\xfb\xf0\xf3\xef\xf4\xf2\xf6\xee\xf4\xf6\xf0\xf6\xf8\xf4\xf7\xf3\xf2\xf7\xfa\xf5\xfa\xfd\xf8\xfa\xf7\xfd\xfb\xff\xf7\xfd\xff\xf9\xff\xff\xfe\xff\xfc\x9cx\xeb\xab\x00\x00\x20\x00IDATx^\xed\x9d\x0fp\x14\xd7\x81\xa7C\x9c\xf3\x83\x89-\x94\x95V\xa7\xb0\x04r\xac8\x14\xd37l\x09F\\N\xa0\xd1\xe9N\xb27\xd2q\x0e\x845\xc1+\xbc\xb6N\xb0\x11\xb2\x1d\xd8(K!\x8e\x15\x0a\xa9S\x20\"\x18\x08Z\x82\x10QJ\x12\x08,\x84A\x90\x0a\x10\xe1\"\xe0\x80b\xc9v\xe2\x12Nd\x1bk\x89\xd6\xc4j;\x80\x8d\x8d\xf0\xd4\xd4\xbd\xd7\xdd3\xf3\xba\xa7{zz\xd4j\x8dF\xbf\xaf\xa8\xa1\xe7\xe9\xbd\xd7\x7f\xa6\xbf\xe9\xee7=\xbf\xf9\x8c?fD\x00\x80\x8a\xcf\x98Ic\x8cY\xd7\x00L4\xa0\x13\x00\xb6\x01\x9d\x00\xb0\x0d\xe8\x04\x80m@'\x00l\x03:%27\xcc*\x00{\x81N\x09KwA\x1aYhV\x09\xd8\x0atJXf\xce\xacj|\xc9\xac\x12\xb0\x15Gu\xea\xde_[]w\xa8\x9fM\xde8\xb1\xa7z\xd7\x19m\xa9\xd3\xf4\x15\xa7%\xcd\x8b\xf2\x8c\xa81\xb5\xde\xacJ\\q\x82\xec7\xab\x02\xec\xc6I\x9dz\xab\xea\xcft\x9f\xd9U;@\xa7\xebk\x8e_h\xafn\xd7\x94:Mf\xda\x86\x82\xa4>\xb3Z2\xf5Sw\x99U\x89+\xdaI\xb3Y\x15`7N\xea\xd4U\xd5M\x1f\x07\xaa\xe8A\xe9x5\xdb\x89\xbb\xaa\xfbU\xa5N\xd3G\xd6\x89\xe2\x98\x1c\x16\x1d\xa0\x8d\xb4\x99U\x01v\xe3\xa4N\xa2t\x00\xeae\xfa\xd4\xcb'\"\xb5\xed\xaaR\xa7\xe9\"\xe3\xeb\xfc\xcd\x12\xd0i\x0cpT'\xca@w\x1d\xdb\x85w\xc9'\"{\xeaU\xa5\x8e2\x98J$\x96J\xcf6d$el\xa0\xff\x9f\x98B\xc8\xaa\xae\xdc\xe9I\xf3\x06\xf9\xca}I\x84L\xae\xd5\xeb&H\xd7\xc2TW\xda\xbc\xb07\x85\x9a\xb4*\xf6_w\xb2<7\x92\xcc\xd7\xe0\xe7\xd6\xbf(\xdd5m\xc1\x09ZZL\\UKg$=$\xd5\xec\xcdMM_\xba4uj\x1d\x9d\xae\x9d3u\xc6R\xed\xd1\xb4\xf3\x89|\xf7\xe2\xa7\xf2\x874\xc5b\x13tr\x1egu\xea\xad\xaa\xaa\xaaa\xfb\xc3\xa1Z\xb6\xb7\x0e\xd4\xecR\x95:\xcb\xf1\xe6\xed\xa4\xac\xb9Y\xdai\x0b\\\xab\xeaW\xb9r\xe9\"\xd5\xd5N\x9b\x91<mi.Q\x0f\x8a\xb577\xbb\xcat\xbbQh\x9a\x9aQ\xd1XF*\xb4\xe5\x0b\xc9<\xf6\xdf`\xcd:\x99\x1a^S~n\xf5\xe4\xe1\xa6\xba\x05\x93\xa9\x03gj\xa7\x90\xd4\xb2\x8ad6\xca=83}C\x99+\xa9\xfa!\xdaq\xc1\xe4\xe2\xfau\xa9\x19\xea\xa1\x93_\xce}\xa6\xa5\xe3\xc0b\xe1]U\xe9`o\xd3\xec\x14\xe77\xe9\x84\xc7Y\x9d\xc4\xde\xeeSu\xb5\xf4e\xee\xaf\xa9\xef\x1d\xe8\xdeSU\xa7*u\x9a\xe0\xc9\xde~i\x14L~\x143Hf\xbf\xde%UR$\x9d\x06\xa6\xcd\xa3'\xad\x83ua\xe3\x1a\x17\xd6\\\x90\xfe\xef\xbd\x20\xd3\xab\xf9{pn\x03ul\x96\xb3\xa4O\x8a\\)T\xf2\xdcT:\xb5\x9d\xd0\xe3\xd5\x06r\x8aN\xd6\x93\x1a\x91\x9d\xc3mW\xb5?\xe0e\"\xed\xcbQ\x1f\x9d\x16\xd2\xe3\xa0\xe3\xc7{\xe0\xb4N\"\xdb\xe5\x1a\xe9c_==$\xb5\xd5\xefQ\x97:LP\xa7\x82Y\xd2\x7f3\x0b\xd8c\x86K\xff\xd3\x9a\x88:\xd5\x93\xe3\x11\xfeJ\xad\"\x01.\xa8\xff\x10\x9a[\xef\x86y\xd3\x93\x89\xb4$.\xb6\x20e.\xfa\xb0\"Ed\xcb\xc9\xb6\xd3\xa2\xe9\x83\x8c\xf4\x02U\xfb\xd7\xbc\x85\x1b\x0f\xfcjHs\xae\xf7\xd2\xfe\xaa\x998:9\x8f\x93:)C\xe1'\xaa\xe4g}7\xc4\xdaC\xdaRG\x09\xea4g\x81\xf4\xdf\xbc\xd9\xec1c\xb6~\xed\x88:\xad#&\x03\xfd\xcd\xf52\xda\xd1\xeb\xe0\xdc\xda\xd2\xa6\xad\xd8\xd5\x9c)\xeb\xc4f%\xe9TAz\xd9a\x93\x1d\x9d2\x14\x1f5w:\\;P^$\xe4\xed\x0d\xbbvj\xc6\xb5\x93\xf38\xa9Sm\x93\xf4\xdf\xf1j\xfa\x20]AHc\xe4|\xa9\xc3\x84\x8eN\xd3\xa5\xff\xa6\xcbG'\x83;s\"\xea\xd4hrt2$8\xb7\x99s\x98\x90\x0b5:\xbd4y\xdeK'f\xcca\x85\xb9\xd3\xdb%\xd4\xa7\x8b\xe7\xb6R\x91\xae\xb5\xcc?\x20j\xc0\xc8\xde\x18\xe0\xa8N\xd2\xa5\x89tZw\xa6\x8a\xee\x14\xfd\x92I\\\xa9\xd3\x04u\xaa\x97.H\xb6\xcbO3\x16\xe9\xd7\x0e\xe9t\xa1,\xcc\x9d\xfe\xf4L\xf6\x0eQ\xbcT\xfb\x87\xc0\xb5\x93\x11\xc1\xb9Mc^\xdd\xc8\xd0\xe8\xd4N\xd2\x08\xc9\x94\x86K\xf6\x13\xe9J\xf3\xf15\xaa\xf6;\x85\x0e\xf6\xdf\xcagE\x0d\xd0i\x0cpR\xa7\xae\xaa\xc63\xddg\xea\xd8\xfd\x0f]U\xed\xdd\xc7k\xeb\x07\xd4\xa5\xce\"\x8f\xec\xc9\xfb\\\xee\xe4\x15\xf5+&\xe7R\xad\xdb\x9agd67k\x15\xa0\xc5\xcd\xae\x82\xe6f\xf9rd\x01\x99\x1a6\xe4\xd0\xf4\xc0\xcc\x0d\xf5\xc5$\xec\x18\xbb@\x1e\xd9\xd3\x87\x9f[\x19Y\xb4aM\x06I\xadh\xeb\xa6\xb3j\x13\xdb\x0b\\\xcd\xdd\xe2qWc}\xb32\xb6\xbe\x82,\xaa\xdbS@\xd4\xe3\xf5;\x05\xcf\x0f\x8e\x1cyF\x96\x8a\xa7\x9d\x1c\xd2\x16\x81\xd1\xc6I\x9d\xc4\xee\xa6\xba\xea]m\x927\xc7\xebj\xea\x8f\x87\x95:\xc9@\x8at%2E\xbe\x1bc\xdd\xac\xa4Y\xd2\xe7N\x93\xa5R\xad\x02\xedr1\x91/\xf0\xaa\xa7\xea\xbc\xf3w-JO\x9e\x1d~\x1fRMj\x84\x8bB~n7*f\xb8R\x17\xd5Lwe\x16\xd3\"\xd7\xa9\xa9\xf4\xb1X<\xe4b\x15\\\x99\xec\xe2I\xdc\x9f\x99\x9a2Gs'\xdeO\x97\xef\xccw{W\x86\xd9$\xf6NYt\xa27\xca\xfb\x11\x81M8\xaaS\xc2\xb0\x8784j\xd6;\xb5\xb8wp\xb0\xaf}a\x0c\xc3t{f\x86\x0d[\x80Q\x06:\xc5@M\xca\xc3fUlbO\x8a2\x11\xd3\xed\xec\xbd'\xc6\xe0\xce\xad\x09\x0dt\xb2Nw\xeaR\xa7NM\xdb'Kgy\xe2\xa9\xc91\x0e\x1c\x02G\x81N\xf1MA\xd2\xc3\xdb\x9b\xb6?\x9c\xe4\xd4\xe1\x10\x8c\x08\xe8\x14\xe7\xd4?\x94\xeeJ\x9f\x17\xcb\xa9\x1ep\x1e\xe8\x04\x80m@\xa7D\xe4G!\xcc\xaa\x02;\x81N\x89\x08t\x1a#\xa0S\"\x02\x9d\xc6\x08\xe8\x94\x88@\xa71\x02:%\"\xd0i\x8c\x80N\x89\x08t\x1a#\xa0S\"\x02\x9d\xc6\x08\xe8\x94\x88(*\xfd\xe3\x7f\xff\xbeJ\xa7\xbe9{D0\x9a8\xaa\x93~\xa82\xe5T\x15>\xf6\xb7H\x87\xf7\x88\xf1\x1fe\x9b\xfeA\xf8{\xcd\xd1\xa9b\xb2\xfa\xbb\x87\xc0f\x9c\xd4I?T\x99\xd2W}(r\x86\x1d\x08\xe3\x88\xa7\xc5\xf8\x8f\xb2M\xee\x7f\x0c;\xd9\xab\x9f\xb2N\xbf\x05\xb0\x05'u\xd2\x0fU\xa6\xd47vC'\xab\x84e\xadp0\x9b\xfe\xaf\xfb\xeft\xae\x9dV\xb9\xf0\x9d\x8dQ\xc4I\x9d\x0cB\x95\xc5S5\x03\xd0\xc9V\x98G\xff\xeb\xbf~\xffG?\xfc\xd1\x8fn\x0c\xaa\xbe\x91\xdb\xaf\xc9\x15\x03\xb6\xe2\xa8N\xa2n\xa8r\x7fu\x97\x98@:\x19\xc42\x8b\x91\xf3\x95\xdf\xcdso\xf5z;6\xe6\xac\xbc&\x8a\xd7\xca\xbd\xee\xfc\xa7.\xd2\xe2\x7f\x16\xdc\xfb\xd6{s\x9e\xfa\x8djR\xbc\xe6\x11\x04A:\xd9\xa3\xa5\x076\x16y\x9ex\x9d=\xb9\xb6v\xb1\xf7_\xfee\xb1\xa7\x85\xda\xf4\xc3\xbf\xf9\x9f\x7f\xf9\x17\x7f\xf1\xd7\x7f9s\xe6\xd2\xdf\xac^\xec\xf6>\xf5\xba<\xa3UIN}Wk\"\xe2\xacN\xba\xa1\xca\xf5\x8db\"\xe9d\x14\xcbl\x92\xaf\xdc\x91-l,\x11\x16\xef\xcc\xdbG\xaf\x8b\x84\xf5\x1d-O\x09\xe7D\xf1b\x8b[\xc8\xdf\xb93\xdfs\x91\x9f\x14\xc5s\x9d\x9d\xf3w\xb2f\xac\xd4\xbbso\xf6j:=T\xe4\xdd\xf7\x83\xf9\x9e\x03O\xec\xa3:\xfd\x93\xfb?L\xce\xcc\x9c\x94\x949{\xd2_}}o\xc7Na\x9f<\x9fC\x088\x1aE\x9c\xd5I/T\xf9\x0c\xf3+\x81t\x12\x0dc\x99#\xe7+{\xd7\x8a\x1d\xc2\x11\xf1\x99\xf5\xa2x\xbd\x85\x1e\xa2\x86\x960E\xc4\xacBv\xb8\xca_\xae\x9e\xa4\xc8:\xd1\xd2\x1cz\xdcY\xeb\xa5S-\x025m\x1f{`\x83\xe4\xee\xfb\xd6\xf8\xfdg>\xf4\xdf\xbb\xef?\xd2\x19\x0d\xb1.\x19\xddd|\xfdL\xd5\xf8\xc2a\x9d\xc4\xb0P\xe5\xfe\xea37n\xdcx\xa96\x912w\x8cb\x99e\x0c\xf2\x95\xbd-\xe29\xe1\x9a\xb8u\xad\xc8\x92]\x9f(\xcc\x16\x96\xb0\xe2\xac\xad\xecq\x1f\xfd\x0b?)r:=C\x1fvf\xd1\x87\xad9\xf4\xe1e\xaa\xa4\xac\xd3_L\x9a4I\\4i\xd2g\xfe7\x9fb\xd6\xad\xc98\x07v\xe2\xa4N\xba\xa1\xca\x17\xaa\x02$\xce\x90\x93Q,\xb3\x82~\xbe\xb2\xb7C<\xe7\x16%\x9d\xcey\xf3\xb7\x1e\xe9,\x91u\x92\xac\xe9dg~\xdc\xa4\xc8\xe9\xc4\xfe\x97t\xda7\x97\x8a\xd6\xa1\x1c\x9d\xfeI\xf8?\x7f\xfd\x97T\xa7\xcf\xfd\xa7\xbf:\xc5\x9fT\xb6!~o\x14qR'\xfdP\xe5^\xc6\xa9\x9a^\xedOK\x8cc\x8cb\x99#\xc2t\xca\x92u*Z~\x9d\x16\x94\xcb:md\x8f\x07\xa4\xa3ShR\xd4\xd3\xe95\xe1\x89\xd7.\x16-\x1f\x92t\xfa\xbe\xfb\xef\xbf\xff?\xa8NS\xfe\x9f\xfb\x1c?\x9b2\x97\xf5\x881\x10-\x8e\xea\xa4\x1b\xaa,\x91`\xd7N\xfa\xb1\xcc\x91\xf3\x959\x9d\xf2\xcb\xe9\xf3\xa1Ge\x9d\xf2\xd8\x05S\xe1J\xf5\xa4\xa8\xa7\xd39!O\x10J\xd8\x08\x9e<P\xfe\xc3\xbfe:\x9d\xf2\xaed\x1fQm\x94\\\x14o\xcc\x8c\x10J\x0bF\x8a\x93:\xe9\x87*Snt\x9f\xaa\xe9\x8e\xf2\x17\x9f\xe3\x1e\x83Xf\xd1$_\xf9b\xce\xbe\xeb-\xee\x8b\xd7\xd7R\x1fv\x0a\xe5\x07v>*,\xde\xdbIm\x11\xbe\xder\xa0(\xe7e\x91\x9f\x1c\xea\xec\xec\x9c\xfflg\xe75\xf1\xb5N\xf7\xb3\x9dC\xbf|\xd6\xdd\xf9\x9axn~GG\xe7\xeb\xcc\x1d\xf9c\xdc\x7f\x90t\xea\xea\x98_t\xa0c\xa3\x20\xff&@\x05i7^\x060R\x9c\xd4\xc9\x20T\x99\x96\xb3K\xa7D9>\x19\xc42\x8b\x91\xf3\x95\x87\xe8\x81\xa5%[\xf0\xb4\x08\xc2jqh_Q\x96w\xed\x81\"\xf7Jv\x86\xf7L\xb6w\xedk\xacNh\xf2\x97\x82\xcc\x01\xf1;\xf4\xd1\xfd+\xf61\xd4w\xc4N7+s/\xbf(\xdfd\xf4\xf7\x7f\xf3\xdf$\x9d\x06_.\xf7f/\x97\xef\xf0kN\x0a\xfbM\x02`#\x8e\xea\x04\xac\x93\xb5SoR\x8f?x\x9e\xfd\xc3\xbb\xef^\xfb\xe5\xea\x9ck\xf2-\xb0\x7f\xf7W\xb2N\xa1*\xdb]\x05\xda\xcf\x96\x81\x9d@\xa78'z\x9d\x8e(?\xe89\xb4\xb8C\xd6\xe9G\xffE\xa3S_:n(\x1f]\xa0S\x9c\x13\xbdN\xe7\xd8\x109\xe5\xa2\xf0+\xe5\\0\xec\xe8\x04F\x19\xe8\x14\xd7\xbc.\x8d4h'\xf5\x19z\xc6\xf3lKG\xcb\xb3\x9e\xf5C\xd0i\x8c\x80Nq\x8d4\xd2\xf0\x9av\xd2\x80\xa1#Ox\xdd\xde'\x8e\x0c\x05\xbe\x8d\xfb\xb7\xd0\xc9a\xa0S\"\x02\x9d\xc6\x08\xe8\x94\x88@\xa71\x02:%0u\xd0\xc9a\xa0S\xc22\xb8a!\xbb\x05v\xc5!\xf8\xe4\x18\xd0)a\x99\xa3|A\xe3\xb3\xbb\xe0\x93S@\xa7De\x80}}\xf0C\xbf\xff\xde}\x0b\x13\xe5v\xc8\xf8\x07:%*\x03\x84df\xde\x97\x969{\xd2\xc3\xd0\xc9)\xa0S\xa22\xb8\xe7s\x9f\x9dt\x7fJJJf;\xc2V\x9c\x02:%,3\xa6\x97U\x1f\xba\xd0\xd5\xf5\x12\xbe/\xe8\x18\x8e\xea\xa4\x1b\xaa<P#}\xb5\xbd&\x91\xae\x97\x1bS\x83!\xd1}\xc5iI\xf3TA\x18\xdfQ2\xbdFF`\x16\x05\x84\x90\xa4.\x9d\x0a'\xc8~\x96\xb1\xa7\xc9\xd9\x03\xa3\x8a\x93:\xe9\x87*\xf7U\x1d\xef\xa6$\xd0w\xdb\xe9\xdaM\x0d\xc6\x05e\xa6m(HR]\xbc\xbc\xde\xe9\x8e|/kT\x04f\xd1\xdd\xdc\xbcA7\xeb\xab\x9d\xa8\xb3(\x80\x038\xa9\x93~\xa8r_U\xa4\xd8\x9f\xf1N\x1fY\x17\x1e\x11frk\xb8U\xdatu\xd2/\x05\xa3\x8a\x93:\xe9\x87*'\xb6N]D\xe7\xb7A\xa0S\xa2\xe2\xa8N\xa2^\xa8r\xc2\xe9\xd4\x97D\xc8d\xe9\xab\xfa\x83\xa9r\x9e\x9e\xf6\x0b\xe5Y\x1b\x9fQ\xd2\x91CpQ\xcb\xba\xa9\xcbb1qU-\x9d\x91\xf4\x10+\x0b\xcd\x82\xa1/N\x13tr\x1egu\xd2\x0bU\xee\xabj\xdcUU\xdb\x9c@\x83\xb9\xed\xcd\xcd\xae2i\xeax\xf3vR\xd6\xdc\xacM\x10d\x19*-K<\x17\xf92.jY?u\xf9L\xed\x14\x92ZV\x91,\x85\x8e\x85f!\xea\xea4\xd8\xdb4;EgDo\x92m\x84\xf7\x0d\x9c\xd6I/T\xb9\xbf\xaa\xf6D\xf7\x99\xba\xed\x09\xe4\x93(&\x05\xf6u\xfd\x93\xbd\xa2\xeb\xa2x\xbdh\xb9\xaa\x90\x8bZ\xd6O]\x16])\xd4\xcb\xdcT\xe5YR$\x9d\x16\xd2c\x9b\xde/\xd0Mz\xdb&\xa0\x93.\x0e\xeb$\x86\x85*\x8b\xe2)&\xd2@MB\x9d\x9a\x98\xe8$\xa5#\x07\xf2'e\xb8\xa8e\x83\xd4e\xd1\xc5~K\xa6\xcc\xa5<\x8b\xa8\xd3K\xfb\xabf\xea\x1e\x9d\xcc4\x89\x16\xe8\xa4\x8b\x93:\xe9\x86*\x07hN\xa8$z\x13\x9dT\xe9\xc8\x0a\\\xd4\xb2~\xea\xb2(\x9d\xdeE\xa7\x13\xa5Y\xaf\x14:\x8d.N\xea\xa4\x1f\xaa\xdc(\xefoM\x09\xf5\xeb\xb8&:\xa9\xd2\x91\xa3\xc6\x9aN\xba\xa5\xd0itqT'\xddP\xe5z\xe9\xb0\xd4W\x13\x0c\xb1L\x04Lt\xf2^g\xbf\xc6\xb42\xfc/\x11qR\xa7I\xd2\xbf\x08\x7f\x0f\xef\x1b8\xab\x93~\xa8rw\xd5\xfe\xae\x97\xda\xab\x13\xe7\xe04\xd8\xd6\xdc\xec*hn\xee\x0f\x8c\xec\x85\xed\xd6Y\xc2\x13\xbf\xe8X.\x05%GO7\xed\xb4Ml/p5w\xf3\xb3\x90\xee\x8aX\xd7\xdc\x1c\xf6iC\xbb\xdeOe0E\x9e\xfb\x82,\xca\xab_\xbb\x7f\xd2\x83\xdf3\xd2\x05:\xc5\x82\x93:\x19\x84*\xf76\xd6\xd6\xd4\x9f\x8a\xd4n|\xd1.\xa7*\x93*q\x20E\x9a\x98rFS\xa3hgy\x20(9z\x8aiO\xaeSS\xe9c17\x0b\xe9\x9e=F\xd8\xaf\x0c\xf4NYt\xa2W{\xbf\x1eS\xe4\x8b\xcf\xc9\xa2|\xf5\x8b\xcf\xbf\xfa\xfc\x97~f\xa0\x0bt\x8a\x05Gu\x02N\xb2g&!\xda_\xc6\x99\x14p\x85\xf2\xb9\x9f\xd3\x87\x9f\xffg\xc5\x8f\xef=8\xe9\xfeo\xbd\xfd\xf6\xf3_\xbe\xef\xbe/\xff:\xa8\xd3\xf7>?\xe9\xfe\xaf\xbd\xcdM<\xff\xe5\xcfMz\xf0\xbb\xd0\xc9\x00\xe8\x94\xc0\xf4\x9e\xd0~\x80\x1c\xae\xd3\xfd\x8aM\x9f\xfb\xde\xab\xcfS\xb5\x1e|\xee\xd5_\x7f\xe5+\x01\x9d\xfe\xf5\xc1\x7f\xa5\x07\xb0\xafq\x13\x0f~\xf3\xd7\xaf\xfe\xec\xcb\xd0\xc9\x00\xe84\xa1\xe0u\x92O\xf6\x94S\xba/|\xf7\xed\x20\xbf\xbe?\xa0\xd3\x17\x7f\xa6\x18\x17\x9c\xf8\xec\xcf\xe5:\xd0I\x17\xe84\xa1\xe0ubC\x11\xf7\x7fK\xd1i\xd2\xaf\xe5\xff\x7fNO\xe6&M\x0a\xe8t\x9f|C\x117\xf1\xd5\xfb\xbe\xf2\xad\x9fC'#\xa0\xd3\x84\x82\xd7I\xe2\xb9\xcf\xcb\xff\xdf\xa7\xe8\xf4\xc5\xaf\xfe\xfc\xedW\x83:MR\x8eE\xa1\x89\xb7\x7f\xf6\xb5/\xdf\xf7M\xe8d\x00t\x9aP\x84\xe9\xf4\xe5\xaf)\x1a)'{\x9f}\x95^(\x05u\xfa\xc27\x95j\xc1\x09\xc6\xf3\xf7A'\x03\xa0\xd3\x84\x82\xd7\xe9K?{\xf5\xf9\xaf|\xe1UY\x91\xe7\xeeW\x86\"\xbe\xf9\xea\xcf>\x1f\xd4\xe9\xb9\xfb\xbe\xf5\xebW\x9f\xfb\x127\xf1\xa5\xef\xbd\xfa\xea7\x1f\x84N\x06@\xa7\x09\x05\x13I\xb9\x0az\xfb\xbb\x9f\xff\xcc\xfd_}5p\xc8\xf9\xee\xe7\xa5\x81\xf2\x9f=8\xe9\xfeo\x06uz\xfb\xb9/~v\xd2\x17\x9f\xe3&\xbe\xf7\xc5I\xf7}\xf9y\xe8d\x00t\x9aPp\xa7y#\x03:\xe9\x02\x9d&\x14\xd0it\x81N\x13\x0a\xe84\xba@\xa7\x09\x05t\x1a]\xa0\xd3\x84\x02:\x8d.\xd0iB\xa1\x1f\xa3\x12\x0bfs\x9a\x988\xaa\x93n\xa82\xe5\xcc\xfe\x9a\xba\xf6\x08\xed\x80B\xc4\xb0f\xdb9A\xf6\x9bU\x01j\x9c\xd4I?TY\x1cl\xacj\xbb\xd0^\xa5\x97\xb3\x0d\xd4D\x0ck\xb6\x1d\xc42[\xc6I\x9d\xf4C\x95\xc5\xc6\xean\xa6\x9a\xf6;v\x20\x12\xbaa\xcd6\x83\x1cY\xcb8\xa9\x93~\xa8rw\x95\xf4\xad\xdcQ\xde5\x12\x0d\xdd\x0c\x0a\x9b\x81N\x96qT'Q/T\xb9\xad:\x91~\x8af\x141\x0bk\xbe\xe8\x16\x84\xad\xbfY[8\xff\x89w\xb9R\xd3\xb0f\xcao\xca\x17\xbb\xbdO\xbd.j@,\xb3e\x9c\xd5I/TyO\xfd\x99]U\xb5\x87\x12*\x04vt0\x09k\xbe\xde\xd2\x92_\x94\xed\xdd\xb8v.\x9fBa\x1a\xd6,\x8a\x1d\x9e\xaf\xef\xed\xd8)\xecS\x15\x1a\xc52\x83H8\xab\x93^\xa8r\x9d\x14\xaa\\[\x87cT\x14D\x0e\x1c\x13\x1f\x15J\xae\x89C\xea\xf8>\xd3\xb0\xe6\xeb\xf9O\xb0\x9c\xb2\x16u3\xa3Xf\x10\x09\x87u\x12\xc3C\x95\xeb\xa5\xf1\x88\xfe\xc4\x0aU\x1e-\xcct\xca\x0a\x8bG2\x0fk\xee\x10~\xa5m$\x1a\xc72\x83H8\xa9\x93~\xa8r\x93<\xf6\xdb\xb4\xc7\xa8\x19\x08a\xa6\xd3\xd7\xc3\xcbL\xc3\x9a\xf7\x09\xd7\xc3[I-q\xedd\x15'u\xd2\x0fUn\xaf\x91>\x8cl\xc4\x99E\x14\x98\xe9T\xaeShF\x87:+=\x04F\xf6,\xe3\xa8N\xba\xa1\xca}\xd2@y\x7f5n\x8b\x88\x82\xd1\xd0\xe9\x9aw\xe5\x10\xfdo\xe3F\xed\x1f\xa0\x93e\x9c\xd4I?TYl\xafn\xef>U\xbb\x0bC\x11&\x98\x855\xbf\xfb\x8b\xce\xa2\x95\x9d\x9d\x16\xc3e)\x1d\xf3\x8b\x0etl\x14\x0eh\xcbuc\x99A$\x9c\xd4\xc9\x20TY\xec\xdaS\xb3\xab\x1d6\x99a\x16\xd6|Q\x90x\xca\xa0y\x04^.\xf7f/?\x12V\xac\x1b\xcb\x0c\"\xe1\xa8N`|\xa1\x17\xcb\x0c\"\x01\x9d@\x04\xc2c\x99A$\xa0\x13\x00\xb6\x01\x9d\x00\xb0\x0d\xe8\x04\x80m@'\x00l\x03:\x01`\x1b\xd0\x09\x00\xdb\x80N\x00\xd8\x06t\x02\xc06\xa0\x13\x00\xb6\x01\x9d\x00\xb0\x0d\xe8\x04\x80m@'\x00l\xc3Q\x9d\xf4B\x95\x07k\xabdjM\x1a\x83\xf8\x80\x0bv\x06Z\x9c\xd4I7Ty\xa0\xaa\xbd\x9b\xd2^u\xc2\xac9\x183\x9a\xb8\xafJs\xc1\xce@\x8b\x93:\xe9\x87*\x9fbv\x0d\xd4\xe2\x8b\x9fq\xcc\xecEf5\x80\x84\x93:\xe9\x87*K\xd4\xe3\xbb\xed\xf1L\x06t\x8a\x0eGu\x12\xf5B\x95\x19R\x12\x0b\x18+\xba\x16\xa6\xba\xd2\xe6I_\x14\xdc\x90\x91\x94\xb1\x81\xfe_L\\UKg$=\xc4\xde\xfa\x94p\xbe\x0c\x91\x0fv\xe6*\x14\xb3\xa2\x0b.2S\xea\xacv\xce\xd4\x19K'j@\x9f\xb3:\xe9\x85*3\xa4$K0F4M\xcd\xa8h,#\x15t\xb2\xc0\xb5\xaa~\x95+\x97\xbe\xc1\xd5N!\xa9e\x15\xc9\x0bE\xb1\xffP\xf3\x8c\xcc\xe6\xe6f)\x99\"\x18\xec\xccU\xe8\x96\x8a\xda\x0a\\\xac\xbc`rq\xfd\xba\xd4\x8c\x09\x1a1\xe1\xacNz\xa1\xca\x94\x0bU\xf8\x0a\xf5\xd810m\xde\x00\xcbk\xa3W\xb3\xfb\xa5\x1fH\x93\x1f])\xf4E\xc9M\x95\xaa\xa8N\xf6\x02\xe1d\\\x05\xc9\xb02\xa6S=\xa9\x11Y\xa4\xd8vqB\xe2\xb0Nbx\xa82\xa5\xb1.r\x130\x9a\xd4\x93@\xa4\x94X0K\xfaof\x01}p\xb1\x07I\x11#\x9dB\x15B:-\x9a>\xc8H/\x10'$N\xea\xa4\x1f\xaaL\xa9Ad\xe5\x18\xb2\x8e\x04\x7f\xbdd\xce\x02\xe9\xbfy\xb3E^\x11#\x9dB\x15B\x93\x19\xca\x85\xd6\x04\x0d@rR'\xfdPe\x96\x03\xab\x8a\xa0\x07\xce\xd2\xc8\x1d\x9d\xa6K\xffM\x97\x8eNZ\x9d\xaa_\x92+\x19\xe9\xb4\x8aM\xe6No\x97\x98\xa0CK\x8e\xea\xa4\x1b\xaa\xcc.\x9dF\xf77^AD\xfa\xd33\xd9\x9b[\xf1Rv\xde\xc7\xaey\xb6K\x81\xcd\xbcN\x99\x99\xf4\xb2\x97(\xa7\xe4::%\xad\x10\xc5\x1b\xb3\xd9\xe4~\xb9\xd6\xe3k\xc4\x09\x89\x93:\x19\x84*S\xb7\xf0[icI\xd3\x0337\xd4\x17\x13v\xd2\x90;yE\xfd\x8a\xc9\xb9\xd2h]A\x9b\xd8^\xe0\x92~\x93\xad\xccU\xb1'sj7\x1f\xec\xac\xaa0'u\xcd\x9a9dJ\xcd\x19Q\\A\x16\xd5\xed)\x20\x13\xf4\x961'u2\x0aU\xbe\x80\xbbV\xc6\x96\xaeE\xe9\xc9\xb3\xe5\x17a\xdd\xac\xa4Y\xf2\xe7N\x84\xb8NM\xa5\x8f\xc5\xf4\xd9@qJR&\x8bD\x0f\x05;\xab*te&%?\xf4\xb8\\w\x7ffj\xca\x9c\xfdF\xb3Jp\x1c\xd5\x09\x80\xc4\x06:\x01`\x1b\xd0\x09\x00\xdb\x80N\x00\xd8\x06t\x02\xc06\xa0\x13\x00\xb6\x01\x9d\x00\xb0\x0d\xe8\x04\x80m@'\x00l\x03:\x01`\x1b\xd0\x09\x00\xdb\x80N\x00\xd8\x06t\x1a7,\"\xa9\xb9g\xcc*\x811\x05:\x8d\x1b\xba\x9bjf\xa6N\xd4\x88\xa0q\x82\xa3:\xe9\x85*\xb3\xa4\x9c]\xd5\xbb\x0ea?\x89\x82&\x82\xac\xdc\xb8\xc6I\x9dtC\x95\xc5\xbe\xda]R)\xbe\x91kN\x1bi3\xab\x02\xc6\x12'u\xd2\x0fUn\xaec_\xad\x1e\xack6i\x0d\xa0S\xdc\xe3\xa4N\xfa\xa1\xca\x8dr8\xd8\x1e$W\x9a\x03\x9d\xe2\x1cGu\x12\xf5B\x95\xfbj\x9b\xfa\x06\xfb\x9aq\xb2\x17\x05'H\x93Y\x150\x968\xab\x93n\xa8\xf2@#-\xdd\x8f\xf4\x95(\x18L\x9d}\xa8{\x82\xe6\x15\x8f\x0b\x9c\xd5I/Ty\xb0\xb1\x8e\xe5\x1b5\xe2'4\xa2`?!d\x81Y%0f8\xac\x93\x18\x1e\xaa\xdc\\7\x20\x95\xe2\x07\x9e\xcc\xe9O\x9d\xbe\xae\x09\x19\x9f\xf1\x8b\x93:\xe9\x87*W\xcb\xf9`R6,\x88L\x1b\xc1\x80M\\\xe3\xa4N\xfa\xa1\xca\xd0)z0\xb2\x17\xe78\xaa\x93n\xa8r\x93t\xb27P\x871+s\xa0S\x9c\xe3\xa4N\xfa\xa1\xca\x03\xdb\xebNu\x9f\xaa\xdb\x8e\xa1=s\xa0S\x9c\xe3\xa4N\x06\xa1\xca\x83m\xbbjv\xb5a`\xcf\x8c\xc1\xee\xf6E.\xfc\xae\\\\\xe3\xa8N`$,$d\xda\x1e\xb3J`L\x81N\xe3\x86\xee\xe384\xc5;\xd0\x09\x00\xdb\x80N\x00\xd8\x06t\x02\xc06\xa0\x13\x00\xb6\x01\x9d\x00\xb0\x0d\xe8\x04\x80m@'\x00l\x03:\x01`\x1b\xd0\x09\x00\xdb\x80N@\xcd\xe0\xc0\xc0\xc0`\xf0?`\x09\xe8\x04T\x1c\x9aL\x08I\xea\xefO\xa2\xffMFX\x9bE\xa0\x13\x90\xd83\xa7W\xfa\xbf\x8e\xd4\xb7\xb5\x9d\x12\xc5Smm\xf5\xa4\xce\xa4\xd1X\xd37'\xce\xee\x09vT'\xfdPen\x12\x18\xf3\x1dAh1\xab\x13\x99\x0dd\xf2v\xa3\xbfU\x902y\xa2\x8e\x04o\xb4\xed\x96u*&\x93G[\xab\x98\xd7\xadb\xf2\x1a\xb3*\x8e\xe2\xa4N\xfa\xa1\xca\xfc$0\xe6\xf5N\xf7N\xb3:\x91\xe9k\x9e]f\xf0\xa7u\x93k\x94\xa9p\x9d\xba\x9b]\xfa\xcd\x9al{\xcd\x0c\xd7\xad\xe3\x9cnq\x88\xfa)\xebLj8\x8a\x93:\xe9\x87*s\x93\x20\"Y#\xd4I\x14\x17\x18\xe8t\xc1\xb5\"0\x19\xae\x93(\x1a\xe84{\x91nqL\x18\xac\xdb\xf2r\xddb\x8eUq\xf5\x8dJ'u\xd2\x0fU\xe6&ADFO\xa7\x82\xf4\xe0\x9b\x99\x05\x9d2F_\xa7GMu\xeaO/0\xab\xe2\x20\x8e\xea$\xea\x85*s\x93\x20\"Y\x1b\x9f\xf1\xe6<\xf5\x1bUYw2\x91I\xe6\xde\xa4\x07\xd3\\\x8f\xa7\xa77\x15\xa7d\xb2\x84\xd0E\xe9\xaei\x0b\xe4\x1f\xb2\xa1:\x15\xd3\xba\x0f\xb0Q\x87\xda9Sg,\x95-\x1aL\x0e\x1e\x9c\xf4u*~8=eA\x17}\xeb\xa3\x8d\x1f\x17\xcb\xe8c]\xbd2\xdf\x0c\xa9\x06\xd7Y8\xef\xe6\xb9\xb7z\xbd\x1d\x1bsV^\x13\xc5k\xe5^w\xfeS\x17i\xf1E\xb7\x20<5\xe4\x15\x84\x9c\xeb\xdc\xba\x1d\x11\x04a\xab\xb8\x93>\xb6\xb0I\xc6\xd7Y'C?]\xee)\xdax\x9dMv>\x91\xef^\xfcT\xfe\x90\xdc\xfb\xaa\xa48J\x19qV'\xbdPe>_\x19D\"K\xf8zK\xcb\x12\xcfE\xbel\xb0f\x9dL\x0d\xff!QS2Y\x9aIR\xd7\xa4U0\x05\x1en\xaa[0Y\xcal\xa1:u\xe7N^\xc7\xa6\x0b&\x17\xd7\xafK\xcd\x90\x12\x9a\xdb\xb9\xe8s]\x9dH\xc6\xf6\xed\x19I\xa7\xc4\xfeC\xd3\x8a{\xc5\xde\xb2\xe4\xe6\xfe\xfeC\xcd32\x9b\x9b\x9b\xa51$\xbe3\x1d:\xb2\x85\x8d%\xc2\xe2\x9dy\xfb\x98.\xeb;Z\x9e\x12\xe85\xd1\xd0/v\x0a\xbf\x10\x7f:\xf7\xa7\xbf\x12\xb9u\xbb\xde\x99\xff\xec\x1f\xc4?\xfc\x20\xbb\xf3\xda\xb5\xce\xce\xa2\x95\x9d\x9d\x9d/\xb3>\x9e\x116v\xec\xf3>J\x1d\xfa\xe5\xdcgZ:\x0e,\x16\xde\x95;?\x14Oq4\xce\xea\xa4\x17\xaa\xccM\x82\x88d\x15\xd17\xe7\xebE\xcbU\x85\xbd\x17dzU\xa5\xe9\xb9T\xa3z\xb1\xe0a\x16\xb9\xc6\xde\xbff-d\xc5T\xa7\x0a\x97t\x1aPO\xd8\xd8C\x1b\xd9.?\x09%\xcb\xea\xea4\x93\x1e\x00\x06f\xcc\xa1\x93e\xechT\x20\x9f_\x05O\xf6T\x9d\xe9\xe1]+v\x08G\xc4g\xd6\xd3\xe5o\xa1\x87\xa8\xa1%\xabY\xf1\xd03\xcb\xafy\xe5\xb3<n\xddv\xb2\xa3\xd13\xcfH\xc5\xc1\x93\xbd#\xc2\x01\xfax\x8e\x0d\x00\x1e\xf02\x91\xf6\xe5(G\xa7n\x12Go\xc4\x0e\xeb$\x86\x87*\xf3\x93\x20\x12Y[\xd9\xe3\x01\xe1\x1aWv\x81\x04Pe-\xa7o\xa7{w\xbf\xb8\x82\xed\xf0\xbd\x1b\xe6MO&\xb3X\xf1\x82\xb2UD>\xa9^4}\x90!_x\xec1\xd3\xe9q\xf6\xb8\x81\xf4\xb1\xb2S\xe2@\xb2\x9cE\x1b\xd4I\xd5\x99\x1e\xde\x16j\xc25q\xebZ:}\xed\xc0\x13\x85\xd9\xc2\x12\xa9\xfc\xfa\xa3yO\xc9Vp\xeb\xf6\xfa\xdc_\x89\xd7\xb3;\xa4\xe2\xa0Nk\x0b\x87\xde\xa5x\xa9e\xafy\x0b7\x1e\xf8\xd5\x90b\x13]\"C\x8b\x9d\xc7I\x9d\xf4C\x95\xd5\x93\xc0\x18\xf9r\xbdSP\x0d\x1e7\xd7\xcb\xa8o`H\xdf/\xb6\xb9DI\xa7\xb6\xb4i+v5g\xca:\xa5%\xcf\x94\x0eSb\x86\"\xa1\xf4\x8c\x8f\xef3\x1e\x8ah&l\xb4h\xdeRq\x7f\x8a|b\x19\xd4I\xd5\x99\x1e\xde\x0e\xf1\x9c[\x94t:\xe7\xcd\xdfz\xa4\xb3D\xd6Il\x11:\xe5\x09~\xdd\x9e\xf8\x17\xb1C9\xf6\x04uZ\xa2\\G=%2!\xcb\x8b\x84\xbc\xbd\x8aOm$\x8e\xf6\x1c'u\xd2\x0fU\xe6'A$\xb26\xb2G\xf5\xd1\xc9\x00N\xa7\x99s\xd8\xdb\xd8BY\xa7\xd4S\x17\x92+\xd8T\xee\xf4v\x09\xe9\x1c\xb1\x9f\x1b\xba\xd3\xd5i){\xac&\xec\xac\xb1>uP9\xd7\x93u\xaa~I\xd3\x99\x1eL\xa7,Y\xa7\xa2\xe5l8\xa1\\\xd6\xe9u\xef\xd6|\xf9\x12\x88_\xb7#9C\xca\xb9\x9e\xac\xd3\x81\xd7\xd8\xd1\xe9\x9c\x04\xfd\xfb\xb9\xadT\xa4k-\xf3\x0f\xc8U\xca\\q\xf4\x11\x8b\xa3:\xe9\x86*s\x93\x20\"Y^\xba'\x0e\x15\xad4\xab'\xaat\x9a\xc6\x8e\x1972\x94\x93=Q\xdc\xefb\x87\xa2\xfd\xb2*\x8f\xcb7\x15,\x9a\x1e\x1cE\xd0\xd5)\x9d\x1a983\x93M\x0f\xa6\xd6\xa7(\xafU&-\xe8eu\xd4\x9d](;.j\xe0t\xcag\x82\x0c=*\xe94\xb4\xfc\x07\xe2\xb3%\xd2Q\x86_\xb7\xa1\xc5Gr\xe4s=q%-\xf8\x03\xbb`\xea\x90o\x9b\xd8J\x0fb;\x05\xe9\x8f+\x9f\x95j\xdc\x989O;\xb71\xc4I\x9d\xf4C\x95\xb9I\x10\x91,\xe1\x89_t,\xcfy\xd9\xac\x1e}\x8bJ\xa9\xe8\xdf\xee:\xd1\x9f\x9b\xd9-\x96\x91E\x1b\xd6d\x90\xd4\x8a\xb6\xfe\xe6\xd9\x05m\x83\xfd\xf3\xd2\x9b\xe8\x1b\xd8\x0a\xb2\xa8nO\x01\xa9\x95\xeawM\xae\x084\xd5\x1f\xd9\x9b\xd7\xd64'E\xbe\xc0Z1]9\xd7\xa3\xc7\x85\x8a=\x99SYuUg\x0b\xc8T\xcd/I^\xcc\xd9w\xbd\xc5}\xf1\xfa\xda\x92\xd7\xa9\x0c\xe5\x07v>*,\xde\xdby\xbd\xf3;\xde\xd7\xc5\xdfdo\xec\xbc\xaeY\xb7\xad\x859\xca\xb0\xdd\xce\xac}GJ<\xaf\xb1\xb2\xb9k[\x8e<\xc3\xa4\xda)x~p\x84N\xca\xc6U\x90x\xfa\xc0\xd2I\x9d\x0cB\x95\xb9I\x10\x89\xa2\x9d\xe5\xd9\xde\xb5\xaf\x99U\xa3\xef\xd8i\x84\xd4&\x93\xa9u\xecr\xe6F\xc5\x0cW\xea\xa2\x9a\xe9\xae\xcc\x0d\xec&\xf1\xf6Z\xfa\xb8\x8aV\xda\x9f\x99\x9a2g\xbf\xd2b\xd5\x03\x81\x0b\x90:\xd2\x15\xf8\x82FW@\xa7\x99e\x8b\x92\xd3s\x15\xcd\xba\xc8\xc3J\xd5\x81\xe2\x94\xa4L\xf9\xaa\x8b\xef\xacz\xaaf\xe8z(O\x10Z\xb2\x05O\x8b\x20\xac\x16\x87\xf6\x15ey\xd7\x1e(r\xaf\xec\xa0\x97B\x1b\xc5\x8d\x820\xb7C\xb3n/\x0b\xeb\x95\xa6\xd77\xe6\xcc_)_+v\x94,\xceY\xce\x14\xfa\xe9\xf2\x9d\xf9n\xefJ\xd9\xa6\xe6\xa4\xa5b\x1c\xe1\xa8N\x20nY\xeaR\xd4\x91>\x9eU\xbe\xa0A\xf4>Z\xefw\x99\x9d\x97\xef!#\xbc\x9a\xb9\x96\xd5aVEa\xbb\xab\x20\xae\xbe\x94\x05\x9d\x80DE\xba<\x900x\xa2-\xf0\x05\x8d\xb6\x13z\xbbj]\xaa\xc9\x0e\\\x93\xf2\xb082Z\x16\xbfkVE\xa6/=\xben(\x87N\xc0\x0aeMb\xa6\xd1]\xe9\x0a\xdd\xa9KGt\x1d\xbc\xb3c\xa8\xe4\x07f\x95\xe2\x15\xe8\x04\xa2g\x80d\x14\xa7k\xc6\x19l\xe6\xba\xf0\xf5\x7f\xf6F\xf1Q@|\x02\x9d\x80\x05\xca\xa6f\x8e\xf6\x17=\x7f\xe0)y\xd9\xacN\xdc\x02\x9d\x00\xb0\x0d\xe8\x04\x80m@'\x00l\x03:\x01`\x1b\xd0\x09\x00\xdb\x80N\x00\xd8\x06t\x02\x8e\x131\xb0y\\\xa79C'\xe04Jn\xb3\xfe\x1f\xc7w\x9a3t\x02#!\x90\xc5l\x85@n\xb3>\\\x9as\xdce&\x9b\xe2\xb4N\xa7\xaa\x94\xbb\x94\xbb\xf7\xd4\xec\xef\x0e\x9b\x04\xd1\x10s\x06\xb1\xedHY\xccV\x17\x87\xfbJ\x15G\x87\xf7H`2\xf8E\xabx\xcbL6\xc5a\x9d\xfa\xaa\x0f\xc9\xdf1\xeb\xaen\xeej\xae\xee\xd6L\x82\xa8\x88=\x83\xd8f\xe4,f\xabq\xcf\xfa:\x1d\xf1\x04\x9d\x0c\xea\x14o\x99\xc9\xa68\xacS}c\xb7\xa4\xd3\x0d)i\xe5P\xed\x0d\xd5$\x88\x96\x983\x88m%\x98\xc5l-\x9fV_'q(8\x15\xd2)\xce2\x93MqV\xa7S5\x03\xb2Ng\xaa\xd9M\xfc\x03\xd5gT\x93\x20Zb\xce\x20\xb6\x95`\x16\xb3-:\x85\xe0t\x8a\xaf\xccdS\x1c\xd5\xa9\xbf\xbaK\x94uj\x94\xbf\x08]\xdf\xa4\x9a\x04\x11\xf8g\xc1}`c\x91\xe7\x89\xd7\xd9\x93\xd83\x88et\xb3\x98\x19]\x0bS]i\xf3\xa4\xc2\x0d\x19I\x19\x1bD\xf6\x834S\xd6\x85\x87*\x8b|\x16\xb3^\xdc\xb3\x0a\xd52p:\xd5\xa4\xc9)q\xd7<\x02w\x01\xc6\xe9\x14_\x99\xc9\xa68\xaaS}\xa3\xa8\xe8\xb4K\x8e&8\xb4G5\x09\"p\xb1\xc5-xw\xee\xcd\x96\xf2Sc\xcf\x20\x96\xd1\xcfb\x16\xc5\xa6\xa9\x19\x15\x8de\xa4\x82N\x16\xb8V\xd5\xafr\xe5\xd2\xd3\x87\xda)d\xda\x9a\xb2i\xeaPe\x91\xcfb\xd6\x8b{\xe6Q/\x03\xa7\xd3B\xa2\xe4\x10\x9d\xeb\xec\x9c\x1f<\xc4\xf1:\xc5Uf\xb2)\x9fy\x1c\x00`\x13#8:\x01\x00\xd4@'\x00l\x03:\x01`\x1b\xd0\x09\x00\xdb\x80N\x00\xd8\x06t\x02\xc06\xa0\x13\x00\xb6\x01\x9d\x00\xb0\x0d\xe8\x04\x80m@'\x00l\x03:\x01`\x1b\xd0\x09\x00\xdb\x80N\x00\xd8\x06t\x02\xc06\xa0\x13\x00\xb6\x01\x9d\x00\xb0\x0d\xe8\x04\x80m@'\x00l\x03:\x01`\x1b\xd0\x09\x00\xdb\x80N\x00\xd8\x06t\x02\xc06\xa0\x13\x00\xb6\x01\x9d\x00\xb0\x0d\xe8\x04\x80m@'\x00l\x03:\x01`\x1b\xd0\x09\x00\xdb\x80N\x00\xd8\x06t\x02\xc06\xa0\x13\x00\xb6\x01\x9d\x00\xb0\x0d\xe8\x04\x80m\x98\xeb4\xb0f\xf6TW\xfa\xc2C\xe1\x7f\xd9@\x1eV\x1eF\xc0\xbd\x8ai\xae\xf4\xc6\xe0\xd3\x81{\xf4\xa1\x96,\x0aU\xa8#\x0b\xc3\x1a\xf1\xac#\xc5\x11\xffn\x82a\xf7\x9a\x053AZn\xb3E5\xc1\x96\xed\x09\xc6\x12S\x9dj\\\xca\x8f\x12\xcf\xfbP\xfb'[^\xfe\x0a\xd6w{\xe0\xd9\x86$6\x97\xb8\xd0I\xbd`&\xc8\xcbm\xb6\xa8&\xd8\xb2=\xc1Xb\xa6\xd3RB\x16\x9e\x10?\xe9]\xe5\"\x99\xf74\x7f\xb3\xe5\xe5\x9fE\x96~r/\xd0\xf3=B\xc2t\xea\xde\xd0\xac\xd7.\xc8\x08u2\xec^\xb5`&(\xcbm\xb6\xa8&\xd8\xb2=\xc1Xb\xa2S\x13!\xdb\xe5\xa9\xe3\x84\xd4h\xfeh\xcb\xcb\x9fN\xbaBO\xf4t2c\x84:\x19\xa2Z0\x13\x94\xe5\x1e!\xb6lO0\x96D\xd6\xe9^Zh_-&\xd35\x7f\xb5\xe5\xe5O#\xdd\xa1'\xf1\xa4\x93j\xc1L\x80N@\"\xb2Nm\x84\x0c\x04\xa6\xfb6tIE\x8b\xd2]I3W\x89~\xf5\xcb?\xb0t\xba+e\xe1q\xb9*\xff\xa4\xbfx\x9a+yN\x8d\xea\xac\xa9\x97\x96\xa5,d\xd7%s\xa4\xcb\xb2\x80\x0f+\xa4g\xddL'qi\xbakzY\xe8\x82D\xaf\x9bS\x0bR\xa7\xce\xbb\xc0t\xaa%\xb3\xe4\xa2\x0a\xe9\xc9\xba\x0fX\xebU\x1fHE\xdc\x02\xd7\x92\x0d\x83\xc5i\x0fd4\xd2\xc9Y\xae\xb4\x9b\xacD(\x00\x00\x1e\xe5IDAT\xa5\xdc\xf5N\xff\x0a\xb6\xc8g\x82}\x07\x16,\xb0wK\xd2jz\x0e\xb6\x09,\xb7\xd2Wh\xf5\xb4-\x14\xf8\x99\xe9o\xcfYd\x85\x1f\x8c?\"\xeb\xb4\x8a\xcc\xd0\x94\x14\x13\x92\x9e\x91F\x1f>P\xbd\xfc\xc7\xa7\x12\xd7\xac\xe9\x84\xac\xf1k\x9e\xf4&\x93\x94\xd93\x08y\x88\xeb\xa3\xdeE\xa6\xb2>\xe8\x1e\xb3&\xd7E\xe6\xe5\xd6)\x7f\xd8\xb3\x88^\xa9\xe5\x0e0;\xd2HZ\x0a!\xb3\xee)\xfb\xbb^7U\x84\xa4\xcdrM\xc9\xa4;\xfa\x07.\xd2+\x95\xcd\x20gh\xeb\xa5\xe9$\x99\xb6\x9e\xf9\x89f\x81kIq*Is\x11\xd2XL\x1e\xa0e\x99\xfe\x80N\x87\x92\xc8\xd4\xd9\xb4$x\xed\x13X0\x8dN|\xcf\xa16\x81\xe5\x96\xfb\xe2VO\xd3B\x81\x9f\x99\xc1\xf6\x84N\xe3\x93\xc8:-\x20\x05\xea\x826\x92\xc4\x0e:\xa7\xa6\x92\x0d\xfc\xcb?\x90LV\xd0\xfd\xe5T\x0ai\xd4<YHVQ%^J&\x87\x82}\xbc4\x99\xac\xa1e\xfb]\xa4\xd6ot\xb2G\xa6w\xd1\x99\xb9\xc8\x1ee\x7f\xd7\xeb\x86\x10\xaa\xe1\x07\xf3\xa4\x83\xdbBY\xe4\x97\xc84\xa9\xf5\x0c\xfa\xce\xdf\xec\"\xbb4\x0bL\xff2\xb3\xd7\x7f/\x97\xb8\x1e\xa8\xbf\xc7\xae\x0b/(\xdd\x0fNe\xfd\xdf\xab\x20\x0f\x84\x8e\"\xf2\x82it\xe2z\xe6\xdb(\xcb-\xf5\xa5Z=U\x0b\x05\xbe\xa1\xc1\xf6\xf4\xd7W\x9d\xf2\x83\xf1Gd\x9d\xe6h\xdf$W\x902\xe5\xffb\xfe\xe5_A\x16H\xc5\xfb\xd9\xd1L\xf5d\x9alKun[\xb0\x8f\x85\xca\x1eZGR\xef\x19\xea$\x1dl\x8a\xd9\\\xa4}T\xa7\x9b\\y\xd9>Le\x95\x9a\xe5\x0b\xbb\xc7\x99U\xb4u_\xb0\xb5j\x81\x95~{\x09\xa9`e\xb3\xd9>.u_F\xe6I\xb52I\xe0@i\xa4S\xa8g\xbe\x0d\xaf\x93j\xf5T-\x14\xf8\x86\x06\xdb\x13\x8cS\"\xeb\xf4\x10Y\xaa)Q\x86\x8e+\xd8a+\xf4\xf2O#MR\xf1'\x93I\xbf\xfa\xc9Cd\xce\x19\xf5h\xf3\xbd$e\xc8\xec\xdeTzff\xa0\x93|)T\xcd\x86$\xa4}4\xbc\x1b\x7f2;\xb6\xf8\x95=\xf1^\x0a\xeb\xf4^\x1a\xdbyk\xc9\xccPk\xd5\x02\xd7\xca\xe7\xae\x9f\x10y\x9e\xb9\xa4Z\xe9~\xa6\xb2\xc8\x83\x83\xa1\xfeuu\xe2z\xe6\xdbp:\xa9WO\xbd,2\xaa\x99\xe9oO0N\x89\xac\xd3\xc3$W[t\xef\xcc\xae\xb2E\xd3\x88\xea\xe5\xff\x90^\xe5\xcc\x91p\x91v\xd5\x13\xff\x19z\xa5\x92\x9c\xbb\x87\xbb\x12\x1f\x20D\xb9\x8e\x98\xc3\xce\xe5\xf4u\x92\x8fo{\xd8\xff\xd2\xfe\x1e\xde\xcd\x07\x81n\xf6Ho\xfcK\xd9\xb1\xaa\x9d\xcc\xe6Z+\x03\x03\xdc\x02\xd7\xca\xc7\x05:\x17v\xe5\xef/\x20UJ\xad\xc0\xa5\x17\x8f\xaeN\\\xcf|\x1bN'\xf5\xeai\x96EB=3\xbd\xed\x09\xc6+\x91u\xaa\xe0\x87\"\x8e\xb3=\xa6:\x8d\x0dbe\xccQ\xbd\xfc\"\x09\xd1\xa8zB\xcf\xac\x0a\x92\xe8\xc4\x94\xe2\xe0\xa5x7\x99\xa2L-`\x9fdE\x1a(\x0f\xe9\x14\xdeM?!\xf2D\xb3\xa4S\x17I\xbbG\xed\xaf\xe5Z\xcb\x0d\xf9\x05V\xfe\x12\xa6\x13=\\q\x87%\x05]\x9dB=\xab\xdap:\xa9WO\xbd,\x12\xea\x99\xe9nO0^\x89\xacS7\xf7\xd2\xf7\x93\xc9]\xec\xce\x9b\xe2\xfa\x0b\x1fjNN>T\xae\x10$TO\x18\xf7N\xad\x99EB;I\x7f4G\xa70\x9d\xc2\xba\xf9\x20\xf0Q\xcf~\xf9\xb2d\x069q/i\xca\x07~\x8dN\xaa\x056\xd2\xc9\xaf]d\x86Z\xa75Z\x9dTm8\x9d\xfa5G\xa70\x9dT\x0d\xf5\xb7'\x18\xaf\x98\xdc\x151-t\xf1\xb4\x82^Z\xd3\xeb\x01\xf9Z}\xa9\xfa\xe5Oe\xc7!\xc6\xa9\xfe{\xea'\x03\xf2\xa7+\xb5dJ\xf0F\xa2\x07\xa2\xb8v\xd2\xea\x14\xde\x8d?E\xe9f\x8d\xacS\x05Y\xd1.\xef\xb3\xfc.\xac^`C\x9df(#\xe4M\x0fU\x07\xbaW\x16\xacJ\xe9\xec\xe10\x9d\xf86\xfc\xb5\x93j\xf5\xf4t\xe2\x1a\x1amO0N1\xd1i\x17!\xf5\xf2\xd4!B6\xb0\xb3:\xe9\xbc\xff\xc3tvQ\x15z\xf9\x8b\xc9li7o#\xae\x0fTODe\xcf\xed#\x93\x83\x1e\x04\x86\xbev\x91\xe4O\xa2\xd4I\xaf\x9bb\xb9\x9b{\xd3e\x9d\x06\xc8\xb4\x15\xf25>\xbf\x0b\xab\x17\xd8P\xa7\x15\xca\xde\xbe@\x1e\xf2\x93\x90\x17\xac^>\xdd\xfd0-L'\xbe\x8d\xde\xc8\x9e\xb4zz:q\x0d\x8d\xb6'\x18\xa7\x98\xdd\x02\xbb\x90^$\x9f\xf9\xf0^\xef\xe3\x84\xcc\xb9\xe7\xbf\x97,\x8dN\xdf\x98G\xd8\x8e\x1ez\xf9\xfb\\\xa4\x98\xeeO])\xec`\xa6z\xf2\x10y\x88\xee\xba\x1f.R\x06\x87\x19\x17\xa6\x90\x0a*ES\x12\x1bX\xd3\xdc\xcb\xe3\"\x8d\x9f\xdc\xd39:\xe9t\xd3\xef\"\x1b\xee\xf9?\xcc\x0d\xdcT\x91I\x92\x92\xa5\xd3,\xd5\xd1I\xb5\xc0\x86:\xf5'\xb1E\xba\xb7\x8eL\x15\x83\xfd\xcb\x0bF\xcf\xdd\xe8_\xfa3I\x98N\xaa6\xf2rK\xe5\xaa\xd5\xd3\xd3\x89kh\xb4=\xf1\xb9\xd38\xc5L\xa7O\x0a\x02\xc3\x0a\x0b\xa4\x91\x08B\xa6-\x98=9\xa5\x8c\x0des/\x7f\x93\x8b<0{:\x91\xef:\xe7\x9f\x0c\xa4\x10\xd7\xccYI$\xad?\xd4g\xfd\x14\x92<;\x9d\xc8\xe7\x91j\x9df\x13\xf6\xa5\x08\x9d\x93=\x9dn\x1a]$mv\x12Y\xa8\xe8\xb4'\xe0\x95j\x17V-\xb0\xa1N\xecs\xd6\x94\xd9t\x1e\xdc\x1d\xe1\xca\x82\x15\x13\x92\x9cN\x92*\xc2tR\xb5\x91\x97[.\xe7WOO'\xbe\xa1\xd1\xf6\xc4]\x11\xe3\x133\x9d\xfc\xfe3\xc53\x1ep\xa5/R>?m\xcbLu\xcd,\xfb\xe0C\x17\x19T\x9d\x9c\xf4/\x9d\xeeJ\x9a]-\x9f\x8a\xf1O\x06\x96\xcep=@[\xf0]v?\x9c\xeeJ\x97oj\xd3\xe8\xd4\x97\xf9@\xf2~\xbd\xa1\x08\xddnr\xd3\x92\xe6\xb45*\x16\xd1E\x0a\\`\xf1\xbb0\xbf\xc0\xc6:\xf9\xfb\x8a\xd3\xa7\xa4\xe6\xf2\x8b\xa2,\xd8\xbd\xdaY\xae\x94\xdc\xbe\x13\xe1:\xf1m\xe4\xe5V\xca\xb9\xd5\xd3\xd5\x89oh\xb0=\xa1\xd3\xf8\xc4\\\xa7\xf1\xc2\x00\xbb\xc1\x08\x80\xb1$qt\xaa\xe0\x86\x11\x00\x18\x13\x12D\xa7\xbe\xc1\xc6\x07\\\xa1a\x04\x00\xc6\x84\x04\xd1)\x97\x10\x1c\x9c\xc0\x98\x93\x20:U'\xa5\xc1&0\xe6$\x88N\x00\xc4\x03\xd0\x09\x00\xdb\x80N\x00\xd8\x06t\x02\xc06\xa0\x13\x00\xb6\x01\x9d\x00\xb0\x0d\xe8\x04\x80m@'\x00l\x03:\x01`\x1bv\xebt\xb6\xe4\xb6Y\x95h\xb8Sr\xd6\xac\x0a\x00q\x87\xa9N\x1fm\xc9\xf3\x94\xfb\xccj\x058(4D]7\x12\xbe\x83\xc2A\xb3:V\xb8\xec=oV\xc5\x84\x91\xf7\x10-\x97\xe6\x0a2^k\x9br3m\xe2yOS\xd8\x20\xb8\xd9\x1b\xd3\x8b\x1e\xa1A\xaf\x0d\xb0\x17S\x9dJ\xbdG7y>6\xab\xa5\xd0*\xbc`V%Z^t\xb7\x9aU\xb1\xc0\xf9\xec\xa8\x8fvW~\xaf[l\xa1\x07\x0e\x83\xce\"V\x18~\xc5\xbd\xb9\x87\xb2[\xb0v\xa0\xbf\xd9\xd3sX\xf8\x9d\xa6\xf0N\xa5\xa7\x94\xfeW\xea\xa9\xbc\xa3\xd7\x06\xd8\x8b\x99Nw\x84V\xbf\xef\xaeI\xa5\x00\xef\xbbw\x9bU\x89\x9e\xbdY7\xcd\xaaX\x20\xfa7\xfa\x95\xeb\xf5\xcb\xa3\xef\x81\xc3\xa8\xb3\xc8\x15\xb2\xa4#\xc9\x0b\x82e\x03\xae\x86\xe9\xe4\xdf]N\xdf\x0c\xefx\xcam|e\x80!f:\xbd'X8\xc7\xd9\xec\x1d6\xab\x12=\xc3\xde\xcdfUF\x85\xc7\xcc\x0c\xb0\x82ig\xba\x15d\x9d\xde\xdba\xd9`=\x9d*KO\xfb_X]\x09\x9d\x9c\x20\xa2N\xbe\xc5\xf2I\xfc\x0e\xbf\x7f\xab@\xcf\xe3\xdew\x0bK\xd8\xa4\xfb\xd8\x8e%\x9e\xd5\xf2\xc1\xe3\xbdo\xe7\xb9\xbd\xe5\xd2\xb4/[~\xcd\xder\x0b\xc2\xde\xf7*\x0b=\xab?\xe5:\xe3J\x8d:\xf3\xbdP\x92\xbddG@\xc9\xbd\x1e\x8d\x9c\x96{P\xf8\xd8C\xd7\xe1$\x9bR/z\x88\x9e\xd5\x85\xee\xbc\xf2B\x9f\xff\xbcr\xd9\xb2\\\xbd\x16\x06=\xdc\xa9\\\\\xb8c\xc7\xe2\xec\xd3\\\x0f\x1c\\g\xaa%\xd3\x9b\x9b\x8a\xac\xc0u\xce\x1f\xe92\x94\xfb\x0a\x05!\xc7\xe7\xbf\xbb>\xdf]X\xfe\x96\xdf\xffi\x9e\xfb\xc7\x85\xf9W\xb6\xe5\x94\xdee\x8b\xd3\xba%?\xa7\\\xb9d\x0a\xea\x14\x9a\xdb\xee\xca\xc3O\xfb\xcb\x8f2\x9d\x82=h\x9b\x01\xbb\x88|tz\xb3\xe7\xa4\xd0\xd0\xd3s\x8b\x9d\x98\xbb\x1b\xfc\xbe\xab\x9b\xb3\xe8k|\xd2-x\x1b\x0e\xe6<\xcdj\\\xc9^~\xf0r\x83\x20]\xe7\xbc)\xf4H\xad\x86O\x9f,,\xca.\xdcQ9\xf7\xcf\\_\\\xa9Qg\x9b\x85m/\xb6z\x1fS\xf6\xc9W\x84\xdf\xfaUX\xee!\xc0\xd5\x9e\x9e\xf9\xd2\x1e\xaa\xaa\x1b\xe2\x8d\xb9\x9b\xce^>\x96'|J\xaf[z\x8a\x9e\xa4\xd7-\xff\xaeY\x0b\xdd\x1e|\x8fz\x0f7\xcc\xf7\x1c[\xdd\xca\xf5\xc0\xc1u\xc6/\x99\xee\xdcTd\xed\x1d\x1e^\xbf\x83\xcd\xe1\xb7\x0dt#\xbc0\xf7\x857\x99\x9c[\xae\x9c.\x17\xae\xd2m\x9e#\xec(\x15\x16\x1f\xcck\x95\x16\xa7\xb0\xa1\xa1\xd0\xf3\x8e\xd40\xa8Shn\xbb+of\x7f4\xff\x16\xd3)\xd4\x83\xa6\x19\xb0\x8b\xe8O\xf6\xa4\xb7\xcc\x86,i2\x87\xbe9Wz\xe9\xd4p\xe1j\xfa\x92\xf9NKg\xf9/\x0a\x7f\x0a4{L\xa0o\x9da\xd7\\\xa1R\xdd\xce\xceK\x03\x19W\xe5\xc3\x005X\x08\xbf\xf8\xb7\xd6\x03\x87Gy\xc3\x0f\xd5\xe58\xece\x1a\x1c\xce\x91,\xe4N\xbfTk\x11\xde\xc3i\x81\xbe\xd3\x1f\x16\xde\xd1\xf6\xc0\x11\xec\x8c[2\xa3\xb9\x85\xc8b\x87\xacri\xd2\xb7\xa9\xe4N\xa14\xc89|\x92.\x88o\x19\xf3\xd8[I\xb7\xf5y\xff\xa6-\xacn!-\xfe\xb8\xb0D\xaa\x1d\xd0\x89\x9b\xdb\xeeJ\xff\xb2-+\xfdL'\xae\x07u3`\x17\xb1\xe9\xb4)0yIx3T\xf7\xac\xf0~`\xf2\xb1,\xfe\xc0\x14^\xaa\xdb\xd9\xfaG|\x9fR\x0a7\xcbun\x0a\xa7G\xd8\x03GP\x86`]\x8e?\xe7\x17n;\xf6\xa6Ov\x81\xd7\x89_\x8b\xf0\x1ev\xe7\xd0\x87?I\xd2\xabz\xe0\x08v\xc6-\x99\xd1\xdcBdm\xb9zu\x99\xac\x93\x7f\xf8\x1by\xca\xe7\x14w\x0e\xaf~$GXF\xa7\xbc'\xa9*\xc3\xcc\x14ZW:\xc1>,\x0f[\x04t\xe2\xe6F+\xb1\x0f\x1d\xa4k\xa7P\x0f\xeaf\xc0.b\xd3)8\xd9*p\xd7)\xdc\x85\xf0c\xda\xcb\x01M\xa9ng\xcb\x94K\x09\xe5T\xec\xaa\xf0\xca\x08{\xe0\x08\xca\x10j\xc6\xf1\xf1\xd1\xf5K\x84\xbc\x83aG'~-\xc2{h\x9d{\x87\xbd\xa1\xbc\xa3\xed\x81#\xd8\x19\xbfd\x06s\x0b\xc1fq\xe9\xb2\xf2\xe4\xac\xb2\x19\xaez\x0bw\x9f\xed)\x95t\xba\xe4\xbf\xea\xf6+:I\x8b\xd5#H\x03\xee\x81W\x80\x9b\x1b\xadtg\xc7mI'\xae\x07u3`\x17\x16u\xda\xab\xd9\x7f/\xf3\xaf\xc7p\xf0\x12\xda\xff\xd8\xb7\xfd:\x84Ju;\xab|\xe4\xf7\x12\xca;f\x83;|\x80\xdeZ\x0f\x1c\x11u\xba\xba\x9b\xee\xda\x1f\x9f\xf4\x1cfO\xa4\x1d\xfc\x98tXR\xadEx\x0f\x7f\x16\xca\xff\xfcVQ\x89O\xdb\x03G\xb03n\xc9\x8c\xe6\x16B\xd9\x8eo\xb1\xd5\xb8\xe9\xdd[\xf8\x11{\xb6\xa4\x84\xbds};\xa0SV@\xa7m\xec\x8f\xc7\x04i[\x05t\xe2\xe6&U\xf2K:q=\xa8\x9b\x01\xbb\x88^'\x0f}A|+5\xfb\xef]o)\xdb\x9b\xb6I/\x8e\xff\xdbE\x81wg\xfd\x01\xe2P\xa9ng\x97\xe4k\x9e\xbd\xf2\xfd\x10\xbe%\xe5#\xec\x81'\xa2N\x0d\xc2%\xf6_\xe9\x16\xe9\xb1\xd4\xef\xbf-\xf7\xa3Z\x8b\xf0\x1e~/\xe4\x09B\xe9\xcd\xb0\x1e8\x82\x9dqKf47\xff\xfb\x07\xdf\xe7fA\xada\xabX\xb2\xd7\xbfE\xda\xc6\x85\xccm\xdf7\xc2t\xca\xa3F\x0c?R*\xb5\x08\xe8\xc4\xcd\x8d\xd3\x89\xebA\xdd\x0c\xd8E4#{W%IJ\xbc\x07\x1bJ\x04\xf7\x0b\x7f\xbc\xd5\xe3\xde|\xd5\xf7\xe6f7\x1b\xf1\xbb2\xff\xd1\xa3\x97\xb6\x09\xc7\xa4\xea\xef\xc9w\x06}\xfa[i\xc0*x!%\xa3*\xd5\xefl\xf7\xdc\xca\xb3g7+wV\xb4\x86\x9d\x88X\xed!\x80\xefw==\xf37\xf7\xf4\xdc\xf5\xab\xea\x86h\x10\xb2\x7fr\x9e6\xbb\"=\xc9j=[\x9a\xfdg\xd5\xdc\xf4{\xf8\xfd\xfc\xcb/\xf6\xdc\xf4\x85\xf5\xc0\xf7\xact\xc6/\x99\xee\xdc\x18O\xcb\xc3\x0f\x81\xbb\"zrv\x0c\xbf\xb2\xd5{\xd3\xff~\xce\xb6W|\xb4\xd9\xfa\xc3\x0d\x8f\xd1\x93\xc4\xab\x7f\xcci\x1d>\xe9~k\xb8\x92\xa9\x9c%,?y\xac(\xe7O\xf4`\xc9\xee\x8ah\xed\xe9Q\xcd\xed\xa3\xcaR\xb6\xaa\xb7J+?\xe2z\xe0\x9b\x01\x1b\x89\xfc\xb9S\x8et\x06\xee\x96\x06r\xdf+\xf5d\xaf\xfe\xb1\x20l\xdd\xca\x8a\xfe-\x9b>n\xa5\xc5\x7fZ_\x98\x1d\xbc_u\xef|v\x9e\xff\x96|\xe2\xae9\xb8\xa8J\x0d:\xbbT\xba8\xe7I\xe9\xbd\xdb\xdf\xe3\xd9\xe1\xd7`\xb1\x87\x20o(W\x12G\xfd\xea\xbaA^x\xb2\xa1\xd0\xed-\x95]\xf0m\xcb\xf1<yU=7\xfd\x1e^q\xb32\xf7\x93\xefhz\xe0\x08t\xe6\xe7\x96Lwn\x8cc\xde\xa3RE!@+\x9b\xdc\xe6\xdf&\x08s\xaf\xf8}\xadEY\xde\xf5\xc7\x8a\xdcO\xd2c\xe2\xc9l!\xfb\xa4tm\x94\xb5csN~%Sf\x93\xd2j=?\xb7\x06A\xd8D\x9fn\x11\x84\x86P\x0f\xa5|3`#f'{\xd6\xf0ms\x9f4\xab\x13\x1d\xa7\xdd\x9bU\x9f\xe1\xc4!\xb7\xb3\xb7\xdc\xfe\xf4\xd3\x8f\xdfx:'\xda[\x1aG\x83\xd0\xf5\xaa%bl\x06\"c\xafN\xf4\x0d\xb0\xd0\x96\xa1\xd7;\xde\xb0A\xb2\xb8\xe3\xac\xf2)\x93/Ou4\x1c~_\xc6\xc6\xfb\xad\"\x11\xa3\x1716\x03\x91\xb1Y\xa7\x89\xc4\xef\xe5!r\xff;\xfcgo~\xff\x93\xca9\xd7\x93\xba\x8dl'F/bl\x06\"\x03\x9db\xc6\xb7\xd9\xb3\xe5\xf4\x95\xd3[<[T\x07\xd2?\x9d\x97q\xe4*\xff\xa642bV+\x8c\x18\x9b\x013\xa0S\xec\xf8\xce\xaf\xcew\xe7\xaf>?\x86\xfb\xa542\xa2\x19B\x8d\x82\x18\x9b\x013\xa0\x13\x00\xb6\x01\x9d\x00\xb0\x0d\xe8\x04\x80m@'\x00l\x03:\x01`\x1b\xd0\x09\x00\xdb\x80N\x00\xd8\x06t\x02\xc06\xa0\x13\x00\xb6a\xb7N\xc8(\x07\x13\x18S\x9d\x90Q.3\xf2\x1e\xa2\xe5\xa4t\x07\xed\xe2\xa7\xa3\xb9\x09h\xab\xa0\x93\xdad\xc0\xd1P]+\xb3\xb0Rw\xc2'\xa2\x9b\xea\x84\x8cr\x19\x0b=p\xc4\x92Q~\xf7\xbc\xf0\xe3\x9e\x9e\x17\x96\xe5}\xa4S_\x83\x947\x18%\x1f\xf7\x94\x04\xea\x1a\xce\xc2\xca\xe2\xe8\xd4\x9d\xf0\x89\xe8f:!\xa3<@\xf4=p\xc4\x94Q~K:\x8a\xdc\xf1D%\x8a\x95oZ\x94\x07\xeb\x1a\xcd\xc2\xca\xe2\xe8\xd5\x9d\xe8\x89\xe8f:!\xa3|D\x98v\xa6WA\xde\x7f\xfd\xcb\xcc\xdaJ\x8cD\xa7\xf0YXY\x1c\xbd\xba\x13=\x11=rV\x042\xca\xc7\"\xa3\\\xde\x7foK)\xce\xc1fF[=k\x87\\\xca'\x97si\xe4\xaa\xd7\x82\xea\xb4\x8d\xcen\xfe\x1dn\x16l\x19~\xcc\"%\x84\xd3\x06\xcbk\xa5\xeeDOD\x8f&\xc9\x08\x19\xe5\xcef\x94\xdf\x12\x8e\x0d\xdf\xfd\xdd\xb2%w\xf9fF[=KXr\xf2l\xde\xd3\xaa\xe4r.\x8d\\\xb5\x16T\xa7\x9b\x95B\xebU~\x16\xc3\xaf\x14n\xb9\xed\xbf\xfd\x93\x9c\x9e\xbb\x06\xcbk\xa5\xeeDOD\x8f\xfed\x8f\x8f]EF\xf9hf\x94\xdf\x92\xde\xf7\x8bn\xaa\x9b\x19l\xf5,\xefG\xec,\xdb\xafJ.\xe7\xd3\xc8\xb9\xb5\xa0:\xb5f\x9d\xd7\xcc\xc2\xdf\xc0\x8e0\x9b6\xcbuu\x96\xd7J\xdd\x89\x9e\x88\x1e\x9bN\x9b\x02\x93\xc8(\x1f\x85\x8c\xf2[\xc2O\xae^\xbd\xb4i\xf1\x1b\xea\x15\xd2\xdf\xeaY[\x02\xa5|r9\x97F\xce\xadEy\xc3^\xe5\xe5\xe4f\xe1\xbf9\xf7\xdf\xfc\xbe\x1c9\xc3Yoy\xad\xd4\x9d\xe8\x89\xe8\xb1\xe9\x14\x9cDF\xf9(d\x94+\xd7\xfe\xeb\x8b|\xaaf\xfa[=T\xca%\x97\xf3i\xe4\xdcZ\x94{s\x96<\xed\xd3\xcc\x82\x16\xef\xf0_\xca\xd1\xd8\xcd\xcd\xd8J\xdd\x89\x9e\x88nQ'd\x94s=\x8cVF\xb9\xb2\xff\x1e\xa3o\xe6\xfc\x0a\xe9ou\xb5NY\xb2N|\x1a9\xb7\x16\xe5\xdew\xde\xcfi\xd5\xcc\x82\x9e\xac\xe5\xf9\x94\xf37\xdd\xe5\xb5Rw\xa2'\xa2G\xaf\x132\xca\xb5=\x8cVF\xb9\xb2\xffn\xca\xf6\xa9VH\x7f\xab\xeb\xea\xc4\xa7\x91sk\xc1\x06\xca/\xb9\xaf\xaag\xc1\x82\x02\xcf+\xe7o\xba\xcbk\xa5\xeeDOD\x8ffd\x0f\x19\xe5\x8ef\x94\xcb\xb7!\x9c\xad\x946f\xb0\x99\xfeV\xe7J\xf9\xe4\xf2P\x1a9\xb7\x16\x1f\xf7\x94l\xfe\x9d\xefn\xb9\xf7\xcam\xd5,\xe8L\x1e\xc9\xf9\xd4py\xad\xd4\x9d\xf0\x89\xe8\x91?wBF\xf9\x18d\x94\xcb7\xc9\xb9\x97\x1d\xf3\xf1\xcd\xf4\xb7z\xa8t\x0b\x9f\\\x1eJ#\xe7\xd6\xe2(\x9bx\x83\xf5\xbeW=\x0b\xff\x9f\x84\xc0QUgy\xad\xd4\x9d\xf0\x89\xe8f'{\xd6@F\xf9\xb8\xe4nV\xd8\xfb\x80!V\xea\x06\x09]T'8\xf6\xea\x84\x8cr\xc73\xca\xed\xe0\xf4\xe2\xe8\xdf\xba\xac\xd4\x0d\x02\x9d\x80\x19\xf1\x91Q>b\x1a\xae\xf8J\x7fbVI\xc1J]\x1e\xe8\x04\xcc\x88\x87\x8c\xf2\x913,,\xdf\xea\x8d\xf2l\xd5J]\x8e\x09\x94\x88\x0e\x9db'\x0e2\xcam\xa0!\xbbTs\xcf\xa01V\xea\x86\x98@\x89\xe8\xd0\x09\x00\xdb\x80N\x00\xd8\x06t\x02\xc06\xa0\x13\x00\xb6\x01\x9d\x00\xb0\x0d\xe8\x04\x80m@'\x00l\x03:\x01`\x1b\xd0\x09\x00\xdb\x80N\x00\xd8\xc6\xc8ur.\xbc;:\xd6\x1f\x95\xff\xbf\xb2:\xcf]\xb8yb\xdc\xdb\x02\xe2\x04\x0b:\x8dyx\xb7!|\xdd7\x84\xdd\xd2\xffW\xe7\x96\x1c\xbb|\xb4(o\x02|g\x0d\xc4\x0d\x16t\x1a\xfb\xf0n#Bu}=\xf9\x8aNg\x05\x16ipS\xf9\xda=\x00N`A'\xd3\xbcm+\x98vfZ\x81#Tw\xbd\xb0\xde#\xeb\xe4\x93\x0eKz\xe1b\x00\x8c\x16&:\xc5Ux\xb7*o\xdbwt\xb9\xe7\x1b\x87}\x9a\xcen\xdf\xf2\xe7\xec\x0e\xd4\xf7\xdd\xb9T\xf8\x8d\x98\x8e\x9d\x00\xc4Dd\x9d\xe2+\xbc[5\xe3MY{_\xdc\x9b\xb5\xd9\x1fV7\xa8\xd3\x9b\xd4\xb1l\x0cE\x00\x07\x89\xacS\x9c\x85ws3\xbe$\x85\xd5\xc9\x8f\xea\xbaA\x9d|W\xaf\xb4\x16z\xe1\x13p\x8e\xc8:\xc5Yx77\xe3\xcdr$\xe3\xa3\x9b\xc3\xea\x86N\xf6(\x1f\xe7\xebd_\x020J\x98\\;\xc5Wx77\xe3\x12Y\x93\xf2\x92\xb0\xba\x8aNw\xe496\xb8q\xf1\x04\x1c#\xb2Nq\x16\xde\xcd\xcdx\xf3#\xacw_\xe1\xa6\xb0\xba\xb2N\xbe\x1c\xb9f\x83'\x86\x1c+\x00b#\xb2Nq\x16\xde\xcd\xcd\xf8E\xe9O'\x85\x17\xb5u\x03:\xe5\xb1\x9f@\xf2\x7f\\\x18\xfec\x1a\x00\x8c\x16f:\xc5Sx\xb7*\xa3\xbcR\xd8}~\xb7P\xa9\xe9\xecNOO\xf6z)T\xfd\x05\xa1\xfc\xe8\xe5\xd6\xc2<;\x7f\xaf\x1a\x80\xc8D\xd6)\xbe\xc2\xbbU\x19\xe5\xbe\xd6e\x9ee\x87}\x9a\xba{\xa5\x1an\xf6+\xac\xbf}\xbap\xfe\x92m\xb6d\xd2\x02\x10\x1d\x16\xee\x8a\x88\x9a\xc4\x09\xef\x06\xc0\x12\xa3\xa1S\xe2\x84w\x03`\x89\xd1\xd0)A\xc2\xbb\x01\xb0\xcah\xe8\x94\x18\xe1\xdd\x00Xf4tJ\x90\xf0n\x00\xac2*:\x0101\x81N\x00\xd8\x06t\x02\xc06\xa0\x13\x00\xb6\x01\x9d\x00\xb0\x0d\xe8\x04\x80m@'\x00l\x03:\x01`\x1b\xd0\x09\x00\xdb\xb0[\xa7\xb3%\xb7\xcd\xaa\xa8\xb9S\x12K\x88,\x00\xf1\x88\xa9N\x1fm\xc9\xf3\x94G}\xb7\xd0A\xa1!\xea\xba2\xbe\x83\xc2A\xb3:V\x18yd\xfa\xc8{\x88\x96\xb3n\xe5\xae`\xb7\xf9[J\x83\\\xe9E\x8f\xd0`V\x15\x8c\x19\xa6:\x95z\x8fn\xf2D\xfb\xbd\xa5V)\xe5\xcb\"/\xba[\xcd\xaaX\xc0Bd\xfa\x98\x87\xae\x1f\x14zz\x0e\x0b\xad==s\x8d\xdfQ\x02\xcd\xeeTzJ\xe9\x7f\xa5\x9eJ|#2~1\xd3\xe9\x8e\xd0\x1a\x0c\xd43\xe5}7\x1f\xca\x155{\xb3\xec\xfc\x06z\xf4G\xc71\x0f]ou\xb3\x84\xc1\xdf\xf9\xfd\x1e\xe37\x94`\xb3\xdd\xe5\xf4]\xed\x8e\xa7<\xa6M\x0c\x9c\xc1L\xa7\xf7\x04\x0bg>\x9b\xbd1}5p\xd8\xbb\xd9\xac\xca\xa8`%\x06\xdd\x14\xd3\xcet*\xf8\xdeStz\xcf\xd8\xe0`\xb3\xdd\x95\xa5\xa7\xfd/\xac\xae\x84NqLD\x9d|\x8b\xe5S\xfb\x1d,\x82\x9c\x9e\xc7\xbd\xef\x16\x96h\xd2\xc8\xfd\xef};\xcf\xed-\x97\xa6}\xd9\xf2K\xcde\x89\x1b5\xe3\x82\xc9){=\x1a\x0d-\xf7\xa0`\x10\x99\xce\x11W\xa1\xeb\x0cI\xa7h\x9a\xed\xae<\xfc\xb4\xbf\xfc(\xd3\xe9\xee\xfa|wa\xf9[~i![\xb7\xe4\xe7\x94\xbf\x17\xd6/\x18\x0b\"\x1f\x9d\xde\xec9)4\xf4\xf4\xdc\xf2\xfbo\xf6\xb8\x1b\xfc\xbe\xab\x9b\xb3\xd4i\xe4\xfe+\xd9\xcb\x0f^n\x10\xa4\x93\x957\x85\x1e\xa9\x15\x97%n\xd4,\x14L\xcexE\xf8\xadz\xbe\x96{\x08\xa0\x1b\x99\xce\x11_\xa1\xeb\x8c\xa0Nf\xcdvW\xde\xcc\xfeh\xfe-\xa6\xd3ya\xcb\x95\xd3\xe5\xc2Uy!\x0b\x1b\x1a\x0a=\xef\x84u\x0c\xc6\x80\xe8O\xf6B1z\\\x1a\xf9p!\x8b\xb3\xf3\x9d\x96.\x8f_\x14\x82_\xb4\x0d\x85\x98\xeb6\xe3\x82\xc9\x197\x85\xf0\x8b\x7fk=p\x84G\xa6s\xc4]\xe8zP'\xd3f\xbb+\xfd\xcb\xb6\xac\xf43\x9d\x86O\xd2%\xf5-\x93\xde*\xb2\x0a\xef\xb28\xc1\x12\xbd\xbe\x81\xd3\xc4\xa6\xd3\xa6\xc0\xe4%>\x0e\xe2\xac\x10\xcc\xd7\x0f\x85\x98\xeb6\xe3\x82\xc9\x197\x85\xd3~-\xd6z\xe0\x08\x8fL\xe7\x88\xbb\xd0\xf5\xa0N\xa6\xcd\xa8N\xec3\x05\xe9\xda\xe9\xce\xe1\xd5\x8f\xe4\x08RL{\x96t\x82}X\xc0x_<\x10\x9bN\xc1\xc9V\x81\xbbz\x09\x9e\xb7\xf0!\xe6\xba\xcd\xf8`r\xbf\xfe\x8f\x9aY\xeb\x81#<\x94\x96'\xdeB\xd7\x83\x1b\xcd\xb4\x19\xd5\xe9\xce\x8e\xdb\x92NW\xbd\x85\xbb\xcf\xf6\x94\xca:I\x0b\xdb#\x98\x8d\xd3\x03'\xb0\xa8\xd3^\xcd^}\x99\x7f\x19\x87\xb3\x1a\x02\x93\xa1\x10s\xddf\\09\xa3\xc1\x1d>\x14o\xad\x07\x8e\x88:\xc5]\xe8zP'\xd3f\xbb\xe5\xc4[\xa6\xd3\x92\x12\xf6&\xf6mY\xa7m\xec\xf1\x98\x10\xed\x87\x19`4\x89^'\xf6#\x99\xbe\x95\x9a\xbd\xfa\xae\xb7\x94\xedc\xdb\xa4\xd7\xd4\xff\xed\xa2\xc0{v\xe8\xadX\xb7\x19\x17LN\xf1-\xd1\xf9\xd5\x18K=\xf0D\xd4)\xeeB\xd7\x83:\x996\xe3t*d\xf2\xfb\xbe!\xeb\x94GE\x1a~\xa4\xd4\x0f\xe2\x80hF\xf6\xaeJ\x92\x94x\x0f6\x94\x08\xee\x17\xfe\xc8\xa7\x91\xfb\xaf\xcc\x7f\xf4\xe8\xa5m\xca\x0f:\xbf'\xdf/\xa4\xca\x12\xd7o\x16\x0a&\xf7\xb33F\xed\x99\x8a\xd5\x1e\x02\xe8G\xa6s\xc4W\xe8:\xe5\xcd\xc3B\xab\x1c\x1dm\xd2\xec\xa3\xcaR\xb6&\xb7J+?\xa2\x15\xd6\x1fnx\x8c\x9e\x0e\xd2\x96Y\xc2\xf2\x93\xc7\x8ar\x90\xb6\x16\x17D\xfe\xdc)G:\x9dwK\xc3\xbb\xef\x95z\xb2W\xffX\x10\xb6\xf2i\xe4\xf4\x02}}av\xf0.\xd6\xbd\xf3\xd9E\x90*K\xdc\xa0Y0\x98\x9c\x9e\xf7{vhgl\xb1\x87\x20\xfa\x91\xe9\x1c\xf1\x15\xbaN\x0f+\xac\x7f\xcf\xdd(\x9a5\x08\xc2&\xfat\x8b\x204\xf8}\xadEY\xde\xf5\xc7\x8a\xdc\xf4\x98\x94\xb5csN~\xa5\xea=\x03\x8c\x19f'{\xd6\xf0ms\x87\x8f\\\x9bp\xda\xbd9\xde\x7f\x82)\x9eC\xd7C\xd7\xab`\xec\xb1W'\xfa\xbeYhq\xc4\xf6\x8e7l\x90,\xee\x88\xe7\xd0u\xe8\x14O\xd8\xacSb\x12\xcf\xa1\xeb\xd0)\x9e\x80NQ\x10\xbf\xa1\xeb7\xa5\xf1\x12\xb3Z\xc0)\xa0S4\xc4m\xe8\xba4^\xf2\xbe\x1f\xc4\x09\xd0\x09\x00\xdb\x80N\x00\xd8\x06t\x02\xc06\xa0\x13\x00\xb6\x01\x9d\x00\xb0\x0d\xe8\x04\x80m@'\x00l\x03:\x01`\x1b\xd0\x09\x00\xdb\xb0['d\x94\x83\x09\x8c\xa9N\xc8(\x1f=\x90Q\x9eh\x98\xea\x84\x8c\xf2\xe8@F90\xd7\x09\x19\xe5Q\x82\x8cr`\xae\x132\xca\xa3\xc4\xb43d\x94O\x00\"gE\x20\xa3\x1c\x19\xe5\xc0\x02\xd1$\x19!\xa3\x1c\x19\xe5\x20*\xa2?\xd9CF92\xca\x81\x09\xb1\xe9\xb4)0\x89\x8c\xf2`\x0f\x81\xceL\xc3\xc6\xd5\x20\xa3<\x91\x88M\xa7\xe0$2\xca\x83=\x04:3\x0d\x1bW\x83\x8c\xf2D\xc2\xa2N\xc8(GF90&z\x9d\x90Q\x8e\x8cr`B4#{\xc8(GF9\x88\x8a\xc8\x9f;!\xa3\x1c\x19\xe5\xc0\x02f'{\xd6@F\xb9\xe3\x20\x056\x9e\xb0W'd\x94;\x0et\x8a'l\xd6)1AF9\x88\x0e\xe8\x14\x05\xc8(\x07\xd1\x01\x9d\xa2\x01\x19\xe5\x20*\xa0\x13\x00\xb6\x01\x9d\x00\xb0\x0d\xe8\x04\x80m@'\x00l\x03:\x01`\x1b\xd0\x09\x00\xdb\x80N\x00\xd8\x06t\x02\xc06\xa0\x13\x00\xb6a\xb7N\x963\xca\xf5Ar9\x18\x8f\x98\xea4\xda\x19\xe5\xfa\x20\xb9\\@r\xf98\xc4T\xa7\xd1\xcf(\xd7\x07\xc9\xe5H.\x1f\x7f\x98\xe9\xe4DF\xb9>H.Gr\xf9\xb8\xc3L''2\xca\xf5Ar9\x92\xcb\xc7\x1d\x91\xb3\"F\x9cQ\xceuf9w\x1c\xc9\xe5Q4Cry\\\x11M\x92\xd1\x082\xca\xb9\xbe,\xe7\x8e#\xb9<\x8afH.\x8f+\xa2?\xd9\x8b1\xa3\x9c\xc7Z\xee8\x92\xcb\xa3h\x86\xe4\xf2\xb8\"6\x9d6\x05&\xcd3\xcay\xac\xe5\x8e#\xb9<\x8afH.\x8f+b\xd3)8i\x9eQ\xcec-w\x1c\xc9\xe5Q4Cry\\aQ'\xeb\x19\xe5<\xd6r\xc7\x91\\\x1eE3$\x97\xc7\x15\xd1\xeb\x14cF9\x8f\xa5\xdcq$\x97\xfb\xa3h\x86\xe4\xf2\xb8\"\x9a\x91\xbd\x11e\x94\x07\xb1\x9a;\x8e\xe4r\xbfy3$\x97\xc7\x17\x91?w\xb2!\xa3<\x88\xc5\xdcq$\x97#\xb9|\xfcav\xb2g\x8dX2\xca\xf5Ar\xf9\x88@6\xec\xd8`\xafN1d\x94\xeb\x83\xe4\xf2\x91\x01\x9d\xc6\x06\x9bu\x9aH\x20\xb9\x1ch\x81N1\x83\xe4r\xa0\x05:\xc5\x0e\x92\xcb\x81\x06\xe8\x04\x80m@'\x00l\x03:\x01`\x1b\xd0\x09\x00\xdb\x80N\x00\xd8\x06t\x02\xc06\xa0\x13\x00\xb6\x01\x9d\x00\xb0\x0d\xe8\x04\x80m\xd8\xad\x132\xca\xc1\x04\xc6T'd\x94\xcb\x8c\xbc\x87hAF\xf9\xf8\xc5T'd\x94\xcbX\xe8\x81\x03\x19\xe5\x13\x0b3\x9d\x90Q\x1e\x20\xfa\x1e8\x90Q>\xb10\xd3\x09\x19\xe5#\xc2\xb43d\x94'\x14\x91\xb3\"\x90Q\x8e\x8cr`\x81h\x92\x8c\x90Q\x8e\x8cr\x10\x15\xd1\x9f\xec!\xa3\\\xdb\x032\xca\x81\x86\xd8t\xda\x14\x98DF92\xcaA\x88\xd8t\x0aN\"\xa3\x1c\x19\xe5\x20\x84E\x9d\x90Q\x8e\x8cr`L\xf4:!\xa3\\\xdb\x032\xca\x81\x86hF\xf6\x90Q\x8e\x8cr\x10\x15\x91?wBF92\xca\x81\x05\xccN\xf6\xac\x81\x8c\xf28\x01)\xb0c\x83\xbd:!\xa3\x1c\x19\xe5\x13\x1a\x9bu\x9aH\x20\xa3\x1ch\x81N1\x83\x8cr\xa0\x05:\xc5\x0e2\xca\x81\x06\xe8\x04\x80m@'\x00l\x03:\x01`\x1b\xd0\x09\x00\xdb\x80N\x00\xd8\x06t\x02\xc06\xa0\x13\x00\xb6\x01\x9d\x00\xb0\x0d\xe8\x04\x80m\xd8\xad\x132\xca\xc1\x04\xc6T'd\x94\xcb\x8c\xbc\x87h9)\xddA\xbb\xf8\xe9h\xee\x12\xda*\xe8\xa46\x19p4T\xd74\xda\xbcg\xaeP\xe2\xf3\x9f\x15\x84\xb9\x97\xd5\x7fxgu\xb6\xb7\xf2\x8a\xf7\xb6\x95\x19O\x20LuBF\xb9\x8c\x85\x1e8b\xc9(\xbf{^\xf8qO\xcf\x0b\xcb\xf2>\xd2\xa9\xafA\xca\x1b\x8c\x92\x8f{J\x02uM\xa3\xcd\x87/\x0b\x9e\x1e\xff\xdd\xb3\xc2e\xf5\xf7L.e\xaf<vr\x99\x20\xfc\xbb\x95\x19O\x20\xcctBFy\x80\xe8{\xe0\x88%\xa3\xdc\x7fKz\xe3\xbf\xe3\x89j\x7f\xb5\xf2U\x8c\xf2`]\xd3h\xf3\xbbB\xe5f\xfa\x82j\xb2[\xee\xe4<M7\xc3p\x91\xf0\xef\xd6f<a0\xd3\x09\x19\xe5#\xc2\xb43\xbd\x0a\xb2N\xfeefm%\xac\xec\xd5\x9cNf\xd1\xe6w\x85\xcb\xd9\xc3a:\xed\xf6H\xdf\x97o\x15\xde\xb36\xe3\x09C\xe4\xac\x08d\x94\x8fEF\xb9\xac\xd3m)\xc59\xd8\xcch\xabg\xed\x90K?\xcds\xff\xb80\xff\xca\xb6\x9c\xd2\xbb|\\\xb9\xea\xb5\xa0:m\xa3\xb3\x9b\x7f\xc7<\xda\xfc\xae\xf0\xfecg\x15\x9d|G\x97{\xbeq\x98\xadZ\xd1&i\xeewZ}\xa1\x19kz\xd0\xdd:z+\x9f\x90D\x93d\x84\x8crg3\xcao\x09\xc7\x86\xef\xfen\xd9\x92\xbb|3\xa3\xad\x9e%,9y6\x8f\x96^\xc9\x11v\x94\x0a\x8b\x0f\xe6\xb5\xf2q\xe5\xaa\xb5\xa0:\xdd\xac\x942\x92L\xa3\xcd\xa9N\xad\xe5\x8aN\x9b\xb2\xf6\xbe\xb87k3]a\x81\xbb\x9e\x0d\xceX\xd3\x83\xce\xd6\xd1]\xf9\x84$\xfa\x93=>v\x15\x19\xe5\xa3\x99Q~K:d\x15\xddT73\xd8\xeaY\xde\x8f\xd8Y6\x9d\xf2V\xd2W\xe0\xbc\x7f\xd3\x16u\\9\xb7\x16T\xa7\xd6,\xe9\xf54\x8d6\xa7:\xdd\x99\x7fG\xd2\xe9\x92\x14\xf0\xc7\x1e\xdf\xe7_\x91\xd0\x8cU=\xe8m\x1d\xa3\x95O<b\xd3iS`\x12\x19\xe5\xa3\x90Q~K\xf8\xc9\xd5\xab\x976-~C\xbdB\xfa[=kK\xa0\xd4{\x92\xee\xb1\xc3r\x90%\x17W\xce\xadEy\xc3^\xe5\xe54\x8d6\xa7:\xf9\xcb[%\x9d6\xcb\xdd<\xba\xd9\xef\xe3G[C3V\xf5\xa0\xb7u\x8cV>\xf1\x88M\xa7\xe0$2\xcaG!\xa3\\\x19\x8aX_\xe4S5\xd3\xdf\xea\xa1R\xef%\xffU\xb7\x9c\x0b\xcb\xc7\x95skQ\xee\xcdY\xf2\xb44[\xd3hs\xa6\xd3\xd9oH:\x95\xc8\xc1\x86\xe5%\xc1k'\xdf%\xd5\x8c\xc3z\xd0n\x1d\xa3\x95O<,\xea\x84\x8cr\xae\x87\xd1\xca(Wt:F\x0f\x12\xfc\x0a\xe9ou\xb5NY\xb2N|\\9\xb7\x16\xe5\xdew\xde\xcf\x91\x0e0\xa6\xd1\xe6L\xa7\xe1\xec\xcb\xd2\xd1\xe9\x11\xb6\x86\xbe\xc2MldO\xda\xb2'\x85\xdb\xfc\x8cU=\xe8m\x1d\xa3\x95O<\xa2\xd7\x09\x19\xe5\xda\x1eF+\xa3\\\xd1iS\xb6O\xb5B\xfa[]W'>\xae\x9c[\x0b6P~\xc9\xcd2gM\xa3\xcd\x99N\xfeM\x95L\xa7\x17\xa5e8)\xbc\x18\xf8\xdc\xc9WZ\xa8\x9a\xb1\xaa\x07\xbd\xadc\xb4\xf2\x89G4#{\xc8(w4\xa3\\\xbe+\xe2l\xa5\xb41\x83\xcd\xf4\xb7:W\xfa\xc7\x9c\xd6\xe1\x93\xee\xb7\x86+\xe9.\x1c\x8a+\xe7\xd6\xe2\xe3\x9e\x92\xcd\xbf\xf3\xdd-\xf7^\xb9m\x1am>|Y8{\xd7\xdf\xe3\x91F\xf6*\x85\xdd\xe7w\x0b\x92\x80\x97<%/\x9c]\xed~E\xb58\xa1\x1e\x0c\xb6\x8e\xd1\xca'\x1e\x91?wBF\xf9\x18d\x94\xcb\xf7\xec\xb9\x97\x1d\xf3\xf1\xcd\xf4\xb7z\xa8t\x0b=\x16\x9c\xcc\x16\xb2O\xb2K\x94P\\9\xb7\x16G\xd9\xc4\x1b\xac\xf7\xbd\xa6\xd1\xe6W\xe6\x0a\xc2i\xbf\xaf\xc4+\x9d\xe7\xb5.\xf3,;,\x8b\xf1\xcejO\xde\xd3\xff\xa6^\x9cP\x0f\x06[\xc7h\xe5\x13\x0f\xb3\x93=k\x20\xa3||\x13\xba\xf8\x1d1\x09\xb8u\xa2\xc0^\x9d\x90Q\x1e'\x19\xe51b\xa3N\x06['\xc1\xb1Y\xa7\x89D<g\x94\xc7\x88\x8d:\x19l\x9d\x04\x07:\xc5L\xfcf\x94\xc7\x88\xad\xd1\xe6\x06['\xc1\x81N\xb1\x13\xb7\x19\xe51bo\xb4y\xa2m\x9d\xa8\x80N\x00\xd8\x06t\x02\xc06\xa0\x13\x00\xb6\x01\x9d\x00\xb0\x0d\xe8\x04\x80m\xfc\x7f\xe7\xbd\xe7\x84\xdaB\xea\xf4\x00\x00\x00\x00IEND\xaeB`\x82",
+
+	"analysis/call-eg.png": "\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x02\x1e\x00\x00\x01\xc8\x08\x03\x00\x00\x006\x92\x13\xd7\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x02\xfdPLTE\x00\x01\x00\x05\x07\x03\x0a\x0d\x09\x0d\x12\x14\x0e\x12\x1d\x11\x13\x10\x1b\x1a\x14\x20\x20\x19\x15\"6\x1f#%!#\x20$%#&(&+)\x1e++$*,*0.\"-/,\x1c0Z120463/6B;8-8:7<>;C@4#Cx@B?AFHDFDKH<MH7\x00f\x00IJH\x00i\x01\x00j\x02LNK8P|0R\x95\x07m\x06PROYTC\x0fp\x0bTVS/Z\xa3\x0es\x18,`\xae7]\xad\x15v\x1c[]Zb]K9a\xab\x19y\x1e;b\xac_a^=d\xae@f\xb0*{!bdaBh\xb3efdDi\xb4fheMj\xaf.\x81.Fm\xb1qjROl\xb2Ho\xb3kmj0\x859Jq\xb5Kr\xb7npmMs\xb8;\x86:cr\x8fNu\xba|t]Xv\xb6sur?\x8a>Zx\xb8vxu]{\xbb_}\xbez|y\x84|dX\x80\xbfL\x91K`\x81\xbbb\x80\xc0}\x7f|\x7f\x81~c\x83\xbdN\x95Ue\x85\xbf\x7f\x83\x92W\x95V\x82\x84\x81\x8d\x84fg\x88\xc2m\x87\xbci\x89\xc3j\x8a\xc4[\x9aZ\x87\x89\x86q\x8c\xc1\x8a\x8b\x88\\\x9dc\x96\x8cnt\x8e\xc4v\x90\xc6g\xa0g}\x91\xc2\x8f\x91\x8ey\x93\xc9\x92\x94\x91\x80\x95\xc6z\x97\xc6\x82\x96\xc7s\xa4l\xa1\x96x\x95\x97\x94\x83\x98\xc9\x82\x9a\xc4\x97\x99\x96\x7f\x9d\xcct\xa9v\x86\x9b\xcc\x81\x9e\xce\x9a\x9c\x99\x82\xa0\xcf\xa8\x9d\x7f\x9c\x9e\x9b\x88\xa0\xca\x7f\xabz\x8a\xa2\xcc\x9f\xa1\x9e\x9e\xa3\xa5\xa1\xa3\xa0\x8d\xa6\xd0\x82\xb1\x85\xa3\xa5\xa2\xb3\xa7\x83\x91\xa9\xd4\xa7\xa9\xa6\x8d\xb5\x8a\x94\xac\xd6\x99\xac\xd1\xaa\xac\xa9\xa4\xac\xc0\x9e\xad\xcd\x8d\xb8\x93\x9b\xaf\xd4\xbb\xae\x89\x9d\xb1\xd6\xaf\xb1\xae\x97\xbb\x97\x9f\xb3\xd8\xa1\xb5\xda\xc2\xb5\x90\xb4\xb6\xb3\xa3\xbf\x9d\xac\xb7\xd2\xa2\xc0\xa4\xa9\xb9\xd9\xb7\xb9\xb6\xac\xbc\xdc\xba\xbc\xb9\xa6\xc5\xa8\xb1\xbd\xd7\xaf\xbe\xdf\xbd\xbf\xbc\xcc\xbf\x9a\xb1\xc0\xe1\xb8\xc0\xd5\xb1\xc8\xad\xc0\xc2\xbf\xb7\xc2\xdd\xb9\xc5\xe0\xbd\xc5\xda\xb2\xcd\xb7\xc2\xc6\xd6\xbb\xc7\xe2\xd6\xc7\x9d\xc6\xc8\xc5\xbc\xc8\xe3\xbf\xca\xe5\xbd\xd0\xbc\xc6\xcb\xdb\xca\xcc\xc8\xc1\xcc\xe7\xc2\xcd\xe9\xbf\xcf\xe2\xc7\xce\xe4\xca\xcf\xdf\xce\xd0\xcd\xc8\xd3\xc1\xc8\xd0\xe6\xdf\xd1\xa5\xca\xd1\xe7\xcb\xd3\xe9\xca\xd8\xcc\xd3\xd5\xd1\xce\xd6\xeb\xd3\xd7\xe0\xd6\xd8\xd4\xd0\xd8\xed\xe7\xd8\xac\xd5\xdb\xd0\xcb\xdb\xee\xd5\xd9\xe9\xd9\xdb\xd8\xd7\xdb\xeb\xd8\xdc\xec\xd2\xde\xec\xd5\xdf\xda\xd9\xdd\xed\xdc\xdd\xe7\xdd\xdf\xdc\xd7\xe1\xdb\xdb\xdf\xef\xd5\xe1\xef\xf0\xe0\xb3\xdd\xe1\xf1\xe0\xe2\xdf\xde\xe3\xe6\xd9\xe5\xf4\xe0\xe4\xf4\xe3\xe5\xe2\xde\xe6\xef\xe8\xe5\xea\xe5\xe7\xe4\xe6\xe7\xf1\xe0\xe9\xf1\xe3\xe9\xeb\xe7\xe9\xe6\xe3\xec\xf4\xeb\xed\xea\xe6\xee\xf7\xec\xed\xf7\xe8\xf0\xf9\xef\xf0\xfb\xf0\xf3\xef\xf4\xf2\xf6\xef\xf4\xf7\xf4\xf7\xf3\xf2\xf7\xfa\xf5\xfa\xfd\xf8\xfa\xf7\xfd\xfb\xff\xf7\xfd\xff\xf9\xfe\xff\xfe\xff\xfcA\x10\x8d\xd1\x00\x00\x20\x00IDATx^\xed\x9d\x0ft\x14U\xbe\xe7\x07wqv+\xfd^\xcc&O\x98\xe51\xd3@va\xc8\x86\x82\x89M\"\x98`^\xc0\x13a}\x20\x08\x0e\x88\x99l\x1c=A\x98\xe1\xafg2\xcf3YI\x8eA\xf2@$\x98\xacsL\x00\xb3J\x88\x12\x91\x80\xae\xe6\x80\xe1\xa8\xbc#\x90LD\x87\x93\x991\x83\xd0\xbe\xc9L\xc4RQD\x12\xfb\xd4\xd9{oUu\xdd[\xa9\xbe71\xe9\xaa\xea\xee\xdf\xe7p\x92\xdb\x95[\xb7nU}\xba\xeaVu\x7f\xa9\xef\xa9#@\x01\xe2\x9c\xef\x89\x14\xe0!j\x1c\x88u@\x0f\x80\x03\xe8\x01p\x00=\x00\x0e\xa0\x07\xc0\x01\xf4\x008\xb8\xa7G_kMeM\x9b\xfe\xa2\xad\xaejG\x0b\xaf6\xe0\x0a\xee\xe9QW\xd5z\xaa\xa5\xb2\x05\x17{\x1b\xca\x9bO\xb5\x94\xb7\x0b\xe6\x00\x1c\xc75=Z+\xbb\xd0\xcf\xf6\xca\x1e\xf4\xb3\xa1\xb2CQ\xba\xca\x8dC\x09\xe0\x19\\\xd3\xa3\xae\x81\xfc\xaanQ\x94\x8e\xf2V\\\xec\xe1U\x07\\\xc15=j\x1a\xb5_u\x8a\xd2\\\xd9+\xa8\x0c\xb8\x84kz4Uc'\x82U\xbb\xb0\"m5\xe5\xd5MA\xd1,\x80\xe3\xb8\xa6GOU]W\xb0\xa3\xa6|\x87\xa2\xec(\xafn\xedh\xab\xde\x01\xc7\x10\xcf\xe1\x9a\x1e\xca\xf9\xba\xf2\xf2\xf2\xe6\xba\x1a4\x0c!\xe3\xd3\x9e\xaaf\xd1,\x80\xd3\xb8\xa7\x07:\xb3\x9c\xefS\xaa\x9b\x14\xa5\xb1\x86\xbc\xd4\x7f\x01\x1e\xc2==\xc8\xa9\xa4\xbd\x1c]\xd2\xb6T\xf5\xe1rC\x1d\x7f\x06\xc0y\\\xd3\xa3\xad\xbc\x0b\x9dP\xaa\xf1\xf5\xcbyra\xdb\xa3\xdd\"\x03\xbc\x84kz\xb4\x97\xb7t\xb4V\xd7\x91\xcb\x95\x96\xca\x1644\xad\x81\xa1\xa9\xe7pM\x0f\xa5uWU]\xab^n\xaf\xa9\xda\xd5\x02vx\x0f\xf7\xf4\x00b\x00\xd0\x03\xe0\x00z\x00\x1c@\x0f\x80\x03\xe8\x01p\x00=\x00\x0e\xa0\x07\xc0\x01\xf4\x008\x80\x1e\x00\x07\xd0\x03\xe0\x00z\x00\x1c@\x0f\x80\x03\xe8\x01p\x00=\x14e\xcd\x1a\xbb\"\xa0\xb8\xa9G8D\xd9[]\xaeQ-\x9a%:\xfc\x9b\x7f\xa3M\x11\xb3\xc7\x8f\xc8\xbc\xfb\xc4\xe0yF\x8f\x12\xb4\x88\xa9gE\xb5\xdc\xc2==\xc2!\xca`yK\x07\xa2\xa5\xbcUq\x85\x92)\xef\xdb\x141=\xbf\xf5?qd\xcf\"\xff\x8b\x83g\x1a5\xce\x1e9\xb2\xc7\x7fDT\xcb-\\\xd3\x83\x0aQ\xb6\xe1\xaf\x8c\x05\xf1\x97\x92\xdd\xe0\xacy\xc48\xcb\x1e<\x14\xe5\x041c\xd1\xadJTy\x0d\xf4\x18\x04\x15\xa2\xd4^\xefr\xe9\xdbb\x91\x0f\x1e\x86\x1eO\xfa\xa3\x9b\xd0\x02=\x06C\x85(1\xe4\x9b\xc9npv\xf2F\x9b\xa2\x8e\xa6G\xc9LE\xf9\x85\xdf\xbf\x17U\xf0\xdf\x8e\x8b\x93\xf7l\xbcu\xea\xdd\x83\x06\x0c{\x17M\xbdu#2\xe9\xc4d\xbf\x7f\xa52\xd3\xef\x9f\xde\xab\xf4\xac\x9c9y\xe6J4z\xe9\xcb\x9c\xfc\xeb\x993_\xdc8}Q\x10\xb7\xf0\xe4\x9a\x99\xd3W\xea-\x98z\x18-x\x06\xd7\xf4\xa0B\x94\x98]\x0d\xdc\xda\xd1c\xcd\xe4\xb36E\x9d\x13\xfe\x17\x82g7\xfa\x9fP\x94\xf7\x8fL~\x0c\xed\xc8\x92)h\xfc\xbaw\xb2?\xf3\xb1'\xa7\xae\xb4T.\xf1o|\xe1\xc9\xcc\xdb\xfb\x94\xbe\xd7\x1e\xf3\xbf\xa6\xfc\xd6\xbf\x17Y\xf1\x82\x7f\xcd\x8b{W\xa2\x97\xca\x8b\xd3\xfd\x1b\xef\xf6g>\x91\xf9$ia\xe6\x13\x8f\xcd\x9c\xaa\x0dz\xc3z\x84[\xf0\x0c\xae\xe9A\x85(\x11\xa7p\xde\xc5\x0d\xceN^cS48\x81/]\xfc%\xa4<\x05\xe9\xa1<6\x85\x14\xa7#\x8fJ2\xd9\xba/\xf8\x7f\xab\xe0]\xbd\x17\xbf(\xb9\xe3\x8f3\x9f\xc0\x85\xe0^|0\xb8\x1d\xab\x94Y\x82\xea\xfc_\xa5\x04/e\xca-h\xcc\xf5\xe7\x99w\x909\x0d=\x98\x16\xbc\x81kzP!JD\xc3.A\xedh\xf1\x0b\xf3\x88\xf1\x8bA\x07\x0f\xa4\xc7\x93\xaf\xbd\xb0h\x0a\x99N\xebQ\x12.\x9a\xac\xbc\xa5\xb7\x0f1\x93\xc8\x14\xbc=S?\xb8\xfcq\xcf\xdd\xb7L\xc7\xe7$%s/\xda\xf5Ae\xe3\xcfp\x0b\xe44\xb6\xc7\xffg\xfc\xcb\xd0\x83i\xc1\x1b\xb8\xa7\x87\x19\xa2DT\xb5\xf0\xabF\x8b\xf7\xa7\xac\xb1)\x86!c\x8f?\xfb\xf7\xe02\xad\x87Y4\xb9\xdd\xaf\xa1i\xb1W\xdf\xe5\xafe\xce\xdc\xb8\xf7\xc8\xddD\x8f\x17\x95\xd7&+\xba\x1e\xb8\x05\xe5\x88\x9f\x9c]\x0c=\xd8\x16<\x81{z\x98!J\xfc?\x03\x9d\xe2W\x8e\x16\x1b\xfdgm\x8aa\xb4\xa1\xe9t\xb2/Ez\x94\xdcr\x82\xf0G\xfc\xe2\xfd\xcc\xc7f\x92\xc2\xedw\xe0\x93\xcbJC\x8f)\x0a{\xf4\x20\xff\xe5\x8d\xa1\x07\xd3\x827pM\x0f*D\x89\x87\x1e\xe7\x05\xd5\xa3\xc3\xfbSJl\x8a&\x9a\x1e\x99\x1bO\xa0q\xc4T\xb4G\xfb\xee\x88\xac\xc7\x8b\xda\x98\xe11<\xe4\xe8\xbd\xe31e\xcd\xddx\x889\x13\x1f\x0a\xfan\x1f\xa4G&\xb2&x\xcb\"2\xa7\xa1\x07\xdd\x02\x1a\x0a=\xe1\x81{\xa9\xae\xe9A\x87(\x91+\xee\\\xcd\x09\x0e\x1e\xf8\xca\x05\xfd\xbc{e\x09\xda\xbd\x8b2\x9fx\xe2\x0e\xff\xe4\xdf\x9e@\x171%\xaf)'J&\x1fa\xef\x92l\xf4\xffl\xef\xde\x12\xb4\x8b\x83G\xd6d\x9eU\xceN\xdfx\xa4Wy\xcc\xbfr\xcf\x13\xb7\xa3\x0b\x96\xd7NL\x7f2\xb8w\xf2\x89\xe0\xcf\xf0\x15\xf1\x14\xff\xed{\xf7\xdc\x8aG\xb8\xe4\xae\xe9\x93G\x8e\x9c\xa5[\xc0\xac\xf4\xc2I\xc65=\x98\x10\xe5)wF\xa6\xefO-\xb1)\x9a\xecE\x03\x01\xf4V>qk&\xb2\xe4\xec\xa2)S\xef\xfe\xb5\xdf\xbff\x0d\x9a:\xf9\xc4T\xf4\xd32Xy\xf1\xee\xcc\xe9\x8b^\xc0G\x01\xbf\x7f#\xda\xd7~t\xec\xe9{\xf2\xd6)\x99+\xf7\xdc:yQ\xa6\xdf\xbfw\xaa\x7f\xea^2\xb6\x98\xb2\xb1d\xfa\xcc\x12\xacW\x09=\xe00Z\xc0\xec\xc9\xdc\xa3\xb8\x8e{zx\x80_\xfb\xff\xcd\xa6\xe8\x04\xda\xd0\xd4\xfb$\xb4\x1e\xee}\x92\x0fz\x00\x1c@\x0f\x20\"\xda\xe06\x16\x00=\\\x80\x0cn=p\xd9*\x06\xf4\x008\x80\x1e\x00\x07\xd0\x03\xe0\x00z\x00\x1c@\x0f\x80\x03\xe8\x01p\x00=\x00\x0e\xa0\x07\xc0\x01\xf4\x18\x16/\xad\xfaPQ\xceOK\x98\x87\x03\xb8\xa7\x07\xf5$\xca\x9e\xa6]\x95\xbb\x9a\xbc\xf7\xb0\xb0Wg\xc8\x1a\x81w\xb4\x09\xb5r\xed\xa7\xe8\xd7\xe6\xa4u\xdc\xf9\xe2\x07\xf7\xf40\x9fDy\xbe\xba\xa6\xad\xa3mW\xb5;\xdf\x18\xe3\xf0\xf1\xab\xf9\x0f\xbd\x8a8\x20\xbfB^?#\x1f\xd0\xfeP\x97\xf4\x08g\xb68\xc25=\xa8\x10e#y\x0eT\xef\x0e\x97R\x94<\x0a\x7f\x85\x7f\xfeN\xd3\xe3wY\xbf\xe9\xeb\xeb\xeb\xc5\xdf\x11|\xc0\xe7R\xf0\xc2a\\\xd3\x83\x0aQ6h\xa7\xf2\x1a\xb7\x92P\x1c4=>}\xe6c\xf4\xb3\xfd\x1f\xfe\xd3?\xce\x9a4q\xe2\x0adt\xcf\xb8;\x05s\xc6\x07\xae\xe9A\x85(\xcfW7v\xf5v5V;\x1f\xa3\xfc\xf4\xc0\xaa\xec\xc5\xbf\xc1\xfb^\xf9x\xd3\xbc\xf9\xbf\xf9\xcd\xbc\xec\xe7\x99\x0a\x9a\x1e\x84^\xdf\x7f\xce\xc8\x18\x93\x9c\x91>\xe6\x81\x20:|$\xbb\xf3\xedX\x87qM\x0f:D\x19l(//opa{o\x92\x7f\xf3\xca3\xf9\xf7\xa0\xe1\xe6\xa7\x8b\xf3\x9f\xa9\x0dd?\xf3\xd03L\x85\xc2M\x1f\x7f\xfc\xcc*R\x0c\xfe\x87u\xaa\xdavU\x1d\xb8a6\xf2\xb8IJ\x88'\xdf\xb9\xa6\x07\x15\xa2\xecm\xd8\x81\x86\xa6;\x1a\x1c\x8f\xe8\xbfD\x86\x9ao\xca\xe8\x88\xf1\xbc\xfc\x0e\x1ez\xbec\xa9Q\x88\xaf[\xf2I1\xf8\x0fc\xc6\x8cQ\xf2\xc6\x8c\xb9a\x1d\x1aCwH\xee|{\xdaa\\\xd3\x83\x0aQ6\xed\xc0\x07\x0e\x17\x86\xa6\x9b\x0a?\xfd\x1bb\xfe&E\xd96W\xc1C\xd0\x97,5\x0a\x1fz\xf3\xcdM\x9a\x1e\xca\xff\xf9/\xff\x88\xf4\x18;g]{/\xd6c\x87\xb5\xb5x\xc4==\xcc\x10e\xa5\x96gh\xad\x14\xd4\x1fu\x96\xea\xb75\x1eB\x07\x8e\x19h\x04\xf2\xca\xe0\xa3\x07\x1a{\xbc\xa3\x9fo\xfe\xdf\xff\xf8_H\x8f\xa4\xd6.|\x94k\x96\x1c\x97\xd9\x0d\xdc\xd3\xc3\x0cQ\xba\xa6\xc7\xa6\xc27\x09\x1f\xe3\x03\xc7C\xbf{g\xf1\xaaO-5\xa8\xa1\xa9\xf2q\xe0\x7fb=\xdaI\xc7\xd7\xf9\xbcw\x17/\x0a\xb8\xa6\x07\x15\xa2l\x20'\x97\xe0\x0e\xc7/l_\x91\xc9u\xca\xb6Z<\x00)\x90\xe5\xa2\x0f\xf5?\x9cZ\xd7\xae\x15h=\x94\xf5\xff-\xacG\xdf\xc4YJ\"\xe0\x9a\x1eT\x882\xb8\x83\x0cMw8\x7f\xe9\xb2m\xc6\xa6\xe7_\xda\x84%y3\xf0\xca+\xaf~h\x1c<fKd\xef\x93\xbb\xa6\x1f\x87k\xff\xee\xbf\x86\xf5\xd8,\xb5\x0cn-\x0eqM\x0f:D\xd9\xdb\xbc\xab\xaa\xa6\xd9\xf1\x0b\x17\xc4+E\xf3\xe6\x16\xe1[\xa2\xaff\xe1AHV\x916\xf8\xa8J+\xc7\xbf\xc8g.\xd4\x8d\x90\xf0\xc9\xa5)y\x85]k\xf1\x87{zx\x8a\x0f\xb3\x7f\xf5\xe1\xdf\xfe\xf6\xf1\x9bks\xcd\x83\xc5`\xfeU\xd7c\x87\xefN7\\v\x01\xd0\x83\xf0\xd2\\\xed\xbc\xf2\xe9\xbcW8\xb5vhz\x9c\x1f\x97(\x1f\xd8\x82\x1e\x1ao\xea\x97\xb4\xef\x0c\xba\xb4\xd5!\x17\xc0x\xec\xf1\x1f\xff{\x82\x1c8\x08\xa0\x07\xe1\xd3M\xd9\xbfz\xfe\xd5\xe7\x7f\x95]f\xbd\xb4\xd5\x01=\x86\x8f\xa8\xf1\x18\xe2\xd3\x97\x1e\xca\xcf\xca\x7f\xe8\xa5\x08vh\xec0\xef{$\x08\xa0\xc70\x00=\x86\x85\xa8\xf1x\x03\xf4\x18\x16\xa2\xc6\xe3\x8b\xdeGoCz\xdc\xb8\xba)\x81\xfc\x00=\x86\xcc4\xe3\x03}\xb7\x1e\x0e\xe0\x02\xa0\xc7P\x09\x8e]\xa7\xaaWUu`\xec\x1c\xcf}g:j\x80\x1eC%(I\x19\x19c\xd32\xd2\xc7\xdc\xe9\xfc\xb7\x1e\xdd\x02\xf4\x18*\xbdM\xe3RS'\xa4\xa6\xa6f\xb48\xff\xd9\xa1[\x80\x1eC\xa6\xb7\xa3\xbd\xbd\xfd\x14\xfa\x97\x10\xdf\xf4\xd0\x00=\x86\x01\xce\xb8h9\x97D\xc1==\xa8\x10%U\x04<\x85{z\x98!J\xba\xe8\x09\x1a[D5\x12\x05\xd7\xf4\xa0B\x94T\xd1\x1b\xa4\xe7\x89j\xe8\x8c\x19}D\x8bt\x16\xd7\xf4\xa0B\x94\xd6\x87R\xbaN\xfam\xa2\x1a:c.\x8e6\xa0\x87\x06\x15\xa2\xb4<\x94\xd21Z\x93$\xe9\x81\xf6\x05\xe3\x93g\xe1\xdb\xa0\xd5\xd3\x92'\xe0\xf8l\x9d\xa4\x91\xae(K\xa4\xa4j\xe5\x94O\x9a\xc8\xd4ES\xcb\x97MH\x9e\xd5\x01z\xf0\x115\xce\x83\x0aQZ\x1eJ\xe9\x18\xc1\x1d\xd5\xe3'\xa4\x8c[\x96'\x9dR\x94;\x93\x96\xd5=\x92\x96\xde\xa7\xf445N\xc8hllDC\xe5\x8e\xc6\xa4\x87\x15\xa5y\x81\x8f\xa9\xdbV\x9d$\xa5=\xbc9e\x0e\xe8\xc1G\xd48\x0f*D\xc9>\x94\xd2Q\xd2\xa5\x0c\x9c\xb7\xc7\xc7\x8c*\x05\x87\x9bH\x1f\xcc\x93\x8b\x0f\xe9\xa1<\xecc\xeb*\xbeTt\xe4X\x90\x06z\xf0\x115\xce\x85z\x12%\xf3PJGI\xf7\xe9\x0f\xb1\xcb\x1b\xdf\x8b\xd1\xfe_\x86\x08z\x18u\x15\xdf\x02}\xea\xf0\xf5\x18C\xfeq\xfe\xaex\x0a\xf7\xf4`\x9eDI\x15\x1d%=]/L\xd2G\x1cs\xc8T{=\x8c\xba\xe6T\xbc\xa7\x9f\xbaI\xdb\xdf\xda\x85\xc7X\xaa`\xbb\xfbA\x8