Loading...
Loading...
An elegant full-screen quote card — the kind used for interview soundbites, motivational segments, or editorial intros. The text, divider, and attribution reveal in a staggered sequence for cinematic impact.
The magic here is CSS transition delays. Each element starts hidden and below its final position. When the .visible class is added, they animate in sequence:
delay: 0.3sQuote text
Fades up from below
delay: 0.4sDivider line
Scales from center
delay: 0.5sAttribution
Fades up last
.quote {
position: absolute; /* against the graphic's root, not the viewport */
inset: 0; /* fills the render area */
opacity: 0;
}
.quote.visible { opacity: 1; transition: opacity 0.8s ease; }
/* Each child has its own delay */
.quote.visible .quote-text {
opacity: 1;
transform: translateY(0);
transition: opacity 0.6s ease 0.3s, /* 300ms delay */
transform 0.8s ease-out 0.3s;
}
.quote.visible .quote-line {
transform: scaleX(1);
transition: transform 0.6s ease-out 0.4s; /* 400ms delay */
}
.quote.visible .quote-attr {
opacity: 1;
transform: translateY(0);
transition: opacity 0.6s ease 0.5s, /* 500ms delay */
transform 0.8s ease-out 0.5s;
}Design tip
The background uses scale(1.1) as its starting state, then transitions to scale(1). This creates a subtle "camera settling" effect — the background gently zooms in as the quote appears. Cinematic feel with one line of CSS.
This template uses two fonts for contrast:
"The quote"
Instrument Serif — italic, large (48px). The editorial, elegant voice.
The Attribution
ROLE / TITLE
Inter — clean, modern sans-serif. The factual voice.
Since the staggered animation takes longer than a simple slide, the playAction() promise waits 1200ms — enough for all three elements to finish their reveal before the renderer considers the graphic "ready."
async playAction({ skipAnimation } = {}) {
this._root.classList.remove('out');
if (skipAnimation) {
this._root.classList.add('visible');
return { statusCode: 200, currentStep: 0 };
}
void this._root.offsetWidth;
this._root.classList.add('visible');
// Wait for all staggered animations to complete
// Quote text: 0.3s delay + 0.8s duration = 1.1s
// Attribution: 0.5s delay + 0.8s duration = 1.3s
await new Promise(r => setTimeout(r, 1200));
return { statusCode: 200, currentStep: 0 };
}{
"$schema": "https://ograf.ebu.io/v1/specification/json-schemas/graphics/schema.json",
"id": "dev.ograf.tutorial.quote",
"version": "1.0.0",
"name": "Full Page Quote",
"description": "Cinematic full-screen quote card with staggered reveal of text, divider, and attribution. 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": {
"text": {
"type": "string",
"title": "Quote",
"gddType": "multi-line",
"default": "Open graphics, open broadcast, open standards. That's the future."
},
"author": {
"type": "string",
"title": "Author",
"gddType": "single-line",
"default": "Demo Quote"
},
"role": {
"type": "string",
"title": "Role",
"gddType": "single-line",
"default": "Sample Credit"
}
}
}
}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.
quote.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
You've learned staggered CSS transitions, full-screen layouts, typographic contrast, and timing coordination between CSS and JavaScript.

Lower Third
Name & title overlay

Bug / LIVE
Corner indicator with pulse

News Ticker
Scrolling headline crawl

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

Social Media Card
Post overlay with avatar