diff --git a/bun.lock b/bun.lock
index 15cbffe..3d7e924 100644
--- a/bun.lock
+++ b/bun.lock
@@ -5,7 +5,8 @@
"": {
"name": "toolbench",
"dependencies": {
- "@kevinschaich/components": "^0.8.0",
+ "@fontsource/fragment-mono": "^5.3.0",
+ "@kevinschaich/components": "^0.8.2",
"d3-array": "^3.2.5",
"d3-scale": "^4.0.3",
"d3-shape": "^3.2.1",
@@ -120,6 +121,8 @@
"@fontsource-variable/public-sans": ["@fontsource-variable/public-sans@5.3.0", "", {}, "sha512-AVfkmAt50BMXWpOO21FAntiJFKGX6xTc2dSL8dxtDteONe9IuRXJWGbs0EbG955vAMCq23ENeuopuW87cGWDSQ=="],
+ "@fontsource/fragment-mono": ["@fontsource/fragment-mono@5.3.0", "", {}, "sha512-vpOLZhWq7Z6wUPo1qMl16Y4yjQJiN0d9TXX2oUtiYXmqUrXU23Wuto73Knyak+osdlIuHz46srpBZb2pfhHCIA=="],
+
"@fontsource/public-sans": ["@fontsource/public-sans@5.3.0", "", {}, "sha512-kjODI0S3zdv0mBYCIQ8TbBayaiqszpc2UbhJiO3bjIqVVXzcWfHSt2o3WBCLOY3juaGaQoy4MoWCcgmfI5hCuA=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
@@ -132,7 +135,7 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
- "@kevinschaich/components": ["@kevinschaich/components@0.8.0", "", { "dependencies": { "@base-ui/react": "^1.9.0", "@fontsource-variable/jetbrains-mono": "^5.3.0", "@fontsource-variable/public-sans": "^5.3.0", "@shadcn/react": "^0.3.1", "@types/geojson": "^7946.0.16", "class-variance-authority": "^0.7.1", "cmdk": "^1.1.1", "cn": "^0.5.0", "date-fns": "^4.4.0", "embla-carousel-react": "^8.6.0", "input-otp": "^1.5.0", "lucide-react": "^1.63.0", "react-day-picker": "^10.0.2", "react-resizable-panels": "^4.15.1", "recharts": "3.10.1", "shadcn": "^4.23.0", "shiki": "^4.5.0", "tw-animate-css": "^1.4.0" }, "peerDependencies": { "maplibre-gl": ">=5", "react": ">=19", "react-dom": ">=19" }, "optionalPeers": ["maplibre-gl"] }, "sha512-evPvKujk4mzqv3ZCZvcD8A4+aBtQFmwOp55cIvS+wy04I4aAh3f+t1GpSrwnnt/4YESwWRuR/fdJVCVnqfW8ew=="],
+ "@kevinschaich/components": ["@kevinschaich/components@0.8.2", "", { "dependencies": { "@base-ui/react": "^1.9.0", "@fontsource-variable/jetbrains-mono": "^5.3.0", "@fontsource-variable/public-sans": "^5.3.0", "@shadcn/react": "^0.3.1", "@types/geojson": "^7946.0.16", "class-variance-authority": "^0.7.1", "cmdk": "^1.1.1", "cn": "^0.5.0", "date-fns": "^4.4.0", "embla-carousel-react": "^8.6.0", "input-otp": "^1.5.0", "lucide-react": "^1.63.0", "react-day-picker": "^10.0.2", "react-resizable-panels": "^4.15.1", "recharts": "3.10.1", "shadcn": "^4.23.0", "shiki": "^4.5.0", "tw-animate-css": "^1.4.0" }, "peerDependencies": { "maplibre-gl": ">=5", "react": ">=19", "react-dom": ">=19" }, "optionalPeers": ["maplibre-gl"] }, "sha512-fNcyyMX9kQ8MbFvwzpOcC+cKCjLijp9prMTI/dZr1xvmi+gO+NHUzuovbsEdG7gWzfoPRSeYKlYkOp51yKpeDA=="],
"@modelcontextprotocol/core": ["@modelcontextprotocol/core@2.4.0", "", { "dependencies": { "zod": "^4.2.0" } }, "sha512-z8QBKqdfXLPzkLQuG4xp8hrVEMm1PZ1IgxMN2sMbJiFiAKOWXnAzogXn2W4aqIBlP1ea6U5yiqVquvUNM1Cj6Q=="],
diff --git a/index.html b/index.html
index f52b74d..14f905a 100644
--- a/index.html
+++ b/index.html
@@ -7,6 +7,17 @@
Toolbench | Darkgrade
+
diff --git a/package.json b/package.json
index b51306e..7ebc9a0 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,8 @@
"data": "bun scripts/build-data.ts"
},
"dependencies": {
- "@kevinschaich/components": "^0.8.0",
+ "@fontsource/fragment-mono": "^5.3.0",
+ "@kevinschaich/components": "^0.8.2",
"d3-array": "^3.2.5",
"d3-scale": "^4.0.3",
"d3-shape": "^3.2.1",
diff --git a/scripts/prerender.ts b/scripts/prerender.ts
index bf98972..79ddc69 100644
--- a/scripts/prerender.ts
+++ b/scripts/prerender.ts
@@ -7,6 +7,7 @@ import { Resvg } from '@resvg/resvg-js'
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { dirname, join, resolve } from 'node:path'
+import { OgCard, backdropDataURI, darkgradeMark } from '@kevinschaich/components/og'
import satori from 'satori'
interface Route {
@@ -25,58 +26,29 @@ const server = (await import(join(root, '.server', 'server.js'))) as {
const output = join(root, 'dist', server.BENCH.path)
const template = readFileSync(join(output, 'index.html'), 'utf8')
const fonts = createRequire(import.meta.url)
-const font = (weight: number) => readFileSync(fonts.resolve(`@fontsource/public-sans/files/public-sans-latin-${weight}-normal.woff`))
+const font = (name: string, weight: number) => readFileSync(fonts.resolve(`@fontsource/${name}/files/${name}-latin-${weight}-normal.woff`))
const FONTS = [
- { name: 'Public Sans', data: font(400), weight: 400 as const, style: 'normal' as const },
- { name: 'Public Sans', data: font(700), weight: 700 as const, style: 'normal' as const },
+ { name: 'body', data: font('public-sans', 400), weight: 400 as const, style: 'normal' as const },
+ { name: 'heading', data: font('public-sans', 600), weight: 600 as const, style: 'normal' as const },
+ { name: 'heading', data: font('public-sans', 500), weight: 500 as const, style: 'normal' as const },
+ { name: 'mono', data: font('fragment-mono', 400), weight: 400 as const, style: 'normal' as const },
]
const escape = (text: string): string => text.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"')
/**
- * The preview card, Darkgrade's look: obsidian with a gold accent. The heading and numbers sit in the
- * middle with wide margins, because chat apps crop and zoom previews from the edges in.
+ * The preview card: the shared Darkgrade card from @kevinschaich/components. Stats fold into one line under the heading.
*/
async function card(route: Route): Promise {
- const text = (children: string, style: Record) => ({ type: 'div', props: { style: { display: 'flex', ...style }, children } })
- const element = {
- type: 'div',
- props: {
- style: {
- width: 1200,
- height: 630,
- display: 'flex',
- flexDirection: 'column',
- justifyContent: 'center',
- gap: 44,
- padding: '0 120px',
- background: 'radial-gradient(circle at 85% 0%, #2a2216 0%, #0b0b0c 60%)',
- color: '#eae6dc',
- fontFamily: 'Public Sans',
- },
- children: [
- text(`DARKGRADE · ${route.card.eyebrow.toUpperCase()}`, { fontSize: 22, letterSpacing: 6, color: '#f4c66e' }),
- text(route.card.heading, { fontSize: route.card.heading.length > 44 ? 54 : 68, fontWeight: 700, lineHeight: 1.1, maxWidth: 960 }),
- {
- type: 'div',
- props: {
- style: { display: 'flex', gap: 24 },
- children: route.card.stats.map(stat => ({
- type: 'div',
- props: {
- style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '18px 26px', border: '1px solid #3a3326', borderRadius: 16, background: 'rgba(244,198,110,0.06)' },
- children: [
- text(stat.label.toUpperCase(), { fontSize: 18, letterSpacing: 3, color: '#9c968a' }),
- text(stat.value, { fontSize: 38, fontWeight: 700, color: '#f4c66e' }),
- ],
- },
- })),
- },
- },
- ],
- },
- }
- const svg = await satori(element as Parameters[0], { width: 1200, height: 630, fonts: FONTS })
+ const sub = route.card.stats.length ? route.card.stats.map(stat => `${stat.label} ${stat.value}`).join(' · ') : route.description
+ const element = OgCard({
+ logo: { mark: darkgradeMark.dark, name: 'Darkgrade' },
+ main: route.card.heading,
+ sub,
+ address: `darkgrade.com${server.BENCH.path}${route.path}`.replace(/\/$/, ''),
+ background: await backdropDataURI(),
+ })
+ const svg = await satori(element, { width: 1200, height: 630, fonts: FONTS })
return Buffer.from(new Resvg(svg).render().asPng())
}