aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-09-02 20:18:10 +0200
committeruser <user@clank>2026-09-02 20:18:10 +0200
commitdc72a22da1b24ffb835681a1a8e771365020041b (patch)
tree81efe0b89b2ea60c9595bc6626af07ca68396e54
parentREADME.md: chore fix (diff)
downloadslosh-dc72a22da1b24ffb835681a1a8e771365020041b.tar.gz
slosh-dc72a22da1b24ffb835681a1a8e771365020041b.tar.xz
slosh-dc72a22da1b24ffb835681a1a8e771365020041b.zip
web: card
-rwxr-xr-xweb/build-site5
-rw-r--r--web/card.pngbin0 -> 40025 bytes
-rwxr-xr-xweb/gen-card107
-rw-r--r--web/index.html13
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
new file mode 100644
index 0000000..22857f5
--- /dev/null
+++ b/web/card.png
Binary files differ
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>