1/**
2 * Takumi template for Open Graph cards. Consumed only by the static
3 * `og/[...slug].png.ts` endpoint at build time — never shipped to the
4 * browser. The element tree is rendered directly to PNG by Takumi.
5 *
6 * Two visual variants:
7 *
8 * - `doc` / `marketing` — parchment background, serif headline, the
9 * yantra glyph + eyebrow up top, hand-drawn "alchemy.run" caption
10 * bottom-right. Mirrors the homepage hero.
11 *
12 * - `blog` — dark variant inspired by Bun's release-note cards. Title
13 * anchored top-left, dense multi-line description filling the body,
14 * publish date + yantra mark in the footer. Designed to look full
15 * and editorial; relies on posts having a meaty `description`/
16 * `excerpt` in frontmatter.
17 *
18 * Title and description are rendered verbatim from the source page's
19 * frontmatter. Fonts come from npm bundles, with variable Source Serif for
20 * optical sizing and JetBrains Mono for programming ligatures.
21 */
22
23import type { ReactNode } from "react";
24import { yantraSvg } from "./yantra";
25
26const COLORS = {
27 bg: "#f5efe3",
28 fg1: "#2a2620",
29 fg2: "#4e402c",
30 fg3: "#85714f",
31 accent: "#5c7a3e",
32 accentDeep: "#3f5a2a",
33 hairline: "rgba(42,38,32,0.14)",
34 // Blog (dark) palette.
35 darkBg: "#161310",
36 darkFg1: "#f5efe3",
37 darkFg2: "#bdb09a",
38 darkFg3: "#7d705c",
39 darkAccent: "#a8c47a",
40 darkHairline: "rgba(245,239,227,0.12)",
41} as const;
42
43export type OgCardKind = "marketing" | "doc" | "blog";
44
45export interface OgCardProps {
46 title: ReactNode;
47 description?: ReactNode;
48 /** Drives the eyebrow label (e.g. "guide", "concept", "blog"). */
49 eyebrow?: string;
50 kind?: OgCardKind;
51 /** ISO date string (YYYY-MM-DD). Rendered in the blog footer. */
52 date?: string;
53}
54
55const W = 1200;
56const H = 630;
57
58/**
59 * Sri Yantra geometry for the card backgrounds, echoing the landing page's
60 * hero rings and section patterns: concentric rings, a lotus of 16 petals,
61 * and nine interlocking triangles (four apex-up, five apex-down) around a
62 * bindu. Returned as an SVG data URL so Takumi can draw it as an <img>.
63 */
64function yantraField(stroke: string, opacity: number): string {
65 const c = 500;
66 const circle = (r: number) => `<circle cx="${c}" cy="${c}" r="${r}"/>`;
67 const tri = (r: number, down: boolean, dy: number) => {
68 const pts = [0, 1, 2]
69 .map((k) => {
70 const a = (((down ? 90 : -90) + 120 * k) * Math.PI) / 180;
71 return `${(c + r * Math.cos(a)).toFixed(1)},${(c + dy + r * Math.sin(a)).toFixed(1)}`;
72 })
73 .join(" ");
74 return `<polygon points="${pts}"/>`;
75 };
76 const petals = Array.from({ length: 16 }, (_, k) => {
77 const a = (2 * Math.PI * k) / 16;
78 const h = (Math.PI / 16) * 0.6;
79 const p = (r: number, t: number) =>
80 `${(c + r * Math.cos(t)).toFixed(1)},${(c + r * Math.sin(t)).toFixed(1)}`;
81 return `<path d="M${p(330, a - h)} Q${p(395, a - h * 1.1)} ${p(420, a)} Q${p(395, a + h * 1.1)} ${p(330, a + h)}"/>`;
82 }).join("");
83 const r = 250;
84 const shapes = [
85 ...[90, 170, 330, 425, 470, 495].map(circle),
86 petals,
87 tri(r, false, -0.1 * r),
88 tri(r * 0.8, false, -0.02 * r),
89 tri(r * 0.6, false, 0.06 * r),
90 tri(r * 0.4, false, 0.12 * r),
91 tri(r, true, 0.1 * r),
92 tri(r * 0.84, true, 0.03 * r),
93 tri(r * 0.68, true, -0.04 * r),
94 tri(r * 0.5, true, -0.1 * r),
95 tri(r * 0.32, true, -0.02 * r),
96 `<circle cx="${c}" cy="${c}" r="5" fill="${stroke}"/>`,
97 ].join("");
98 const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" fill="none" stroke="${stroke}" stroke-width="1.6" stroke-linejoin="round" opacity="${opacity}">${shapes}</svg>`;
99 return `data:image/svg+xml;base64,${Buffer.from(svg).toString("base64")}`;
100}
101
102/** The geometry, large and bleeding off the card's right edge. */
103function Field({
104 stroke,
105 opacity,
106 right = -250,
107}: {
108 stroke: string;
109 opacity: number;
110 right?: number;
111}) {
112 return (
113 <img
114 src={yantraField(stroke, opacity)}
115 width={820}
116 height={820}
117 style={{ position: "absolute", right, top: -95, display: "flex" }}
118 />
119 );
120}
121
122export function OgCard(props: OgCardProps): any {
123 const kind = props.kind ?? "doc";
124 if (kind === "blog") return BlogCard(props);
125 if (kind === "marketing") return MarketingCard(props);
126 return DocCard(props);
127}
128
129// ────────────────────────────────────────────────────────────────────────────
130// Doc / marketing variant — parchment hero.
131// ────────────────────────────────────────────────────────────────────────────
132
133function DocCard({ title, description, eyebrow, kind }: OgCardProps) {
134 const eyebrowText = (eyebrow ?? defaultEyebrow(kind ?? "doc")).toUpperCase();
135
136 return (
137 <div
138 style={{
139 width: W,
140 height: H,
141 display: "flex",
142 flexDirection: "column",
143 backgroundColor: COLORS.bg,
144 padding: "56px 64px",
145 fontFamily: "'Source Serif 4'",
146 color: COLORS.fg1,
147 position: "relative",
148 }}
149 >
150 <Field stroke={COLORS.accentDeep} opacity={0.16} />
151 <div style={{ display: "flex", alignItems: "center", gap: 18 }}>
152 <img
153 src={yantraSvg({
154 size: 96,
155 theme: "light",
156 output: "url",
157 })}
158 width={56}
159 height={56}
160 style={{ display: "flex" }}
161 />
162 <div
163 style={{
164 fontFamily: "JetBrains Mono",
165 fontSize: 18,
166 letterSpacing: 3,
167 color: COLORS.accentDeep,
168 fontWeight: 400,
169 }}
170 >
171 {eyebrowText}
172 </div>
173 </div>
174 <div
175 style={{
176 display: "flex",
177 flexWrap: "wrap",
178 alignItems: "baseline",
179 marginTop: 56,
180 fontFamily: "'Source Serif 4'",
181 fontVariationSettings: "'opsz' 60",
182 fontWeight: 500,
183 fontSize: 110,
184 lineHeight: 1.02,
185 letterSpacing: -2,
186 color: COLORS.fg1,
187 }}
188 >
189 {title}
190 </div>
191 {description ? (
192 <div
193 style={{
194 display: "flex",
195 marginTop: 36,
196 fontSize: 26,
197 lineHeight: 1.45,
198 color: COLORS.fg2,
199 maxWidth: 980,
200 }}
201 >
202 {description}
203 </div>
204 ) : null}
205 <div style={{ display: "flex", flexGrow: 1 }} />
206
207 <div
208 style={{
209 display: "flex",
210 alignItems: "flex-end",
211 justifyContent: "space-between",
212 borderTop: `1px solid ${COLORS.hairline}`,
213 paddingTop: 24,
214 }}
215 >
216 <div
217 style={{
218 fontFamily: "'Source Serif 4'",
219 fontStyle: "italic",
220 fontWeight: 400,
221 fontSize: 32,
222 color: COLORS.fg1,
223 }}
224 >
225 alchemy
226 </div>
227 <div
228 style={{
229 fontFamily: "Caveat",
230 fontWeight: 400,
231 fontSize: 36,
232 color: COLORS.accentDeep,
233 }}
234 >
235 alchemy.run
236 </div>
237 </div>
238 </div>
239 );
240}
241
242// ────────────────────────────────────────────────────────────────────────────
243// Marketing variant — the dark hero: mark, headline, subtitle, and the
244// yantra rings glowing behind it.
245// ────────────────────────────────────────────────────────────────────────────
246
247function MarketingCard({ title, description, eyebrow }: OgCardProps) {
248 return (
249 <div
250 style={{
251 width: W,
252 height: H,
253 display: "flex",
254 flexDirection: "column",
255 backgroundColor: COLORS.darkBg,
256 padding: "60px 72px",
257 fontFamily: "'Source Serif 4'",
258 color: COLORS.darkFg1,
259 position: "relative",
260 }}
261 >
262 <Field stroke={COLORS.darkAccent} opacity={0.3} right={-360} />
263 <div style={{ display: "flex", alignItems: "center", gap: 18 }}>
264 <img
265 src={yantraSvg({ size: 96, theme: "dark", output: "url" })}
266 width={60}
267 height={60}
268 style={{ display: "flex" }}
269 />
270 <div
271 style={{
272 fontFamily: "JetBrains Mono",
273 fontSize: 18,
274 letterSpacing: 3,
275 color: COLORS.darkAccent,
276 }}
277 >
278 {(eyebrow ?? defaultEyebrow("marketing")).toUpperCase()}
279 </div>
280 </div>
281 <div
282 style={{
283 display: "flex",
284 flexWrap: "wrap",
285 marginTop: 48,
286 maxWidth: 820,
287 fontVariationSettings: "'opsz' 60",
288 fontWeight: 500,
289 fontSize: 96,
290 lineHeight: 1.02,
291 letterSpacing: -2,
292 }}
293 >
294 {title}
295 </div>
296 {description ? (
297 <div
298 style={{
299 display: "flex",
300 marginTop: 26,
301 maxWidth: 740,
302 fontSize: 28,
303 lineHeight: 1.4,
304 color: COLORS.darkFg2,
305 }}
306 >
307 {description}
308 </div>
309 ) : null}
310 <div style={{ display: "flex", flexGrow: 1 }} />
311 <div
312 style={{
313 display: "flex",
314 alignItems: "flex-end",
315 justifyContent: "space-between",
316 borderTop: `1px solid ${COLORS.darkHairline}`,
317 paddingTop: 22,
318 }}
319 >
320 <div style={{ fontStyle: "italic", fontSize: 32 }}>alchemy</div>
321 <div
322 style={{
323 fontFamily: "JetBrains Mono",
324 fontSize: 20,
325 letterSpacing: 2,
326 color: COLORS.darkAccent,
327 }}
328 >
329 alchemy.run
330 </div>
331 </div>
332 </div>
333 );
334}
335
336// ────────────────────────────────────────────────────────────────────────────
337// Blog variant — dark, dense, Bun-inspired release card.
338// ────────────────────────────────────────────────────────────────────────────
339
340function BlogCard({ title, description, date }: OgCardProps): any {
341 return (
342 <div
343 style={{
344 width: W,
345 height: H,
346 display: "flex",
347 flexDirection: "column",
348 backgroundColor: COLORS.darkBg,
349 padding: "72px 80px",
350 fontFamily: "'Source Serif 4'",
351 color: COLORS.darkFg1,
352 position: "relative",
353 }}
354 >
355 <Field stroke={COLORS.darkAccent} opacity={0.12} />
356 <div
357 style={{
358 display: "flex",
359 flexWrap: "wrap",
360 fontFamily: "'Source Serif 4'",
361 fontVariationSettings: "'opsz' 60",
362 fontWeight: 500,
363 fontSize: 84,
364 lineHeight: 1.05,
365 letterSpacing: -1.5,
366 color: COLORS.darkFg1,
367 }}
368 >
369 <span style={{ color: COLORS.darkAccent }}>{title}</span>
370 </div>
371 {description ? (
372 <div
373 style={{
374 display: "flex",
375 marginTop: 32,
376 fontSize: 28,
377 lineHeight: 1.5,
378 color: COLORS.darkFg2,
379 maxWidth: 1040,
380 }}
381 >
382 {description}
383 </div>
384 ) : null}
385 <div style={{ display: "flex", flexGrow: 1 }} />
386 <div
387 style={{
388 display: "flex",
389 alignItems: "center",
390 justifyContent: "space-between",
391 borderTop: `1px solid ${COLORS.darkHairline}`,
392 paddingTop: 28,
393 }}
394 >
395 <div
396 style={{
397 display: "flex",
398 flexDirection: "column",
399 gap: 4,
400 }}
401 >
402 <div
403 style={{
404 fontFamily: "'Source Serif 4'",
405 fontSize: 24,
406 color: COLORS.darkFg2,
407 }}
408 >
409 {formatDate(date)}
410 </div>
411 <div
412 style={{
413 fontFamily: "JetBrains Mono",
414 fontSize: 16,
415 letterSpacing: 3,
416 color: COLORS.darkFg3,
417 }}
418 >
419 ALCHEMY.RUN
420 </div>
421 </div>
422 <img
423 src={yantraSvg({ size: 96, theme: "dark", output: "url" })}
424 width={72}
425 height={72}
426 style={{ display: "flex" }}
427 />
428 </div>
429 </div>
430 );
431}
432
433// ────────────────────────────────────────────────────────────────────────────
434// Helpers
435// ────────────────────────────────────────────────────────────────────────────
436
437const MONTHS = [
438 "January",
439 "February",
440 "March",
441 "April",
442 "May",
443 "June",
444 "July",
445 "August",
446 "September",
447 "October",
448 "November",
449 "December",
450];
451
452function formatDate(iso: string | undefined): string {
453 if (!iso) return "";
454 // Parse YYYY-MM-DD without timezone surprises.
455 const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso);
456 if (!m) return iso;
457 const year = Number(m[1]);
458 const month = Number(m[2]);
459 const day = Number(m[3]);
460 return `${MONTHS[month - 1]} ${day}, ${year}`;
461}
462
463function defaultEyebrow(kind: OgCardKind): string {
464 switch (kind) {
465 case "marketing":
466 return "infrastructure as effects";
467 case "blog":
468 return "blog · alchemy.run";
469 case "doc":
470 default:
471 return "alchemy · documentation";
472 }
473}