Carregando...
Carregando...
O alerta de plantão é uma interrupção em tela cheia — uma sobreposição vermelha e chamativa que exige atenção. Diferente de outros grafismos, ele usa stepCount: 0, ou seja, é do tipo "dispara e esquece": um play o coloca no ar, ele se mantém e depois sai sozinho, sem que o operador precise tirá-lo.
Dispara e esquece
stepCount: 0 no manifesto. O grafismo entra, fica 3.5 s no ar e sai sozinho. Nenhum operador precisa tirá-lo, embora um stop ainda possa encerrá-lo antes.
Sobreposição em tela cheia
Preenche toda a área do grafismo com um fundo preto a 85% e desfocado. O selo vermelho "Breaking News" e a manchete ficam centralizados por cima.
Revelação escalonada
Uma única classe visible controla três elementos com atrasos de transição diferentes: o selo, depois a manchete, depois a linha de destaque.
O tempo fica em três constantes: 1.2 s para a entrada se completar, 3.5 s no ar, 0.6 s para a saída.
// In: the staggered reveal ends when the accent line lands (0.6s delay + 0.6s
// transition). Hold: time on air. Out: the 0.6s fade.
const IN_MS = 1200;
const HOLD_MS = 3500;
const OUT_MS = 600;playAction adiciona a classe visible e espera só a entrada. Em seguida inicia _autoDismiss sem aguardá-lo e retorna currentStep: undefined. A permanência e a saída continuam em segundo plano, e o renderizador fica livre para enviar a próxima ação assim que o alerta estiver na tela.
// Each action takes the next revision number. Anything that finishes after a
// newer action has started checks it and backs off, so play → stop → play
// sent without waiting ends on air instead of hidden by the stale stop.
async playAction({ skipAnimation } = {}) {
this._initDom();
const rev = ++this._rev;
this._root.classList.remove('out');
if (skipAnimation) {
this._root.classList.add('instant');
} else {
this._root.classList.remove('instant');
void this._root.offsetWidth;
}
this._root.classList.add('visible');
// stepCount is 0: the graphic runs start → end on its own. The promise
// resolves once it is on screen, so the renderer is free for the next
// action; the hold and the exit carry on in the background.
if (!skipAnimation) await sleep(IN_MS);
this._autoDismiss(rev, skipAnimation);
return { statusCode: 200, currentStep: undefined };
}
async _autoDismiss(rev, skipAnimation) {
await sleep(HOLD_MS);
if (rev !== this._rev) return;
if (skipAnimation) {
this._root.classList.remove('visible', 'out');
return;
}
this._root.classList.add('out');
await sleep(OUT_MS);
if (rev === this._rev) this._root.classList.remove('visible', 'out');
}Ponto-chave: stepCount: 0
stepCount: 0 diz ao renderizador que o grafismo não tem passos próprios: ele entra e termina sozinho. O renderizador ainda chama playAction, que precisa retornar currentStep: undefined. A especificação diz que a promise deve resolver quando o grafismo estiver pronto para a próxima ação, então ela resolve depois da entrada (IN_MS), e não depois de toda a permanência e a saída.
"Dispara e esquece" não quer dizer impossível de parar. O renderizador ainda pode chamar stopAction para tirar o alerta antes do fim da permanência, e updateAction pode trocar a manchete enquanto ele está no ar. Cada ação pega o próximo this._rev. O _autoDismiss em segundo plano confere esse número depois da permanência e de novo depois da saída, então uma saída pendente de um play anterior nunca esconde um mais novo: play → stop → play enviados sem esperar terminam no ar. customAction precisa existir mesmo que o manifesto não declare nenhuma, e responde a qualquer id com 404.
async stopAction({ skipAnimation } = {}) {
this._initDom();
const rev = ++this._rev;
if (skipAnimation) {
this._root.classList.add('instant');
this._root.classList.remove('visible', 'out');
return { statusCode: 200 };
}
this._root.classList.remove('instant');
this._root.classList.add('out');
await sleep(600);
if (rev === this._rev) this._root.classList.remove('visible', 'out');
return { statusCode: 200 };
}
// Every OGraf graphic must expose customAction, even without any declared.
// The renderer passes { id, payload, skipAnimation }; an unknown id is a 4xx.
async customAction({ id } = {}) {
return { statusCode: 404, statusMessage: `Unknown custom action: ${id ?? ''}` };
}A sobreposição inteira aparece com fade em 0.4 s. Dentro dela, o selo cresce a partir do centro após 0.2 s, a manchete sobe para o lugar após 0.4 s e a linha de destaque é desenhada após 0.6 s: tudo disparado pela mesma classe visible, escalonado apenas por transition-delay. A linha de destaque é a última a terminar, em 1.2 s, e é daí que vem IN_MS. Na saída, as regras de out recolhem o selo e a linha e fazem tudo sumir em 0.6 s.
.breaking.visible {
opacity: 1;
transition: opacity 0.4s ease;
}
.breaking.visible .breaking-badge {
transform: scaleX(1);
transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.2s;
}
.breaking.visible .breaking-headline {
transform: translateY(0);
opacity: 1;
transition:
transform 0.7s cubic-bezier(0.16, 1, 0.3, 1) 0.4s,
opacity 0.5s ease 0.4s;
}
.breaking.visible .breaking-line {
transform: scaleX(1);
transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1) 0.6s;
}.breaking-badge-dot {
width: 8px;
height: 8px;
background: #ffffff;
border-radius: 50%;
animation: badgePulse 1.5s ease infinite;
}
@keyframes badgePulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}Dica de design
O ponto branco pulsante no selo vermelho transmite urgência sem distrair. É uma convenção sutil, mas eficaz, da TV: o espectador associa esse ponto piscando a conteúdo ao vivo e importante. A animação é lenta (um ciclo de 1.5 s) para não parecer frenética.
Repare em stepCount: 0: é assim que o OGraf declara um grafismo que entra e termina sozinho. O renderizador ainda chama playAction, que retorna currentStep: undefined assim que a entrada se completa.
{
"$schema": "https://ograf.ebu.io/v1/specification/json-schemas/graphics/schema.json",
"id": "dev.ograf.tutorial.breaking-news",
"version": "1.0.0",
"name": "Breaking News",
"description": "Fire-and-forget full-screen breaking news overlay that plays in, holds, and auto-dismisses. Tutorial from ograf.dev.",
"author": {
"name": "ograf.dev",
"url": "https://ograf.dev"
},
"main": "graphic.mjs",
"stepCount": 0,
"supportsRealTime": true,
"supportsNonRealTime": false,
"thumbnails": [
{
"file": "thumbnail.webp",
"resolution": {
"width": 1920,
"height": 1080
}
}
],
"schema": {
"type": "object",
"properties": {
"headline": {
"type": "string",
"title": "Headline",
"gddType": "single-line",
"default": "City council approves new riverside transit line"
}
}
}
}Um pacote OGraf Graphics Definition v1 de verdade. Um renderizador compatível lê o manifesto e conduz o ciclo de vida. Licença MIT; coloque em qualquer sistema compatível com OGraf.
breaking-news.ograf.json
Manifesto — o que o renderizador lê (id, schema, flags de ciclo de vida)
graphic.mjs
Web Component com load / play / update / stop / customAction / dispose
style.css
Folha de estilo, carregada pelo graphic.mjs com uma tag <link>
thumbnail.webp
Prévia em 1920×1080, declarada no manifesto
README.md
Instruções de uso
LICENSE
MIT
Dispara e esquece com stepCount: 0, uma revelação escalonada e uma saída automática em segundo plano que um stop ainda pode antecipar.

Lower third
Nome e cargo sobre a imagem

Bug / AO VIVO
Indicador de canto com pulso

Ticker de notícias
Manchetes rolando na tela

Citação em tela cheia
Tipografia cinematográfica em tela cheia

Barras de eleição
Gráfico de porcentagens animado

Escalação esportiva
Grade com o elenco do time

Placar
Placar de partida ao vivo

Contagem regressiva
Relógio que avança sozinho

Previsão do tempo
Condições atuais e previsão de 3 dias

Card de rede social
Post sobreposto com avatar