Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Draw avatars with DiceBear instead of by hand

97f77001eed76cc287aeba532c4d670488121a18 · Keenan Nemetz · 7d ago · parent ca098f62

Draw avatars with DiceBear instead of by hand

The marks this package drew itself were a design I kept losing arguments
with. Avatars are now DiceBear's Shapes style, which is what the request
asked for.

DiceBear is JavaScript and Vole is a Go binary, so the split is: the
server renders the account's initial on its Everforest ground, and an
embedded bundle replaces it with the mark when the page runs. The
initial is what a browser without scripts keeps, so nothing depends on
the bundle arriving; the seed is the account name, so the same person
draws the same mark everywhere; and the ground is read from the element,
so vole.css remains the only palette.

The bundle is core 9.3.1 plus Shapes 9.4.2 (MIT code, CC0 design), built
with esbuild and committed like the other vendored scripts: 18KB, 5.5KB
gzipped. Its source is scripts/avatars-entry.js, and the licence files
beside it are inventoried in NOTICE, so the verify workflow's licence
check keeps passing. No request leaves the instance and no avatar is
stored.

Two details worth naming. Each mark gets a unique clip-mask id, because
SVG ids are document-wide and a directory page holds fifty of them. And
the RDF metadata block DiceBear embeds is stripped: it is identical for
every avatar and its licence is in NOTICE, so repeating it in the DOM
would be weight without information.

server/web/static/vole.css +13−9

@@ -1160,12 +1160,13 @@ pre code { background: none; border: 0; padding: 0; font-size: inherit; }
1160 1160 .contributors .cname { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: calc(13.75px * var(--font-scale)); }
1161 1161 .contributors .muted { font-size: calc(12.65px * var(--font-scale)); }
1162 1162
1163 /* A name's mark is a curled stroke derived from the name (see
1164 server/web/avatar.go). The sheet owns the palette and the SVG owns the
1165 geometry: seven Everforest grounds pulled toward the page, one cream line, and
1166 the same stroke language for everyone. The tones are identical in every
1167 Everforest variant on purpose; a person who is moss green on one theme must not
1168 be clay on another. */
1163 /* A name's avatar. The ground and the fallback letter are server-rendered here;
1164 the mark itself is drawn in the browser by DiceBear (see
1165 server/web/avatar.go and scripts/avatars-entry.js). The seven grounds are the
1166 Everforest accents pulled toward the page, identical in every Everforest
1167 variant on purpose: a person who is moss green on one theme must not be clay on
1168 another. The bundle reads the computed colour, so this sheet remains the only
1169 palette. */
1169 1170 .avatar {
1170 1171 display: inline-grid;
1171 1172 place-items: center;
@@ -1175,7 +1176,11 @@ pre code { background: none; border: 0; padding: 0; font-size: inherit; }
1175 1176 border-radius: 50%;
1176 1177 overflow: hidden;
1177 1178 background: var(--av-bg, #6b7561);
1178 --av-mark: #f0e6d2;
1179 color: #f0e6d2;
1180 font-size: calc(10px * var(--font-scale));
1181 font-weight: 600;
1182 line-height: 1;
1183 text-transform: uppercase;
1179 1184 }
1180 1185 .avatar.c1 { --av-bg: #6f8a5f; }
1181 1186 .avatar.c2 { --av-bg: #a2734f; }
@@ -1185,7 +1190,6 @@ pre code { background: none; border: 0; padding: 0; font-size: inherit; }
1185 1190 .avatar.c6 { --av-bg: #94843f; }
1186 1191 .avatar.c7 { --av-bg: #7a6b5f; }
1187 1192 .avatar svg { display: block; width: 100%; height: 100%; }
1188 .avatar .av-mark { fill: none; stroke: var(--av-mark); stroke-linecap: round; stroke-linejoin: round; }
1189 1193
1190 1194 .lang { display: grid; grid-template-columns: 1fr auto; gap: 2px 8px; margin-bottom: 8px; font-size: calc(13.2px * var(--font-scale)); }
1191 1195 .lang .muted { font-size: calc(12.65px * var(--font-scale)); }
@@ -2897,7 +2901,7 @@ textarea:focus-visible {
2897 2901 .profile-joined { margin: 4px 0 0; font-size: calc(12.65px * var(--font-scale)); }
2898 2902 .profile-scope { margin: 8px 0 0; font-size: calc(12.65px * var(--font-scale)); }
2899 2903
2900 .profile-who .avatar { width: 52px; height: 52px; }
2904 .profile-who .avatar { width: 52px; height: 52px; font-size: calc(22px * var(--font-scale)); }
2901 2905
2902 2906 /* The four numbers a profile leads with. Each one is a link to the feed it
2903 2907 counts, so a number is a door rather than a dead end. */