Build stamp, live listening strip, OG cards, closer shiki ground
- footer: 'built <ts> · <hash> · <n> notes', hash linking to Gitea (GARDEN_COMMIT passed in by build.sh; '+' marks dirty-tree builds) - homepage header: live strip fed by api.c0smere.net/spotify/summary (weekly aggregate + monthly top artist/genre), fail-closed like the odometer and header quote - /og/<slug>.png satori+resvg cards for every note + the homepage; og:/twitter: meta in Base - shiki github-dark-default (#0d1117 sits closer to the page ground) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
// Build-time OG images: 1200x630 dark cards matching the site design,
|
||||
// one per note (plus /og/index.png for the homepage). satori renders an
|
||||
// element tree to SVG, resvg rasterizes to PNG. Base.astro points
|
||||
// og:image here via the ogSlug prop.
|
||||
import fs from 'node:fs';
|
||||
import type { APIRoute } from 'astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
import satori from 'satori';
|
||||
import { Resvg } from '@resvg/resvg-js';
|
||||
import { slugForId } from '../../lib/quartz-compat.mjs';
|
||||
|
||||
const FONT_DIR = 'node_modules/@fontsource/jetbrains-mono/files';
|
||||
const mono400 = fs.readFileSync(`${FONT_DIR}/jetbrains-mono-latin-400-normal.woff`);
|
||||
const mono800 = fs.readFileSync(`${FONT_DIR}/jetbrains-mono-latin-800-normal.woff`);
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const entries = await getCollection('garden', (e: any) => !e.data.draft);
|
||||
return entries.map((e: any) => {
|
||||
const isIndex = e.id === 'index';
|
||||
const title = isIndex
|
||||
? 'Wesley Ray'
|
||||
: (e.data.title ?? e.id.split('/').pop());
|
||||
const dir = e.id.includes('/') ? e.id.slice(0, e.id.lastIndexOf('/')) : '';
|
||||
return {
|
||||
params: { slug: isIndex ? 'index' : slugForId(e.id) },
|
||||
props: {
|
||||
title,
|
||||
dir,
|
||||
subtitle: isIndex ? 'projects, notes, and experiments' : dir,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export const GET: APIRoute = async ({ props }) => {
|
||||
const { title, subtitle } = props as { title: string; subtitle: string };
|
||||
const size = title.length > 55 ? 44 : title.length > 32 ? 56 : 68;
|
||||
|
||||
const svg = await satori(
|
||||
{
|
||||
type: 'div',
|
||||
props: {
|
||||
style: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: '#0b0d12',
|
||||
borderLeft: '14px solid #218107',
|
||||
padding: '64px 72px',
|
||||
fontFamily: 'JetBrains Mono',
|
||||
color: '#ffffff',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'div',
|
||||
props: {
|
||||
style: {
|
||||
display: 'flex',
|
||||
fontSize: '26px',
|
||||
color: 'rgba(255,255,255,0.64)',
|
||||
letterSpacing: '2px',
|
||||
},
|
||||
children: 'the garden // Wesley Ray',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'div',
|
||||
props: {
|
||||
style: {
|
||||
display: 'flex',
|
||||
fontSize: `${size}px`,
|
||||
fontWeight: 800,
|
||||
lineHeight: 1.25,
|
||||
letterSpacing: '-1px',
|
||||
},
|
||||
children: title,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'div',
|
||||
props: {
|
||||
style: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
fontSize: '26px',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'div',
|
||||
props: {
|
||||
style: {
|
||||
display: 'flex',
|
||||
color: 'rgba(255,255,255,0.64)',
|
||||
},
|
||||
children: subtitle || ' ',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'div',
|
||||
props: {
|
||||
style: { display: 'flex', color: '#2fa50f' },
|
||||
children: 'garden.c0smere.net',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
width: 1200,
|
||||
height: 630,
|
||||
fonts: [
|
||||
{ name: 'JetBrains Mono', data: mono400, weight: 400, style: 'normal' },
|
||||
{ name: 'JetBrains Mono', data: mono800, weight: 800, style: 'normal' },
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
const png = new Resvg(svg, {
|
||||
fitTo: { mode: 'width', value: 1200 },
|
||||
}).render().asPng();
|
||||
|
||||
return new Response(png, { headers: { 'Content-Type': 'image/png' } });
|
||||
};
|
||||
Reference in New Issue
Block a user