diff options
| author | user <user@clank> | 2026-09-02 20:18:10 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-09-02 20:18:10 +0200 |
| commit | dc72a22da1b24ffb835681a1a8e771365020041b (patch) | |
| tree | 81efe0b89b2ea60c9595bc6626af07ca68396e54 | |
| parent | README.md: chore fix (diff) | |
| download | slosh-dc72a22da1b24ffb835681a1a8e771365020041b.tar.gz slosh-dc72a22da1b24ffb835681a1a8e771365020041b.tar.xz slosh-dc72a22da1b24ffb835681a1a8e771365020041b.zip | |
web: card
| -rwxr-xr-x | web/build-site | 5 | ||||
| -rw-r--r-- | web/card.png | bin | 0 -> 40025 bytes | |||
| -rwxr-xr-x | web/gen-card | 107 | ||||
| -rw-r--r-- | web/index.html | 13 |
4 files changed, 125 insertions, 0 deletions
diff --git a/web/build-site b/web/build-site index 06e6451..e5766f9 100755 --- a/web/build-site +++ b/web/build-site @@ -37,6 +37,11 @@ publish() { sed "s/@VERSION@/v$(cat "$root/VERSION")/g" "$here/index.html" > "$out/index.html" cp "$here/site.css" "$out/" +# The social-embed card (og:image / twitter:image). Rendered by hand with +# web/gen-card when logo.txt or the palette changes, committed, and only +# copied here -- regenerating would make Pillow a build dependency. +cp "$here/card.png" "$out/" + # The docs, rendered by the same generator `make docs` uses. python3 "$root/contrib/gen-docs" "$out/docs" diff --git a/web/card.png b/web/card.png Binary files differnew file mode 100644 index 0000000..22857f5 --- /dev/null +++ b/web/card.png diff --git a/web/gen-card b/web/gen-card new file mode 100755 index 0000000..b303c29 --- /dev/null +++ b/web/gen-card @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +"""Render the social-embed card, web/card.png, from logo.txt. + +The card is what Discord/X/Slack show when someone pastes slosh.foo: a +1200x630 PNG of the logo in the site's palette (contrib/themes/sl0p.kdl -- +hot pink on near-black) over the tagline. The logo is drawn as geometry, +exactly like web/gen-logo-mask does for the page (half-block cells, 6x10 +units, horizontal runs merged, EPS overlap against seam pinstripes), so the +card can never disagree with the mask. + +Like gen-logo-mask this is run by hand when logo.txt or the palette moves, +and the result is committed; build-site only copies it. Needs Pillow. +""" + +import os +import sys + +from PIL import Image, ImageDraw, ImageFont + +HERE = os.path.dirname(os.path.abspath(__file__)) +LOGO = os.path.join(os.path.dirname(HERE), "logo.txt") +OUT = os.path.join(HERE, "card.png") + +# site.css :root, verbatim. +BG = "#0b0b0e" +FG = "#e8e8ea" +DIM = "#8a8a95" +PINK = "#ff5fd7" + +W, H = 1200, 630 +SS = 2 # supersample, for crisp downscaled edges and text + +# One logo cell in device pixels (at 1x). 6x10 units in the mask; the same +# ratio here, just bigger. +CW, CH, EPS = 19, 32, 1.0 + +TAGLINE = "a modern terminal multiplexer" +SUBLINE = "bringing the bling back to your shells" +SITE = "https://slosh.foo" + + +def runs(row, want): + out, start = [], None + for i, ch in enumerate(row + " "): + hit = ch in want + if hit and start is None: + start = i + if not hit and start is not None: + out.append((start, i)) + start = None + return out + + +def font(size, *names): + for name in names: + for d in ("/usr/share/fonts/TTF", "/usr/share/fonts/truetype/dejavu"): + p = os.path.join(d, name) + if os.path.exists(p): + return ImageFont.truetype(p, size) + sys.exit(f"gen-card: none of {names} found -- install DejaVu fonts") + + +lines = open(LOGO).read().rstrip("\n").splitlines() +cols = max(len(l) for l in lines) + +img = Image.new("RGB", (W * SS, H * SS), BG) +draw = ImageDraw.Draw(img) + +cw, ch, eps = CW * SS, CH * SS, EPS * SS +lw, lh = cols * cw, len(lines) * ch +lx, ly = (W * SS - lw) // 2, 150 * SS + +for j, row in enumerate(lines): + for a, b in runs(row, "\u2580\u2588"): # top halves + draw.rectangle( + [lx + a * cw, ly + j * ch - eps, lx + b * cw, ly + j * ch + ch / 2 + eps], + fill=PINK, + ) + for a, b in runs(row, "\u2584\u2588"): # bottom halves + draw.rectangle( + [ + lx + a * cw, + ly + j * ch + ch / 2 - eps, + lx + b * cw, + ly + (j + 1) * ch + eps, + ], + fill=PINK, + ) + +mono = font(36 * SS, "DejaVuSansMono.ttf") +mono_sm = font(27 * SS, "DejaVuSansMono.ttf") + + +def centred(text, y, fnt, fill): + tw = draw.textlength(text, font=fnt) + draw.text(((W * SS - tw) / 2, y * SS), text, font=fnt, fill=fill) + + +centred(TAGLINE, 442, mono, FG) +centred(SUBLINE, 502, mono_sm, DIM) +centred(SITE, 560, mono_sm, PINK) + +img = img.resize((W, H), Image.LANCZOS) +img.save(OUT, optimize=True) +print( + f"gen-card: {W}x{H}, logo {lw // SS}x{lh // SS} at ({lx // SS},{ly // SS}) -> {os.path.relpath(OUT, HERE)}" +) diff --git a/web/index.html b/web/index.html index bef0235..cffca3b 100644 --- a/web/index.html +++ b/web/index.html @@ -5,6 +5,19 @@ <meta name="viewport" content="width=device-width, initial-scale=1"> <title>slosh: a terminal multiplexer</title> <meta name="description" content="slosh: panes, tabs, sessions you can detach from. One static binary, written in C on libghostty-vt."> +<!-- The embed card, for Discord/X/Slack link previews. card.png is drawn + from logo.txt by web/gen-card; theme-color is the accent stripe Discord + puts on the embed. URLs are absolute because scrapers want them so. --> +<meta property="og:title" content="slosh: a terminal multiplexer"> +<meta property="og:description" content="Panes, tabs, sessions you can detach from. One static binary, written in C on libghostty-vt."> +<meta property="og:url" content="https://slosh.foo/"> +<meta property="og:type" content="website"> +<meta property="og:image" content="https://slosh.foo/card.png"> +<meta property="og:image:width" content="1200"> +<meta property="og:image:height" content="630"> +<meta name="twitter:card" content="summary_large_image"> +<meta name="twitter:image" content="https://slosh.foo/card.png"> +<meta name="theme-color" content="#ff5fd7"> <link rel="stylesheet" href="site.css"> <link rel="stylesheet" href="recordings/player.css"> </head> |
