diff --git a/bun.lock b/bun.lock
index c2eb021..ee3aa8d 100644
--- a/bun.lock
+++ b/bun.lock
@@ -54,6 +54,7 @@
"postgres": "^3",
},
"devDependencies": {
+ "@resvg/resvg-js": "^2.6.2",
"@types/bun": "latest",
"drizzle-kit": "^0.30",
"typescript": "^5",
@@ -470,6 +471,32 @@
"@remix-run/node-fetch-server": ["@remix-run/node-fetch-server@0.13.0", "", {}, "sha512-1EsNo0ZpgXu/90AWoRZf/oE3RVTUS80tiTUpt+hv5pjtAkw7icN4WskDwz/KdAw5ARbJLMhZBrO1NqThmy/McA=="],
+ "@resvg/resvg-js": ["@resvg/resvg-js@2.6.2", "", { "optionalDependencies": { "@resvg/resvg-js-android-arm-eabi": "2.6.2", "@resvg/resvg-js-android-arm64": "2.6.2", "@resvg/resvg-js-darwin-arm64": "2.6.2", "@resvg/resvg-js-darwin-x64": "2.6.2", "@resvg/resvg-js-linux-arm-gnueabihf": "2.6.2", "@resvg/resvg-js-linux-arm64-gnu": "2.6.2", "@resvg/resvg-js-linux-arm64-musl": "2.6.2", "@resvg/resvg-js-linux-x64-gnu": "2.6.2", "@resvg/resvg-js-linux-x64-musl": "2.6.2", "@resvg/resvg-js-win32-arm64-msvc": "2.6.2", "@resvg/resvg-js-win32-ia32-msvc": "2.6.2", "@resvg/resvg-js-win32-x64-msvc": "2.6.2" } }, "sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q=="],
+
+ "@resvg/resvg-js-android-arm-eabi": ["@resvg/resvg-js-android-arm-eabi@2.6.2", "", { "os": "android", "cpu": "arm" }, "sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA=="],
+
+ "@resvg/resvg-js-android-arm64": ["@resvg/resvg-js-android-arm64@2.6.2", "", { "os": "android", "cpu": "arm64" }, "sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ=="],
+
+ "@resvg/resvg-js-darwin-arm64": ["@resvg/resvg-js-darwin-arm64@2.6.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A=="],
+
+ "@resvg/resvg-js-darwin-x64": ["@resvg/resvg-js-darwin-x64@2.6.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw=="],
+
+ "@resvg/resvg-js-linux-arm-gnueabihf": ["@resvg/resvg-js-linux-arm-gnueabihf@2.6.2", "", { "os": "linux", "cpu": "arm" }, "sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw=="],
+
+ "@resvg/resvg-js-linux-arm64-gnu": ["@resvg/resvg-js-linux-arm64-gnu@2.6.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg=="],
+
+ "@resvg/resvg-js-linux-arm64-musl": ["@resvg/resvg-js-linux-arm64-musl@2.6.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg=="],
+
+ "@resvg/resvg-js-linux-x64-gnu": ["@resvg/resvg-js-linux-x64-gnu@2.6.2", "", { "os": "linux", "cpu": "x64" }, "sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw=="],
+
+ "@resvg/resvg-js-linux-x64-musl": ["@resvg/resvg-js-linux-x64-musl@2.6.2", "", { "os": "linux", "cpu": "x64" }, "sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ=="],
+
+ "@resvg/resvg-js-win32-arm64-msvc": ["@resvg/resvg-js-win32-arm64-msvc@2.6.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ=="],
+
+ "@resvg/resvg-js-win32-ia32-msvc": ["@resvg/resvg-js-win32-ia32-msvc@2.6.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w=="],
+
+ "@resvg/resvg-js-win32-x64-msvc": ["@resvg/resvg-js-win32-x64-msvc@2.6.2", "", { "os": "win32", "cpu": "x64" }, "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ=="],
+
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.1", "", { "os": "android", "cpu": "arm" }, "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.1", "", { "os": "android", "cpu": "arm64" }, "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA=="],
diff --git a/packages/relay/package.json b/packages/relay/package.json
index bd2fd97..f398acf 100644
--- a/packages/relay/package.json
+++ b/packages/relay/package.json
@@ -10,6 +10,7 @@
"db:push": "drizzle-kit push",
"db:migrate": "drizzle-kit migrate",
"db:seed-dashboard": "bun run scripts/seed-dashboard.ts",
+ "og:generate": "bun run scripts/generate-og.ts",
"lint": "eslint src/"
},
"dependencies": {
@@ -30,8 +31,9 @@
"postgres": "^3"
},
"devDependencies": {
- "drizzle-kit": "^0.30",
+ "@resvg/resvg-js": "^2.6.2",
"@types/bun": "latest",
+ "drizzle-kit": "^0.30",
"typescript": "^5"
}
}
diff --git a/packages/relay/public/og-image.png b/packages/relay/public/og-image.png
new file mode 100644
index 0000000..b7b1103
Binary files /dev/null and b/packages/relay/public/og-image.png differ
diff --git a/packages/relay/public/og-image.svg b/packages/relay/public/og-image.svg
new file mode 100644
index 0000000..57d02a3
--- /dev/null
+++ b/packages/relay/public/og-image.svg
@@ -0,0 +1,91 @@
+
diff --git a/packages/relay/scripts/fonts/BricolageGrotesque-Bold.ttf b/packages/relay/scripts/fonts/BricolageGrotesque-Bold.ttf
new file mode 100644
index 0000000..f18a177
Binary files /dev/null and b/packages/relay/scripts/fonts/BricolageGrotesque-Bold.ttf differ
diff --git a/packages/relay/scripts/fonts/BricolageGrotesque-Medium.ttf b/packages/relay/scripts/fonts/BricolageGrotesque-Medium.ttf
new file mode 100644
index 0000000..8da1bfa
Binary files /dev/null and b/packages/relay/scripts/fonts/BricolageGrotesque-Medium.ttf differ
diff --git a/packages/relay/scripts/fonts/BricolageGrotesque-Regular.ttf b/packages/relay/scripts/fonts/BricolageGrotesque-Regular.ttf
new file mode 100644
index 0000000..d4f35fb
Binary files /dev/null and b/packages/relay/scripts/fonts/BricolageGrotesque-Regular.ttf differ
diff --git a/packages/relay/scripts/generate-og.ts b/packages/relay/scripts/generate-og.ts
new file mode 100644
index 0000000..e3a66a6
--- /dev/null
+++ b/packages/relay/scripts/generate-og.ts
@@ -0,0 +1,20 @@
+import { readFileSync, writeFileSync } from 'node:fs'
+import { Resvg } from '@resvg/resvg-js'
+
+const svg = readFileSync(new URL('../public/og-image.svg', import.meta.url), 'utf8')
+
+const fontDir = new URL('./fonts/', import.meta.url).pathname
+
+const resvg = new Resvg(svg, {
+ fitTo: { mode: 'width', value: 1200 },
+ font: {
+ loadSystemFonts: false,
+ fontDirs: [fontDir],
+ defaultFontFamily: 'Bricolage Grotesque',
+ },
+ background: '#f8f3e8',
+})
+
+const png = resvg.render().asPng()
+writeFileSync(new URL('../public/og-image.png', import.meta.url), png)
+console.log(`wrote public/og-image.png (${png.byteLength} bytes)`)
diff --git a/packages/relay/src/index.ts b/packages/relay/src/index.ts
index 8dce3af..fbdb72c 100644
--- a/packages/relay/src/index.ts
+++ b/packages/relay/src/index.ts
@@ -90,6 +90,15 @@ export const app = new Elysia()
{ detail: { hide: true } },
)
.get('/cover.mp4', () => Bun.file(new URL('../public/cover.mp4', import.meta.url).pathname))
+ .get('/og-image.png', ({ set }) => {
+ set.headers['cache-control'] = 'public, max-age=86400'
+ return Bun.file(new URL('../public/og-image.png', import.meta.url).pathname)
+ })
+ .get('/og-image.svg', ({ set }) => {
+ set.headers['cache-control'] = 'public, max-age=86400'
+ set.headers['content-type'] = 'image/svg+xml'
+ return Bun.file(new URL('../public/og-image.svg', import.meta.url).pathname)
+ })
.get('/health', async () => {
const checks = {
db: 'unknown' as string,
diff --git a/packages/relay/src/views/dashboard/page.tsx b/packages/relay/src/views/dashboard/page.tsx
index 8d7711a..6c738eb 100644
--- a/packages/relay/src/views/dashboard/page.tsx
+++ b/packages/relay/src/views/dashboard/page.tsx
@@ -14,6 +14,7 @@ export const DashboardPage = () => (
diff --git a/packages/relay/src/views/landing/page.tsx b/packages/relay/src/views/landing/page.tsx
index 2862ebf..da38034 100644
--- a/packages/relay/src/views/landing/page.tsx
+++ b/packages/relay/src/views/landing/page.tsx
@@ -23,6 +23,7 @@ export const LandingPage = () => (
diff --git a/packages/relay/src/views/landing/styles.css b/packages/relay/src/views/landing/styles.css
index bad28f7..4ff2b70 100644
--- a/packages/relay/src/views/landing/styles.css
+++ b/packages/relay/src/views/landing/styles.css
@@ -140,7 +140,7 @@ section h2 {
}
.hero h1 {
- font-size: clamp(2.5rem, 5.5vw + 1rem, 4.5rem);
+ font-size: clamp(4.25rem, 6vw + 2.3rem, 5.5rem);
margin-bottom: var(--space-md);
}
diff --git a/packages/relay/src/views/layout.tsx b/packages/relay/src/views/layout.tsx
index ac968da..c4338fb 100644
--- a/packages/relay/src/views/layout.tsx
+++ b/packages/relay/src/views/layout.tsx
@@ -1,3 +1,4 @@
+import { PUBLIC_BASE_URL } from '@/agent-meta/routes'
import { sharedStyles } from './shared-styles'
interface LayoutProps {
@@ -5,35 +6,62 @@ interface LayoutProps {
description?: string
themeColor?: string
extraStyles?: string
+ path?: string
children?: string | string[]
}
+const OG_IMAGE_URL = `${PUBLIC_BASE_URL}/og-image.png`
+
export const Layout = ({
title,
description,
themeColor = '#1d35cf',
extraStyles,
+ path = '/',
children,
-}: LayoutProps) => (
- <>
- {''}
-
-
-
-
-
- {title}
- {description && }
-
-
-
-
- {extraStyles && }
-
- {children}
-
- >
-)
+}: LayoutProps) => {
+ const canonicalUrl = `${PUBLIC_BASE_URL}${path}`
+ return (
+ <>
+ {''}
+
+
+
+
+
+ {title}
+ {description && }
+
+
+
+
+
+ {description && }
+
+
+
+
+
+
+
+
+ {description && }
+
+
+
+
+
+
+ {extraStyles && }
+
+ {children}
+
+ >
+ )
+}