Communitygithub.com

Kreesten-hsh/video

Dieses Skill sollte verwendet werden, wenn der Benutzer bittet, einen Countdown-Timer zu erstellen, eine Countdown-Animation zu erzeugen, einen Stream-Start-Countdown zu bauen, einen Event/Launch/Sale-Countdown zu machen, einen mm:ss- oder dd:hh:mm:ss-Timer zu animieren, Ziffern-Roll/Flip-Übergänge zu einem Zähler hinzuzufügen oder einen Countdown-Hintergrund zu loopen. Es deckt frame-genaue (driftfreie) Timer, Ziffern-Flip/Roll-Bewegungen, Zeitformatierung und eine konfigurierbare Dauer-Prop ab.

Was ist video?

Dieses Skill ermöglicht die Erstellung von frame-genauen Countdown-Timern, die in der Videoproduktion (Remotion) und auf Live-Webseiten eingesetzt werden können. Die Kernphilosophie ist, die verbleibende Zeit immer aus einer autoritativen Uhr (wie `useCurrentFrame()` oder `performance.now()`) abzuleiten, anstatt sie mit `setInterval` zu dekrementieren, um Drift und Akkumulationsfehler zu vermeiden. Es unterstützt verschiedene Zeitformate (`mm:ss`, `hh:mm:ss`, `dd:hh:mm:ss`) und beinhaltet Vorlagen für Ziffern-Roll- und Flip-Animationen, die die Änderung der Nummer fließend darstellen. Zudem werden nahtlos looping Hintergründe für Livestream-„Starting Soon“-Bildschirme bereitgestellt. Das Skill enthält ausführbare Referenzkomponenten (`references/countdown-cookbook.md` und `references/digit-flip.md`) und Skripte zur Überprüfung und zum Rendern der finalen MP4-Datei.

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

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Countdown-Video

Erstelle einen Countdown, der genau dann endet, wenn er soll. Das ganze Handwerk basiert auf einer Idee: Leite die verbleibende Zeit vom Frame (oder einem monotonen Zeitstempel) ab, formatiere und animiere diese Zahl – zähle niemals mit einem setInterval herunter. Deckt Livestream-„Starting Soon“-Bildschirme, Launch-/Sale-Countdowns und loopende Hintergründe ab.

Wann verwenden

  • „Starting soon“ / „Be right back“ Livestream-Bildschirme mit einem 5–10 Minuten-Timer.
  • Launch- und Flash-Sale-Countdowns (dd:hh:mm:ss bis zu einem Zieldatum; mm:ss für Dringlichkeit).
  • Jeder animierte Zähler, bei dem die Zahl auf dem exakten Frame auf Null landen muss.

Die eine Regel: Treibe die Zahl von der Uhr, niemals von einem Tick

Ein setInterval(fn, 1000) Countdown driftet – der Callback feuert mindestens 1000 ms später, niemals exakt, und staut sich auf, wenn der Tab gedrosselt wird oder ein Render-Frame langsam ist. Berichte über ~1 Sekunde Verlust pro Minute sind üblich. Die Lösung ist für gerendertes Video und Live-Web dieselbe: Berechne die verbleibende Zeit als reine Funktion einer autoritativen Uhr, sodass ein später Frame im nächsten Frame selbst korrigiert wird, anstatt Fehler zu akkumulieren.

KontextAutoritative UhrVerbleibende Zeit
Remotion-RenderuseCurrentFrame()total − frame/fps
Web (Live-Seite)performance.now() oder Date.now()targetMs − now
Bis zu einem festen DatumDate.now()targetTimestamp − Date.now()

Speichere die verbleibende Zeit niemals im State und dekrementiere sie nicht. Berechne sie in jedem Frame neu.

Frame-genauer Countdown (Remotion → MP4)

useCurrentFrame() ist deterministisch: Frame N wird immer zu exakt N/fps Sekunden gerendert. Subtrahiere von der Gesamtzeit, und der Timer ist per Konstruktion driftfrei.

import { useCurrentFrame, useVideoConfig } from "remotion";

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

  // verbleibende Sekunden, bei 0 gedeckelt – ceil, damit "1" für die volle Sekunde angezeigt wird
  const elapsed = frame / fps;
  const remaining = Math.max(0, Math.ceil(durationInSeconds - elapsed));

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

durationInSeconds ist eine Prop, keine Konstante – dieselbe Komposition rendert einen 5-Minuten-Stream-Timer oder einen 30-Sekunden-Sale-Timer durch Änderung einer Eingabe. Setze durationInFrames der Komposition auf durationInSeconds * fps, damit das Video exakt so lang ist wie der Countdown.

Web-Timer (Live-Seite, requestAnimationFrame)

Für eine Live-Seite treibe eine rAF-Schleife mit performance.now() an und male nur neu, wenn sich die angezeigte Sekunde ändert (keine Arbeit 60×/Sek. für eine 1-Hz-Zahl):

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);

Das Herunterzählen bis zu einem festen Datum funktioniert mit Date.now() nach dem gleichen Prinzip: Math.max(0, target - Date.now()). Siehe references/countdown-cookbook.md für die vollständige Datumsziel-Version mit Zeitzonenbehandlung.

Zeitformatierung

Zerlege eine Ganzzahl der verbleibenden Sekunden mit Division + Modulo in Felder und fülle sie mit Nullen auf. Wähle das Format passend zur Dauer – zeige niemals 00d 00h 05m an, wenn nur Minuten wichtig sind.

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)}`;
};
FormatVerwendungFelder ausblenden, wenn
mm:ssStream „starting soon“, kurze Timer (<1h)—
hh:mm:ssFlash-Sale, Event am selben Tagdd ausblenden, solange 0
dd:hh:mm:ssLaunch / mehrtägiger CountdownFührende Null-Einheiten für sauberere Lesbarkeit ausblenden

Setze immer font-variant-numeric: tabular-nums (oder eine monospace Ziffernschriftart), sodass Ziffern gleich breit bleiben und das Layout nicht wackelt, wenn sich die Zahlen ändern.

Ziffern-Roll- / Flip-Übergänge

Ein einfacher Austausch der Zahl wirkt billig; der rollende „Kilometerzähler“-Look verkauft den Countdown. Rendere jede Ziffer als vertikalen Streifen 0–9 und verschiebe ihn um -digit × digitHeight. Animiere die Position, nicht den Textinhalt, sodass die Änderung ein Gleiten und kein Aufpoppen ist.

.reel { height: 1em; overflow: hidden; }
.reel .col { transition: transform .45s cubic-bezier(.22,1,.36,1); }
/* col enthält gestapelte 0..9; Verschiebung um -Wert em */

In Remotion treibe den Streifen-Offset auf Basis des Frames anstelle eines CSS-Übergangs (damit es deterministisch ist). Der Flip-Card-Stil (obere Hälfte klappt über die untere) und die pro-Ziffer-Rolle sind beide detailliert mit ausführbaren Komponenten in references/digit-flip.md beschrieben.

Looping-Hintergrund

Stream-Bildschirme laufen Minuten lang, daher muss der Hintergrund nahtlos loopen. Baue Bewegung auf einem Zyklus, der gleichmäßig in die Zeitachse passt (z. B. ein Farbverlauf oder Partikeldrift, dessen Periode loopFrames ist), und lies ihn als frame % loopFrames, damit an der Nahtstelle kein sichtbarer Übergang entsteht. Halte ihn kontrastarm und langsam – die Zahl ist das Subjekt; der Hintergrund ist Atmosphäre. Rezepte in references/countdown-cookbook.md.

Ausgabe-Checkliste

  • Verbleibende Zeit wird in jedem Frame aus useCurrentFrame() / performance.now() neu berechnet – kein setInterval-Dekrement.
  • Timer erreicht 00:00 auf dem exakt vorgesehenen Frame; durationInFrames === durationInSeconds × fps.
  • Dauer ist eine Eingabe-Prop, kein hartcodierter Wert.
  • Ziffern verwenden tabular-nums; Layout verschiebt sich nicht, wenn sich Zahlen ändern.
  • Format passt zur Spanne (mm:ss vs. dd:hh:mm:ss); führende Null-Einheiten werden weggelassen, wenn sie ablenken.
  • Die Hintergrund-Loop-Periode teilt die Zeitachse gleichmäßig; kein sichtbarer Übergang an der Nahtstelle.

Liefern & verifizieren (gerenderte Standbilder → MP4)

Verpacktes Hilfsprogramm (scripts/): Lege deine Standbilder mit scripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.png als Tafel an und überprüfe dann die Kodierung mit scripts/probe-mp4.sh out.mp4 [WxH] [fps]. Siehe scripts/README.md.

Der Countdown wird als Remotion-Komposition ausgeliefert (<Composition> + Zod schema + defaultProps, durationInSeconds eine Prop) – der Timer ist eine reine Funktion von useCurrentFrame(), niemals setInterval / Date.now() / Math.random(). Setze durationInFrames = durationInSeconds * fps. Liefergegenstand = out/*.mp4 + das Projekt. 9:16 Hochformat (1080×1920) ist die Voreinstellung.

Verifikations-Loop – Standbilder rendern → prüfen → kodieren. Der ganze Sinn ist Framegenauigkeit, also überprüfe den Ziffernwert bei exakten Frames vor dem Kodieren.

# Standbilder am Start / Mitte / Ende – MIT AUSGELIEFERTEN PROPS (die echte Dauer, die du rendern wirst)
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

# Überprüfe jedes PNG – die Ziffern müssen EXAKT der frame/fps-Mathematik entsprechen:
#  - bei Frame N, angezeigt = ceil(durationInSeconds - N/fps)  (z. B. 300s @ 30fps, Frame 4500 → 150s → "02:30")
#  - Letzter Frame zeigt 00:00 (oder den Endzustand); kein Drift, kein Off-by-One-Sekunden-Fehler
#  - tabular-nums: Layout wackelt nicht zwischen Frames
#  - 9:16-Safe-Area: Timer + Markenblock liegen klar außerhalb der oberen ~12% und unteren ~20-35% UI und der rechten Aktionsleiste

npx remotion render Countdown out/countdown.mp4 --props='{"durationInSeconds":300}'   # kodieren, sobald Ziffern verifiziert
npx remotion render Countdown out/demo.gif --codec=gif                                 # README-Beweisclip

Verwende npx remotion compositions, um durationInFrames/fps zu bestätigen, und berechne dann den Frame für jede Sekunde, die du überprüfen möchtest.

Bevor du abschließt:

  1. Standbilder rendern sauber bei Frame 0, Mitte und am Ende – keine Fehler.
  2. Der Ziffernwert bei Frame N entspricht exakt ceil(durationInSeconds - N/fps); letzter Frame erreicht 00:00 auf dem vorgesehenen Frame (kein Drift / Off-by-One).
  3. Timer + Marke bleiben bei jedem geprüften Frame innerhalb der 9:16-Safe-Area.
  4. Nur Frame-getrieben – kein setInterval / Date.now() / Timer; tabular-nums, damit Layout stabil bleibt.
  5. Ausgelieferte durationInSeconds (nicht nur defaultProps) rendert korrekt; MP4 kodiert, GIF optional.

Referenzdateien

  • references/countdown-cookbook.md – Vollständige ausführbare Remotion <Countdown> (konfigurierbare Dauer, mm:ss / dd:hh:mm:ss, Endzustand-Halten), die Countdown-bis-zu-einem-festen-Datum Web-Komponente mit Zeitzonenbehandlung, ein „Starting Soon“-Stream-Bildschirm-Layout (Markenblock + Social-Media-Handles + loopender Farbverlaufshintergrund) und das nahtlos loopende Hintergrundmuster.
  • references/digit-flip.md – Zwei deterministische Ziffern-Animation-Komponenten: die Kilometerzähler-Rolle (vertikaler 0–9 Streifen, vom Frame getrieben) und der Flip-Card-Effekt (obere Hälfte klappt um), sowie wann welche für Launch vs. Sale vs. Stream geeignet ist.

Individual skills in this repo

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

Verwandte Skills