Commit 307a6f3

Eric Bower  ·  2026-05-13 22:31:44 -0400 EDT
parent 84dd9bd
style: design tweaks
13 files changed,  +407, -797
+5, -6
 1@@ -29,16 +29,15 @@ test:
 2 	go test ./...
 3 .PHONY: test
 4 
 5-static: build clean
 6-	./pgit \
 7+static:
 8+	go run main.go \
 9 		--out ./public \
10 		--label pgit \
11-		--desc "static site generator for git" \
12 		--clone-url "https://github.com/picosh/pgit.git" \
13-		--home-url "https://git.erock.io" \
14-		--theme "dracula" \
15+		--issues-url "https://github.com/picosh/pgit/issues" \
16+		--contrib-url "https://github.com/picosh/pgit/pulls" \
17 		--revs main
18-.PHONY:
19+.PHONY: static
20 
21 dev: static
22 	rsync -rv --delete ./public/ pgs.sh:/git-pgit-local/
+0, -3
 1@@ -11,14 +11,11 @@
 2     {{template "meta" .}}
 3 
 4     <link rel="stylesheet" href="{{.Repo.RootRelative}}vars.css" />
 5-    <link rel="stylesheet" href="{{.Repo.RootRelative}}smol.css" />
 6     <link rel="stylesheet" href="{{.Repo.RootRelative}}main.css" />
 7   </head>
 8   <body>
 9     <header class="box">{{template "header" .}}</header>
10-    <hr class="my" />
11     <main>{{template "content" .}}</main>
12-    <hr class="my" />
13     <footer>{{template "footer" .}}</footer>
14   </body>
15 </html>
+12, -12
 1@@ -5,19 +5,19 @@
 2 {{end}}
 3 
 4 {{define "content"}}
 5-  <dl>
 6-    <dt>commit</dt>
 7-    <dd><a href="{{.CommitURL}}">{{.CommitID}}</a></dd>
 8+  <h2>Commit <code>{{.CommitID}}</code></h2>
 9 
10-    <dt>parent</dt>
11-    <dd><a href="{{.ParentURL}}">{{.Parent}}</a></dd>
12-
13-    <dt>author</dt>
14-    <dd>{{.Commit.Author.Name}}</dd>
15+  <div class="flex justify-between">
16+    <div class="flex items-center gap-xs">
17+      <span>{{.Commit.Author.Name}}</span>
18+      <span>&nbsp;&centerdot;&nbsp;</span>
19+      <span>{{.Commit.Author.When}}</span>
20+    </div>
21 
22-    <dt>date</dt>
23-    <dd>{{.Commit.Author.When}}</dd>
24-  </dl>
25+    <div>
26+      parent <a href="{{.ParentURL}}">{{.Parent}}</a>
27+    </div>
28+  </div>
29 
30   <pre class="white-space-bs">{{.Commit.Message}}</pre>
31 
32@@ -30,7 +30,7 @@
33 
34     <div>
35     {{range .Diff.Files}}
36-      <div class="my-sm">
37+      <div class="my">
38         <span>{{.FileType}}</span>
39         <a href="#diff-{{.Name}}">{{.Name}}</a>
40       </div>
+7, -4
 1@@ -5,10 +5,15 @@
 2 {{end}}
 3 
 4 {{define "content"}}
 5-  <div class="text-md text-transform-none">
 6+  <div class="box sticky flex items-center gap border-visited" style="margin: 0 !important;">
 7+    <code>{{.RevData.Name}}</code>
 8+
 9     {{range .Item.Crumbs}}
10-      <a href="{{.URL}}">{{.Text}}</a> {{if .IsLast}}{{else}}/{{end}}
11+      <a href="{{.URL}}">{{.Text}}</a>
12+      <span>/</span>
13     {{end}}
14+
15+    {{.Item.Name}}
16   </div>
17 
18   {{if .Repo.HideTreeLastCommit}}
19@@ -31,7 +36,5 @@
20   </div>
21   {{end}}
22 
23-  <h2 class="text-lg text-transform-none">{{.Item.Name}}</h2>
24-
25   {{.Contents}}
26 {{end }}
+1, -1
1@@ -1,5 +1,5 @@
2 {{define "footer"}}
3-<div>
4+<div class="mb">
5   built with <a href="https://pgit.pico.sh">pgit</a>
6 </div>
7 {{end}}
+37, -9
 1@@ -1,14 +1,42 @@
 2 {{define "header"}}
 3 <div class="flex flex-col">
 4-  <nav>
 5-    <h1 class="text-xl p-0">
 6-        {{.Repo.RepoName}}
 7-    </h1>
 8-    <a href="{{.SiteURLs.SummaryURL}}">README</a> |
 9-    <a href="{{.SiteURLs.RefsURL}}">refs</a> |
10-    <span class="font-bold">{{.RevData.Name}}</span> |
11-    <a href="{{.RevData.TreeURL}}">code</a> |
12-    <a href="{{.RevData.LogURL}}">commits</a>
13+  <nav class="flex gap">
14+    <a class="btn-nav {{if eq .ActivePage `readme`}}btn-active{{end}}" href="{{.SiteURLs.SummaryURL}}">{{.Repo.RepoName}}</a>
15+    <a class="btn-nav flex items-center {{if eq .ActivePage `code`}}btn-active{{end}}" href="{{.RevData.TreeURL}}">
16+      <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" height="16" width="16" viewBox="0 0 16 16" style="margin-right: 5px;">
17+        <path fill="currentColor" d="m11.28 3.22 4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L13.94 8l-3.72-3.72a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215Zm-6.56 0a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042L2.06 8l3.72 3.72a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L.47 8.53a.75.75 0 0 1 0-1.06Z"></path>
18+      </svg>
19+      code
20+    </a>
21+    <a class="btn-nav flex items-center {{if eq .ActivePage `commits`}}btn-active{{end}}" href="{{.RevData.LogURL}}">
22+      <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" height="16" width="16" viewBox="0 0 16 16" style="margin-right: 5px;">
23+          <path fill="currentColor" d="M11.93 8.5a4.002 4.002 0 0 1-7.86 0H.75a.75.75 0 0 1 0-1.5h3.32a4.002 4.002 0 0 1 7.86 0h3.32a.75.75 0 0 1 0 1.5Zm-1.43-.75a2.5 2.5 0 1 0-5 0 2.5 2.5 0 0 0 5 0Z"></path>
24+      </svg>
25+      commits
26+    </a>
27+    <a class="btn-nav flex items-center {{if eq .ActivePage `refs`}}btn-active{{end}}" href="{{.SiteURLs.RefsURL}}">
28+      <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" height="16" width="16" viewBox="0 0 16 16" style="margin-right: 5px;">
29+        <path fill="currentColor" d="M1 7.775V2.75C1 1.784 1.784 1 2.75 1h5.025c.464 0 .91.184 1.238.513l6.25 6.25a1.75 1.75 0 0 1 0 2.474l-5.026 5.026a1.75 1.75 0 0 1-2.474 0l-6.25-6.25A1.752 1.752 0 0 1 1 7.775Zm1.5 0c0 .066.026.13.073.177l6.25 6.25a.25.25 0 0 0 .354 0l5.025-5.025a.25.25 0 0 0 0-.354l-6.25-6.25a.25.25 0 0 0-.177-.073H2.75a.25.25 0 0 0-.25.25ZM6 5a1 1 0 1 1 0 2 1 1 0 0 1 0-2Z"></path>
30+      </svg>
31+      refs
32+    </a>
33+    {{if .SiteURLs.IssuesURL}}
34+      <a class="btn-nav flex items-center" href="{{.SiteURLs.IssuesURL}}">
35+        <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" height="16" width="16" viewBox="0 0 16 16" style="margin-right: 5px;">
36+          <path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path>
37+          <path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z"></path>
38+        </svg>
39+        issues
40+      </a>
41+    {{end}}
42+    {{if .SiteURLs.ContribURL}}
43+      <a class="btn-nav flex items-center" href="{{.SiteURLs.ContribURL}}">
44+        <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" height="16" width="16" viewBox="0 0 16 16" style="margin-right: 5px;">
45+          <path d="M1.5 3.25a2.25 2.25 0 1 1 3 2.122v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.25 2.25 0 0 1 1.5 3.25Zm5.677-.177L9.573.677A.25.25 0 0 1 10 .854V2.5h1A2.5 2.5 0 0 1 13.5 5v5.628a2.251 2.251 0 1 1-1.5 0V5a1 1 0 0 0-1-1h-1v1.646a.25.25 0 0 1-.427.177L7.177 3.427a.25.25 0 0 1 0-.354ZM3.75 2.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm0 9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm8.25.75a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Z"></path>
46+        </svg>
47+        contrib
48+      </a>
49+    {{end}}
50   </nav>
51 </div>
52 {{end}}
+19, -15
 1@@ -5,27 +5,31 @@
 2 
 3 {{define "content"}}
 4   <div class="group-2">
 5-    <div><span class="font-bold">({{.NumCommits}})</span> commits</div>
 6+    <div class="box sticky flex items-center gap border-visited" style="margin: 0 !important;">
 7+      <code>{{.RevData.Name}}</code>
 8+      <div><span class="font-bold">{{.NumCommits}}</span> commits</div>
 9+    </div>
10+
11     {{range .Logs}}
12       <div>
13-        <div class="flex justify-between items-center">
14+        <div class="flex items-center gap mb text-sm">
15           <a href="{{.URL}}" class="mono">{{.ShortID}}</a>
16 
17-          <div class="mono">
18-            {{range .Refs}}
19-              {{if .URL}}
20-                <a href="{{.URL}}">({{.Refspec}})</a>
21-              {{else}}
22-                ({{.Refspec}})
23-              {{end}}
24-            {{end}}
25+          <div class="flex items-center gap-xs">
26+            <span>{{.AuthorStr}}</span>
27+            <span>&nbsp;&centerdot;&nbsp;</span>
28+            <span>{{.WhenStr}}</span>
29           </div>
30-        </div>
31 
32-        <div class="flex items-center gap-xs text-sm">
33-          <span>{{.AuthorStr}}</span>
34-          <span>&nbsp;&centerdot;&nbsp;</span>
35-          <span>{{.WhenStr}}</span>
36+          {{range .Refs}}
37+            {{if .URL}}
38+              <code>
39+                <a href="{{.URL}}">{{.Refspec}}</a>
40+              </code>
41+            {{else}}
42+              <code>{{.Refspec}}</code>
43+            {{end}}
44+          {{end}}
45         </div>
46 
47         <div>
+11, -9
 1@@ -4,15 +4,17 @@
 2 {{define "meta"}}{{end}}
 3 
 4 {{define "content"}}
 5-  <h2 class="text-lg font-bold">refs</h2>
 6+  <h2>refs</h2>
 7 
 8-  <ul>
 9-  {{range .Refs}}
10-    {{if .URL}}
11-      <li><a href="{{.URL}}">{{.Refspec}}</a></li>
12-    {{else}}
13-      <li>{{.Refspec}}</li>
14+  <div class="box" style="padding: 0 !important;">
15+    {{range .Refs}}
16+      <div class="flex justify-between items-center gap-2 py px tree-row border-b">
17+        {{if .URL}}
18+          <div><a href="{{.URL}}">{{.Refspec}}</a></div>
19+        {{else}}
20+          <div>{{.Refspec}}</div>
21+        {{end}}
22+      </div>
23     {{end}}
24-  {{end}}
25-  </ul>
26+  </div>
27 {{end}}
+6, -5
 1@@ -1,13 +1,14 @@
 2 {{template "base" .}}
 3 
 4-{{define "title"}}{{.Repo.RepoName}}{{if .Repo.Desc}}- {{.Repo.Desc}}{{end}}{{end}}
 5+{{define "title"}}{{.Repo.RepoName}}{{end}}
 6 {{define "meta"}}
 7 <link rel="stylesheet" href="{{.Repo.RootRelative}}syntax.css" />
 8 {{end}}
 9 
10 {{define "content"}}
11-    <div>
12-        {{if .SiteURLs.CloneURL}}<pre class="mb-0">git clone {{.SiteURLs.CloneURL}}</pre>{{end}}
13-    </div>
14-    {{.Readme}}
15+  {{if .SiteURLs.CloneURL}}
16+    <div class="box sticky border-visited mono">git clone {{.SiteURLs.CloneURL}}</div>
17+  {{end}}
18+
19+  {{.Readme}}
20 {{end}}
+12, -10
 1@@ -4,19 +4,21 @@
 2 {{define "meta"}}{{end}}
 3 
 4 {{define "content"}}
 5-  <div>
 6-    <div class="text-md text-transform-none mb">
 7-      {{range .Tree.Crumbs}}
 8-        {{if .IsLast}}
 9-          <span class="font-bold">{{.Text}}</span>
10-        {{else}}
11-          <a href="{{.URL}}">{{.Text}}</a> {{if .IsLast}}{{else}}/{{end}}
12-        {{end}}
13+  <div class="box sticky flex items-center gap border-visited">
14+    <code>{{.RevData.Name}}</code>
15+
16+    {{range .Tree.Crumbs}}
17+      {{if .IsLast}}
18+        <span class="font-bold">{{.Text}}</span>
19+      {{else}}
20+        <a href="{{.URL}}">{{.Text}}</a> {{if .IsLast}}{{else}}<span>/</span>{{end}}
21       {{end}}
22-    </div>
23+    {{end}}
24+  </div>
25 
26+  <div class="box" style="padding: 0 !important;">
27     {{range .Tree.Items}}
28-      <div class="flex justify-between items-center gap-2 p tree-row border-b">
29+      <div class="flex justify-between items-center gap-2 py px tree-row border-b">
30         <div class="flex-1 tree-path flex items-center gap">
31           {{if .IsDir}}
32             <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" height="16" width="16" viewBox="0 0 512 512">
+40, -30
  1@@ -40,8 +40,6 @@ type Config struct {
  2 	// optional params
  3 	// generate logs anad tree based on the git revisions provided
  4 	Revs []string
  5-	// description of repo used in the header of site
  6-	Desc string
  7 	// maximum number of commits that we will process in descending order
  8 	MaxCommits int
  9 	// name of the readme file
 10@@ -53,8 +51,11 @@ type Config struct {
 11 	HideTreeLastCommit bool
 12 
 13 	// user-defined urls
 14-	HomeURL  template.URL
 15 	CloneURL template.URL
 16+	// repo bug tracking url
 17+	IssuesURL template.URL
 18+	// repo code contribution url (e.g. pull requests, patches)
 19+	ContribURL template.URL
 20 
 21 	// https://developer.mozilla.org/en-US/docs/Web/API/URL_API/Resolving_relative_references#root_relative
 22 	RootRelative string
 23@@ -164,16 +165,18 @@ type BranchOutput struct {
 24 }
 25 
 26 type SiteURLs struct {
 27-	HomeURL    template.URL
 28+	IssuesURL  template.URL
 29+	ContribURL template.URL
 30 	CloneURL   template.URL
 31 	SummaryURL template.URL
 32 	RefsURL    template.URL
 33 }
 34 
 35 type PageData struct {
 36-	Repo     *Config
 37-	SiteURLs *SiteURLs
 38-	RevData  *RevData
 39+	Repo       *Config
 40+	SiteURLs   *SiteURLs
 41+	RevData    *RevData
 42+	ActivePage string
 43 }
 44 
 45 type SummaryPageData struct {
 46@@ -454,58 +457,63 @@ func (c *Config) copyStatic(dir string) error {
 47 	return nil
 48 }
 49 
 50-func (c *Config) writeRootSummary(data *PageData, readme template.HTML) {
 51+func (c *Config) writeRootSummary(data PageData, readme template.HTML) {
 52+	data.ActivePage = "readme"
 53 	c.Logger.Info("writing root html", "repoPath", c.RepoPath)
 54 	c.writeHtml(&WriteData{
 55 		Filename: "index.html",
 56 		Template: "html/summary.page.tmpl",
 57 		Data: &SummaryPageData{
 58-			PageData: data,
 59+			PageData: &data,
 60 			Readme:   readme,
 61 		},
 62 	})
 63 }
 64 
 65-func (c *Config) writeTree(data *PageData, tree *TreeRoot) {
 66+func (c *Config) writeTree(data PageData, tree *TreeRoot) {
 67+	data.ActivePage = "code"
 68 	c.Logger.Info("writing tree", "treePath", tree.Path)
 69 	c.writeHtml(&WriteData{
 70 		Filename: "index.html",
 71 		Subdir:   tree.Path,
 72 		Template: "html/tree.page.tmpl",
 73 		Data: &TreePageData{
 74-			PageData: data,
 75+			PageData: &data,
 76 			Tree:     tree,
 77 		},
 78 	})
 79 }
 80 
 81-func (c *Config) writeLog(data *PageData, logs []*CommitData) {
 82+func (c *Config) writeLog(data PageData, logs []*CommitData) {
 83+	data.ActivePage = "commits"
 84 	c.Logger.Info("writing log file", "revision", data.RevData.Name())
 85 	c.writeHtml(&WriteData{
 86 		Filename: "index.html",
 87 		Subdir:   getLogBaseDir(data.RevData),
 88 		Template: "html/log.page.tmpl",
 89 		Data: &LogPageData{
 90-			PageData:   data,
 91+			PageData:   &data,
 92 			NumCommits: len(logs),
 93 			Logs:       logs,
 94 		},
 95 	})
 96 }
 97 
 98-func (c *Config) writeRefs(data *PageData, refs []*RefInfo) {
 99+func (c *Config) writeRefs(data PageData, refs []*RefInfo) {
100+	data.ActivePage = "refs"
101 	c.Logger.Info("writing refs", "repoPath", c.RepoPath)
102 	c.writeHtml(&WriteData{
103 		Filename: "refs.html",
104 		Template: "html/refs.page.tmpl",
105 		Data: &RefPageData{
106-			PageData: data,
107+			PageData: &data,
108 			Refs:     refs,
109 		},
110 	})
111 }
112 
113-func (c *Config) writeHTMLTreeFile(pageData *PageData, treeItem *TreeItem) string {
114+func (c *Config) writeHTMLTreeFile(pageData PageData, treeItem *TreeItem) string {
115+	pageData.ActivePage = "code"
116 	d := filepath.Dir(treeItem.Path)
117 	readme := ""
118 	b, err := treeItem.Entry.Blob().Bytes()
119@@ -531,7 +539,7 @@ func (c *Config) writeHTMLTreeFile(pageData *PageData, treeItem *TreeItem) strin
120 		Filename: fmt.Sprintf("%s.html", treeItem.Entry.Name()),
121 		Template: "html/file.page.tmpl",
122 		Data: &FilePageData{
123-			PageData: pageData,
124+			PageData: &pageData,
125 			Contents: template.HTML(contents),
126 			Item:     treeItem,
127 		},
128@@ -540,7 +548,8 @@ func (c *Config) writeHTMLTreeFile(pageData *PageData, treeItem *TreeItem) strin
129 	return readme
130 }
131 
132-func (c *Config) writeLogDiff(repo *git.Repository, pageData *PageData, commit *CommitData) {
133+func (c *Config) writeLogDiff(repo *git.Repository, pageData PageData, commit *CommitData) {
134+	pageData.ActivePage = "commits"
135 	commitID := commit.ID.String()
136 
137 	c.Mutex.RLock()
138@@ -601,7 +610,7 @@ func (c *Config) writeLogDiff(repo *git.Repository, pageData *PageData, commit *
139 	rnd.Files = fls
140 
141 	commitData := &CommitPageData{
142-		PageData:  pageData,
143+		PageData:  &pageData,
144 		Commit:    commit,
145 		CommitID:  getShortID(commitID),
146 		Diff:      rnd,
147@@ -680,7 +689,8 @@ func (c *Config) getCommitURL(commitID string) template.URL {
148 
149 func (c *Config) getURLs() *SiteURLs {
150 	return &SiteURLs{
151-		HomeURL:    c.HomeURL,
152+		IssuesURL:  c.IssuesURL,
153+		ContribURL: c.ContribURL,
154 		CloneURL:   c.CloneURL,
155 		RefsURL:    c.getRefsURL(),
156 		SummaryURL: c.getSummaryURL(),
157@@ -808,8 +818,8 @@ func (c *Config) writeRepo() *BranchOutput {
158 		Repo:     c,
159 		SiteURLs: c.getURLs(),
160 	}
161-	c.writeRefs(data, refInfoList)
162-	c.writeRootSummary(data, template.HTML(mainOutput.Readme))
163+	c.writeRefs(*data, refInfoList)
164+	c.writeRootSummary(*data, template.HTML(mainOutput.Readme))
165 	return mainOutput
166 }
167 
168@@ -1066,13 +1076,13 @@ func (c *Config) writeRevision(repo *git.Repository, pageData *PageData, refs []
169 			})
170 		}
171 
172-		c.writeLog(pageData, logs)
173+		c.writeLog(*pageData, logs)
174 
175 		for _, cm := range logs {
176 			wg.Add(1)
177 			go func(commit *CommitData) {
178 				defer wg.Done()
179-				c.writeLogDiff(repo, pageData, commit)
180+				c.writeLogDiff(repo, *pageData, commit)
181 			}(cm)
182 		}
183 	}()
184@@ -1107,7 +1117,7 @@ func (c *Config) writeRevision(repo *git.Repository, pageData *PageData, refs []
185 					return
186 				}
187 
188-				readmeStr := c.writeHTMLTreeFile(pageData, entry)
189+				readmeStr := c.writeHTMLTreeFile(*pageData, entry)
190 				if readmeStr != "" {
191 					readme = readmeStr
192 				}
193@@ -1122,7 +1132,7 @@ func (c *Config) writeRevision(repo *git.Repository, pageData *PageData, refs []
194 			wg.Add(1)
195 			go func(tree *TreeRoot) {
196 				defer wg.Done()
197-				c.writeTree(pageData, tree)
198+				c.writeTree(*pageData, tree)
199 			}(t)
200 		}
201 	}()
202@@ -1170,8 +1180,8 @@ func main() {
203 	var themeFlag = flag.String("theme", "dracula", "theme to use for site")
204 	var labelFlag = flag.String("label", "", "pretty name for the subdir where we create the repo, default is last folder in --repo")
205 	var cloneFlag = flag.String("clone-url", "", "git clone URL for upstream")
206-	var homeFlag = flag.String("home-url", "", "URL for breadcumbs to go to root page, hidden if empty")
207-	var descFlag = flag.String("desc", "", "description for repo")
208+	var issuesFlag = flag.String("issues-url", "", "where the repo tracks bug reports")
209+	var contribFlag = flag.String("contrib-url", "", "where the repo tracks code contributions")
210 	var rootRelativeFlag = flag.String("root-relative", "/", "html root relative")
211 	var maxCommitsFlag = flag.Int("max-commits", 0, "maximum number of commits to generate")
212 	var hideTreeLastCommitFlag = flag.Bool("hide-tree-last-commit", false, "dont calculate last commit for each file in the tree")
213@@ -1212,8 +1222,8 @@ func main() {
214 		Theme:              theme,
215 		Logger:             logger,
216 		CloneURL:           template.URL(*cloneFlag),
217-		HomeURL:            template.URL(*homeFlag),
218-		Desc:               *descFlag,
219+		IssuesURL:          template.URL(*issuesFlag),
220+		ContribURL:         template.URL(*contribFlag),
221 		MaxCommits:         *maxCommitsFlag,
222 		HideTreeLastCommit: *hideTreeLastCommitFlag,
223 		RootRelative:       *rootRelativeFlag,
+257, -5
  1@@ -1,10 +1,232 @@
  2+*,
  3+::before,
  4+::after {
  5+  box-sizing: border-box;
  6+}
  7+
  8+::-moz-focus-inner {
  9+  border-style: none;
 10+  padding: 0;
 11+}
 12+:-moz-focusring {
 13+  outline: 1px dotted ButtonText;
 14+}
 15+:-moz-ui-invalid {
 16+  box-shadow: none;
 17+}
 18+
 19+:root {
 20+  --line-height: 1.3rem;
 21+  --grid-height: 0.65rem;
 22+}
 23+
 24+html {
 25+  background-color: var(--bg-color);
 26+  color: var(--text-color);
 27+  font-size: 16px;
 28+  line-height: var(--line-height);
 29+  font-family:
 30+    -apple-system,
 31+    BlinkMacSystemFont,
 32+    "Segoe UI",
 33+    Roboto,
 34+    Oxygen,
 35+    Ubuntu,
 36+    Cantarell,
 37+    "Fira Sans",
 38+    "Droid Sans",
 39+    "Helvetica Neue",
 40+    Arial,
 41+    sans-serif,
 42+    "Apple Color Emoji",
 43+    "Segoe UI Emoji",
 44+    "Segoe UI Symbol";
 45+  -webkit-text-size-adjust: 100%;
 46+  -moz-tab-size: 4;
 47+  -o-tab-size: 4;
 48+  tab-size: 4;
 49+}
 50+
 51+body {
 52+  margin: 0 auto;
 53+  padding: 0 1rem;
 54+}
 55+
 56+img {
 57+  max-width: 100%;
 58+  height: auto;
 59+}
 60+
 61+b,
 62+strong {
 63+  font-weight: bold;
 64+}
 65+
 66+code,
 67+kbd,
 68+samp,
 69+pre {
 70+  font-family: monospace;
 71+}
 72+
 73+code,
 74+kbd,
 75+samp {
 76+  border: 2px solid var(--code);
 77+}
 78+
 79+pre > code {
 80+  background-color: inherit;
 81+  padding: 0;
 82+  border: none;
 83+  border-radius: 0;
 84+}
 85+
 86+code {
 87+  font-size: 90%;
 88+  border-radius: 0.3rem;
 89+  padding: 0.025rem 0.3rem;
 90+  border: 1px solid var(--border);
 91+}
 92+
 93 pre {
 94   border: 1px solid var(--border);
 95   padding: var(--grid-height);
 96+  border-radius: 1px;
 97+  overflow-x: auto;
 98 }
 99 
100-body {
101-  max-width: 900px;
102+h1,
103+h2,
104+h3,
105+h4 {
106+  font-style: normal;
107+  font-size: 1rem;
108+  font-weight: bold;
109+  line-height: var(--line-height);
110+  margin: 0 0 var(--grid-height) 0;
111+  padding: 0;
112+  border: 0;
113+}
114+
115+path {
116+  fill: var(--text-color);
117+}
118+
119+a {
120+  text-decoration: none;
121+  color: var(--text-color);
122+}
123+
124+a:hover,
125+a:visited:hover {
126+  color: var(--visited);
127+  text-decoration: underline;
128+}
129+
130+a:visited {
131+  color: var(--text-color);
132+}
133+
134+header {
135+  margin: 1rem auto;
136+}
137+
138+p {
139+  margin-top: var(--line-height);
140+  margin-bottom: var(--line-height);
141+}
142+
143+footer {
144+  text-align: center;
145+}
146+
147+.font-bold {
148+  font-weight: bold;
149+}
150+
151+.mono {
152+  font-family: monospace;
153+}
154+
155+.text-sm {
156+  font-size: 0.8rem;
157+}
158+
159+.flex {
160+  display: flex;
161+}
162+
163+.flex-col {
164+  flex-direction: column;
165+}
166+
167+.items-center {
168+  align-items: center;
169+}
170+
171+.m-0 {
172+  margin: 0;
173+}
174+
175+.mb {
176+  margin-bottom: var(--grid-height);
177+}
178+
179+.mb-0 {
180+  margin-bottom: 0;
181+}
182+
183+.my {
184+  margin-top: var(--grid-height);
185+  margin-bottom: var(--grid-height);
186+}
187+
188+.px {
189+  padding-left: 0.5rem;
190+  padding-right: 0.5rem;
191+}
192+
193+.py {
194+  padding-top: var(--grid-height);
195+  padding-bottom: var(--grid-height);
196+}
197+
198+.justify-between {
199+  justify-content: space-between;
200+}
201+
202+.justify-center {
203+  justify-content: center;
204+}
205+
206+.gap {
207+  gap: var(--grid-height);
208+}
209+
210+.gap-2 {
211+  gap: var(--line-height);
212+}
213+
214+.group {
215+  display: flex;
216+  flex-direction: column;
217+  gap: var(--grid-height);
218+}
219+
220+.group-2 {
221+  display: flex;
222+  flex-direction: column;
223+  gap: var(--line-height);
224+}
225+
226+.flex-1 {
227+  flex: 1;
228+}
229+
230+.box {
231+  border: 2px solid var(--grey-light);
232+  padding: var(--grid-height);
233 }
234 
235 .border-b {
236@@ -21,6 +243,10 @@ body {
237   border: 1px solid var(--border);
238 }
239 
240+.border-visited {
241+  border-color: var(--visited);
242+}
243+
244 .tree-size {
245   width: 60px;
246   text-align: right;
247@@ -30,9 +256,15 @@ body {
248   text-wrap: wrap;
249 }
250 
251+.sticky {
252+  position: sticky;
253+  top: 0;
254+  left: 0;
255+  background-color: var(--bg-color);
256+}
257+
258 .diff-file {
259   align-items: center;
260-  height: 62px;
261   position: sticky;
262   top: 0;
263   left: 0;
264@@ -43,11 +275,31 @@ body {
265   white-space: break-spaces;
266 }
267 
268-.mb-0 {
269-  margin-bottom: 0;
270+.btn-nav {
271+  border-radius: 4px;
272+  padding: 6px 10px;
273+  border: 1px solid var(--border);
274+}
275+
276+.btn-nav:hover, .btn-active {
277+  border-color: var(--visited);
278+  text-decoration: none;
279+  color: var(--text-color);
280 }
281 
282 @media only screen and (max-width: 900px) {
283+  body {
284+    padding: 0 1rem;
285+  }
286+
287+  header {
288+    margin: 0;
289+  }
290+
291+  .flex-collapse {
292+    flex-direction: column;
293+  }
294+
295   .tree-commit {
296     display: none;
297   }
+0, -688
  1@@ -1,688 +0,0 @@
  2-*,
  3-::before,
  4-::after {
  5-  box-sizing: border-box;
  6-}
  7-
  8-::-moz-focus-inner {
  9-  border-style: none;
 10-  padding: 0;
 11-}
 12-:-moz-focusring {
 13-  outline: 1px dotted ButtonText;
 14-}
 15-:-moz-ui-invalid {
 16-  box-shadow: none;
 17-}
 18-
 19-:root {
 20-  --line-height: 1.3rem;
 21-  --grid-height: 0.65rem;
 22-}
 23-
 24-html {
 25-  background-color: var(--bg-color);
 26-  color: var(--text-color);
 27-  font-size: 16px;
 28-  line-height: var(--line-height);
 29-  font-family:
 30-    -apple-system,
 31-    BlinkMacSystemFont,
 32-    "Segoe UI",
 33-    Roboto,
 34-    Oxygen,
 35-    Ubuntu,
 36-    Cantarell,
 37-    "Fira Sans",
 38-    "Droid Sans",
 39-    "Helvetica Neue",
 40-    Arial,
 41-    sans-serif,
 42-    "Apple Color Emoji",
 43-    "Segoe UI Emoji",
 44-    "Segoe UI Symbol";
 45-  -webkit-text-size-adjust: 100%;
 46-  -moz-tab-size: 4;
 47-  -o-tab-size: 4;
 48-  tab-size: 4;
 49-}
 50-
 51-body {
 52-  margin: 0 auto;
 53-}
 54-
 55-img {
 56-  max-width: 100%;
 57-  height: auto;
 58-}
 59-
 60-b,
 61-strong {
 62-  font-weight: bold;
 63-}
 64-
 65-code,
 66-kbd,
 67-samp,
 68-pre {
 69-  font-family: monospace;
 70-}
 71-
 72-code,
 73-kbd,
 74-samp {
 75-  border: 2px solid var(--code);
 76-}
 77-
 78-pre > code {
 79-  background-color: inherit;
 80-  padding: 0;
 81-  border: none;
 82-  border-radius: 0;
 83-}
 84-
 85-code {
 86-  font-size: 90%;
 87-  border-radius: 0.3rem;
 88-  padding: 0.025rem 0.3rem;
 89-}
 90-
 91-pre {
 92-  font-size: 0.8rem;
 93-  border-radius: 1px;
 94-  padding: var(--line-height);
 95-  overflow-x: auto;
 96-  background-color: var(--pre);
 97-}
 98-
 99-small {
100-  font-size: 0.8rem;
101-}
102-
103-details {
104-  border: 2px solid var(--grey-light);
105-  padding: calc(var(--grid-height) - 2px) 1ch;
106-  margin-bottom: var(--grid-height);
107-}
108-
109-details[open] summary {
110-  margin-bottom: var(--grid-height);
111-}
112-
113-summary {
114-  display: list-item;
115-  cursor: pointer;
116-}
117-
118-h1,
119-h2,
120-h3,
121-h4 {
122-  margin: 0;
123-  padding: 0;
124-  border: 0;
125-  font-style: normal;
126-  font-weight: inherit;
127-  font-size: inherit;
128-}
129-
130-path {
131-  fill: var(--text-color);
132-  stroke: var(--text-color);
133-}
134-
135-hr {
136-  color: inherit;
137-  border: 0;
138-  height: 2px;
139-  background: var(--grey);
140-  margin: calc(var(--grid-height) - 2px) auto;
141-}
142-
143-a {
144-  text-decoration: none;
145-  color: var(--link-color);
146-}
147-
148-a:hover,
149-a:visited:hover {
150-  text-decoration: underline;
151-}
152-
153-a:visited {
154-  color: var(--visited);
155-}
156-
157-section {
158-  margin-bottom: 1.4rem;
159-}
160-
161-section:last-child {
162-  margin-bottom: 0;
163-}
164-
165-header {
166-  margin: 1rem auto;
167-}
168-
169-p {
170-  margin-top: var(--line-height);
171-  margin-bottom: var(--line-height);
172-}
173-
174-article {
175-  overflow-wrap: break-word;
176-}
177-
178-blockquote {
179-  border-left: 5px solid var(--blockquote);
180-  background-color: var(--blockquote-bg);
181-  padding: var(--grid-height);
182-  margin: var(--line-height) 0;
183-}
184-
185-blockquote > p {
186-  margin: 0;
187-}
188-
189-blockquote code {
190-  border: 1px solid var(--blockquote);
191-}
192-
193-ul {
194-  padding: 0 0 0 var(--line-height);
195-  list-style-position: inside;
196-  list-style-type: square;
197-  margin: var(--line-height) 0;
198-}
199-
200-ul[style*="list-style-type: none;"] {
201-  padding: 0;
202-}
203-
204-ol {
205-  padding: 0 0 0 var(--line-height);
206-  list-style-type: decimal;
207-  margin: var(--line-height) 0;
208-}
209-
210-ol[style*="list-style-type: none;"] {
211-  padding: 0;
212-}
213-
214-ol ul, ol ol, ul ol, ul ul {
215-  padding: 0 0 0 3ch;
216-  margin: 0;
217-}
218-
219-li {
220-  margin: 0;
221-  padding: 0;
222-}
223-
224-li::marker {
225-  line-height: 0;
226-}
227-
228-footer {
229-  text-align: center;
230-  margin-bottom: calc(var(--line-height) * 3);
231-}
232-
233-dt {
234-  font-weight: bold;
235-}
236-
237-dd {
238-  margin-left: 0;
239-}
240-
241-dd:not(:last-child) {
242-  margin-bottom: 0.5rem;
243-}
244-
245-figure {
246-  margin: 0;
247-}
248-
249-sup {
250-  line-height: 0;
251-}
252-
253-#toc {
254-  margin-top: var(--line-height);
255-}
256-
257-.container {
258-  max-width: 50em;
259-  width: 100%;
260-}
261-
262-.container-sm {
263-  max-width: 40em;
264-  width: 100%;
265-}
266-
267-.mono {
268-  font-family: monospace;
269-}
270-
271-.link-alt-hover,
272-.link-alt-hover:visited,
273-.link-alt-hover:visited:hover,
274-.link-alt-hover:hover {
275-  color: var(--hover);
276-  text-decoration: none;
277-}
278-
279-.link-alt-hover:visited:hover,
280-.link-alt-hover:hover {
281-  text-decoration: underline;
282-}
283-
284-.link-alt,
285-.link-alt:visited,
286-.link-alt:visited:hover,
287-.link-alt:hover {
288-  color: var(--white);
289-  text-decoration: none;
290-}
291-
292-.link-alt:visited:hover,
293-.link-alt:hover {
294-  text-decoration: underline;
295-}
296-
297-.text-2xl code, .text-xl code, .text-lg code, .text-md code {
298-  text-transform: none;
299-}
300-
301-.text-2xl {
302-  font-size: var(--line-height);
303-  font-weight: bold;
304-  line-height: var(--line-height);
305-  margin-bottom: var(--grid-height);
306-  text-transform: uppercase;
307-}
308-
309-.text-xl, .text-lg, .text-md {
310-  font-size: 1rem;
311-  font-weight: bold;
312-  line-height: var(--line-height);
313-  margin-bottom: var(--grid-height);
314-  text-transform: uppercase;
315-}
316-
317-.text-sm {
318-  font-size: 0.8rem;
319-}
320-
321-.w-full {
322-  width: 100%;
323-}
324-
325-.border {
326-  border: 2px solid var(--grey-light);
327-}
328-
329-.text-left {
330-  text-align: left;
331-}
332-
333-.text-center {
334-  text-align: center;
335-}
336-
337-.text-underline {
338-  text-decoration: underline;
339-  text-decoration-thickness: 2px;
340-}
341-
342-.text-hdr {
343-  color: var(--hover);
344-}
345-
346-.font-bold {
347-  font-weight: bold;
348-}
349-
350-.font-italic {
351-  font-style: italic;
352-}
353-
354-.inline {
355-  display: inline;
356-}
357-
358-.inline-block {
359-  display: inline-block;
360-}
361-
362-.max-w-half {
363-  max-width: 50%;
364-}
365-
366-.flex {
367-  display: flex;
368-}
369-
370-.flex-col {
371-  flex-direction: column;
372-}
373-
374-.flex-wrap {
375-  flex-wrap: wrap;
376-}
377-
378-.items-center {
379-  align-items: center;
380-}
381-
382-.m-0 {
383-  margin: 0;
384-}
385-
386-.mt-0 {
387-  margin-top: 0;
388-}
389-
390-.mt {
391-  margin-top: var(--grid-height);
392-}
393-
394-.mt-2 {
395-  margin-top: var(--line-height);
396-}
397-
398-.mt-4 {
399-  margin-top: calc(var(--line-height) * 2);
400-}
401-
402-.mb {
403-  margin-bottom: var(--grid-height);
404-}
405-
406-.mb-2 {
407-  margin-bottom: var(--line-height);
408-}
409-
410-.mb-4 {
411-  margin-bottom: calc(var(--line-height) * 2);
412-}
413-
414-.mr {
415-  margin-right: 0.5rem;
416-}
417-
418-.ml {
419-  margin-left: 0.5rem;
420-}
421-
422-.my {
423-  margin-top: var(--grid-height);
424-  margin-bottom: var(--grid-height);
425-}
426-
427-.my-2 {
428-  margin-top: var(--line-height);
429-  margin-bottom: var(--line-height);
430-}
431-
432-.my-4 {
433-  margin-top: calc(var(--line-height) * 2);
434-  margin-bottom: calc(var(--line-height) * 2);
435-}
436-
437-.p-0 {
438-  padding: 0;
439-}
440-
441-.px {
442-  padding-left: 0.5rem;
443-  padding-right: 0.5rem;
444-}
445-
446-.px-4 {
447-  padding-left: 2rem;
448-  padding-right: 2rem;
449-}
450-
451-.py {
452-  padding-top: var(--grid-height);
453-  padding-bottom: var(--grid-height);
454-}
455-
456-.py-4 {
457-  padding-top: calc(var(--line-height) * 2);
458-  padding-bottom: calc(var(--line-height) * 2);
459-}
460-
461-.justify-between {
462-  justify-content: space-between;
463-}
464-
465-.justify-center {
466-  justify-content: center;
467-}
468-
469-.gap {
470-  gap: var(--grid-height);
471-}
472-
473-.gap-2 {
474-  gap: var(--line-height);
475-}
476-
477-.group {
478-  display: flex;
479-  flex-direction: column;
480-  gap: var(--grid-height);
481-}
482-
483-.group-2 {
484-  display: flex;
485-  flex-direction: column;
486-  gap: var(--line-height);
487-}
488-
489-.group-h {
490-  display: flex;
491-  gap: var(--grid-height);
492-  align-items: center;
493-}
494-
495-.flex-1 {
496-  flex: 1;
497-}
498-
499-.items-end {
500-  align-items: end;
501-}
502-
503-.items-start {
504-  align-items: start;
505-}
506-
507-.justify-end {
508-  justify-content: end;
509-}
510-
511-.font-grey-light {
512-  color: var(--grey-light);
513-}
514-
515-.hidden {
516-  display: none;
517-}
518-
519-.align-right {
520-  text-align: right;
521-}
522-
523-.text-transform-none {
524-  text-transform: none;
525-}
526-
527-/* ==== MARKDOWN ==== */
528-
529-.md h1,
530-.md h2,
531-.md h3,
532-.md h4 {
533-  padding: 0;
534-  margin: 0;
535-  /* margin: 1.5rem 0 0.9rem 0; */
536-  font-weight: bold;
537-}
538-
539-.md h1 a,
540-.md h2 a,
541-.md h3 a,
542-.md h4 a {
543-  color: var(--grey-light);
544-  text-decoration: none;
545-}
546-
547-h1 code, h2 code, h3 code, h4 code {
548-  text-transform: none;
549-}
550-
551-.md h1 {
552-  font-size: 1rem;
553-  line-height: var(--line-height);
554-  margin-top: calc(var(--line-height) * 2);
555-  margin-bottom: var(--grid-height);
556-  text-transform: uppercase;
557-}
558-
559-.md h2, .md h3, .md h4 {
560-  font-size: 1rem;
561-  line-height: var(--line-height);
562-  margin-top: calc(var(--line-height) * 2);
563-  margin-bottom: var(--line-height);
564-  text-transform: uppercase;
565-  color: var(--white-dark);
566-}
567-
568-/* ==== HELPERS ==== */
569-
570-.logo-header {
571-  line-height: 1;
572-  display: inline-block;
573-  background-color: #ff79c6;
574-  background-image: linear-gradient(to right, #ff5555, #ff79c6, #f8f859);
575-  color: transparent;
576-  background-clip: text;
577-  border: 3px solid #ff79c6;
578-  padding: 8px 10px 10px 10px;
579-  border-radius: 10px;
580-  background-size: 100%;
581-  margin: 0;
582-  -webkit-background-clip: text;
583-  -moz-background-clip: text;
584-  -webkit-text-fill-color: transparent;
585-  -moz-text-fill-color: transparent;
586-}
587-
588-.btn {
589-  border: 2px solid var(--link-color);
590-  color: var(--link-color);
591-  padding: 0.4rem 1rem;
592-  font-weight: bold;
593-  display: inline-block;
594-}
595-
596-.btn-link,
597-.btn-link:visited {
598-  border: 2px solid var(--link-color);
599-  color: var(--link-color);
600-  padding: var(--grid-height);
601-  text-decoration: none;
602-  font-weight: bold;
603-  display: inline-block;
604-}
605-
606-.box {
607-  border: 2px solid var(--grey-light);
608-  padding: var(--grid-height);
609-}
610-
611-.box-sm {
612-  border: 2px solid var(--grey-light);
613-  padding: var(--grid-height);
614-}
615-
616-.box-alert {
617-  border: 2px solid var(--hover);
618-  padding: var(--line-height);
619-}
620-
621-.box-sm-alert {
622-  border: 2px solid var(--hover);
623-  padding: var(--grid-height);
624-}
625-
626-.list-none {
627-  list-style-type: none;
628-}
629-
630-.list-square {
631-  list-style-type: square;
632-}
633-
634-.list-disc {
635-  list-style-type: disc;
636-}
637-
638-.list-decimal {
639-  list-style-type: decimal;
640-}
641-
642-.pill {
643-  border: 1px solid var(--link-color);
644-  color: var(--link-color);
645-}
646-
647-.pill-alert {
648-  border: 1px solid var(--hover);
649-  color: var(--hover);
650-}
651-
652-.pill-info {
653-  border: 1px solid var(--visited);
654-  color: var(--visited);
655-}
656-
657-@media only screen and (max-width: 40em) {
658-  body {
659-    padding: 0 1rem;
660-  }
661-
662-  header {
663-    margin: 0;
664-  }
665-
666-  .flex-collapse {
667-    flex-direction: column;
668-  }
669-}
670-
671-#debug {
672-  position: relative;
673-}
674-
675-#debug .debug-grid {
676-  width: 100%;
677-  height: 100%;
678-  position: absolute;
679-  top: 0;
680-  left: 0;
681-  right: 0;
682-  bottom: 0;
683-  z-index: -1;
684-  background-image:
685-    repeating-linear-gradient(var(--code) 0 1px, transparent 1px 100%),
686-    repeating-linear-gradient(90deg, var(--code) 0 1px, transparent 1px 100%);
687-  background-size: 1ch var(--grid-height);
688-  margin: 0;
689-}