socle

the entrance: repos as exhibit stands on pedestals

commit dc0cfb64ecb1f5db844e01685c93c02272f572f5 · parent 307eaf9bb4 · browse this tree
Ximianth, 2026-10-26 23:16:26 UTC

Each repo at the index stands on a plinth miniature drawn by the ground
glyph's generator family, with the description as a placard beneath and
the newest commit subject as a one-line caption in soft ink.  The stands
flow in a responsive grid, so the entrance also stops crushing at phone
widths.  The placard carries a dominant-language dot when the language
meter knows one; enrichment stays best-effort, matching the caption's
existing stance.  The swatch style is shared with the meter's legend
rather than duplicated.
diff --git a/chrome.go b/chrome.go
index 51a9152..ed8bc62 100644
--- a/chrome.go
+++ b/chrome.go
@@ -188,6 +188,42 @@ nav.arc.corner a {
 	font-size: .72rem;
 }
 
+/* The entrance: repos as exhibit stands, surveyed rather than parsed.
+   Each stand is name / pedestal / placard / one-line "now showing" caption. */
+article ul.entrance {
+	list-style: none;
+	margin: 2.5rem 0 1rem;
+	padding: 0;
+	display: grid;
+	grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+	gap: 2.8rem 1.5rem;
+}
+.stand { text-align: center; min-width: 0; }
+.stand .name { font-size: 1.35rem; margin: 0; }
+.stand .name a { text-decoration: none; }
+.stand .name a:hover { text-decoration: underline; }
+.stand .pedestal {
+	height: 84px;
+	background-position: top center;
+	background-size: 140px 84px;
+	background-repeat: no-repeat;
+}
+.stand .placard {
+	font-size: .8rem;
+	letter-spacing: .12em;
+	color: var(--ink-soft);
+	margin: .7rem 0 .15rem;
+}
+.stand .showing {
+	font-size: .78rem;
+	font-style: italic;
+	color: var(--ink-soft);
+	margin: 0;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
+
 /* The tagline: the repo description, read as the exhibit's subtitle. */
 article .tagline {
 	text-align: center;
@@ -231,7 +267,8 @@ article .langmeter {
 }
 .langmeter .chip { white-space: nowrap; }
 .langmeter .chip + .chip { margin-left: .9rem; }
-.langmeter .swatch {
+/* The swatch is shared: the meter's legend and the entrance placards both wear it. */
+.swatch {
 	display: inline-block;
 	width: 10px;
 	height: 10px;
@@ -316,6 +353,7 @@ func sketchCSS(scheme string) string {
 	return fmt.Sprintf(`
 .flank { background-image: %s; }
 footer.ground .floor { background-image: %s; }
+.stand .pedestal { background-image: %s; }
 /* Hand-ruled lines replace solid borders on the nav and headings. */
 nav.crumbs, article h1, article h2 {
 	border-bottom: none;
@@ -350,7 +388,7 @@ nav.arc a.va:active { background-image: %s; }
 nav.arc a.vb:active { background-image: %s; }
 nav.arc a.vc:active { background-image: %s; }
 `,
-		u("flank"), u("ground"), u("rule"), u("frame"), u("frame"),
+		u("flank"), u("ground"), u("plinth"), u("rule"), u("frame"), u("frame"),
 		u("circle-a"), u("circle-b"), u("circle-c"),
 		u("circle-b"), u("circle-c"), u("circle-a"),
 		u("circle-c"), u("circle-a"), u("circle-b"))
diff --git a/pages.go b/pages.go
index 9fc9775..9d01fd7 100644
--- a/pages.go
+++ b/pages.go
@@ -87,27 +87,42 @@ func repoCrumbs(name string, more ...crumbLink) []display.Crumb {
 
 // ---- index ----
 
+// The entrance: every repo is an exhibit stand -- the name atop a pedestal
+// (the ground glyph's family), the description as its placard, and the newest
+// commit subject as a one-line "now showing" caption in soft ink.
 var indexTmpl = template.Must(template.New("index").Parse(`<h1>repositories</h1>
-<table class="log">
-<tbody>
-{{range .}}<tr><td class="name"><a href="/{{.Name}}/">{{.Name}}</a></td><td>{{.Description}}</td><td class="soft">{{.Head}}</td></tr>
-{{end}}</tbody>
-</table>
+<ul class="entrance">
+{{range .}}<li class="stand">
+<p class="name"><a href="/{{.Name}}/">{{.Name}}</a></p>
+<div class="pedestal"></div>
+{{if or .Description .LangColor}}<p class="placard">{{if .LangColor}}<span class="swatch" style="background:{{.LangColor}}" title="{{.Lang}}"></span>{{end}}{{.Description}}</p>
+{{end}}{{if .Head}}<p class="showing" title="latest commit on {{.Branch}}">{{.Head}}</p>
+{{end}}</li>
+{{end}}</ul>
 `))
 
 type indexRow struct {
 	Name        string
 	Description string
+	Branch      string // the default branch the caption was read from
 	Head        string // subject of the newest commit on the default branch; empty if none
+	Lang        string // dominant language at that commit; empty if nothing classified
+	LangColor   string // its linguist color; the placard dot only shows when one exists
 }
 
 func (s *server) serveIndex(w http.ResponseWriter, r *http.Request) {
 	var rows []indexRow
 	for _, name := range sortedNames(s.repos) {
 		rp := s.repos[name]
-		row := indexRow{Name: name, Description: rp.description}
-		if cs, err := rp.commits(rp.defaultBranch(), 1); err == nil && len(cs) > 0 {
+		row := indexRow{Name: name, Description: rp.description, Branch: rp.defaultBranch()}
+		// Enrichment is best-effort, matching the caption's existing stance:
+		// the entrance still stands when one repo cannot answer.
+		if cs, err := rp.commits(row.Branch, 1); err == nil && len(cs) > 0 {
 			row.Head = cs[0].subject
+			if stats, err := s.langStats(rp, cs[0].hash); err == nil && len(stats) > 0 {
+				row.Lang = stats[0].name
+				row.LangColor = enry.GetColor(stats[0].name)
+			}
 		}
 		rows = append(rows, row)
 	}
diff --git a/sketch.go b/sketch.go
index 3f1a993..16b5284 100644
--- a/sketch.go
+++ b/sketch.go
@@ -138,6 +138,7 @@ func sketchAssets(seed string) map[string][]byte {
 		m["rule-"+p.scheme+".svg"] = ruleSVG(seed, p)
 		m["flank-"+p.scheme+".svg"] = flankSVG(seed, p)
 		m["ground-"+p.scheme+".svg"] = groundSVG(seed, p)
+		m["plinth-"+p.scheme+".svg"] = plinthSVG(seed, p)
 		for _, v := range []string{"a", "b", "c"} {
 			m["circle-"+v+"-"+p.scheme+".svg"] = circleSVG(seed+"/"+v, p)
 		}
@@ -317,3 +318,24 @@ func groundSVG(seed string, p sketchPalette) []byte {
 	sb.WriteString("</svg>\n")
 	return []byte(sb.String())
 }
+
+// plinthSVG is the entrance's pedestal: the ground glyph's cap/die/plinth family,
+// redrawn at exhibit-stand size, with the usual crisp and ghost passes.
+// Repos stand on these at the index; the footer's empty pedestal echoes them.
+func plinthSVG(seed string, p sketchPalette) []byte {
+	rng := newSketchRand(seed + "/plinth/" + p.scheme)
+	var sb strings.Builder
+	svgOpen(&sb, 140, 84)
+	boxes := func(d *strings.Builder, amp, over float64) {
+		roughBox(d, rng, 20, 10, 100, 14, amp, over)
+		roughBox(d, rng, 34, 24, 72, 40, amp, over)
+		roughBox(d, rng, 10, 64, 120, 16, amp, over)
+	}
+	var d1, d2 strings.Builder
+	boxes(&d1, 1.0, 2.5)
+	boxes(&d2, 1.5, 3.0)
+	svgPath(&sb, d1.String(), p.ink, 1.6, 0.75)
+	svgPath(&sb, d2.String(), p.ink, 1.2, 0.28)
+	sb.WriteString("</svg>\n")
+	return []byte(sb.String())
+}
diff --git a/testdata/pages.md b/testdata/pages.md
index 7356d41..f5b0546 100644
--- a/testdata/pages.md
+++ b/testdata/pages.md
@@ -38,11 +38,14 @@ Content-Type: text/html; charset=utf-8
 socle<span class="theme">@test</span>
 </nav>
 <main><article><h1>repositories</h1>
-<table class="log">
-<tbody>
-<tr><td class="name"><a href="/fixturama/">fixturama</a></td><td>the fixture</td><td class="soft">add docs and src</td></tr>
-</tbody>
-</table>
+<ul class="entrance">
+<li class="stand">
+<p class="name"><a href="/fixturama/">fixturama</a></p>
+<div class="pedestal"></div>
+<p class="placard"><span class="swatch" style="background:#00ADD8" title="Go"></span>the fixture</p>
+<p class="showing" title="latest commit on main">add docs and src</p>
+</li>
+</ul>
 </article></main>
 </div>
 <footer class="ground">
diff --git a/testdata/sketch.md b/testdata/sketch.md
index 1cf595f..b55977c 100644
--- a/testdata/sketch.md
+++ b/testdata/sketch.md
@@ -46,6 +46,16 @@ Regenerate with `go test -run TestSketchSnapshots -testmark.regen` and review th
 </svg>
 ```
 
+### plinth-light.svg
+
+[testmark]:# (plinth-light.svg)
+```svg
+<svg xmlns="http://www.w3.org/2000/svg" width="140" height="84" viewBox="0 0 140 84">
+<path d="M17.4 9.8C52.4 10.3 87.4 9.7 122.5 9.9M119.5 10.1C120.4 15.0 120.7 19.9 120.0 24.8M121.2 24.0C87.0 23.2 52.8 23.5 18.6 24.0M20.0 25.3C19.9 19.7 19.3 14.0 19.9 8.4M32.8 23.9C57.4 24.1 82.1 24.1 106.8 24.1M105.6 21.8C105.1 35.8 106.5 49.9 106.0 63.9M106.8 64.1C82.4 63.1 57.9 63.2 33.4 64.1M33.7 66.7C33.0 51.7 34.0 36.8 34.2 21.9M8.0 64.0C49.3 63.8 90.7 63.0 132.1 63.8M129.5 63.8C129.3 69.9 130.1 76.0 130.5 82.0M129.8 80.2C89.2 79.5 48.7 79.1 8.1 79.6M10.2 80.3C9.4 74.0 9.5 67.8 9.5 61.5" fill="none" stroke="#24292f" stroke-width="1.6" stroke-opacity="0.75" stroke-linecap="round"/>
+<path d="M18.4 10.7C52.8 10.8 87.2 8.9 121.6 9.8M120.6 9.2C120.8 14.9 121.0 20.6 120.3 26.3M120.6 23.9C86.7 22.4 52.9 22.4 19.1 23.3M19.7 24.8C20.6 19.0 21.6 13.3 20.5 7.4M32.5 24.5C57.0 24.6 81.6 24.4 106.1 23.4M106.1 21.5C104.6 36.4 106.1 51.5 105.8 66.4M106.2 64.7C81.5 65.4 56.8 66.0 32.1 64.6M33.8 67.2C33.2 52.6 34.3 38.0 33.6 23.4M9.6 64.6C50.0 64.0 90.3 65.2 130.7 63.6M129.5 62.2C129.9 68.9 129.4 75.7 130.7 82.4M130.1 79.7C89.8 80.0 49.5 80.0 9.1 80.6M9.3 81.3C9.8 75.1 10.1 69.0 9.7 62.8" fill="none" stroke="#24292f" stroke-width="1.2" stroke-opacity="0.28" stroke-linecap="round"/>
+</svg>
+```
+
 ### circle-a-light.svg
 
 [testmark]:# (circle-a-light.svg)
@@ -117,6 +127,16 @@ Regenerate with `go test -run TestSketchSnapshots -testmark.regen` and review th
 </svg>
 ```
 
+### plinth-dark.svg
+
+[testmark]:# (plinth-dark.svg)
+```svg
+<svg xmlns="http://www.w3.org/2000/svg" width="140" height="84" viewBox="0 0 140 84">
+<path d="M17.9 10.4C52.3 11.1 86.6 9.9 121.0 10.5M119.7 9.4C118.9 15.0 119.9 20.5 119.6 26.0M121.0 24.1C87.2 23.9 53.5 24.5 19.8 23.6M19.8 26.2C20.8 20.0 20.4 13.8 20.4 7.6M31.4 24.3C56.8 23.9 82.3 24.8 107.8 23.8M106.0 23.0C106.7 36.9 106.8 50.9 106.2 64.8M107.6 63.6C82.9 64.4 58.2 64.5 33.5 63.9M34.3 64.4C33.7 50.7 33.6 37.0 33.8 23.3M8.8 63.6C49.7 63.3 90.6 63.9 131.5 64.2M129.8 62.7C129.1 68.8 129.1 74.9 130.2 80.9M130.6 80.2C89.7 79.8 48.8 80.1 8.0 80.5M9.8 82.4C10.4 75.7 10.5 68.9 10.1 62.2" fill="none" stroke="#e6edf3" stroke-width="1.6" stroke-opacity="0.75" stroke-linecap="round"/>
+<path d="M17.2 9.8C52.2 10.0 87.1 10.3 122.1 10.6M120.6 9.8C120.3 15.4 119.9 20.9 120.0 26.5M122.1 23.8C87.9 22.3 53.6 24.3 19.4 23.6M20.4 26.6C19.9 20.9 20.2 15.3 20.7 9.7M33.4 23.5C57.9 22.3 82.4 24.1 106.8 24.2M105.9 22.8C105.4 37.0 105.2 51.2 106.5 65.4M106.1 64.5C81.9 65.0 57.8 62.9 33.6 63.3M33.3 66.3C34.2 51.6 32.5 37.0 33.8 22.4M8.2 63.5C49.5 63.2 90.7 64.7 132.0 63.6M129.5 61.5C129.3 68.3 130.3 75.2 129.5 82.0M131.4 79.4C90.5 80.4 49.7 79.0 8.9 80.2M10.6 81.8C9.3 75.7 9.2 69.6 10.4 63.5" fill="none" stroke="#e6edf3" stroke-width="1.2" stroke-opacity="0.28" stroke-linecap="round"/>
+</svg>
+```
+
 ### circle-a-dark.svg
 
 [testmark]:# (circle-a-dark.svg)