Skip to content

Commit c1e8bb6

Browse files
committed
chore: add OG cover image and generation script
- Add og-cover.png (1200x630) for social media previews - Set og_image in _config.yml for jekyll-seo-tag - Add scripts/generate_og_cover.py for future iteration
1 parent f4bba9c commit c1e8bb6

File tree

3 files changed

+163
-0
lines changed

3 files changed

+163
-0
lines changed

_config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ url: "https://superuserlabs.org" # the base hostname & protocol for your site, e
2828
twitter_username: SuperusrLabs
2929
github_username: SuperuserLabs
3030
linkedin_company: superuser-labs
31+
og_image: /media/og-cover.png
3132

3233
# Build settings
3334
#theme: minima

media/og-cover.png

63.5 KB
Loading

scripts/generate_og_cover.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
"""Generate the OG cover image for superuserlabs.org.
2+
3+
Renders at 2x and downscales for sharp antialiasing.
4+
Requires: Pillow (pip install Pillow)
5+
Usage: python scripts/generate_og_cover.py
6+
"""
7+
8+
from PIL import Image, ImageDraw, ImageFont
9+
from pathlib import Path
10+
11+
BASE = Path(__file__).resolve().parent.parent / "media"
12+
13+
SCALE = 2
14+
W, H = 1200 * SCALE, 630 * SCALE
15+
BG_COLOR = (2, 6, 23) # slate-950
16+
17+
18+
def main():
19+
img = Image.new("RGBA", (W, H), BG_COLOR)
20+
draw = ImageDraw.Draw(img)
21+
22+
s = SCALE
23+
font_bold = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 54 * s)
24+
font_tagline = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 22 * s)
25+
font_label = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 15 * s)
26+
font_url = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 15 * s)
27+
28+
# --- Load and fix logo background to match page bg ---
29+
logo_full = Image.open(BASE / "superuserlabs-logo.png").convert("RGBA")
30+
lw, lh = logo_full.size
31+
pixels = logo_full.load()
32+
33+
for y in range(lh):
34+
for x in range(lw):
35+
r, g, b, a = pixels[x, y]
36+
if r < 15 and g < 15 and b < 15:
37+
pixels[x, y] = (BG_COLOR[0], BG_COLOR[1], BG_COLOR[2], a)
38+
39+
# Find SU text bounds (white pixels, not the green cursor)
40+
min_y_su, max_y_su = lh, 0
41+
for y in range(lh):
42+
for x in range(lw):
43+
r, g, b, a = pixels[x, y]
44+
if r > 120 and b > 120:
45+
min_y_su = min(min_y_su, y)
46+
max_y_su = max(max_y_su, y)
47+
48+
su_content_h = max_y_su - min_y_su
49+
su_top_frac = min_y_su / lh
50+
51+
# --- Measure text ---
52+
bbox = draw.textbbox((0, 0), "Superuser Labs", font=font_bold)
53+
text_w = bbox[2] - bbox[0]
54+
bbox_cap = draw.textbbox((0, 0), "S", font=font_bold)
55+
cap_h = bbox_cap[3] - bbox_cap[1]
56+
57+
# Size logo so SU text matches cap height
58+
logo_size = int(cap_h * 1.15 * lh / su_content_h)
59+
logo = logo_full.resize((logo_size, logo_size), Image.Resampling.LANCZOS)
60+
61+
gap = 18 * s
62+
total_w = logo_size + gap + text_w
63+
64+
# --- Vertical layout ---
65+
tagline = "Free and open-source software that empowers you"
66+
bbox_t = draw.textbbox((0, 0), tagline, font=font_tagline)
67+
tagline_h = bbox_t[3] - bbox_t[1]
68+
tw_t = bbox_t[2] - bbox_t[0]
69+
70+
proj_size = 52 * s
71+
proj_label_h = 30 * s
72+
73+
gap_to_tagline = 18 * s
74+
gap_to_divider = 44 * s
75+
gap_to_projects = 28 * s
76+
total_content = (
77+
logo_size
78+
+ gap_to_tagline
79+
+ tagline_h
80+
+ gap_to_divider
81+
+ gap_to_projects
82+
+ proj_size
83+
+ proj_label_h
84+
)
85+
86+
content_top = (H - total_content) // 2 - 15 * s
87+
88+
# --- Heading (logo + text) ---
89+
start_x = (W - total_w) // 2
90+
name_y = content_top + int(su_top_frac * logo_size) - int(bbox[1])
91+
logo_y = content_top
92+
93+
img.paste(logo, (start_x, logo_y), logo)
94+
95+
text_x = start_x + logo_size + gap
96+
bbox_su = draw.textbbox((0, 0), "Superuser ", font=font_bold)
97+
su_w = bbox_su[2] - bbox_su[0]
98+
draw.text((text_x, name_y), "Superuser ", fill=(255, 255, 255), font=font_bold)
99+
draw.text((text_x + su_w, name_y), "Labs", fill=(100, 116, 139), font=font_bold)
100+
101+
# --- Tagline ---
102+
tagline_y = logo_y + logo_size + gap_to_tagline
103+
lockup_center = start_x + total_w // 2
104+
page_center = W // 2
105+
tagline_center = (lockup_center + page_center) // 2
106+
draw.text(
107+
(tagline_center - tw_t // 2, tagline_y),
108+
tagline,
109+
fill=(148, 163, 184),
110+
font=font_tagline,
111+
)
112+
113+
# --- Divider ---
114+
line_y = tagline_y + tagline_h + gap_to_divider
115+
draw.line(
116+
[(page_center - 100 * s, line_y), (page_center + 100 * s, line_y)],
117+
fill=(51, 65, 85),
118+
width=s,
119+
)
120+
121+
# --- Projects ---
122+
projects = [
123+
("activitywatch-logo.png", "ActivityWatch"),
124+
("gptme-logo.png", "gptme"),
125+
]
126+
proj_gap = 95 * s
127+
proj_y = line_y + gap_to_projects
128+
total_pw = len(projects) * proj_size + (len(projects) - 1) * proj_gap
129+
pstart_x = (W - total_pw) // 2
130+
131+
for i, (fname, label) in enumerate(projects):
132+
p = (
133+
Image.open(BASE / fname)
134+
.convert("RGBA")
135+
.resize((proj_size, proj_size), Image.Resampling.LANCZOS)
136+
)
137+
px = pstart_x + i * (proj_size + proj_gap)
138+
img.paste(p, (px, proj_y), p)
139+
bbox_l = draw.textbbox((0, 0), label, font=font_label)
140+
lbw = bbox_l[2] - bbox_l[0]
141+
draw.text(
142+
(px + (proj_size - lbw) // 2, proj_y + proj_size + 8 * s),
143+
label,
144+
fill=(148, 163, 184),
145+
font=font_label,
146+
)
147+
148+
# --- URL ---
149+
url = "superuserlabs.org"
150+
bbox_u = draw.textbbox((0, 0), url, font=font_url)
151+
uw = bbox_u[2] - bbox_u[0]
152+
draw.text(((W - uw) // 2, H - 36 * s), url, fill=(148, 163, 184), font=font_url)
153+
154+
# --- Downscale and save ---
155+
final = img.resize((1200, 630), Image.Resampling.LANCZOS)
156+
out = BASE / "og-cover.png"
157+
final.save(out, format="PNG")
158+
print(f"Saved {out}")
159+
160+
161+
if __name__ == "__main__":
162+
main()

0 commit comments

Comments
 (0)