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()) }