Communitygithub.com

Kreesten-hsh/video

Cette compétence doit être utilisée lorsque l'utilisateur demande de « faire un compte à rebours vidéo », « créer une animation de compte à rebours », « construire un compte à rebours de début de stream », « faire un compte à rebours d'événement/lancement/vente », « animer un minuteur mm:ss ou dd:hh:mm:ss », « ajouter des transitions de roulement/retournement de chiffres à un compteur » ou « boucler un fond de compte à rebours ». Elle couvre les minuteries précises à la trame (sans dérive), les animations de roulement/retournement de chiffres, le formatage du temps, et une propriété de durée configurable.

Qu'est-ce que video ?

Cette compétence permet de créer des comptes à rebours vidéo ou web qui sont parfaitement synchronisés et sans dérive. Son principe fondamental est de calculer le temps restant en fonction de la trame courante (pour la vidéo) ou d'une horodatage monotone (pour le web), plutôt que d'utiliser un `setInterval` qui accumule des erreurs. Elle est idéale pour les écrans « Démarrage imminent » de livestreams, les comptes à rebours de lancement ou de vente flash, et les animations de fond en boucle. La compétence prend en charge les plateformes Remotion (pour le rendu vidéo MP4) et les pages web en direct via `requestAnimationFrame`. Elle propose des composants pour le formatage du temps (mm:ss, hh:mm:ss, dd:hh:mm:ss), des animations de roulement de chiffres (odomètre) et des effets de retournement (flip-card), le tout sans dérive ni erreur d'une seconde.

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

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Compte à Rebours Vidéo

Créez un compte à rebours qui se termine exactement quand il le doit. L'ensemble repose sur une seule idée : déduire le temps restant de la trame (ou d'une horodatage monotone), puis formater et animer ce nombre — ne jamais compter à rebours avec un setInterval. Cela couvre les écrans « Démarrage imminent » de livestream, les comptes à rebours de lancement/vente, et les fonds en boucle.

Quand l'utiliser

  • Écrans de livestream « Démarrage imminent » / « De retour dans un instant » avec un minuteur de 5 à 10 minutes.
  • Comptes à rebours de lancement et de vente flash (dd:hh:mm:ss jusqu'à une date cible ; mm:ss pour l'urgence).
  • Tout compteur animé où le nombre doit atteindre zéro exactement à la trame prévue.

La règle d'or : piloter le nombre par l'horloge, jamais par une tique

Un compte à rebours avec setInterval(fn, 1000) dérive — le rappel se déclenche au moins 1000 ms plus tard, jamais exactement, et s'accumule lorsque l'onglet est ralenti ou qu'une trame de rendu est lente. Des rapports de ~1 seconde perdue par minute sont courants. La solution est la même pour une vidéo rendue et une page web en direct : calculer le temps restant comme une fonction pure d'une horloge faisant autorité, de sorte qu'une trame tardive se corrige d'elle-même à la trame suivante au lieu d'accumuler l'erreur.

ContexteHorloge faisant autoritéTemps restant
Rendu RemotionuseCurrentFrame()total − frame/fps
Web (page en direct)performance.now() ou Date.now()targetMs − now
Vers une date fixeDate.now()targetTimestamp − Date.now()

Ne stockez jamais le temps restant dans un état et ne le décrémentez pas. Recalculez-le à chaque trame.

Compte à rebours précis à la trame (Remotion → MP4)

useCurrentFrame() est déterministe : la trame N est toujours rendue exactement à N/fps secondes. Soustrayez du total et le minuteur est sans dérive par construction.

import { useCurrentFrame, useVideoConfig } from "remotion";

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

  // secondes restantes, plafonnées à 0 — ceil pour que « 1 » s'affiche pendant sa seconde entière
  const elapsed = frame / fps;
  const remaining = Math.max(0, Math.ceil(durationInSeconds - elapsed));

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

durationInSeconds est une propriété, pas une constante — la même composition rend un minuteur de stream de 5 minutes ou un minuteur de vente de 30 secondes en changeant une entrée. Définissez durationInFrames de la composition à durationInSeconds * fps pour que la vidéo soit exactement aussi longue que le compte à rebours.

Minuteur web (page en direct, requestAnimationFrame)

Pour une page en direct, pilotez une boucle rAF à partir de performance.now() et ne repeignez que lorsque la seconde affichée change (pas de travail 60×/s pour un nombre à 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);

Compter à rebours jusqu'à une date fixe utilise la même idée avec Date.now() : Math.max(0, target - Date.now()). Voir references/countdown-cookbook.md pour la version complète avec date cible et gestion du fuseau horaire.

Formatage du temps

Divisez un entier de secondes restantes en champs avec division + modulo, puis ajoutez des zéros non significatifs. Choisissez le format adapté à la durée — n'affichez jamais 00d 00h 05m quand seules les minutes comptent.

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)}`;
};
FormatUtilisationMasquer les champs quand
mm:ss« Démarrage imminent » du stream, minuteurs courts (<1h)—
hh:mm:ssVente flash, événement le jour mêmemasquer dd quand 0
dd:hh:mm:ssLancement / compte à rebours multi-joursmasquer les unités nulles en tête pour plus de lisibilité

Définissez toujours font-variant-numeric: tabular-nums (ou une police à chiffres monospacés) pour que les chiffres conservent une largeur égale et que la disposition ne vibre pas lorsque les nombres changent.

Transitions de roulement / retournement de chiffres

Un simple changement de nombre semble bon marché ; l'aspect « compteur kilométrique » qui roule donne de la crédibilité au compte à rebours. Rendez chaque chiffre comme une bande verticale de 0 à 9 et translatez-la de -chiffre × hauteurChiffre. Animez la position, pas le contenu textuel, pour que le changement soit une glisse, pas un saut.

.reel { height: 1em; overflow: hidden; }
.reel .col { transition: transform .45s cubic-bezier(.22,1,.36,1); }
/* col contient 0..9 empilés ; décalage de -valeur em */

Dans Remotion, pilotez le décalage de la bande en fonction de la trame au lieu d'une transition CSS (pour qu'il soit déterministe). L'effet de carte à retourner (la moitié supérieure se replie sur la moitié inférieure) et le rouleau par chiffre sont tous deux détaillés, avec des composants exécutables, dans references/digit-flip.md.

Fond en boucle

Les écrans de stream durent des minutes, donc le fond doit boucler de manière transparente. Construisez le mouvement sur un cycle qui se divise de manière égale dans la timeline (par exemple un dégradé ou une dérive de particules dont la période est loopFrames), et lisez-le avec frame % loopFrames pour qu'il n'y ait pas de couture visible à la jonction. Gardez-le à faible contraste et lent — le nombre est le sujet ; le fond est l'ambiance. Recettes dans references/countdown-cookbook.md.

Liste de vérification de la sortie

  • Temps restant recalculé à partir de useCurrentFrame() / performance.now() à chaque trame — pas de setInterval décrémental.
  • Le minuteur atteint 00:00 exactement à la trame prévue ; durationInFrames === durationInSeconds × fps.
  • La durée est une propriété d'entrée, pas une constante codée en dur.
  • Les chiffres utilisent tabular-nums ; la disposition ne change pas lorsque les nombres varient.
  • Le format correspond à la durée (mm:ss vs dd:hh:mm:ss) ; les unités nulles en tête sont supprimées lorsqu'elles distraient.
  • La période de boucle du fond divise la timeline de manière égale ; pas de couture à la jonction.

Livrer et vérifier (images fixes rendues → MP4)

Assistant packagé (scripts/) : disposez vos images fixes avec scripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.png, puis vérifiez l'encodage avec scripts/probe-mp4.sh out.mp4 [LxH] [fps]. Voir scripts/README.md.

Le compte à rebours est livré comme une composition Remotion (<Composition> + schéma zod + defaultProps, durationInSeconds comme propriété) — le minuteur est une fonction pure de useCurrentFrame(), jamais setInterval / Date.now() / Math.random(). Définissez durationInFrames = durationInSeconds * fps. Livrable = out/*.mp4 + le projet. La sortie verticale 9:16 (1080×1920) est la valeur par défaut.

Boucle de vérification — rendre les images fixes → inspecter → encoder. Tout l'intérêt est la précision à la trame, alors vérifiez la valeur du chiffre à des trames exactes avant d'encoder.

# Images au début / milieu / fin — AVEC LES PROPRIÉTÉS FOURNIES (la durée réelle que vous allez rendre)
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

# Inspectez chaque PNG — les chiffres doivent correspondre EXACTEMENT au calcul frame/fps :
#  - à la trame N, affiché = ceil(durationInSeconds - N/fps)  (ex. 300s @ 30fps, trame 4500 → 150s → "02:30")
#  - la dernière trame affiche 00:00 (ou le maintien de l'état final) ; pas de dérive, pas de décalage d'une seconde
#  - tabular-nums : la disposition ne vibre pas entre les trames
#  - zone de sécurité 9:16 : le minuteur + le bloc de marque se situent à l'écart du haut ~12% et du bas ~20-35% de l'interface utilisateur et du rail d'action droit

npx remotion render Countdown out/countdown.mp4 --props='{"durationInSeconds":300}'   # encoder une fois les chiffres vérifiés
npx remotion render Countdown out/demo.gif --codec=gif                                 # clip de preuve README

Utilisez npx remotion compositions pour confirmer durationInFrames/fps, puis calculez la trame pour n'importe quelle seconde que vous souhaitez vérifier.

Avant de terminer :

  1. Les images fixes se rendent proprement à la trame 0, au milieu et à la dernière — pas d'erreur.
  2. La valeur du chiffre à la trame N est exactement ceil(durationInSeconds - N/fps) ; la dernière trame atteint 00:00 à la trame prévue (pas de dérive / de décalage d'une seconde).
  3. Le minuteur + la marque restent à l'intérieur de la zone de sécurité 9:16 à chaque trame vérifiée.
  4. Piloté uniquement par trame — pas de setInterval / Date.now() / minuteurs ; tabular-nums pour que la disposition tienne.
  5. La propriété durationInSeconds fournie (pas seulement defaultProps) s'affiche correctement ; MP4 encodé, GIF optionnel.

Fichiers de référence

  • references/countdown-cookbook.md — composant <Countdown> Remotion complet et exécutable (durée configurable, mm:ss / dd:hh:mm:ss, maintien de l'état final), composant web de compte à rebours jusqu'à une date fixe avec gestion du fuseau horaire, une mise en page d'écran de stream « Démarrage imminent » (bloc de marque + poignées sociales + fond dégradé en boucle), et le modèle de boucle transparente pour fond.
  • references/digit-flip.md — deux composants déterministes d'animation de chiffres : le rouleau d'odomètre (bande verticale 0–9 pilotée par trame) et l'effet de carte à retourner (pli de la moitié supérieure), ainsi que quand chacun convient pour un lancement, une vente ou un stream.

Individual skills in this repo

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

Skills associés