diff --git a/src/app/speedtest-rpc/opengraph-image.tsx b/src/app/speedtest-rpc/opengraph-image.tsx new file mode 100644 index 00000000..0caed4dd --- /dev/null +++ b/src/app/speedtest-rpc/opengraph-image.tsx @@ -0,0 +1,141 @@ +import { ImageResponse } from "next/og"; + +export const runtime = "nodejs"; +export const alt = "RPC Speed Test by OpenChainBench"; +export const size = { width: 1200, height: 630 }; +export const contentType = "image/png"; + +// Same dial geometry as the live tool, rendered once for the social +// card. Static content: cache aggressively (content only changes on +// deploy). +const GAUGE_START = -210; +const GAUGE_SWEEP = 240; +const STOPS = [1, 5, 10, 25, 50, 100, 250, 500, 1000]; + +function polar(cx: number, cy: number, r: number, deg: number): [number, number] { + const rad = (deg * Math.PI) / 180; + return [cx + r * Math.cos(rad), cy + r * Math.sin(rad)]; +} +function arcPath(cx: number, cy: number, r: number, a0: number, a1: number): string { + const [x0, y0] = polar(cx, cy, r, a0); + const [x1, y1] = polar(cx, cy, r, a1); + const large = a1 - a0 > 180 ? 1 : 0; + return `M ${x0.toFixed(1)} ${y0.toFixed(1)} A ${r} ${r} 0 ${large} 1 ${x1.toFixed(1)} ${y1.toFixed(1)}`; +} +function msToAngle(ms: number): number { + const t = Math.log(Math.max(1, Math.min(1000, ms))) / Math.log(1000); + return GAUGE_START + t * GAUGE_SWEEP; +} +const GRAD: [number, [number, number, number]][] = [ + [0.0, [16, 185, 129]], + [0.55, [245, 158, 11]], + [1.0, [239, 68, 68]], +]; +function gradColor(t: number): string { + const x = Math.max(0, Math.min(1, t)); + for (let i = 1; i < GRAD.length; i++) { + const [t1, c1] = GRAD[i]; + const [t0, c0] = GRAD[i - 1]; + if (x <= t1) { + const f = (x - t0) / (t1 - t0); + const c = c0.map((v, j) => Math.round(v + (c1[j] - v) * f)); + return `rgb(${c[0]},${c[1]},${c[2]})`; + } + } + return "rgb(239,68,68)"; +} + +export default function OG() { + const READING = 42; // ms, needle in the green + const angle = msToAngle(READING); + const fillT = (angle - GAUGE_START) / GAUGE_SWEEP; + const SEGS = 48; + const segs: { d: string; color: string }[] = []; + for (let i = 0; i < SEGS; i++) { + const t0 = i / SEGS; + if (t0 >= fillT) break; + const t1 = Math.min((i + 1) / SEGS, fillT); + segs.push({ + d: arcPath(210, 210, 160, GAUGE_START + t0 * GAUGE_SWEEP, GAUGE_START + t1 * GAUGE_SWEEP + 0.6), + color: gradColor(t0), + }); + } + const [nx, ny] = polar(210, 210, 128, angle); + + return new ImageResponse( + ( +