Commit 9ba992b54c
9ba992b54ce8e646f6f392804fa8e71917f2d340
parent: 1cfdd9630b
Verified · cmc
cmc <hello@cleberg.net> · 2026-09-19 15:40 UTC
web: one centered container for header, main and footer
Ref #226
internal/web/layout_test.go
added
+31
| @@ -0,0 +1,31 @@ |
| 1 | package web |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "testing" |
| 6 | ) |
| 7 | |
| 8 | // The repository header, main and footer share one centered container: |
| 9 | // the header's inner content is wrapped, and the stylesheet caps and |
| 10 | // centers all three on the same token (desktop layout spec). |
| 11 | func TestSharedCenteredContainer(t *testing.T) { |
| 12 | layout, err := templateFS.ReadFile("templates/layout.html") |
| 13 | if err != nil { |
| 14 | t.Fatal(err) |
| 15 | } |
| 16 | if !strings.Contains(string(layout), "<header class=\"repohead\">\n<div class=\"wrap\">") { |
| 17 | t.Fatalf("repohead is not wrapped in .wrap") |
| 18 | } |
| 19 | css := string(StyleCSS) |
| 20 | for _, want := range []string{ |
| 21 | "--container: 100rem;", |
| 22 | "main.content, footer { max-width: calc(var(--container) + 2 * var(--sp-6)); margin: 0 auto; }", |
| 23 | ".repohead .wrap { max-width: var(--container); margin: 0 auto; }", |
| 24 | "main.reading { max-width: calc(72rem + 2 * var(--sp-6)); margin: 0 auto; }", |
| 25 | "main.bounded { max-width: calc(48rem + 2 * var(--sp-6)); margin: 0 auto; }", |
| 26 | } { |
| 27 | if !strings.Contains(css, want) { |
| 28 | t.Errorf("style.css lacks %q", want) |
| 29 | } |
| 30 | } |
| 31 | } |
internal/web/static/style.css
+12 −5
| @@ -125,6 +125,9 @@ |
| 125 | 125 | --rail-mark: 24px; |
| 126 | 126 | --rail-mark-box: 32px; |
| 127 | 127 | |
| 128 | /* the one page container: header content, main and footer align on it */ |
| 129 | --container: 100rem; |
| 130 | |
| 128 | 131 | --r-ctl: 4px; /* controls and chips */ |
| 129 | 132 | --r-card: 6px; /* cards, lists, code blocks */ |
| 130 | 133 | --shadow: 0 4px 16px rgba(0, 0, 0, 0.12); |
| @@ -372,9 +375,10 @@ main.content { |
| 372 | 375 | width: 100%; |
| 373 | 376 | padding: var(--sp-5) var(--sp-6) var(--sp-7); |
| 374 | 377 | } |
| 375 | | main.wide { max-width: none; } |
| 376 | | main.reading { max-width: calc(72rem + 2 * var(--sp-6)); } |
| 377 | | main.bounded { max-width: calc(48rem + 2 * var(--sp-6)); } |
| 378 | main.content, footer { max-width: calc(var(--container) + 2 * var(--sp-6)); margin: 0 auto; } |
| 379 | main.wide { max-width: calc(var(--container) + 2 * var(--sp-6)); } |
| 380 | main.reading { max-width: calc(72rem + 2 * var(--sp-6)); margin: 0 auto; } |
| 381 | main.bounded { max-width: calc(48rem + 2 * var(--sp-6)); margin: 0 auto; } |
| 378 | 382 | footer { |
| 379 | 383 | width: 100%; |
| 380 | 384 | padding: var(--sp-4) var(--sp-6); |
| @@ -388,9 +392,11 @@ footer a:hover { color: var(--link); } |
| 388 | 392 | |
| 389 | 393 | /* ---- repository header ---- */ |
| 390 | 394 | .repohead { |
| 391 | | padding: var(--sp-4) var(--sp-6) 0; |
| 395 | padding: var(--sp-4) 0 0; |
| 392 | 396 | border-bottom: 1px solid var(--line); |
| 393 | 397 | } |
| 398 | .repohead .wrap { max-width: var(--container); margin: 0 auto; } |
| 399 | .repohead .wrap { padding: 0 var(--sp-6); } |
| 394 | 400 | .identity { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; } |
| 395 | 401 | .grow { flex: 1; } |
| 396 | 402 | /* h1 on the repo home, p elsewhere so each page keeps its own h1 — but |
| @@ -1507,7 +1513,8 @@ svg.icon { vertical-align: -0.125em; } |
| 1507 | 1513 | @media (max-width: 52rem) { |
| 1508 | 1514 | main.content { padding: var(--sp-4) var(--sp-4) var(--sp-5); } |
| 1509 | 1515 | footer { padding: var(--sp-4); } |
| 1510 | | .repohead { padding: var(--sp-3) var(--sp-4) 0; } |
| 1516 | .repohead { padding: var(--sp-3) 0 0; } |
| 1517 | .repohead .wrap { padding: 0 var(--sp-4); } |
| 1511 | 1518 | /* seven tabs do not fit a phone in one row; a hidden scrollbar made the |
| 1512 | 1519 | ones past the edge look absent. Two rows show all of them. */ |
| 1513 | 1520 | nav.tabs { flex-wrap: wrap; overflow: visible; } |
internal/web/templates/layout.html
+2
| @@ -37,6 +37,7 @@ |
| 37 | 37 | <div class="pane"> |
| 38 | 38 | {{with field . "Repo"}} |
| 39 | 39 | <header class="repohead"> |
| 40 | <div class="wrap"> |
| 40 | 41 | <div class="identity"> |
| 41 | 42 | {{if field $ "RepoHome"}}<h1 class="repotitle"><a class="owner" href="/{{.OwnerName}}">{{.OwnerName}}</a><span class="sep">/</span>{{.Name}}</h1> |
| 42 | 43 | {{else}}<p class="repotitle"><a class="owner" href="/{{.OwnerName}}">{{.OwnerName}}</a><span class="sep">/</span><a href="/{{.OwnerName}}/{{.Name}}">{{.Name}}</a></p>{{end}} |
| @@ -66,6 +67,7 @@ |
| 66 | 67 | {{if field $ "HasWiki"}}<a {{if eq $top "wiki"}}aria-current="page" {{end}}href="/{{.OwnerName}}/{{.Name}}/wiki">Wiki</a>{{end}} |
| 67 | 68 | {{if field $ "CanAdmin"}}<a {{if eq $top "settings"}}aria-current="page" {{end}}href="/{{.OwnerName}}/{{.Name}}/settings">Settings</a>{{end}} |
| 68 | 69 | </nav> |
| 70 | </div> |
| 69 | 71 | </header> |
| 70 | 72 | {{end}} |
| 71 | 73 | |