Commit 069bd1e107

069bd1e10730364492cbb50ac43b86fcc3a35c40

parent: 479e6687f8

Verified · cmc

cmc <hello@cleberg.net> · 2026-08-24 18:05 UTC

web: align layout grid, card-based listings

One shared .container aligns header, main, and footer to the same
64rem column, so the wordmark and content share a left edge on wide
screens. Index and owner repo lists become a responsive card grid
(auto-fill 19rem columns, meta pinned to card bottom, single column
under 40rem). Owner pages get a bordered profile header and a
repository count. Tree/refs tables, log rows, and issue lists share
the bordered-card treatment with row hover; discussion threads cap at
a 48rem reading measure.

Layout: unified · split

internal/web/static/style.css +109 −25
@@ -94,10 +94,15 @@ code, pre, .code, td.mode, td.size {
94 font-size: var(--fs-1); 94 font-size: var(--fs-1);
95} 95}
96 96
97/* layout */ 97/* layout: one shared container aligns header, content, and footer */
98.container {
99 max-width: 64rem;
100 margin: 0 auto;
101 padding: 0 var(--sp-5);
102}
98header { 103header {
99 border-bottom: 1px solid var(--faint); 104 border-bottom: 1px solid var(--faint);
100 padding: var(--sp-3) var(--sp-5); 105 padding: var(--sp-3) 0;
101} 106}
102a.site { 107a.site {
103 font-weight: 650; 108 font-weight: 650;
@@ -109,10 +114,10 @@ a.site {
109} 114}
110a.site:hover { text-decoration: none; color: var(--accent); } 115a.site:hover { text-decoration: none; color: var(--accent); }
111a.site svg.mark { display: block; } 116a.site svg.mark { display: block; }
112main { 117main.container {
113 max-width: 64rem; 118 width: 100%;
114 margin: 0 auto; 119 padding-top: var(--sp-3);
115 padding: var(--sp-3) var(--sp-5) var(--sp-6); 120 padding-bottom: var(--sp-6);
116} 121}
117 122
118/* tables */ 123/* tables */
@@ -186,7 +191,21 @@ nav.tabs a.active { color: var(--fg); border-bottom-color: var(--accent); font-w
186} 191}
187.pathbar .crumbs strong { color: var(--fg); } 192.pathbar .crumbs strong { color: var(--fg); }
188 193
189/* file table */ 194/* file and refs tables: bordered cards, rows inside */
195table.tree, table.refs {
196 border: 1px solid var(--line);
197 border-radius: var(--r-lg);
198 border-collapse: separate;
199 border-spacing: 0;
200 overflow: hidden;
201 margin-bottom: var(--sp-4);
202}
203table.tree td, table.refs td {
204 padding: var(--sp-2) var(--sp-4);
205 border-bottom: 1px solid var(--faint);
206}
207table.tree tr:last-child td, table.refs tr:last-child td { border-bottom: none; }
208table.tree tr:hover td, table.refs tr:hover td { background: var(--surface); }
190table.tree td.name { width: 100%; } 209table.tree td.name { width: 100%; }
191table.tree td.name a { color: var(--fg); } 210table.tree td.name a { color: var(--fg); }
192table.tree td.name a:hover { color: var(--accent); } 211table.tree td.name a:hover { color: var(--accent); }
@@ -210,14 +229,23 @@ table.refs td.sha { color: var(--muted); }
210} 229}
211 230
212/* log */ 231/* log */
213ul.loglist { list-style: none; margin: var(--sp-3) 0; padding: 0; } 232ul.loglist {
233 list-style: none;
234 margin: var(--sp-3) 0;
235 padding: 0;
236 border: 1px solid var(--line);
237 border-radius: var(--r-lg);
238 overflow: hidden;
239}
214ul.loglist li { 240ul.loglist li {
215 display: flex; 241 display: flex;
216 align-items: baseline; 242 align-items: baseline;
217 gap: var(--sp-3); 243 gap: var(--sp-3);
218 padding: var(--sp-2) 0; 244 padding: var(--sp-2) var(--sp-4);
219 border-bottom: 1px solid var(--faint); 245 border-bottom: 1px solid var(--faint);
220} 246}
247ul.loglist li:last-child { border-bottom: none; }
248ul.loglist li:hover { background: var(--surface); }
221ul.loglist .commitmain { flex: 1; min-width: 0; } 249ul.loglist .commitmain { flex: 1; min-width: 0; }
222ul.loglist p { margin: 0; } 250ul.loglist p { margin: 0; }
223ul.loglist .subject a { color: var(--fg); font-weight: 550; } 251ul.loglist .subject a { color: var(--fg); font-weight: 550; }
@@ -242,14 +270,59 @@ code.fullsha { color: var(--muted); overflow-wrap: anywhere; }
242svg.icon { vertical-align: -0.125em; } 270svg.icon { vertical-align: -0.125em; }
243.lede { font-size: var(--fs-3); margin: var(--sp-2) 0; } 271.lede { font-size: var(--fs-3); margin: var(--sp-2) 0; }
244 272
245/* repo listings (index, owner pages) */ 273/* repo listings (index, owner pages): responsive card grid */
246ul.repolist { list-style: none; margin: var(--sp-2) 0; padding: 0; } 274.repogrid {
247ul.repolist li { padding: var(--sp-3) 0; border-bottom: 1px solid var(--faint); } 275 display: grid;
248ul.repolist li.empty { color: var(--muted); } 276 grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
249ul.repolist p { margin: 0; } 277 gap: var(--sp-3);
250ul.repolist .reponame { font-size: var(--fs-3); font-weight: 550; } 278 margin: var(--sp-3) 0 var(--sp-5);
251ul.repolist .desc { margin-top: 0.1rem; } 279}
252ul.repolist .meta { margin-top: 0.1rem; } 280.repogrid .empty { color: var(--muted); grid-column: 1 / -1; }
281.repocard {
282 display: flex;
283 flex-direction: column;
284 gap: var(--sp-1);
285 border: 1px solid var(--line);
286 border-radius: var(--r-lg);
287 padding: var(--sp-3) var(--sp-4);
288 min-width: 0;
289}
290.repocard p { margin: 0; }
291.repocard .reponame {
292 font-weight: 550;
293 overflow: hidden;
294 text-overflow: ellipsis;
295 white-space: nowrap;
296}
297.repocard .desc {
298 font-size: var(--fs-2);
299 display: -webkit-box;
300 -webkit-line-clamp: 2;
301 -webkit-box-orient: vertical;
302 overflow: hidden;
303}
304.repocard .meta { margin-top: auto; padding-top: var(--sp-1); }
305.repocard .reponame .sep { color: var(--muted); margin: 0 0.1em; }
306
307/* owner profile header */
308.profilehead {
309 border-bottom: 1px solid var(--faint);
310 padding-bottom: var(--sp-4);
311 margin-bottom: var(--sp-4);
312}
313.profilehead h1 { margin: var(--sp-4) 0 var(--sp-1); }
314.profilehead .lede { margin: 0 0 var(--sp-1); }
315.profilehead .meta { margin: var(--sp-1) 0 0; }
316h2 .count { color: var(--muted); font-weight: 400; font-size: var(--fs-2); }
317
318/* heading row with actions on the right */
319.headrow {
320 display: flex;
321 align-items: baseline;
322 gap: var(--sp-4);
323 flex-wrap: wrap;
324}
325.headrow .spacer { flex: 1; }
253 326
254/* member and org chips on owner pages */ 327/* member and org chips on owner pages */
255a.memberchip { 328a.memberchip {
@@ -394,14 +467,23 @@ nav.filters a {
394nav.filters a:hover { color: var(--fg); background: var(--surface); text-decoration: none; } 467nav.filters a:hover { color: var(--fg); background: var(--surface); text-decoration: none; }
395nav.filters a.active { color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); } 468nav.filters a.active { color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); }
396 469
397ul.issuelist { list-style: none; margin: var(--sp-2) 0; padding: 0; } 470ul.issuelist {
471 list-style: none;
472 margin: var(--sp-2) 0;
473 padding: 0;
474 border: 1px solid var(--line);
475 border-radius: var(--r-lg);
476 overflow: hidden;
477}
398ul.issuelist li { 478ul.issuelist li {
399 display: flex; 479 display: flex;
400 align-items: baseline; 480 align-items: baseline;
401 gap: var(--sp-3); 481 gap: var(--sp-3);
402 padding: var(--sp-3) 0; 482 padding: var(--sp-3) var(--sp-4);
403 border-bottom: 1px solid var(--faint); 483 border-bottom: 1px solid var(--faint);
404} 484}
485ul.issuelist li:last-child { border-bottom: none; }
486ul.issuelist li:hover { background: var(--surface); }
405ul.issuelist li.empty { display: block; color: var(--muted); } 487ul.issuelist li.empty { display: block; color: var(--muted); }
406ul.issuelist .issuemain { flex: 1; min-width: 0; } 488ul.issuelist .issuemain { flex: 1; min-width: 0; }
407ul.issuelist p { margin: 0; } 489ul.issuelist p { margin: 0; }
@@ -414,7 +496,10 @@ h2.issuetitle { margin-bottom: var(--sp-1); font-size: var(--fs-5); }
414.issuemeta { color: var(--muted); font-size: var(--fs-2); margin-top: 0; } 496.issuemeta { color: var(--muted); font-size: var(--fs-2); margin-top: 0; }
415.review { font-size: var(--fs-2); } 497.review { font-size: var(--fs-2); }
416 498
417/* comment cards */ 499/* comment cards; discussion stays at a readable measure */
500article.comment, form.commentform {
501 max-width: 48rem;
502}
418article.comment { 503article.comment {
419 border: 1px solid var(--faint); 504 border: 1px solid var(--faint);
420 border-radius: var(--r-lg); 505 border-radius: var(--r-lg);
@@ -484,10 +569,8 @@ button:hover { filter: brightness(1.08); }
484 569
485/* footer */ 570/* footer */
486footer { 571footer {
487 max-width: 64rem;
488 margin: 0 auto;
489 padding: var(--sp-4) var(--sp-5);
490 border-top: 1px solid var(--faint); 572 border-top: 1px solid var(--faint);
573 padding: var(--sp-4) 0;
491 color: var(--muted); 574 color: var(--muted);
492 font-size: var(--fs-1); 575 font-size: var(--fs-1);
493} 576}
@@ -541,8 +624,9 @@ footer a:hover { color: var(--accent); }
541 624
542/* mobile */ 625/* mobile */
543@media (max-width: 40rem) { 626@media (max-width: 40rem) {
544 header { padding: var(--sp-3) var(--sp-4); } 627 .container { padding-left: var(--sp-4); padding-right: var(--sp-4); }
545 main { padding: var(--sp-2) var(--sp-4) var(--sp-6); } 628 main.container { padding-top: var(--sp-2); }
629 .repogrid { grid-template-columns: 1fr; }
546 .thread { margin-left: var(--sp-3); } 630 .thread { margin-left: var(--sp-3); }
547 table.tree td.mode { display: none; } 631 table.tree td.mode { display: none; }
548 .readme, .code { padding: var(--sp-3); } 632 .readme, .code { padding: var(--sp-3); }
internal/web/templates/index.html +14 −11
@@ -1,23 +1,26 @@
1{{define "title"}}{{.Site}}{{end}} 1{{define "title"}}{{.Site}}{{end}}
2{{define "content"}} 2{{define "content"}}
3<div class="headrow">
4<h1>repositories</h1>
5<span class="spacer"></span>
3{{if .Viewer}}<p class="toolbar">logged in as {{.Viewer}} · <a href="/new">new repository</a> · 6{{if .Viewer}}<p class="toolbar">logged in as {{.Viewer}} · <a href="/new">new repository</a> ·
4<form method="post" action="/logout" class="inline"><button type="submit" class="linklike">logout</button></form></p>{{end}} 7<form method="post" action="/logout" class="inline"><button type="submit" class="linklike">logout</button></form></p>{{end}}
5<h1>repositories</h1> 8</div>
6<ul class="repolist"> 9<div class="repogrid">
7{{range .Repos}}<li> 10{{range .Repos}}<div class="repocard">
8 <p class="reponame"><a href="/{{.OwnerName}}">{{.OwnerName}}</a>/<a href="/{{.OwnerName}}/{{.Name}}">{{.Name}}</a>{{if .Settings.Archived}} <span class="chip chip-stale">archived</span>{{end}}</p> 11 <p class="reponame"><a href="/{{.OwnerName}}">{{.OwnerName}}</a><span class="sep">/</span><a href="/{{.OwnerName}}/{{.Name}}">{{.Name}}</a>{{if .Settings.Archived}} <span class="chip chip-stale">archived</span>{{end}}</p>
9 {{if .Desc}}<p class="desc">{{.Desc}}</p>{{end}} 12 {{if .Desc}}<p class="desc">{{.Desc}}</p>{{end}}
10 <p class="meta">{{template "branchicon"}} {{.DefaultBranch}}</p> 13 <p class="meta">{{template "branchicon"}} {{.DefaultBranch}}</p>
11</li> 14</div>
12{{else}}<li class="empty">no public repositories yet</li>{{end}} 15{{else}}<p class="empty">no public repositories yet</p>{{end}}
13</ul> 16</div>
14{{if .Mine}}<h2>your private repositories</h2> 17{{if .Mine}}<h2>your private repositories</h2>
15<ul class="repolist"> 18<div class="repogrid">
16{{range .Mine}}<li> 19{{range .Mine}}<div class="repocard">
17 <p class="reponame"><a href="/{{.OwnerName}}/{{.Name}}">{{.OwnerName}}/{{.Name}}</a> <span class="chip chip-neutral">{{.Visibility}}</span></p> 20 <p class="reponame"><a href="/{{.OwnerName}}/{{.Name}}">{{.OwnerName}}/{{.Name}}</a> <span class="chip chip-neutral">{{.Visibility}}</span></p>
18 {{if .Desc}}<p class="desc">{{.Desc}}</p>{{end}} 21 {{if .Desc}}<p class="desc">{{.Desc}}</p>{{end}}
19 <p class="meta">{{template "branchicon"}} {{.DefaultBranch}}</p> 22 <p class="meta">{{template "branchicon"}} {{.DefaultBranch}}</p>
20</li> 23</div>
21{{end}} 24{{end}}
22</ul>{{end}} 25</div>{{end}}
23{{end}} 26{{end}}
internal/web/templates/layout.html +3 −3
@@ -9,13 +9,13 @@
9</head> 9</head>
10<body> 10<body>
11<header> 11<header>
12 <nav><a class="site" href="/"><svg class="mark" width="20" height="20" viewBox="0 0 24 24" aria-hidden="true"><rect x="1" y="1" width="22" height="22" rx="6" fill="var(--accent)"/><path d="M5 9.5c2-2.5 4-2.5 6 0s4 2.5 6 0" stroke="var(--accent-fg)" stroke-width="2.2" fill="none" stroke-linecap="round"/><path d="M5 15c2-2.5 4-2.5 6 0s4 2.5 6 0" stroke="var(--accent-fg)" stroke-width="2.2" fill="none" stroke-linecap="round"/></svg>{{.Site}}</a></nav> 12 <nav class="container"><a class="site" href="/"><svg class="mark" width="20" height="20" viewBox="0 0 24 24" aria-hidden="true"><rect x="1" y="1" width="22" height="22" rx="6" fill="var(--accent)"/><path d="M5 9.5c2-2.5 4-2.5 6 0s4 2.5 6 0" stroke="var(--accent-fg)" stroke-width="2.2" fill="none" stroke-linecap="round"/><path d="M5 15c2-2.5 4-2.5 6 0s4 2.5 6 0" stroke="var(--accent-fg)" stroke-width="2.2" fill="none" stroke-linecap="round"/></svg>{{.Site}}</a></nav>
13</header> 13</header>
14<main> 14<main class="container">
15{{template "content" .}} 15{{template "content" .}}
16</main> 16</main>
17<footer> 17<footer>
18 <p>powered by <a href="https://gitbay.org/krz/gitbay">gitbay</a>{{with gitbayVersion}} · <code>{{.}}</code>{{end}}</p> 18 <div class="container"><p>powered by <a href="https://gitbay.org/krz/gitbay">gitbay</a>{{with gitbayVersion}} · <code>{{.}}</code>{{end}}</p></div>
19</footer> 19</footer>
20</body> 20</body>
21</html>{{end}} 21</html>{{end}}
internal/web/templates/owner.html +8 −5
@@ -1,16 +1,19 @@
1{{define "title"}}{{.Owner}} · {{.Site}}{{end}} 1{{define "title"}}{{.Owner}} · {{.Site}}{{end}}
2{{define "content"}} 2{{define "content"}}
3<section class="profilehead">
3<h1>{{.Owner}} <span class="chip chip-neutral">{{.Kind}}</span></h1> 4<h1>{{.Owner}} <span class="chip chip-neutral">{{.Kind}}</span></h1>
4{{if .Profile.Description}}<p class="desc lede">{{.Profile.Description}}</p>{{end}} 5{{if .Profile.Description}}<p class="desc lede">{{.Profile.Description}}</p>{{end}}
5{{if .Profile.Website}}<p class="meta"><a href="{{.Profile.Website}}" rel="nofollow me">{{.Profile.Website}}</a></p>{{end}} 6{{if .Profile.Website}}<p class="meta"><a href="{{.Profile.Website}}" rel="nofollow me">{{.Profile.Website}}</a></p>{{end}}
6{{if .Orgs}}<p class="meta">member of {{range .Orgs}}<a class="memberchip" href="/{{.Username}}">{{.Username}}</a> {{end}}</p>{{end}} 7{{if .Orgs}}<p class="meta">member of {{range .Orgs}}<a class="memberchip" href="/{{.Username}}">{{.Username}}</a> {{end}}</p>{{end}}
7{{if .Members}}<p class="meta">members {{range .Members}}<a class="memberchip" href="/{{.Username}}">{{.Username}} <span class="role">{{.Role}}</span></a> {{end}}</p>{{end}} 8{{if .Members}}<p class="meta">members {{range .Members}}<a class="memberchip" href="/{{.Username}}">{{.Username}} <span class="role">{{.Role}}</span></a> {{end}}</p>{{end}}
8<ul class="repolist"> 9</section>
9{{range .Repos}}<li> 10<h2>repositories <span class="count">{{len .Repos}}</span></h2>
11<div class="repogrid">
12{{range .Repos}}<div class="repocard">
10 <p class="reponame"><a href="/{{.OwnerName}}/{{.Name}}">{{.Name}}</a>{{if eq .Visibility "private"}} <span class="chip chip-neutral">private</span>{{end}}{{if .Settings.Archived}} <span class="chip chip-stale">archived</span>{{end}}</p> 13 <p class="reponame"><a href="/{{.OwnerName}}/{{.Name}}">{{.Name}}</a>{{if eq .Visibility "private"}} <span class="chip chip-neutral">private</span>{{end}}{{if .Settings.Archived}} <span class="chip chip-stale">archived</span>{{end}}</p>
11 {{if .Desc}}<p class="desc">{{.Desc}}</p>{{end}} 14 {{if .Desc}}<p class="desc">{{.Desc}}</p>{{end}}
12 <p class="meta">{{template "branchicon"}} {{.DefaultBranch}}</p> 15 <p class="meta">{{template "branchicon"}} {{.DefaultBranch}}</p>
13</li> 16</div>
14{{else}}<li class="empty">no visible repositories</li>{{end}} 17{{else}}<p class="empty">no visible repositories</p>{{end}}
15</ul> 18</div>
16{{end}} 19{{end}}