Files
editor/apps/community/app/og/route.tsx
T
2026-03-11 12:16:26 +01:00

164 lines
5.1 KiB
TypeScript

import { readFile } from 'node:fs/promises'
import { join } from 'node:path'
import { ImageResponse } from 'next/og'
import * as z from 'zod'
import { siteConfig } from '@/app/seo'
const ogImageSchema = z.object({
title: z.string().default(''),
description: z.string().default(''),
theme: z.enum(['light', 'dark']).default('dark'),
})
let sansFont: ArrayBuffer | null = null
let jakartaFont: ArrayBuffer | null = null
async function loadFonts() {
if (!(sansFont && jakartaFont)) {
const fontDir = join(process.cwd(), 'public', 'fonts')
const [sans, jakarta] = await Promise.all([
readFile(join(fontDir, 'geist-regular.ttf')),
readFile(join(fontDir, 'PlusJakartaSans-SemiBold.ttf')),
])
sansFont = sans.buffer.slice(sans.byteOffset, sans.byteOffset + sans.byteLength)
jakartaFont = jakarta.buffer.slice(jakarta.byteOffset, jakarta.byteOffset + jakarta.byteLength)
}
return { sans: sansFont, jakarta: jakartaFont }
}
function PascalLogo({ color = '#fff', size = 100 }: { color?: string; size?: number }) {
return (
<svg fill="none" height={size} style={{ display: 'flex' }} viewBox="0 0 100 100" width={size}>
<rect fill={color} height="40" width="20" y="60" />
<rect fill={color} height="40" width="20" x="40" y="30" />
<rect fill={color} height="40" width="20" x="80" />
</svg>
)
}
export async function GET(req: Request) {
try {
const url = new URL(req.url)
const values = ogImageSchema.parse(Object.fromEntries(url.searchParams))
const heading =
values.title.length > 140 ? `${values.title.substring(0, 140)}...` : values.title
const { theme } = values
const paint = theme === 'dark' ? '#fff' : '#000'
const logoColor = theme === 'dark' ? 'rgba(255, 255, 255, 0.9)' : 'rgba(0, 0, 0, 0.9)'
const fontSize = heading.length > 100 ? '70px' : '100px'
const showLargeLogo = !(values.title || values.description)
return new ImageResponse(
<div
style={{
color: paint,
background:
theme === 'dark'
? 'linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 50%, #0f0f0f 100%)'
: 'white',
}}
tw="flex relative flex-col w-full h-full items-start bg-cover"
>
{showLargeLogo ? (
<div tw="flex flex-col flex-1 py-10 px-12 h-full justify-center">
<div style={{ display: 'flex', marginBottom: 24 }}>
<PascalLogo color={logoColor} size={80} />
</div>
<div
style={{
fontFamily: 'jakarta-semibold',
fontWeight: 'bolder',
fontSize: '72px',
letterSpacing: '-0.02em',
color: paint,
display: 'flex',
}}
>
Pascal Editor
</div>
<div
style={{
fontFamily: 'sans-regular',
fontWeight: 'normal',
fontSize: '28px',
letterSpacing: '0.05em',
color: 'rgba(255, 255, 255, 0.6)',
marginTop: 16,
display: 'flex',
}}
>
{siteConfig.description}
</div>
</div>
) : (
<div tw="flex flex-col flex-1 py-10 px-12 h-full">
<div style={{ display: 'flex', marginBottom: 24 }}>
<PascalLogo color={logoColor} size={40} />
</div>
<div
style={{
fontFamily: 'jakarta-semibold',
fontWeight: 'bolder',
marginLeft: '-3px',
fontSize,
letterSpacing: '.05rem',
}}
tw="flex leading-[1.1] text-[80px] tracking-tighter font-sans mb-4"
>
{heading}
</div>
<div
style={{
fontFamily: 'sans-regular',
fontWeight: 'normal',
letterSpacing: '.1rem',
}}
tw="flex flex-1 text-[50px] tracking-tight font-sans"
>
{values.description}
</div>
</div>
)}
{!showLargeLogo && (
<div
style={{
fontFamily: 'sans-regular',
fontWeight: 'normal',
letterSpacing: '.1rem',
}}
tw="w-full pt-24 p-10 text-[30px] tracking-tight font-sans text-right w-full"
>
editor.pascal.app
</div>
)}
</div>,
{
width: 1200,
height: 630,
fonts: await loadFonts().then(({ sans, jakarta }) => [
{
name: 'sans-regular',
data: sans,
style: 'normal' as const,
weight: 400 as const,
},
{
name: 'jakarta-semibold',
data: jakarta,
style: 'normal' as const,
weight: 600 as const,
},
]),
},
)
} catch (error) {
console.log('error', error)
return new Response('Failed to generate image', {
status: 500,
})
}
}