Media Generation
Media Generation System for generating OG images, social graphics, and Instagram content using real app screenshots via device-framed iframes and Playwright ...
Media Generation
System for generating OG images, social graphics, and Instagram content using real app screenshots via device-framed iframes and Playwright capture.
Design Source Of Truth
All generated media must follow the root DESIGN.md. This applies to screenshot templates, social graphics, product videos, and any future Remotion compositions.
Agents working on video or media generation must check DESIGN.md before creating visual output and must update it when introducing reusable video, Remotion, social, or print design rules.
Why This Exists
Marketing assets (OG images, Instagram posts/stories/carousels) need to show the actual app UI. Instead of hand-maintained mock components that drift from reality, device frames contain iframes that load the real app in media mode (?media=1), ensuring screenshots always match the current UI.
How It Works
Media Mode (?media=1)
When the main app detects ?media=1:
- Adds
body.media-modeCSS class (hides notifications, cookie banner, login overlay, footer, dev console, keyboard hints) - Waits for
document.fonts.ready - Adds
.media-app-readytodocument.body(signals parent iframe)
URL parameters (only active with ?media=1):
| Parameter | Values | Effect |
|---|---|---|
seed |
Integer | Seeded PRNG for deterministic suggestion card order |
sidebar |
open/closed |
Force sidebar state regardless of viewport |
inspirations |
none/fixed |
Hide or show daily inspiration banner |
#chat-id |
Chat UUID | Open a specific chat |
Template Architecture
src/routes/dev/media/
├── +page.svelte # Gallery index
├── components/ # Reusable: MediaCanvas, DeviceIframe, DevicePhone,
│ # DeviceLaptop, BrandHeader, ThemeScope
├── data/ # YAML config loader + TypeScript types
├── templates/ # Individual templates
│ ├── og-github/ # 1200x630
│ ├── og-social/ # 1200x630
│ ├── instagram-single/ # 1080x1080
│ ├── instagram-carousel/ # 1080x1080 multi-slide
│ └── instagram-story/ # 1080x1920
└── scripts/capture.spec.ts # Playwright automation
Each template has a config.yml (dimensions, brand info, iframe URLs) and a +page.svelte. Templates are dev-only (/dev/ routes gated by hostname).
Components
MediaCanvas– exact pixel dimensions,.media-readysentinel for Playwright, dark gradient backgroundDeviceIframe– loads real app via iframe, watches for.media-app-readyvia MutationObserver, callsonready()DevicePhone/DeviceLaptop– CSS-only device frames with content slotsBrandHeader– logo + headline + feature bullet pointsThemeScope– forces dark/light CSS variables in scoped container
Screenshot Ready Signal Flow
- Playwright navigates to
/dev/media/templates/{template} - Template creates
DeviceIframecomponents with/?media=1...URLs - App loads, renders, waits for fonts, adds
.media-app-ready DeviceIframedetects via MutationObserver, callsonready()- All iframes ready -> template sets
ready=trueonMediaCanvas MediaCanvasadds.media-readyCSS class- Playwright detects
.media-ready, captures screenshot (30s timeout)
Capture Commands
cd frontend/apps/web_app
npx playwright test src/routes/dev/media/scripts/capture.spec.ts # All
npx playwright test --grep "og-github" .../capture.spec.ts # Specific
OUTPUT_DIR=./marketing npx playwright test .../capture.spec.ts # Custom output
MEDIA_BASE_URL=http://localhost:5173 npx playwright test .../capture.spec.ts # Local dev
RECORD_VIDEO=1 npx playwright test --grep "instagram-story" ... # Video
Output goes to media-output/ by default.
Canonical Format Dimensions
| Format | Width | Height | Use Case |
|---|---|---|---|
og |
1200 | 630 | GitHub, Twitter, LinkedIn, Facebook |
instagram-square |
1080 | 1080 | Instagram post |
instagram-story |
1080 | 1920 | Instagram/TikTok story |
twitter-header |
1500 | 500 | Twitter/X profile header |
github-social |
1280 | 640 | GitHub social preview |
Edge Cases
- Login screen in iframe: Media test account not logged in. Playwright capture script logs in via
beforeAll. - Timeout:
.media-readynever set if an iframe fails. Check browser console for errors, increaseWAIT_TIMEOUT. - Non-deterministic content: Missing
&seed=Nparameter. Ensure consistent seed across all iframe URLs. - Sidebar won’t open: Missing
&sidebar=openor viewport too small. Media mode sidebar override ignores viewport width.
Related Docs
- Web App Architecture – main app structure
- Daily Inspiration –
inspirationsparameter control