Turn bare semantic HTML + a classless stylesheet into a social image — the same "water.css" approach as the site, just rendered to PNG. Uses a real Chromium (via Playwright) so the output is exactly what a browser shows. Fonts (Geist / Geist Mono) are provided by Nix through fontconfig and referenced by family name — nothing is bundled or downloaded at runtime.
flake.nix— dev shell + runnable app; pulls Chromium and the Geist fonts.render.py— HTML + CSS → PNG.theme.css— the classless theme (light + indigo). Edit this to restyle.card.html— example, 4:5 LinkedIn card (title + setup + tools + caveat).card-og.html— trimmed example for the short OG banner (title + tools).
With nix run (no shell needed):
nix run . -- --html card.html --format linkedin # 1080x1350 (4:5)
nix run . -- --html card-og.html --format og # 1200x630 (1.91:1)
nix run . -- --html card.html --format square # 1080x1080
nix run . -- --html card.html --width 1080 --height 1080 --scale 3Or in a dev shell:
nix develop
python render.py --html card.html --format linkedin --out out/linkedin.pngOutput defaults to out/<format>.png. --scale is the pixel multiplier
(2 = retina; the file above is 2160x2700).
| preset | size | ratio | use |
|---|---|---|---|
linkedin |
1080 × 1350 | 4:5 | feed image (most real estate) |
og |
1200 × 630 | 1.91:1 | Open Graph / link preview |
square |
1080 × 1080 | 1:1 | consistent series |
The OG canvas is short — give it fewer elements (see card-og.html). The
stylesheet already compacts type/spacing for wide aspect ratios.
Plain semantic HTML, no classes. The vocabulary the theme styles:
h1/h2, p, dl/dt/dd (signature + gloss), ul/ol/li,
blockquote, small, code, strong/em, a, hr, footer. Put the
identifier in <code> inside <dt> to get the accent colour on the name only.
To restyle, edit theme.css (the palette lives in :root). To use a different
font, swap pkgs.geist-font in flake.nix and the family name in theme.css.