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 @@ + + + + + + + + + + + + + + + + + + + + 402md ยท facilitator + + + + + Any chain in, + + your chain out. + + + + + Cross-chain USDC settlement for x402 + MPP. + + + + + + BASE + + + + ETH + + + + OP + + + + ARB + + + + LINEA + + + + UNI + + + + WORLD + + + + SOLANA + + + + STELLAR + + + + + + + + + + + + + + + + + + + + + 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} + + + ) +}