Avatar

256 × 256 viewBox. Metaphorical glyph; reads at favicon and avatar scale.

project one-line tagline a one-line summary of the output a half-line of meta

Hero

1200 × 430 viewBox. Big title left, embedded metaphor below it, four fact tiles right.

PROJECT · ONE-LINE TAGLINE YEAR project first line of tagline; what the project is or does in one breath. second line of tagline; the trade-off you're taking on. one tagline, used here and on the avatar. FLOW DISCIPLINE IN big phrase subtitle that grounds the big word SPEC LINE · ONE OR TWO TERMS POSTURE big word how the project behaves A NEGATION OR GUARANTEE OUT big word what comes out SHAPE OR FORMAT OF THE OUTPUT LIFETIME big word how it stays good over time A STABILITY GUARANTEE TERRITORY · TAG · TAG · TAG · TAG LICENSE · GITLAB.COM/DUNN.DEV/PROJECT

The rules

The design language, stated tersely. Lift the templates, replace the placeholders, keep the rest.

Two artifacts, two voices

Avatar answers what is this like? with a metaphor. Hero answers what does this promise? with principles. Don't mix: a flow diagram in the avatar or a metaphorical hero is the failure mode.

The metaphor goes in both

GitHub strips the repo avatar, so the hero embeds an elaborated version of the same glyph beneath the title. The hero version has more endpoints, more lines, more density; it's the avatar with room to breathe.

One tagline, two surfaces

The italic line on the hero (under the title) and the muted tagline on the avatar (under the title) are the same phrase. It's a tagline, not a stage direction: evocative about the project's posture, not a literal description of the visual ("a basket catching the day's photographs as they fall from the feed" is the failure mode; "collected moments of early learning" is the right register).

One phrase across both artifacts keeps the brand coherent on the two surfaces a viewer hits (avatar in the repo header, hero in the README). Pick a phrase that fits the avatar's narrower budget; the hero has more room than it'll ever need.

Glyph stays in its band

The hero metaphor mark sits in y 230-380 (150 actual units; ~75 local at the template's scale(2)). The bottom rule line at y 386 is hard: a glyph that overlaps it looks like a layout bug. If a glyph won't fit at scale(2), drop a row of elements before scaling down: a less-full layout reads as "the project's still small / still growing," which is usually on-brand. Scaling below 1.7 makes the strokes too thin against the canvas.

The avatar glyph sits in y 90-200 (110 units) at scale(1). Similar discipline: photo + caption + a binding line beats four photos crammed in.

One glyph, three sizes

A brand kit ships three renderings of one composition: the favicon (64-unit viewBox), the avatar's inline glyph band inside the 256 canvas, and the mark in the project site's header, its only place on the site. Drifting between them, different grid densities, different trail shapes, different stroke widths, reads as three competing brand marks.

Design the glyph once at the favicon's 64-unit viewBox. Render the avatar's band by wrapping the same path data in <g transform="translate(...) scale(N)">, picking N so it fills y 90-200. The site glyph is usually the favicon re-saved under another name. When the metaphor evolves, edit one and copy to the others.

The favicon is re-authored, not cropped

Favicons render at 16-32px, where the avatar's glyph shrunk from a 256 viewBox loses every fine detail. The 64 viewBox is the chance to bump stroke widths, drop details that vanish at small size, and re-balance the composition without the surrounding title, tagline and output blocks. Same metaphor, different artifact: an avatar showing a conductor calling out to renderers ships a favicon with the same conductor and fan, simplified.

  • viewBox 0 0 64 64, no outer rect; the browser tab provides the background.
  • Stroke and fill hardcode the moss accent #4a6741, never currentColor: browser-UI text colour inherits as black on light tab strips and washes out on dark.
  • No text. The glyph carries the metaphor alone.
  • Strict XML. Some browsers refuse an SVG favicon that fails well-formedness even when the same file renders fine inline; the common trap is a double hyphen inside a comment. Validate with rsvg-convert favicon.svg -o /dev/null.

A repo with no metaphor of its own keeps the dunn.dev rune from repository/templates/favicon.svg until its glyph lands.

Lines start and end at edges

Connecting lines start at the conductor's edge and end at the renderer's edge; they never pass through circle centres. When the metaphor has both call and response, draw them as parallel lanes (perpendicular offset, same magnitude on both ends), not converging on the same point.

Tile content

Each of the four hero tiles carries four layers: a small-caps mono label, a big bold moss word, a body subtitle that grounds the word, and a small mono spec line at the bottom. Top row covers flow (in / out), bottom row covers discipline (posture / lifetime). The pattern is fixed; the labels can vary per project.

Territory tags name concepts, not stack

The dotted line at the hero's bottom left carries territory tags: stable concepts saying what kind of project this is. Domain words (audio, auth), form words (cli, library), protocol names when they are stable and central (UPnP, OAuth).

What does not go here: language names, library names, framework names, version numbers, LOC counts, file counts, CLI flag names. Anything that might be renamed or swapped is a maintenance detail and belongs in the repo's badge row, not in the brand.

Every fill and stroke is a published token

The whitelist is the estate's SVG palette on Colors, hardcoded as hex because sanitisers strip var() from the surfaces that matter most. In the brand artifacts it maps: the avatar's canvas is --bg; the hero's canvas is --surface with --bg tile fills; --border draws the avatar's outer border and the hero's dividers and tile borders; --accent carries the title, the big tile word and the primary stroke. A colour outside that set makes one repo's artwork read as a different estate's.

Type is a system stack

The avatar uses Helvetica, Arial, sans-serif so the SVG renders identically everywhere it is embedded. The hero uses IBM Plex Sans, Helvetica, Arial, sans-serif with IBM Plex Mono, Menlo, monospace for small-caps headers and spec lines. Set font-family on both the <svg> root and the wrapping <g> so it inherits everywhere.

Required SVG attributes

  • role="img" on the <svg> root.
  • A non-empty aria-label describing the artwork in a sentence.
  • An outer <rect> filling the canvas with paper (avatar) or paper-warm (hero), so the artwork carries its own background.
  • <defs> marker IDs prefixed with the repo slug, so nothing collides when SVGs are inlined together.

Generation

avatar.png is derived from avatar.svg via rsvg-convert -w 256 -h 256. brand.png is derived by stacking the avatar (rendered at 800×800, padded to 1600×800 on a surface canvas) above the hero (rendered at 1600×573), with a 24px surface border around the whole thing. The hero stays SVG-only; GitHub renders SVG natively.

Each repo's justfile adds a brand target that regenerates both PNGs. Run it after editing either SVG.

Character budgets

The only ceiling you can break visibly. SVG text does not wrap; overflow looks unprofessional and hides content.

FieldWhereBudget
titleavatar + hero~10 chars (one short word)
taglineavatar + hero italic~36 chars
title sub-linehero, two lines~70 chars per line
top metadatahero, mono caps~58 chars
tile big wordhero, font-size 34~12 chars
tile subtitlehero, font-size 11~46 chars
tile spec linehero, mono caps~30 chars
territory tagshero bottom-left~38 chars
output lineavatar, font-size 9~50 chars

Treat these as worst-they-still-fit ceilings. Render at canvas size (avatar 256, hero 1200) before shipping, then look at a 3× PNG (avatar 768, hero 3600): some overflow is only obvious there.

Files

Lift, rename, replace placeholders.

  • repository/templates/avatar.svg → copy to your repo as avatar.svg; replace project, tagline, glyph, summary line.
  • repository/templates/hero.svg → copy as hero.svg; replace project, tagline, glyph, four tile contents, territory tags.
  • brand.png → derived; just brand regenerates it from the two SVGs.
  • repository/templates/favicon.svg → copy as favicon.svg; re-author the glyph for the 64 viewBox.
  • repository/templates/glyphs/ → a reusable 64-unit glyph per recurring domain (registry, server stack, network, auth, GPU, config, routing, ideation, tooling, extension, site index, test probe). Its README names the domain each one answers to; brand-init.sh reads them by name. Project-specific metaphors stay in the project's own favicon.
  • Both templates ship the dunn.dev mark as the placeholder glyph. A repo without its own metaphor yet can leave it as-is and the artifact still carries the estate identity.

Consumers can also npm-install the package and reference templates directly: @dunn.dev/reference/repository/templates/avatar.svg.