Felix Itzenplitz

Vellum: ein Hugo-Theme, das jede Seite wie ein technisches Zeichenblatt setzt

Ausgabe
Umfang
8 Min. Lesezeit · 1536 Wörter
Von
Felix Itzenplitz
Auch auf
English
Inhalt

Ich wollte eine einzige Kleinigkeit am Theme dieses Blogs ändern. Ein paar Wochenenden später hatte ich ein komplett neues geschrieben, ihm einen Namen, eine MIT-Lizenz und ein generiertes Changelog verpasst und es beim Hugo-Theme-Verzeichnis eingereicht. Einen Blogbeitrag habe ich in der Zeit nicht geschrieben. Das ist ein bekanntes Muster, und ich bin sehenden Auges hineingelaufen.

Es heißt Vellum, dieser Blog läuft darauf, es steht inzwischen bei 0.2.10, und gelistet ist es unter themes.gohugo.io/themes/vellum . Während ich die erste Fassung dieses Beitrags geschrieben habe, sind zwei Patch-Versionen dazugekommen — was ungefähr zusammenfasst, wie die Wochenenden liefen.

Die Ausgangslage

Blog-Themes sehen alle ungefähr gleich aus: eine schmale zentrierte Spalte, eine graue Zeile unter dem Titel und viel Weißraum, der Arbeit macht, die eine einzige Linie besser erledigen würde. Für Essays ist das in Ordnung. Für das, was ich tatsächlich veröffentliche — Kommandos und Terminal-Ausgaben — ist es falsch.

Also habe ich mir die Vorlage woanders geholt: beim technischen Zeichenblatt. Ein gezeichneter Rahmen um die Seite. Millimeterpapier darin und sonst nirgends. Und statt der grauen Zeile ein linierter Title Block — der Kasten, den eine echte Zeichnung in der Ecke trägt, um zu sagen, was das Blatt ist, wer es gezeichnet hat und wann.

Die einzige Regel, die ich mir gesetzt habe: Die Metapher muss tragen. Sieht ein Element nur aus wie Zeichnungsinventar, ist es Dekoration und fliegt raus. Diese Regel ist strenger, als sie klingt, und sie hat einige Ideen gekillt, die auf Screenshots hervorragend aussahen.

Gebaut mit Claude Code und impeccable

Und jetzt der Teil, den ich offen sagen sollte: Von Hand geschrieben habe ich das meiste davon nicht. Gebaut habe ich es mit Claude Code, und dafür, dass am Ende ein zusammenhängendes Theme herauskam und nicht ein großer Haufen plausibel aussehendes CSS, war ein Skill namens impeccable verantwortlich.

Der Trick von impeccable: Es lässt ein Design nicht nur im Code existieren. Es führt zwei Dokumente neben dem Repo mit:

  • PRODUCT.md — wofür das Theme da ist, für wen, und welche seiner Eigenschaften verbindliche Zusagen sind und welche nur zufällig gerade stimmen. URL-Stabilität und 4,5:1 Kontrast sind verbindlich. Dass es ohne JavaScript funktioniert, ist es nicht — das ist gerade so, und ich darf es später eintauschen, ohne dass es ein Bug wäre.
  • DESIGN.md — die visuelle Welt so, wie sie gebaut ist, nicht wie sie gemeint war. Gleich im Kopf steht der Satz, dass bei Widersprüchen das Stylesheet recht hat und das Dokument veraltet ist.

Und dann prüft es den Code bei jeder Änderung gegen diese Dokumente und macht Screenshots, um zu sehen, ob das, was es gerade beschrieben hat, auch das ist, was auf dem Bildschirm steht.

Für ein Blog-Theme ist das zugegebenermaßen absurder Aufwand. Es ist aber auch der Grund, warum am Ende ein echtes dreistufiges Liniensystem herauskam statt drei Tokens, die alle klammheimlich auf 1px hinauslaufen — genau das war nämlich zwischenzeitlich der Fall. Zwei davon hatten dieselbe Breite, elf Deklarationen waren zwischen ihnen hin- und hergerutscht, und das Ergebnis war pixelgleich. Durch Hinsehen hätte das nie jemand gefunden. Das Dokument hat es gefunden.

Dieser Beitrag ist genauso entstanden, und das Theme sagt es inzwischen von selbst — daher der Stempel im Title Block ganz oben.

Die Zone Rail, oder: der Teil, auf den ich unangemessen stolz bin

Auf einer echten Zeichnung ist der Rand in Zonen eingeteilt — Buchstaben an der einen Kante, Zahlen an der anderen — damit zwei Leute am Telefon sich einig werden können, wovon sie reden. „Die Halterung in C4.“ Ein Koordinatensystem für ein Gespräch.

Vellum behält die Buchstaben und gibt ihnen eine Aufgabe: eine Zone pro Hauptüberschrift, jede ein Link, jede auf der gemessenen Höhe genau dieser Überschrift im Blatt, und die gerade gelesene leuchtet amberfarben. Das ist ein Inhaltsverzeichnis, das null vertikalen Platz kostet, weil es im Rahmenrand sitzt — der vorher nur Linie war.

Eine Vellum-Beitragsseite mit dem linierten Title Block und der Zone Rail am linken Rand des Rahmens.
Der Title Block nennt echte Felder und lässt die weg, für die es keinen Wert gibt — auf einer Zeichnung bedeuten ein leeres und ein fehlendes Feld nicht dasselbe.

Hat eine Seite weniger als zwei Überschriften, wird die Rail gar nicht erst gezeichnet; der Rahmen schließt die Spalte einfach. Einen leeren Rand zu beschriften wäre exakt die Dekoration gewesen, die ich mir vorher verboten hatte.

Gebaut für Code, nicht für Fließtext

Die Lesespalte ist 92 Zeichen breit. Das liegt deutlich über den 65 bis 75, die in Typografiebüchern stehen, und zwar mit Absicht: Ein 80-Zeichen-Paste aus dem Terminal muss ohne Umbruch hineinpassen. Abgezählt habe ich das an einer gerenderten Zeile statt es zu schätzen — meine Schätzungen lagen zuverlässig daneben.

Nichts wirft einen Schatten. Tiefe entsteht ausschließlich über Strichstärke — zwei Breiten, gekreuzt mit drei Tonwerten — und es gibt genau eine Akzentfarbe, ein Amber, das nie mehr als eine Aufgabe gleichzeitig auf dem Bildschirm hat. Der Rotstift des Zeichenbüros.

Ein Vellum-Beitrag im Dark Mode mit einem Go-Codeblock und dem Beginn einer Terminal-Ausgabe, Zone B leuchtet amberfarben.
Dark Mode, Zone B leuchtet auf der Rail. Die Syntaxfarben stammen aus derselben Familie wie der Rest der Seite — Code und Blatt streiten nicht miteinander.

Die Übersichtsseiten bekommen dieselbe Behandlung: Beiträge durch Linien getrennt statt in Karten gepackt, weil eine Zeichnung eine Tabelle mit Linien unterteilt.

Die Startseite der Vellum-Demo: ein Profilblock über einem linierten Index der Beiträge, innerhalb eines gezeichneten Rahmens auf Millimeterpapier.
Das Indexblatt der Demo-Seite. Der Stempel rechts zählt die abgelegten Blätter.

Was in 0.2 steckt

0.2.0 war größtenteils Infrastruktur — Releases laufen jetzt über ein Skript, und CHANGELOG.md wird aus der Commit-Historie erzeugt, statt von Hand geschrieben und sofort vergessen zu werden. Dazu zwei sichtbare Fixes: Die Zone Rail taucht auf schmalen Bildschirmen nicht mehr als kleiner Stapel Marken in der Rahmenecke auf, und der Text einer _index.md hängt jetzt am Seitenkopf statt direkt an der ersten Beitragskarte, wo er wie deren Bildunterschrift wirkte.

0.2.1 hat die AI-Kennzeichnung gebracht, die du bei diesem Beitrag siehst: ein Stempel im Title Block, dieselbe Marke in der Beitragsübersicht — damit man es sieht, bevor man etwas öffnet — und ein Shortcode, um einzelne Passagen statt eines ganzen Beitrags zu markieren. Da ich das Theme mit einer KI gebaut habe und auch damit schreibe, schien es mir besser, das Theme sage das in seiner eigenen Sprache, als es in eine Fußzeile zu schreiben.

0.2.2 hat diese Marken dann leiser gedreht. Sie waren so schwer gezeichnet wie eine Überschrift, obwohl sie Fließtext annotieren — und wirkten damit wichtiger als der Satz, an dem sie hingen. Genau das soll eine Kennzeichnung nicht tun.

Und was danach kam

0.2.3 war die größte Runde, und fast alles darin war Barrierefreiheit. Die Zone Rail steht jetzt hinter dem Blattinhalt im DOM, weil man beim Durchtabben eines langen Beitrags mittendrin wieder an den Seitenanfang geworfen wurde. Ausgegraute Blätterlinks waren in einem Linien-Token gezeichnet, mit 2,06:1 gegen ihren Untergrund. Und die AI-Marke auf einer Beitragskarte wird im Feldraster benannt, statt nur als Hover-Title zu existieren — sie erreicht damit auch Tastatur und Touch. Eine Kennzeichnung, die nur eine Maus findet, ist keine. Dieselbe Version hat den Suchergebnissen das Feldraster der Beitragskarten gegeben und einer Suche ohne Treffer die Auswege der 404-Seite statt einer grauen Zeile.

0.2.5 war eine lange Liste kleiner Korrekturen, die meisten davon nur auf fremden Seiten überhaupt sichtbar: Icon-Pfade, die gegen eine baseURL mit Unterpfad nicht aufgingen, latestPosts = 0, das die Liste nicht wirklich ausgeblendet hat, ein Header-Logo, das aus der Quelle statt aus dem skalierten Bild vermaßt wurde. 0.2.6 hat dann verhindert, dass der JSON-LD-Block in einen JSON-String escaped wird — dadurch war er still und leise nicht mehr parsebar.

0.2.7 ist die Version, die ich am ehesten verteidigen würde. Ein Theme kann die Konfiguration seiner Nutzer nicht selbst schreiben, und drei Einstellungen, von denen es abhängt, erzeugten eine Seite, die klaglos rendert und falsch ist, wenn sie fehlen: ein Suchfeld ohne Index dahinter, ein Publish-Root ohne Fehlerdokument, und Codeblöcke mit einer hellen Palette im Markup, die auf einem dunklen Blatt hell bleibt. Alle drei standen in der README und wurden nirgends erzwungen. Jetzt prüft das Theme sie beim Bauen und gibt das einzufügende TOML aus — als Warnung, nicht als Fehler, denn ein Update darf einen Build nicht kaputt machen, der seit einem Jahr halb konfiguriert durchläuft.

Und dann hat mir das Verzeichnis gesagt, dass eine dieser Prüfungen falsch lag. themes.gohugo.io baut jede Einreichung gegen die eigene Demo-Seite, und dieser Build hat meine Chroma-Warnung ausgegeben. Die Prüfung hatte recht mit dem Symptom und unrecht mit der Abhilfe: Ein Render-Hook kann noClasses pro Aufruf mitgeben und braucht dafür überhaupt keine Einstellung auf der Seite. 0.2.10 hat das in _markup/render-codeblock.html verschoben und die Prüfung, die danach gefragt hat, gelöscht. Die Aufnahme ins Verzeichnis hat einen Fehler in genau dem Code gefunden, den ich geschrieben hatte, um Fehler zu finden. Passender geht es kaum.

Ausprobieren

[module]
  [[module.imports]]
    path = "github.com/cebor/vellum"

Danach hugo mod get -u. Eine Demo läuft unter pages.stkn.org/felix/vellum , Quellcode, README und Changelog liegen auf github.com/cebor/vellum . Vorausgesetzt wird Hugo extended ab 0.158.

Hinweis

Der Eintrag im Verzeichnis ist themes.gohugo.io/themes/vellum . Als ich diesen Beitrag zuerst geschrieben habe, hing die Einreichung noch in der Warteschlange, weil upstream keine neuen Themes aufgenommen hat — falls du die frühere Fassung gelesen und die Suche aufgegeben hast: jetzt ist es da.

Gebaut ist es zuerst für diesen Blog: Wo mein Bedarf und der eines möglichen Nutzers kollidieren, gewinnt mein Blog. Vollständig dokumentiert ist es trotzdem, und die Demo ist echt. Wenn deine Beiträge auch überwiegend aus Code bestehen, könnte es zu dir passen.