Loading...
Loading...
News broadcasts frequently show social media posts on screen — a tweet from a public figure, an official statement, or a viral post. This card displays the user's name, handle, and post text with an auto-generated avatar. It's positioned on the right side of the screen, leaving the anchor visible on the left.
Right-side position
Uses right: 48px instead of left. This keeps the anchor visible on the left side of the frame — a common broadcast convention for displayed content.
Auto-generated avatar
No image needed. The avatar circle shows the user's initials, extracted from their name with _getInitials(). Works for any name, any language.
Platform badge
A small badge indicates the source platform (X, Instagram, etc.). This provides editorial transparency — viewers know where the post came from.
Instead of requiring a profile photo URL (which may break, be low-res, or have rights issues), the social card generates an avatar from the user's name. The _getInitials method takes the first letter of each word in the name.
_getInitials(name) {
if (!name) return '?';
return name
.split(' ')
.filter(word => word.length > 0)
.map(word => word[0].toUpperCase())
.slice(0, 2)
.join('');
}
// "Jane Smith" → "JS"
// "Dr. Martin King" → "DM" (first two words)
// "Madonna" → "M"
// "" → "?"
async load({ data }) {
if (data?.user) {
this._userName.textContent = data.user;
this._avatar.textContent = this._getInitials(data.user);
}
if (data?.handle) this._handle.textContent = data.handle;
if (data?.text) this._postText.textContent = data.text;
return { statusCode: 200 };
}
async playAction() {
this._root.classList.add('visible');
await new Promise(r => setTimeout(r, 600));
return { statusCode: 200, currentStep: 0 };
}
async stopAction() {
this._root.classList.add('out');
await new Promise(r => setTimeout(r, 400));
this._root.classList.remove('visible', 'out');
return { statusCode: 200 };
}The card slides in from the right edge with a blue left-border accent. The avatar circle uses a gradient background that gives each card a unique but consistent feel.
.social-card {
position: absolute; /* against the graphic's root, not the viewport */
top: 50%;
right: 48px;
transform: translateY(-50%) translateX(30px);
width: 360px;
background: rgba(15, 15, 25, 0.92);
backdrop-filter: blur(12px);
border-radius: 12px;
border-left: 3px solid #3b82f6;
padding: 20px;
opacity: 0;
transition: opacity 0.5s ease,
transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.social-card.visible {
opacity: 1;
transform: translateY(-50%) translateX(0);
}
.social-card.out {
opacity: 0;
transform: translateY(-50%) translateX(30px);
transition-duration: 0.3s;
}
.social-avatar {
width: 44px;
height: 44px;
border-radius: 50%;
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 16px;
color: white;
flex-shrink: 0;
}
.social-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.social-user-name {
font-weight: 600;
color: white;
font-size: 15px;
}
.social-handle {
color: rgba(255, 255, 255, 0.5);
font-size: 13px;
}
.social-text {
color: rgba(255, 255, 255, 0.85);
font-size: 15px;
line-height: 1.5;
}
.social-platform {
margin-top: 12px;
font-size: 11px;
color: rgba(255, 255, 255, 0.35);
text-transform: uppercase;
letter-spacing: 1px;
}Design tip
The avatar circle uses the first letter of each word in the user's name — "Jane Smith" becomes "JS". This avoids the need for external image assets entirely. The gradient background ensures the circle always looks intentional, not like a missing image fallback.
{
"$schema": "https://ograf.ebu.io/v1/specification/json-schemas/graphics/schema.json",
"id": "dev.ograf.tutorial.social-card",
"version": "1.0.0",
"name": "Social Media Card",
"description": "Social post overlay that slides in from the right with an auto-generated avatar from the user's initials. Tutorial from ograf.dev.",
"author": {
"name": "ograf.dev",
"url": "https://ograf.dev"
},
"main": "graphic.mjs",
"stepCount": 1,
"supportsRealTime": true,
"supportsNonRealTime": false,
"schema": {
"type": "object",
"properties": {
"user": {
"type": "string",
"title": "User",
"gddType": "single-line",
"default": "Johan Nyman"
},
"handle": {
"type": "string",
"title": "Handle",
"gddType": "single-line",
"default": "@nytamin"
},
"text": {
"type": "string",
"title": "Post",
"gddType": "multi-line",
"default": "Just shipped our first OGraf template to production."
}
}
}
}A real OGraf Graphics Definition v1 package. A compliant renderer reads the manifest and drives the lifecycle. MIT-licensed; drop it into any OGraf-compatible system.
social-card.ograf.json
Manifest — what a renderer reads (id, schema, lifecycle flags)
graphic.mjs
Web Component with load / play / update / stop / customAction / dispose
style.css
Stylesheet, loaded by graphic.mjs via a <link> tag
README.md
Usage notes
Right-side positioning, auto-generated initials avatar, and a clean blue accent — ready to display social posts on air.

Lower Third
Name & title overlay

Bug / LIVE
Corner indicator with pulse

News Ticker
Scrolling headline crawl

Full Page Quote
Cinematic full-screen typography

Election Bars
Animated percentage chart

Sport Lineup
Team roster grid

Score Bug
Live match scoreboard

Countdown Timer
Self-ticking clock

Breaking News
Full-screen urgent alert

Weather Forecast
Conditions & 3-day outlook