content/static: add readme headings to the sidenav

This change adds the readme headings to the outline
in the sidenav and unifies the navigation tree into
a single accessible structure.

Styles within the tree are based on the accessiblity
tree structure. This will make it easier to see when
updates to the tree are inserted incorrectly because
they'll be matched by visual breakages on the page.

Sidenav and readme click handlers are updated to
ensure that when an outline item is clicked the
corresponding content is visible.

Change-Id: Ie6062c24f37bec9fffe242aa1398ec815606c613
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/271319
Trust: Jamal Carvalho <jamal@golang.org>
Run-TryBot: Jamal Carvalho <jamal@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Julie Qiu <julie@golang.org>
diff --git a/content/static/css/unit_details.css b/content/static/css/unit_details.css
index 78ae5d5..ec616d1 100644
--- a/content/static/css/unit_details.css
+++ b/content/static/css/unit_details.css
@@ -9,7 +9,6 @@
 @import './unit_files.css';
 @import './unit_directories.css';
 @import './unit_meta.css';
-@import './legacy_unit_outline.css';
 
 .UnitDetails {
   column-gap: 2rem;
diff --git a/content/static/css/unit_outline.css b/content/static/css/unit_outline.css
index 71c8a37..a74a1bb 100644
--- a/content/static/css/unit_outline.css
+++ b/content/static/css/unit_outline.css
@@ -4,26 +4,11 @@
  * license that can be found in the LICENSE file.
  */
 
+/* TODO(jamal): remove these styles with legacy code. */
 .Documentation-index,
-.DocNav,
 .DocNav-index {
   display: block;
 }
-.DocNav {
-  max-height: calc(100vh - var(--header-height));
-  overflow-x: unset;
-  overflow-y: unset;
-  padding-top: 0.5rem;
-  padding-left: unset;
-  position: unset;
-  top: unset;
-}
-@media only screen and (min-width: 52rem) {
-  .DocNav [role='tree'],
-  .DocNav [role='group'] {
-    padding: 0;
-  }
-}
 
 .UnitOutline {
   display: flex;
@@ -32,30 +17,9 @@
   position: sticky;
   top: 4.5rem;
 }
-a.UnitOutline-accordion {
-  align-items: center;
-  color: var(--gray-2);
-  display: flex;
-  font-size: 1.125rem;
-  font-weight: 500;
-  height: 2.5rem;
-  padding: 1rem;
-}
-a.UnitOutline-accordion[aria-expanded='true'] {
-  background-color: var(--gray-9);
-}
-.UnitOutline-panel {
-  padding: 0 18px;
-  background-color: white;
-  display: block;
-  overflow-y: auto;
-}
-.UnitOutline-panel[aria-hidden='true'] {
-  display: none;
-}
 .UnitOutline-jumpTo {
   display: flex;
-  margin-bottom: 0.5625rem;
+  margin-bottom: -0.1625rem;
 }
 .UnitOutline-jumpTo button {
   background-color: white;
@@ -77,6 +41,7 @@
   border-radius: 0.5rem;
   color: var(--gray-6);
   content: 'f';
+  content: 'f' / 'find';
   font-size: 0.75rem;
   padding: 0.0625rem 0;
   position: absolute;
@@ -98,3 +63,112 @@
 .UnitOutline-jumpToInput:disabled {
   background-color: var(--gray-9);
 }
+.UnitOutline ul[role='tree'],
+.UnitOutline ul[role='treeitem'],
+.UnitOutline ul[role='group'] {
+  list-style: none;
+  padding-left: 0;
+}
+.UnitOutline li:last-of-type {
+  padding-bottom: 0.25rem;
+}
+.UnitOutline [role='treeitem'][aria-expanded='false'] + ul[role='group'] {
+  display: none;
+}
+.UnitOutline [role='treeitem'][aria-expanded='true'] + ul[role='group'] {
+  display: block;
+}
+.UnitOutline [role='treeitem'][aria-level='1'] + ul[role='group'] {
+  max-height: calc(100vh - 20rem);
+  overflow-y: auto;
+  padding: 0.5rem 0.25rem 0 0.25rem;
+}
+.UnitOutline a {
+  color: var(--gray-2);
+  display: block;
+  line-height: 1.5rem;
+  overflow: hidden;
+  padding: 0.125rem 0 0.125rem 1.25rem;
+  position: relative;
+  text-overflow: ellipsis;
+  user-select: none;
+  white-space: nowrap;
+}
+.UnitOutline a:focus,
+.UnitOutline a:hover {
+  outline: transparent;
+  text-decoration: underline;
+}
+.UnitOutline [role='treeitem'][aria-selected='true'] {
+  color: var(--gray-1);
+}
+.UnitOutline [role='treeitem'][aria-selected='true'] {
+  font-weight: 500;
+}
+.UnitOutline [role='treeitem'][aria-level='1'] {
+  display: block;
+  font-size: 1.125rem;
+  font-weight: 500;
+  line-height: 2.5rem;
+  padding: 0 1rem;
+}
+.UnitOutline [role='treeitem'][aria-level='1'][aria-selected='true'],
+.UnitOutline [role='treeitem'][aria-level='1'][aria-expanded='true'] {
+  background-color: var(--gray-9);
+}
+.UnitOutline [role='treeitem'][aria-level='3'][aria-expanded='true'] {
+  margin-bottom: 0.375em;
+}
+.UnitOutline [role='treeitem'][aria-level='2'] {
+  position: relative;
+  margin-bottom: 0.25rem;
+}
+.UnitOutline [role='treeitem'][aria-level='3'] {
+  font-size: 0.875rem;
+  padding-left: 2.5rem;
+}
+.UnitOutline [role='treeitem'][aria-level='4'] {
+  border-left: 0.125rem solid var(--gray-9);
+  font-size: 0.875rem;
+  margin-left: 2.5rem;
+  padding-left: 0.5rem;
+}
+.UnitOutline [role='treeitem'][aria-selected='true'][aria-level='2']:not([aria-expanded])::before,
+.UnitOutline [role='treeitem'][aria-selected='true'][aria-level='3']:not([aria-expanded])::before {
+  border-radius: 50%;
+  background-color: var(--turq-dark);
+  content: '';
+  display: block;
+  height: 0.3125rem;
+  left: 0.4688rem;
+  position: absolute;
+  top: 0.6875rem;
+  width: 0.3125rem;
+}
+.UnitOutline [role='treeitem'][aria-expanded][aria-owns][aria-level='2']::before,
+.UnitOutline [role='treeitem'][aria-expanded][aria-owns][aria-level='3']::before {
+  border-bottom: 0.25rem solid transparent;
+  border-left: 0.25rem solid var(--gray-4);
+  border-right: 0;
+  border-top: 0.25rem solid transparent;
+  content: '';
+  display: block;
+  height: 0;
+  left: 0.5rem;
+  position: absolute;
+  top: 0.625rem;
+  transition: transform 0.1s linear;
+  width: 0;
+}
+.UnitOutline [role='treeitem'][aria-expanded='true'][aria-level='2']::before,
+.UnitOutline [role='treeitem'][aria-expanded='true'][aria-level='3']::before {
+  transform: rotate(90deg);
+}
+.UnitOutline [role='treeitem'][aria-expanded][aria-level='3']:not([empty]):before,
+.UnitOutline [role='treeitem'][aria-selected][aria-level='3']:not([empty]):before {
+  left: 1.5rem;
+  top: 0.75rem;
+}
+.UnitOutline [role='treeitem'][aria-selected='true'][aria-level='4'] {
+  border-left: 0.125rem solid var(--turq-dark);
+}
diff --git a/content/static/html/doc/outline.tmpl b/content/static/html/doc/outline.tmpl
new file mode 100644
index 0000000..f888594
--- /dev/null
+++ b/content/static/html/doc/outline.tmpl
@@ -0,0 +1,109 @@
+<!--
+  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.
+-->
+<ul role="group" id="doc-outline">
+  {{if or .Doc (index .Examples.Map "")}}
+    <li role="none">
+      <a href="#pkg-overview" role="treeitem" aria-level="2" tabindex="-1">Overview</a>
+    </li>
+  {{end}}
+  {{- if or .Consts .Vars .Funcs .Types -}}
+    <li class="DocNav-overview" role="none">
+      <a href="#pkg-index" role="treeitem" aria-level="2" tabindex="-1" aria-owns="nav-group-index"
+          {{if .Examples.List}} aria-expanded="false"{{end}}>
+        Index
+      </a>
+      {{if .Examples.List}}
+        <ul role="group" id="nav-group-index">
+          <li role="none">
+            <a href="#pkg-examples" role="treeitem" aria-level="3" tabindex="-1">Examples</a>
+          </li>
+        </ul>
+      {{end}}
+    </li>
+    <li class="DocNav-constants" role="none">
+      <a href="#pkg-constants" role="treeitem" aria-level="2" tabindex="-1">Constants</a>
+    </li>
+    <li class="DocNav-variables" role="none">
+      <a href="#pkg-variables" role="treeitem" aria-level="2" tabindex="-1">Variables</a>
+    </li>
+    <li class="DocNav-functions" role="none">
+      <a href="#pkg-functions" role="treeitem" aria-level="2" tabindex="-1" aria-owns="nav-group-functions"
+           {{if gt (len .Funcs) 0}}aria-expanded="false"{{end}}>
+        Functions
+      </a>
+      <ul role="group" id="nav-group-functions">
+        {{range .Funcs}}
+          <li role="none">
+            <a href="#{{.Name}}" role="treeitem" aria-level="3" tabindex="-1"
+                 title="{{render_short_synopsis .Decl}}">
+              {{render_short_synopsis .Decl}}
+            </a>
+          </li>
+        {{end}}
+      </ul>
+    </li>
+    <li class="DocNav-types" role="none">
+      <a href="#pkg-types" role="treeitem" aria-level="2" tabindex="-1" aria-owns="nav-group-types"
+           {{if gt (len .Types) 0}}aria-expanded="false"{{end}}>
+        Types
+      </a>
+      <ul role="group" id="nav-group-types">
+        {{range .Types}}
+          {{$tname := .Name}}
+          <li role="none">
+            {{if or .Funcs .Methods}}
+              {{$navgroupname := (printf "nav.group.%s" $tname)}}
+              {{$navgroupid := (safe_id $navgroupname)}}
+              <a href="#{{$tname}}" role="treeitem" aria-expanded="false" aria-level="3" tabindex="-1"
+                   data-aria-owns="{{$navgroupid}}">
+                type {{$tname}}
+              </a>
+              <ul role="group" id="{{$navgroupid}}">
+                {{range .Funcs}}
+                  <li role="none">
+                    <a href="#{{.Name}}" role="treeitem" aria-level="4" tabindex="-1"
+                        title="{{render_short_synopsis .Decl}}">
+                      {{render_short_synopsis .Decl}}
+                    </a>
+                  </li>
+                {{end}}
+                {{range .Methods}}
+                  <li role="none">
+                    <a href="#{{$tname}}.{{.Name}}" role="treeitem" aria-level="4" tabindex="-1"
+                        title="{{render_short_synopsis .Decl}}">
+                      {{render_short_synopsis .Decl}}
+                    </a>
+                  </li>
+                {{end}}
+              </ul>
+            {{else}}
+              <a href="#{{$tname}}" role="treeitem" aria-level="3" tabindex="-1">
+                type {{$tname}}
+              </a>
+            {{end}} {{/* if or .Funcs .Methods */}}
+          </li>
+        {{end}} {{/* range .Types */}}
+      </ul>
+    </li>
+  {{end}}
+  {{if .Notes}}
+    <li role="none">
+      <a href="#pkg-notes" role="treeitem" aria-expanded="false" aria-level="2" tabindex="-1"
+           aria-owns="nav-group-notes">
+        Notes
+      </a>
+      <ul role="group" id="nav-group-notes">
+        {{range $marker, $item := .Notes}}
+          <li role="none">
+            <a href="#pkg-note-{{$marker}}" role="treeitem" aria-level="3" tabindex="-1">
+              {{(index $.NoteHeaders $marker).Label}}s
+            </a>
+          </li>
+        {{end}}
+      </ul>
+    </li>
+  {{end}}
+</ul>
diff --git a/content/static/html/helpers/_unit_outline.tmpl b/content/static/html/helpers/_unit_outline.tmpl
index 3e873fa..8a47cc7 100644
--- a/content/static/html/helpers/_unit_outline.tmpl
+++ b/content/static/html/helpers/_unit_outline.tmpl
@@ -5,47 +5,56 @@
 -->
 
 {{define "unit_outline"}}
-  <div class="UnitOutline js-accordion">
+  <div class="UnitOutline">
     <div class="UnitOutline-jumpTo">
       <button class="UnitOutline-jumpToInput js-jumpToInput">
         Jump to ...
       </button>
     </div>
-    {{if .Readme.String}}
-      <a href="?readme=expanded#section-readme" class="UnitOutline-accordion js-accordionTrigger js-readmeExpand"
-          role="button" aria-expanded="false" aria-controls="readme-panel" id="readme-accordion">
-        README
-      </a>
-      <div class="UnitOutline-panel js-accordionPanel"
-          id="readme-panel" role="region" aria-labelledby="readme-accordion" aria-hidden="true"></div>
-    {{end}}
-    {{if .IsPackage}}
-      <a class="UnitOutline-accordion  js-accordionTrigger" href="#section-documentation"
-          role="button" aria-expanded="false" aria-controls="outline-panel" id="outline-accordion">
-        Documentation
-      </a>
-      <div class="UnitOutline-panel js-accordionPanel"
-          id="outline-panel" role="region" aria-labelledby="outline-accordion" aria-hidden="true">
-        <div class="Documentation">
+    <ul class="js-tree" role="tree" aria-label="Outline">
+      {{if .Readme.String}}
+        <li role="none" class="js-readmeOutline">
+          <a href="#section-readme" role="treeitem" aria-expanded="false" aria-selected="false"
+              aria-level="1" aria-owns="readme-outline" tabindex="0">
+            README
+          </a>
+          <ul role="group" id="readme-outline">
+            {{range .ReadmeOutline}}
+              <li role="none">
+                <a href="#{{.ID}}" role="treeitem" aria-selected="false" aria-level="2"
+                    tabindex="-1">
+                  {{.Text}}
+                </a>
+              </li>
+            {{end}}
+          </ul>
+        </li>
+      {{end}}
+      {{if .IsPackage}}
+        <li role="none">
+          <a href="#section-documentation" role="treeitem" aria-expanded="false" aria-level="1"
+              aria-selected="false"aria-owns="doc-outline" tabindex="-1">
+            Documentation
+          </a>
           {{.DocOutline}}
-        </div>
-      </div>
-    {{end}}
-    {{if .SourceFiles}}
-      <a class="UnitOutline-accordion js-accordionTrigger" href="#section-sourcefiles"
-          role="button" aria-expanded="false" aria-controls="files-panel" id="files-accordion">
-        Source Files
-      </a>
-      <div class="UnitOutline-panel js-accordionPanel"
-          id="files-panel" role="region" aria-labelledby="files-accordion" aria-hidden="true"></div>
-    {{end}}
-    {{if (or .Subdirectories .NestedModules)}}
-      <a class="UnitOutline-accordion js-accordionTrigger" href="#section-directories"
-          role="button" aria-expanded="false" aria-controls="directories-panel" id="directories-accordion">
-        Directories
-      </a>
-      <div class="UnitOutline-panel js-accordionPanel"
-          id="directories-panel" role="region" aria-labelledby="directories-accordion" aria-hidden="true"></div>
-    {{end}}
+        </li>
+      {{end}}
+      {{if .SourceFiles}}
+        <li role="none">
+          <a href="#section-sourcefiles" role="treeitem" aria-expanded="false"
+              aria-selected="false" aria-level="1" tabindex="-1">
+            Source Files
+          </a>
+        </li>
+      {{end}}
+      {{if (or .Subdirectories .NestedModules)}}
+        <li role="none">
+          <a href="#section-directories" role="treeitem" aria-expanded="false"
+              aria-selected="false" aria-level="1" tabindex="-1">
+            Directories
+          </a>
+        </li>
+      {{end}}
+    </ul>
   </div>
 {{end}}
diff --git a/content/static/html/pages/unit.tmpl b/content/static/html/pages/unit.tmpl
index 536f496..18450f1 100644
--- a/content/static/html/pages/unit.tmpl
+++ b/content/static/html/pages/unit.tmpl
@@ -7,7 +7,11 @@
 {{define "pre_content"}}
   <link href="/static/css/unit.css?version={{.AppVersionLabel}}" rel="stylesheet">
   {{block "unit_pre_content" .}}{{end}}
-  <link href="/static/css/legacy_unit_outline.css?version={{.AppVersionLabel}}" rel="stylesheet">
+  {{if (.Experiments.IsActive "readme-outline")}}
+    <link href="/static/css/unit_outline.css?version={{.AppVersionLabel}}" rel="stylesheet">
+  {{else}}
+    <link href="/static/css/legacy_unit_outline.css?version={{.AppVersionLabel}}" rel="stylesheet">
+  {{end}}
 {{end}}
 
 {{define "main_content"}}
diff --git a/content/static/html/pages/unit_details.tmpl b/content/static/html/pages/unit_details.tmpl
index 71cd63c..804805c 100644
--- a/content/static/html/pages/unit_details.tmpl
+++ b/content/static/html/pages/unit_details.tmpl
@@ -14,9 +14,13 @@
     <div class="UnitDetails-outline" role="navigation"
         aria-label="{{if eq .PageType "std"}}module
         {{else}}{{.PageType}}{{end}}details navigation">
-      {{block "legacy_unit_outline" .Details}}{{end}}
+      {{if (.Experiments.IsActive "readme-outline")}}
+        {{block "unit_outline" .Details}}{{end}}
+      {{else}}
+        {{block "legacy_unit_outline" .Details}}{{end}}
+      {{end}}
     </div>
-    <div class="UnitDetails-content" role="main" data-test-id="UnitDetails-content">
+    <div class="UnitDetails-content js-unitDetailsContent" role="main" data-test-id="UnitDetails-content">
       {{if .Details.Readme.String}}
         {{block "unit_readme" .Details}}{{end}}
       {{end}}
@@ -84,7 +88,13 @@
   <script>
     loadScript("/static/js/playground.min.js", {async: true, defer: true});
   </script>
-  <script>
-    loadScript('/static/js/legacy_sidenav.js', {async: true, defer: true});
-  </script>
+  {{if (.Experiments.IsActive "readme-outline")}}
+    <script>
+      loadScript('/static/js/sidenav.js', {type: 'module', async: true, defer: true})
+    </script>
+  {{else}}
+    <script>
+      loadScript('/static/js/legacy_sidenav.js', {async: true, defer: true});
+    </script>
+  {{end}}
 {{end}}
diff --git a/content/static/js/sidenav.js b/content/static/js/sidenav.js
index a6355aa..2e3236f 100644
--- a/content/static/js/sidenav.js
+++ b/content/static/js/sidenav.js
@@ -142,6 +142,10 @@
     if (!this._selectedEl) {
       return;
     }
+
+    if (this._selectedEl.getAttribute('aria-level') === '1') {
+      this._selectedEl.setAttribute('aria-expanded', 'true');
+    }
     this._selectedEl.setAttribute('aria-selected', 'true');
     this.expandAllParents(this._selectedEl);
     this.scrollElementIntoView(this._selectedEl);
@@ -211,7 +215,6 @@
       this.toggleItemExpandedState(el);
     }
     this.closeInactiveDocNavGroups(el);
-    this.closeInactiveDocNavTypeGroups(el);
   }
 
   /**
@@ -220,25 +223,9 @@
    * @private
    */
   closeInactiveDocNavGroups(el) {
-    if (el.classList.contains('js-docNav')) {
-      document.querySelectorAll('.js-docNav').forEach(nav => {
-        if (nav.getAttribute('aria-expanded') === 'true' && nav !== el) {
-          nav.setAttribute('aria-expanded', 'false');
-        }
-      });
-      this.updateVisibleItems();
-      this._focusedIndex = this._visibleItems.indexOf(el);
-    }
-  }
-
-  /**
-   * Closes inactive type level nav groups when a new tree item clicked.
-   * @param {!Element} el
-   * @private
-   */
-  closeInactiveDocNavTypeGroups(el) {
-    if (el.classList.contains('js-docNavType')) {
-      document.querySelectorAll('.js-docNavType').forEach(nav => {
+    if (el.hasAttribute('aria-expanded')) {
+      const level = el.getAttribute('aria-level');
+      document.querySelectorAll(`[aria-level="${level}"]`).forEach(nav => {
         if (nav.getAttribute('aria-expanded') === 'true' && nav !== el) {
           nav.setAttribute('aria-expanded', 'false');
         }
@@ -482,7 +469,7 @@
    * @param {Element} contentEl
    */
   constructor(sideNavEl, mobileNavEl, contentEl) {
-    if (!sideNavEl || !mobileNavEl || !contentEl) {
+    if (!sideNavEl || !contentEl) {
       console.warn('Unable to find all elements needed for navigation');
       return;
     }
@@ -507,7 +494,9 @@
      * @type {!MobileNavController}
      * @private
      */
-    this._mobileNavController = new MobileNavController(mobileNavEl);
+    if (mobileNavEl) {
+      this._mobileNavController = new MobileNavController(mobileNavEl);
+    }
 
     this.updateSelectedIdFromWindowHash();
   }
@@ -527,7 +516,9 @@
   updateSelectedIdFromWindowHash() {
     const targetId = this.targetIdFromLocationHash();
     this._navController.setSelectedId(targetId);
-    this._mobileNavController.setSelectedId(targetId);
+    if (this._mobileNavController) {
+      this._mobileNavController.setSelectedId(targetId);
+    }
     if (targetId !== '') {
       const targetEl = this._contentEl.querySelector(`[id='${targetId}']`);
       if (targetEl) {
@@ -640,7 +631,7 @@
 }
 
 new DocPageController(
-  document.querySelector('.js-sideNav'),
+  document.querySelector('.js-tree'),
   document.querySelector('.js-mobileNav'),
-  document.querySelector('.js-docContent')
+  document.querySelector('.js-unitDetailsContent')
 );
diff --git a/content/static/js/unit.js b/content/static/js/unit.js
index c26cd13..997c5df 100644
--- a/content/static/js/unit.js
+++ b/content/static/js/unit.js
@@ -9,6 +9,7 @@
 
 /**
  * Instantiates accordion controller for the left sidebar.
+ * Can be removed when readme-outline experiment is turned on.
  */
 const accordion = document.querySelector('.js-accordion');
 if (accordion) {
@@ -20,9 +21,13 @@
  */
 const readme = document.querySelector('.js-readme');
 const readmeContent = document.querySelector('.js-readmeContent');
+const readmeOutline = document.querySelector('.js-readmeOutline');
 const readmeExpand = document.querySelectorAll('.js-readmeExpand');
 const readmeCollapse = document.querySelector('.js-readmeCollapse');
-if (readme && readmeContent && readmeExpand.length && readmeCollapse) {
+if (readme && readmeContent && readmeOutline && readmeExpand.length && readmeCollapse) {
+  if (window.location.hash.includes('readme')) {
+    readme.classList.add('UnitReadme--expanded');
+  }
   readmeExpand.forEach(el =>
     el.addEventListener('click', e => {
       e.preventDefault();
@@ -38,17 +43,21 @@
   readmeContent.addEventListener('keyup', e => {
     readme.classList.add('UnitReadme--expanded');
   });
+  readmeContent.addEventListener('click', e => {
+    readme.classList.add('UnitReadme--expanded');
+  });
+  readmeOutline.addEventListener('click', e => {
+    readme.classList.add('UnitReadme--expanded');
+  });
 }
 
 /**
  * Disable unavailable sections in navigation dropdown on mobile.
  */
 const readmeOption = document.querySelector('.js-readmeOption');
-if (!readme) {
+if (readmeOption && !readme) {
   readmeOption.setAttribute('disabled', true);
 }
-
-const unitFiles = document.querySelector('.js-unitFiles');
 const unitDirectories = document.querySelector('.js-unitDirectories');
 const directoriesOption = document.querySelector('.js-directoriesOption');
 if (!unitDirectories) {
diff --git a/internal/godoc/dochtml/dochtml.go b/internal/godoc/dochtml/dochtml.go
index eecf595..fa4d887 100644
--- a/internal/godoc/dochtml/dochtml.go
+++ b/internal/godoc/dochtml/dochtml.go
@@ -147,6 +147,9 @@
 
 	body = exec("body.tmpl")
 	outline = exec("sidenav.tmpl")
+	if experiment.IsActive(ctx, internal.ExperimentReadmeOutline) {
+		outline = exec("outline.tmpl")
+	}
 	mobileOutline = exec("sidenav-mobile.tmpl")
 	if err != nil {
 		return safehtml.HTML{}, safehtml.HTML{}, safehtml.HTML{}, err
diff --git a/internal/godoc/dochtml/template.go b/internal/godoc/dochtml/template.go
index 6e79ea8..c24af9e 100644
--- a/internal/godoc/dochtml/template.go
+++ b/internal/godoc/dochtml/template.go
@@ -25,19 +25,20 @@
 func LoadTemplates(dir template.TrustedSource) {
 	loadOnce.Do(func() {
 		join := template.TrustedSourceJoin
-		test := template.TrustedSourceFromConstant
+		tc := template.TrustedSourceFromConstant
 
-		example := join(dir, test("example.tmpl"))
+		example := join(dir, tc("example.tmpl"))
 		legacyTemplate = template.Must(template.New("legacy.tmpl").
 			Funcs(tmpl).
-			ParseFilesFromTrustedSources(join(dir, test("legacy.tmpl")), example))
+			ParseFilesFromTrustedSources(join(dir, tc("legacy.tmpl")), example))
 		unitTemplate = template.Must(template.New("unit.tmpl").
 			Funcs(tmpl).
 			ParseFilesFromTrustedSources(
-				join(dir, test("unit.tmpl")),
-				join(dir, test("sidenav.tmpl")),
-				join(dir, test("sidenav-mobile.tmpl")),
-				join(dir, test("body.tmpl")),
+				join(dir, tc("unit.tmpl")),
+				join(dir, tc("outline.tmpl")),
+				join(dir, tc("sidenav.tmpl")),
+				join(dir, tc("sidenav-mobile.tmpl")),
+				join(dir, tc("body.tmpl")),
 				example))
 	})
 }
diff --git a/internal/middleware/secureheaders.go b/internal/middleware/secureheaders.go
index cac3ddc..c36a274 100644
--- a/internal/middleware/secureheaders.go
+++ b/internal/middleware/secureheaders.go
@@ -33,7 +33,8 @@
 	// From content/static/html/pages/unit_details.tmpl
 	"'sha256-CFun5NgnYeEpye8qcbQPq5Ycwavi4IXuZiIzSMNqRUw='",
 	"'sha256-IHdniK/yZ8URNA2OYbc4R7BssOAe3/dFrSQW7PxEEfM='",
-	"'sha256-n5SNZQqoMuOrVKSN3pszZlsWNIrr5HaLOezim/jDLuk='",
+	"'sha256-5ThDRcuVP5qPTu7X6eUxhVjOI8mccPcKwzrWDReVV24='",
+	"'sha256-FZ2G7vOsuMYf1kUB+6G3sewY2N9djmeB36q2IBEdBUE='",
 }
 
 // SecureHeaders adds a content-security-policy and other security-related