Communitygithub.com

Vídeo de Contagem Regressiva

Cria animações de contagem regressiva precisas, sem desvio (drift-free), para telas de "em breve", lançamentos, promoções e looping de fundo. Usa lógica baseada em frames/tempo, nunca um contador decremental.

O que é Vídeo de Contagem Regressiva?

Esta skill cria contagens regressivas que terminam exatamente no momento certo, evitando o desvio temporal comum em `setInterval`. O princípio fundamental é derivar o tempo restante a partir do frame (ou de um timestamp monotônico), formatar e animar esse número, sem nunca usar um contador decremental. Ela abrange telas de "começando em breve" para lives, contagens regressivas para lançamentos e promoções, e fundo com loop contínuo. Suporta tanto renderização de vídeo quadro a quadro (Remotion/MP4) quanto timers ao vivo em páginas web (requestAnimationFrame). Inclui formatação de tempo (mm:ss, hh:mm:ss, dd:hh:mm:ss), animações de dígitos estilo odômetro ou flip-card, e uma checklist de verificação para garantir precisão de frame, sem drifts e com layout estável.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Kreesten-hsh/video/tree/HEAD/.agents/skills/countdown-video

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

O que Vídeo de Contagem Regressiva faz?

Faça uma contagem regressiva que termina exatamente quando deveria. Toda a lógica se baseia em uma ideia: derivar o tempo restante do frame (ou de um timestamp monotônico), formatar e animar esse número — nunca contar regressivamente com um setInterval. Abrange telas de "começando em breve" para lives, contagens regressivas para lançamentos e promoções, e fundos com loop contínuo.

Quando usar

  • Telas de "Começando em breve" / "Já volto" para lives com timer de 5 a 10 min.
  • Contagens regressivas de lançamentos e promoções relâmpago (dd:hh:mm:ss até uma data alvo; mm:ss para urgência).
  • Qualquer contador animado onde o número deve chegar a zero exatamente no frame correto.

A regra de ouro: derive o número do relógio, nunca de um tick

Um setInterval(fn, 1000) para contagem regressiva desvia — o callback sempre dispara pelo menos 1000 ms depois, nunca exatamente, e se acumula quando a aba é suprimida ou um frame de renderização é lento. Relatos de ~1 segundo perdido por minuto são comuns. A solução é a mesma para vídeo renderizado e web ao vivo: calcular o tempo restante como função pura de um relógio autoritativo, para que um frame atrasado se autocorrija no próximo frame, em vez de acumular erro.

ContextoRelógio autoritativoTempo restante
Renderização RemotionuseCurrentFrame()total − frame/fps
Web (página ao vivo)performance.now() ou Date.now()targetMs − now
Para uma data fixaDate.now()targetTimestamp − Date.now()

Nunca armazene o tempo restante em estado e o decremente. Recalcule a cada frame.

Contagem regressiva precisa por frame (Remotion → MP4)

useCurrentFrame() é determinística: o frame N é sempre renderizado em exatamente N/fps segundos. Subtraia do total e o timer estará livre de desvios por construção.

import { useCurrentFrame, useVideoConfig } from "remotion";

export const Countdown: React.FC<{ durationInSeconds: number }> = ({
  durationInSeconds,
}) => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  // segundos restantes, limitados a 0 — usa ceil para que "1" mostre durante seu segundo completo
  const elapsed = frame / fps;
  const remaining = Math.max(0, Math.ceil(durationInSeconds - elapsed));

  return <div style={{ fontVariantNumeric: "tabular-nums" }}>{formatMMSS(remaining)}</div>;
};

durationInSeconds é uma prop, não uma constante — a mesma composição renderiza um timer de 5 minutos para live ou um timer de 30 segundos para promoção, alterando apenas uma entrada. Defina durationInFrames da composição como durationInSeconds * fps para que o vídeo tenha exatamente a duração da contagem regressiva.

Timer web (página ao vivo, requestAnimationFrame)

Para uma página ao vivo, use um loop rAF baseado em performance.now() e só repinte quando o segundo exibido mudar (sem trabalho 60×/s para um número de 1 Hz):

const start = performance.now();
let lastShown = -1;
function tick(now) {
  const remaining = Math.max(0, Math.ceil(duration - (now - start) / 1000));
  if (remaining !== lastShown) { render(remaining); lastShown = remaining; }
  if (remaining > 0) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);

Contar regressivamente até uma data fixa é a mesma ideia com Date.now(): Math.max(0, target - Date.now()). Veja references/countdown-cookbook.md para a versão completa com data alvo e tratamento de fuso horário.

Formatação de tempo

Converta um inteiro de segundos restantes em campos usando divisão + módulo, depois preencha com zeros. Escolha o formato adequado à duração — nunca mostre 00d 00h 05m quando minutos é tudo que importa.

const pad = (n) => String(n).padStart(2, "0");
const formatMMSS = (s) => `${pad(Math.floor(s / 60))}:${pad(s % 60)}`;
const formatDHMS = (s) => {
  const d = Math.floor(s / 86400), h = Math.floor((s % 86400) / 3600);
  const m = Math.floor((s % 3600) / 60), sec = s % 60;
  return `${pad(d)}:${pad(h)}:${pad(m)}:${pad(sec)}`;
};
FormatoUsoOcultar campos quando
mm:sslive "começando em breve", timers curtos (<1h)—
hh:mm:sspromoção relâmpago, evento no mesmo diaocultar dd enquanto 0
dd:hh:mm:sslançamento / contagem regressiva de vários diasocultar unidades com zeros à esquerda para leitura mais limpa

Sempre defina font-variant-numeric: tabular-nums (ou uma fonte de dígitos monoespaçada) para que os dígitos tenham largura igual e o layout não oscile conforme os números mudam.

Transições de dígito (rolagem / flip)

Uma simples troca de número parece barata; o visual de odômetro girando valoriza a contagem regressiva. Renderize cada dígito como uma faixa vertical de 0 a 9 e a desloque por -dígito × alturaDígito. Anime a posição, não o conteúdo do texto, para que a mudança seja um deslize, não um pulo.

.reel { height: 1em; overflow: hidden; }
.reel .col { transition: transform .45s cubic-bezier(.22,1,.36,1); }
/* col contém 0..9 empilhados; desloca por -valor em */

No Remotion, direcione o deslocamento da faixa a partir do frame, em vez de uma transição CSS (para que seja determinístico). O estilo flip-card (metade superior dobra sobre a metade inferior) e o carretel por dígito são detalhados, com componentes executáveis, em references/digit-flip.md.

Fundo com loop

Telas de live duram minutos, então o fundo precisa se repetir perfeitamente. Construa o movimento em um ciclo que divida uniformemente a timeline (por exemplo, um gradiente ou deriva de partículas cujo período seja loopFrames) e leia como frame % loopFrames para que não haja emenda visível no corte. Mantenha baixo contraste e lento — o número é o assunto; o fundo é ambiente. Receitas em references/countdown-cookbook.md.

Checklist de saída

  • Tempo restante recalculado de useCurrentFrame() / performance.now() a cada frame — sem decremento por setInterval.
  • Timer chega a 00:00 exatamente no frame pretendido; durationInFrames === durationInSeconds × fps.
  • Duração é uma prop de entrada, não uma constante fixa.
  • Dígitos usam tabular-nums; layout não se desloca conforme os números mudam.
  • Formato corresponde ao intervalo (mm:ss vs dd:hh:mm:ss); unidades com zeros à esquerda são removidas quando distraem.
  • Período do loop do fundo divide a timeline uniformemente; sem emenda no corte.

Entregar e verificar (stills renderizados → MP4)

Script auxiliar (scripts/): monte seus stills com scripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.png, depois verifique a codificação com scripts/probe-mp4.sh out.mp4 [WxH] [fps]. Veja scripts/README.md.

A contagem regressiva é entregue como uma composição Remotion (<Composition> + esquema zod + defaultProps, durationInSeconds como prop) — o timer é uma função pura de useCurrentFrame(), nunca setInterval / Date.now() / Math.random(). Defina durationInFrames = durationInSeconds * fps. Entregável = out/*.mp4 + o projeto. 9:16 vertical (1080×1920) é o padrão.

Verificação em loop — renderizar stills → inspecionar → codificar. O objetivo é precisão de frame, então verifique o valor do dígito em frames exatos antes de codificar.

# Stills no início / meio / fim — COM AS PROPS ENVIADAS (a duração real que será renderizada)
npx remotion still Countdown out/f-start.png --frame=0   --props='{"durationInSeconds":300}'
npx remotion still Countdown out/f-mid.png   --frame=N   --props='{"durationInSeconds":300}'
npx remotion still Countdown out/f-end.png   --frame=L   --props='{"durationInSeconds":300}'  # L = durationInFrames-1

# Inspecione cada PNG — os dígitos devem ler EXATAMENTE a matemática frame/fps:
#  - no frame N, exibido = ceil(durationInSeconds - N/fps)  (ex.: 300s @ 30fps, frame 4500 → 150s → "02:30")
#  - último frame mostra 00:00 (ou o estado final); sem desvio, sem erro de um segundo
#  - tabular-nums: layout não oscila entre frames
#  - área segura 9:16: o timer + bloco de marca ficam limpos de ~12% superior e ~20-35% inferior da UI e da barra de ações direita

npx remotion render Countdown out/countdown.mp4 --props='{"durationInSeconds":300}'   # codifica assim que os dígitos verificarem
npx remotion render Countdown out/demo.gif --codec=gif                                 # clipe de prova para README

Use npx remotion compositions para confirmar durationInFrames/fps, depois calcule o frame para qualquer segundo que queira verificar.

Antes de finalizar:

  1. Stills renderizam limpos no frame 0, meio e último — sem erros.
  2. O valor do dígito no frame N é exatamente ceil(durationInSeconds - N/fps); último frame chega a 00:00 no frame pretendido (sem desvio / erro de um segundo).
  3. Timer + marca permanecem dentro da área segura 9:16 em todos os frames verificados.
  4. Apenas orientado por frame — sem setInterval / Date.now() / timers; tabular-nums para manter o layout.
  5. durationInSeconds enviado (não apenas defaultProps) renderiza corretamente; MP4 codificado, GIF opcional.

Arquivos de referência

  • references/countdown-cookbook.md — componente Remotion <Countdown> completo e executável (duração configurável, mm:ss / dd:hh:mm:ss, estado final), componente web de contagem até data fixa com tratamento de fuso horário, layout de tela de live "começando em breve" (bloco de marca + redes sociais + fundo gradiente com loop) e padrão de fundo com loop contínuo.
  • references/digit-flip.md — dois componentes determinísticos de animação de dígitos: o carretel de odômetro (faixa vertical 0–9 orientada por frame) e o efeito flip-card (dobra da metade superior), além de quando cada um se adequa a lançamento vs promoção vs live.

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

Habilidades Relacionadas