From 7feaffcfd6fbdb4d5a4cd0152a89d988586e01e7 Mon Sep 17 00:00:00 2001 From: Shane Date: Fri, 10 Jul 2026 11:24:44 -0500 Subject: [PATCH] Add a Blog post in-context preview mockup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modeled on the real supabase.com/blog/[slug] page (BlogPostRenderer.tsx): sticky nav, back-to-blog link, title, date/read-time, author, then the hero image — cropped to a fixed 1.91:1 box (object-cover), same as the live site does regardless of the source image's own aspect ratio, so this shows exactly how much the Thumb image gets cropped rather than assuming it survives untouched. Distinct from the existing "Blog listing" grid mockup, which approximates the index page instead of a single post. Wires thumbImgUrl through so the mockup can prefer the Thumb render (matching the real page's imgThumb-over-imgSocial priority) even when the OG/Thumb toggle itself isn't currently showing Thumb. Co-Authored-By: Claude Sonnet 5 --- apps/og-generator/app/InContextPreview.tsx | 65 +++++++++++++++++++++- apps/og-generator/app/page.tsx | 7 ++- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/apps/og-generator/app/InContextPreview.tsx b/apps/og-generator/app/InContextPreview.tsx index 3e2a7882c99..c42b3e904c0 100644 --- a/apps/og-generator/app/InContextPreview.tsx +++ b/apps/og-generator/app/InContextPreview.tsx @@ -12,18 +12,26 @@ * as authentic, not just "dark card on our UI". */ -export type InContextMode = 'none' | 'twitter' | 'linkedin' | 'blog' +export type InContextMode = 'none' | 'twitter' | 'linkedin' | 'blog-post' | 'blog' type PreviewTheme = 'light' | 'dark' export const IN_CONTEXT_OPTS: { value: InContextMode; label: string }[] = [ { value: 'none', label: 'None' }, { value: 'twitter', label: 'Twitter / X' }, { value: 'linkedin', label: 'LinkedIn' }, + { value: 'blog-post', label: 'Blog post' }, { value: 'blog', label: 'Blog listing' }, ] interface Props { imgUrl: string | null + /** + * The Thumb render, when the active format has one — the real blog post + * page's hero crops whichever image it's given to a fixed 1.91:1 box, so + * passing this through the Blog-post mockup shows the actual crop instead + * of assuming the OG image's own aspect ratio survives untouched. + */ + thumbImgUrl?: string | null headline: string eyebrow: string | null aspect: string @@ -152,6 +160,60 @@ function LinkedInCardMockup({ imgUrl, headline, aspect }: Props) { ) } +// Real supabase.com/blog/[slug] hero crops whatever image it's given into a +// fixed 1.91:1 box via object-cover — independent of the source image's own +// aspect ratio (brief: apps/www/app/blog/[slug]/BlogPostRenderer.tsx). +const BLOG_POST_HERO_ASPECT = '1.91 / 1' + +function BlogPostMockup({ imgUrl, thumbImgUrl, headline }: Props) { + const heroSrc = thumbImgUrl ?? imgUrl + return ( +
+ {/* Sticky site nav — just enough to anchor "above the fold", not a + pixel-match of the real header. */} +
+
+ Supabase +
+ +
+
+ + + + Blog +
+ +

{headline}

+ +
+ Jul 10, 2026 + · + 4 minute read +
+ +
+
+
+ Supabase Team + Marketing +
+
+ + {heroSrc && ( +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + +
+ )} +
+
+ ) +} + function BlogListingMockup({ imgUrl, headline, eyebrow, aspect }: Props) { const fillerCards = [ { eyebrow: 'Engineering', title: 'Scaling Postgres connections with Supavisor' }, @@ -205,6 +267,7 @@ export function InContextPreview({ mode, ...rest }: Props & { mode: InContextMod return (
{mode === 'linkedin' && } + {mode === 'blog-post' && } {mode === 'blog' && }
) diff --git a/apps/og-generator/app/page.tsx b/apps/og-generator/app/page.tsx index be734488e9b..7c699fa2d2c 100644 --- a/apps/og-generator/app/page.tsx +++ b/apps/og-generator/app/page.tsx @@ -649,6 +649,10 @@ export default function Page() { const showOg = view !== 'thumb' const showThumb = view !== 'og' && hasSecondSlot + // The Blog-post in-context mockup mirrors the real page's hero, which + // prefers the Thumb image — fetch it even if the OG/Thumb toggle itself + // isn't showing Thumb right now. + const wantsThumbForBlogPost = inContext === 'blog-post' && hasSecondSlot // Content controls (Layout/Eyebrow/Headline) stay visible whenever a full // composition is on screen — that includes viewing just the second slot // when it's a full composition too (e.g. Social's Instagram), unlike the @@ -696,7 +700,7 @@ export default function Page() { }, [brandId, formatId, hasSecondary, headline, eyebrow, template, icon, scale]) const og = useRenderedImage(ogEndpoint, showOg) - const thumb = useRenderedImage(thumbEndpoint, showThumb) + const thumb = useRenderedImage(thumbEndpoint, showThumb || wantsThumbForBlogPost) const count = [...headline].length const counterColor = @@ -796,6 +800,7 @@ export default function Page() {