HyperFrames
HTML est la source de vérité pour la vidéo. Une composition est un fichier HTML avec des attributs data-* pour le timing, une timeline GSAP pour l'animation et du CSS pour l'apparence. Le framework gère la visibilité des clips, la lecture multimédia et la synchronisation de la timeline.
Intégration Open Design (porteuse pour cette surface)
Lorsque ce skill s'exécute dans Open Design (c'est-à-dire $OD_PROJECT_DIR est défini), le flux de sortie est fixe : seul le fichier .mp4 rendu doit atterrir à la racine du projet. Les fichiers sources de la composition (hyperframes.json, meta.json, index.html, assets) appartiennent à un répertoire cache caché pour ne pas encombrer le FileViewer de l'utilisateur ni les chips « fichiers produits » du chat.
Workflow de rendu dans OD — chemin rapide :
Pour la plupart des requêtes OD (« test vidéo », « démo produit 5s », « clip de démonstration »), N'ÉCRIVEZ PAS la composition HTML from scratch. Utilisez l'échafaudage intégré d'HyperFrames et ne modifiez que ce que le prompt change réellement. Le chemin « auteur from scratch » coûte des minutes de génération de modèle et de temps silencieux d'outil de chat ; le chemin d'échafaudage coûte des secondes.
# 1. Choisir un emplacement de cache caché. Le préfixe point → le listing des fichiers du projet OD
# le saute, donc les fichiers sources n'encombrent jamais le chat.
COMP_REL=".hyperframes-cache/$(date +%s)-$(openssl rand -hex 2)"
COMP="$OD_PROJECT_DIR/$COMP_REL"
# 2. Obtenir un échafaudage immédiatement rendable (hyperframes.json,
# meta.json, index.html avec GSAP CDN + window.__timelines.main
# déjà enregistré). Cela s'exécute dans votre shell — simple copie de fichier,
# pas de Chrome, pas de réseau au-delà du cache npx.
npx hyperframes init "$COMP" --example blank --skip-skills --non-interactive
# 3. Modifier UNIQUEMENT $COMP/index.html — changer `data-duration` sur la racine
# si vous avez besoin d'une longueur non par défaut, échanger la palette de placeholder
# dans <style>, ajouter 1–3 <div> de clips pour le texte/les images, et ajouter
# les tweens GSAP correspondants à l'intérieur du bloc existant
# `window.__timelines["main"] = gsap.timeline({paused:true})`.
# Garder les modifications minimales ; l'échafaudage est déjà un HF valide.
# 4. Envoyer le rendu via le démon OD. Ne PAS exécuter `npx hyperframes
# render` depuis ce shell — le démon l'exécute pour vous dans un
# processus non sandboxé. (De nombreux CLI d'agents, Claude Code en particulier,
# encapsulent Bash dans sandbox-exec macOS sous lequel le sous-processus Chrome de puppeteer
# se bloque en partie pendant la capture d'images. Le processus du démon
# n'est pas sandboxé, donc les rendus se terminent de manière fiable.)
#
# Le dispatcher renvoie dans ~1s avec un {taskId} ; conduisez le
# rendu à terme en bouclant les appels `"$OD_NODE_BIN" "$OD_BIN" media wait <taskId>`.
# Chaque appel fait un long-poll jusqu'à 25s (bien en dessous de la limite par défaut
# de 30s de votre outil shell) et se termine avec un code 0/2/5 pour signaler fait/en cours/échec.
out=$("$OD_NODE_BIN" "$OD_BIN" media generate \
--project "$OD_PROJECT_ID" \
--surface video \
--model hyperframes-html \
--output "<descriptive-name>.mp4" \
--composition-dir "$COMP_REL")
ec=$?
task_id=$(printf '%s\n' "$out" | tail -1 | jq -r '.taskId // empty')
since=$(printf '%s\n' "$out" | tail -1 | jq -r '.nextSince // 0')
while [ "$ec" -eq 2 ] && [ -n "$task_id" ]; do
out=$("$OD_NODE_BIN" "$OD_BIN" media wait "$task_id" --since "$since")
ec=$?
since=$(printf '%s\n' "$out" | tail -1 | jq -r '.nextSince // '"$since")
done
[ "$ec" -ne 0 ] && { echo "$out" >&2; exit "$ec"; }
Chaque appel generate et wait dure au maximum ~25s, donc la limite par défaut de ~30s de l'outil shell de l'agent ne se déclenche jamais. Les lignes de progression de HF (Capturing frame N/M) sont diffusées sur stderr en direct tout au long de la boucle. Lorsque le rendu est terminé, la dernière ligne stdout est {"file": { "name": "<output>", "size": …, "kind": "video", … }} — citez file.name dans votre réponse pour que l'utilisateur sache ce qui a été produit.
Ignorer la Porte de l'Identité Visuelle dans OD. La section HARD-GATE ci-dessous (sous « Approche ») vous dit de lire DESIGN.md / visual-style.md ou de vous arrêter et de poser 3 questions d'ambiance avant d'écrire une composition. Cette porte concerne les projets HF autonomes. Les projets OD ont déjà leur propre couche de système de design — l'utilisateur a choisi sa direction visuelle à la création du projet. Pour un test de rendu OD, par défaut : canevas sombre (#0b0b0f), un accent chaud (#ffb76b), un accent froid (#7da4ff), mouvement retenu. Ne demandez un avis stylistique que si le prompt de l'utilisateur est trop vague pour même choisir un sujet (très rare).
Quand ignorer l'échafaudage et écrire from scratch : uniquement lorsque l'utilisateur demande explicitement quelque chose que le modèle vide ne peut clairement pas héberger (par exemple, timelines multi-compositions, incrustations audio-réactives, sous-titres synchronisés sur une piste TTS qu'ils ont déjà générée). Pour tout le reste, init + édition est le chemin par défaut.
Les sous-commandes HF plus légères que vous POUVEZ encore exécuter depuis votre propre shell (elles n'ont pas besoin de lancer Chrome) :
npx hyperframes lint "$COMP"— valider la composition avant envoinpx hyperframes transcribe <audio>— générer des sous-titresnpx hyperframes tts <texte>— générer une narration
Réservez l'envoi au démon pour render/inspect/preview (tout ce qui nécessite Chrome).
Ne PAS appeler "$OD_NODE_BIN" "$OD_BIN" media generate --model hyperframes-html — ce chemin de dispatcher retourne une 400 (AGENT_RENDERED) intentionnellement. HyperFrames est rendu par vous directement via npx.
Ne PAS déposer hyperframes.json / meta.json / index.html dans la racine du projet ; le listing des fichiers OD scanne récursivement et l'utilisateur verrait trois fichiers non liés apparaître dans le chat.
Pour les options CLI au-delà de render (lint, preview, transcribe, tts, inspect, benchmark), appelez-les directement depuis votre outil shell lorsque la tâche le justifie (par exemple, générer un audio TTS dans le cache avant de le référencer dans la composition).
Approche
Avant d'écrire du HTML, réfléchissez à un haut niveau :
- Quoi — que doit vivre le spectateur ? Identifiez l'arc narratif, les moments clés et les battements émotionnels.
- Structure — combien de compositions, lesquelles sont des sous-compositions ou en ligne, quelles pistes portent quoi (vidéo, audio, incrustations, sous-titres).
- Timing — quels clips déterminent la durée, où atterrissent les transitions, quel est le rythme.
- Mise en page — construisez d'abord l'état final. Voir « Mise en page avant l'animation » ci-dessous.
- Animer — puis ajoutez du mouvement en utilisant les règles ci-dessous.
Pour les petites modifications (corriger une couleur, ajuster le timing, ajouter un élément), passez directement aux règles.
Porte de l'Identité Visuelle
Vérifiez dans cet ordre :
- DESIGN.md existe dans le projet ? → Lisez-le. Utilisez ses couleurs, polices, règles de mouvement et contraintes « Ce qu'il ne faut PAS faire » exactes.
- visual-style.md existe ? → Lisez-le. Appliquez son
style_prompt_fullet ses champs structurés. (Note :visual-style.mdest un fichier spécifique au projet.visual-styles.mdest la bibliothèque de styles avec 8 préréglages nommés — fichiers différents.) - L'utilisateur a nommé un style (par exemple, « Swiss Pulse », « dark and techy », « marque de luxe ») ? → Lisez visual-styles.md pour les 8 préréglages nommés. Générez un DESIGN.md minimal avec :
## Style Prompt(un paragraphe),## Colors(3-5 valeurs hex avec rôles),## Typography(1-2 familles de polices),## What NOT to Do(3-5 anti-patrons). - Rien de ce qui précède ? → Posez 3 questions avant d'écrire du HTML :
- Quelle est l'ambiance ? (explosif / cinématique / fluide / technique / chaotique / chaleureux)
- Canevas clair ou sombre ?
- Des couleurs de marque, polices ou références visuelles spécifiques ? → Générez ensuite un DESIGN.md minimal à partir des réponses.
Chaque composition doit faire remonter sa palette et sa typographie à un DESIGN.md, visual-style.md ou une direction utilisateur explicite. Si vous utilisez #333, #3b82f6 ou Roboto — vous avez sauté cette étape.
Pour les valeurs par défaut de mouvement, dimensionnement, patrons d'entrée et d'aisance — suivez house-style.md. Le style maison gère COMMENT les choses bougent. Le DESIGN.md gère À QUOI ressemblent les choses.
Mise en page avant l'animation
Positionnez chaque élément là où il doit être à son moment le plus visible — l'image où il est complètement entré, correctement placé et pas encore en sortie. Écrivez cela d'abord en HTML+CSS statique. Pas encore de GSAP.
Pourquoi c'est important : Si vous positionnez les éléments à leur état de départ animé (hors écran, mis à l'échelle à 0, opacité 0) et que vous les animez vers l'endroit où vous pensez qu'ils devraient atterrir, vous devinez la mise en page finale. Les chevauchements sont invisibles jusqu'au rendu de la vidéo. En construisant d'abord l'état final, vous pouvez voir et corriger les problèmes de mise en page avant d'ajouter du mouvement.
Le processus
- Identifiez l'image héroïque pour chaque scène — le moment où le plus d'éléments sont simultanément visibles. C'est la mise en page que vous construisez.
- Écrivez du CSS statique pour cette image. Le conteneur
.scene-contentDOIT remplir toute la scène en utilisantwidth: 100%; height: 100%; padding: Npx;avecdisplay: flex; flex-direction: column; gap: Npx; box-sizing: border-box. Utilisez le remplissage pour pousser le contenu vers l'intérieur — JAMAISposition: absolute; top: Npxsur un conteneur de contenu. Les conteneurs de contenu positionnés absolument débordent lorsque le contenu est plus haut que l'espace restant. Réservezposition: absoluteuniquement pour les éléments décoratifs. - Ajoutez des entrées avec
gsap.from()— animez DEPUIS hors écran/invisible VERS la position CSS. La position CSS est la vérité de base ; la tween décrit le voyage pour y arriver. - Ajoutez des sorties avec
gsap.to()— animez VERS hors écran/invisible DEPUIS la position CSS.
Exemple
/* scene-content remplit la scène, le padding positionne le contenu */
.scene-content {
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
height: 100%;
padding: 120px 160px;
gap: 24px;
box-sizing: border-box;
}
.title {
font-size: 120px;
}
.subtitle {
font-size: 42px;
}
/* Le conteneur remplit toute taille de scène (1920x1080, 1080x1920, etc.).
Le padding positionne le contenu. Flex + gap gère l'espacement. */
FAUX — dimensions codées en dur et positionnement absolu :
.scene-content {
position: absolute;
top: 200px;
left: 160px;
width: 1920px;
height: 1080px;
display: flex; /* ... */
}
// Étape 3 : Animez VERS ces positions
tl.from(".title", { y: 60, opacity: 0, duration: 0.6, ease: "power3.out" }, 0);
tl.from(".subtitle", { y: 40, opacity: 0, duration: 0.5, ease: "power3.out" }, 0.2);
tl.from(".logo", { scale: 0.8, opacity: 0, duration: 0.4, ease: "power2.out" }, 0.3);
// Étape 4 : Animez DEPUIS ces positions
tl.to(".title", { y: -40, opacity: 0, duration: 0.4, ease: "power2.in" }, 3);
tl.to(".subtitle", { y: -30, opacity: 0, duration: 0.3, ease: "power2.in" }, 3.1);
tl.to(".logo", { scale: 0.9, opacity: 0, duration: 0.3, ease: "power2.in" }, 3.2);
Lorsque les éléments partagent l'espace dans le temps
Si l'élément A quitte avant que l'élément B n'entre dans la même zone, les deux doivent avoir des positions CSS correctes pour leurs images héroïques respectives. L'ordre de la timeline garantit qu'ils ne coexistent jamais visuellement — mais si vous sautez l'étape de mise en page, vous ne détecterez pas le cas où ils se chevauchent accidentellement en raison d'une erreur de timing.
Ce qui compte comme chevauchement intentionnel
Les effets superposés (lueur derrière le texte, éléments d'ombre, motifs d'arrière-plan) et les conceptions empilées en z (piles de cartes, couches de profondeur) sont intentionnels. L'étape de mise en page vise à détecter le chevauchement non intentionnel — deux titres tombant l'un sur l'autre, une statistique couvrant une étiquette, du contenu débordant du cadre.
Attributs de données
Tous les clips
| Attribut | Requis | Valeurs |
|---|---|---|
id | Oui | Identifiant unique |
data-start | Oui | Secondes ou référence d'ID de clip ("el-1", "intro + 2") |
data-duration | Requis pour img/div/compositions | Secondes. La vidéo/l'audio par défaut est la durée du média. |
data-track-index | Oui | Entier. Les clips d'une même piste ne peuvent pas se chevaucher. |
data-media-start | Non | Décalage de coupe dans la source (secondes) |
data-volume | Non | 0-1 (défaut 1) |
data-track-index n'affecte pas la superposition visuelle — utilisez CSS z-index.
Clips de composition
| Attribut | Requis | Valeurs |
|---|---|---|
data-composition-id | Oui | ID de composition unique |
data-start | Oui | Heure de début (composition racine : utiliser "0") |
data-duration | Oui | Prime sur la durée de la timeline GSAP |
data-width / data-height | Oui | Dimensions en pixels (1920x1080 ou 1080x1920) |
data-composition-src | Non | Chemin vers un fichier HTML externe |
Structure de la composition
Les sous-compositions chargées via data-composition-src utilisent un wrapper <template>. Les compositions autonomes (le main index.html) n'utilisent PAS <template> — elles placent la div data-composition-id directement dans <body>. Utiliser <template> sur un fichier autonome cache tout contenu au navigateur et casse le rendu.
Structure d'une sous-composition :
<template id="my-comp-template">
<div data-composition-id="my-comp" data-width="1920" data-height="1080">
<!-- contenu -->
<style>
[data-composition-id="my-comp"] {
/* styles protégés */
}
</style>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
// tweens...
window.__timelines["my-comp"] = tl;
</script>
</div>
</template>
Chargement dans la racine : <div id="el-1" data-composition-id="my-comp" data-composition-src="compositions/my-comp.html" data-start="0" data-duration="8"></div>
Attributs de données de composition racine
Le <body> de index.html utilise les mêmes attributs de données qu'un élément de sous-composition, sauf que data-composition-src est omis. Au lieu de cela, les données de composition racine sont définies avec des attributs data-* supplémentaires sur <body> :
| Attribut | Requis | Valeurs |
|---|---|---|
data-width | Oui | Largeur en pixels (par exemple 1920) |
data-height | Oui | Hauteur en pixels (par exemple 1080) |
data-duration | Oui | Durée en secondes |
data-fps | Non | Images par seconde (défaut 60) |
data-bg | Non | Couleur d'arrière-plan (défaut transparent) |
Commencer à partir de zéro (npx hyperframes init)
Lorsque vous démarrez un nouveau projet, utilisez npx hyperframes init pour générer un index.html vide avec GSAP et la timeline principale déjà enregistrée :
npx hyperframes init mon-projet --non-interactive
Si vous souhaitez un type particulier de composition initiale, utilisez l'option --example :
npx hyperframes init mon-projet --example blank # vidéo noire avec une seule scène vide
npx hyperframes init mon-projet --example text # scène de texte simple
npx hyperframes init mon-projet --example media # scène avec élément vidéo
Moteur de rendu (npx hyperframes render)
Le moteur de rendu prend la composition et génère une vidéo MP4 :
npx hyperframes render chemin/vers/index.html --output sortie.mp4
Validateur (npx hyperframes lint)
Utilisez npx hyperframes lint pour valider votre composition avant le rendu :
npx hyperframes lint chemin/vers/index.html
Il vérifie la structure des attributs de données, les références de clips, les dépendances, la validité de la timeline GSAP et les problèmes potentiels avant le rendu.
Modèle de timeline window.__timelines
Chaque composition doit enregistrer une timeline GSAP nommée dans window.__timelines. Le framework HyperFrames gère l'initialisation, la lecture et la synchronisation de toutes les timelines enregistrées. La timeline doit être créée avec { paused: true }. Le framework HyperFrames appellera play() au bon moment.
Exemple
window.__timelines["main"] = gsap.timeline({ paused: true });
window.__timelines["main"].from(".title", { opacity: 0, y: 50, duration: 0.5 });
Utilisation avec des sous-compositions
Chaque sous-composition peut avoir sa propre propriété nommée dans window.__timelines. Cela permet une mise en scène indépendante. Le framework HyperFrames examine l'attribut data-composition-id et appelle la timeline correspondante.
Important : Utilisez des noms uniques par composition pour éviter les collisions.
// Dans index.html racine
window.__timelines["main"] = gsap.timeline({ paused: true });
window.__timelines["main"].from(".intro", { opacity: 0, y: 50, duration: 0.5 });
// Dans la sous-composition my-comp.html
window.__timelines["my-comp"] = gsap.timeline({ paused: true });
window.__timelines["my-comp"].from(".content", { opacity: 0, scale: 0.8, duration: 0.6 });
Architecture du projet
mon-projet/
├── index.html # Composition racine (obligatoire)
├── hyperframes.json # Métadonnées du projet (généré par init)
├── meta.json # Métadonnées supplémentaires (généré par init)
├── compositions/ # Dossier optionnel pour les sous-compositions
│ └── my-comp.html
└── assets/ # Fichiers média
├── video.mp4
└── audio.mp3
Pour plus d'informations, visitez github.com/hyperframes/hyperframes.