HyperFrames
Le 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 des médias et la synchronisation de la timeline.
Approche
Découverte (uniquement pour les demandes exploratoires)
Pour les demandes ouvertes ("fais-moi une vidéo de lancement de produit", "crée quelque chose pour notre marque") où l'utilisateur ne s'est pas engagé dans une direction, comprenez l'intention avant de choisir les couleurs :
- Public — qui regarde ça ? Des développeurs ? Des dirigeants ? Des consommateurs grand public ?
- Plateforme — où est-ce diffusé ? Réseaux sociaux (15s), page d'accueil du site, démo produit, interne ?
- Priorité — qu'est-ce qui compte le plus ? La qualité du mouvement ? La précision du contenu ? La fidélité à la marque ? La rapidité ?
- Variantes — l'utilisateur veut-il des options, ou une seule meilleure version ?
Pour les demandes spécifiques ("ajoute un carton-titre", "corrige le timing de la scène 3"), passez la phase de découverte.
Pour les demandes exploratoires, envisagez de proposer 2 à 3 variantes qui diffèrent significativement — pas seulement des changements de couleur, mais des rythmes, niveaux d'énergie ou approches structurelles différents. Une version sûre/prévue, une version ambitieuse. N'imposez pas ce choix — c'est un outil disponible quand c'est approprié.
Étape 1 : Système de design
Si design.md ou DESIGN.md existe dans le projet, lisez-le d'abord (vérifiez les deux cas — ce sont des fichiers différents sous Linux). C'est la source de vérité pour les couleurs de la marque, les polices et les contraintes. Utilisez ses valeurs exactes — n'inventez pas de couleurs et ne remplacez pas les polices. Tout format fonctionne (frontmatter YAML, prose, tableaux — extrayez simplement les valeurs).
S'il nomme des polices que vous ne trouvez pas localement (pas de répertoire fonts/ avec des fichiers .woff2, ni de police intégrée), avertissez l'utilisateur avant d'écrire du HTML : "design.md spécifie la police [nom de la police] mais aucun fichier de police trouvé. Veuillez ajouter des fichiers .woff2 dans fonts/ ou je me rabattrai sur [alternative intégrée la plus proche]."
Si aucun design.md n'existe, proposez à l'utilisateur un choix :
- L'utilisateur a nommé un style ou une ambiance ? → Lisez visual-styles.md pour les 8 presets nommés. Choisissez la correspondance la plus proche.
- Souhaitez-vous parcourir les options visuellement ? → Lancez le sélecteur de design : lisez references/design-picker.md pour le flux de travail complet. Cela sert une page de sélection visuelle. L'utilisateur configure l'ambiance, la palette, la typographie et le mouvement dans le navigateur, puis copie le design.md généré et le colle dans la conversation.
- Vous voulez passer rapidement ? → Demandez : l'ambiance, clair ou sombre, des couleurs/polices de marque ? Ensuite choisissez une palette dans house-style.md.
design.md définit la marque. Il ne définit pas les règles de composition vidéo. Celles-ci viennent de references/video-composition.md et house-style.md. Utilisez les couleurs de la marque à une échelle adaptée à la vidéo — pas à l'opacité d'une UI web.
Étape 2 : Développement du prompt
Toujours exécuté sur chaque composition (sauf pour les pièces à scène unique et les modifications triviales). Cette étape ancre l'intention de l'utilisateur par rapport à design.md et house-style.md et produit un intermédiaire cohérent que tout agent en aval lira de la même manière.
Lisez references/prompt-expansion.md pour le processus complet et le format de sortie.
Étape 3 : Planification
Avant d'écrire du HTML, réfléchissez à un niveau élevé :
- Quoi — que doit vivre le spectateur ? Identifiez l'arc narratif, les moments clés et les temps émotionnels.
- Structure — combien de compositions, lesquelles sont des sous-compositions vs en ligne, quelles pistes transportent quoi (vidéo, audio, incrustations, sous-titres).
- Rythme — déclarez votre rythme de scène avant de l'implémenter. Quelles scènes sont des frappes rapides, lesquelles sont des poses, où se placent les shaders, où culmine l'énergie. Nommez le modèle : rapide-rapide-LENT-rapide-SHADER-pose. Lisez references/beat-direction.md pour les modèles de rythme.
- Timing — quels clips déterminent la durée, où se placent les transitions, quel est le rythme.
- Mise en page — construisez d'abord l'état final. Voir "Mise en page avant l'animation" ci-dessous.
- Animation — ajoutez ensuite le mouvement en utilisant les règles ci-dessous.
Construisez ce qui a été demandé. Une demande pour "un carton-titre" n'est pas une demande pour "un carton-titre + 3 scènes supplémentaires + musique d'ambiance + sous-titres." Chaque scène, chaque élément, chaque tween doit gagner sa place. Si des scènes ou éléments supplémentaires amélioreraient réellement la pièce, proposez-les — ne les ajoutez pas.
Pour les petites modifications (corriger une couleur, ajuster le timing, ajouter un élément), passez directement aux règles.
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 train de sortir. É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 doivent atterrir, vous devinez la mise en page finale. Les chevauchements sont invisibles jusqu'au rendu de la vidéo. En construisant l'état final d'abord, 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 de 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 le CSS statique pour cette image. Le conteneur
.scene-contentDOIT remplir la scène entière avecwidth: 100%; height: 100%; padding: Npx;avecdisplay: flex; flex-direction: column; gap: Npx; box-sizing: border-box. Utilisez padding pour pousser le contenu vers l'intérieur — JAMAISposition: absolute; top: Npxsur un conteneur de contenu. Les conteneurs de contenu en position absolue débordent lorsque le contenu est plus grand que l'espace restant. Réservezposition: absoluteuniquement pour les éléments décoratifs. - Ajoutez les entrées avec
gsap.from()— animez DEPUIS l'état hors écran/invisible VERS la position CSS. La position CSS est la vérité de base ; le tween décrit le voyage pour y arriver. (Dans les sous-compositions chargées viadata-composition-src, préférezgsap.fromTo()— voir les règles GSAP de charge dans references/motion-principles.md.) - Ajoutez les sorties avec
gsap.to()— animez VERS l'état 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 s'adapte à toute taille de scène (1920x1080, 1080x1920, etc.).
Le padding positionne le contenu. Flex + gap gère l'espacement. */
MAUVAIS — 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 HORS de 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);
Quand les éléments partagent l'espace dans le temps
Si l'élément A sort 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 en couches (lueur derrière le texte, ombres, motifs d'arrière-plan) et les conceptions en pile-z (piles de cartes, couches de profondeur) sont intentionnels. L'étape de mise en page consiste à détecter les chevauchements non intentionnels — deux titres atterrissant 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. Vidéo/audio par défaut : durée du média. |
data-track-index | Oui | Entier. Les clips sur la 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 (par 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 | Identifiant unique de composition |
data-start | Oui | Temps de début (composition racine : utilisez "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 |
data-variable-values | Non | Objet JSON des remplacements de variables par instance sur un hôte de sous-comp |
Sur l'élément racine <html> :
| Attribut | Requis | Valeurs |
|---|---|---|
data-composition-variables | Non | Tableau JSON des variables déclarées (id/type/label/default) — pilote l'UI d'édition Studio et fournit les valeurs par défaut pour getVariables() |
Structure de la composition
Les sous-compositions chargées via data-composition-src utilisent un wrapper <template>. Les compositions autonomes (le fichier index.html principal) n'utilisent PAS <template> — elles placent la div data-composition-id directement dans <body>. L'utilisation de <template> sur un fichier autonome cache tout contenu au navigateur et casse le rendu.
Structure de la 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 limités à ce scope */
}
</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="10" data-track-index="1"></div>
Variables (compositions paramétrées)
Rendez la même composition avec un contenu différent — titre, couleur de thème, prix, sous-titres — sans modifier le HTML source.
Modèle en trois étapes :
- Déclarez les variables sur la racine
<html>de la composition avecdata-composition-variables. Chaque entrée nécessiteid,type(parmistring,number,color,boolean,enum),labeletdefault. Les entrées de type enum nécessitent égalementoptions: [{value, label}, ...]. - Lisez les valeurs résolues dans le script de la composition avec
window.__hyperframes.getVariables(). Retourne le résultat fusionné des valeurs par défaut déclarées + des remplacements par instance + des remplacements CLI. - Remplacez au moment du rendu avec
npx hyperframes render --variables '{...}'(niveau supérieur) ou avecdata-variable-values='{...}'sur l'élément hôte (par instance pour les sous-comps).
<!doctype html>
<html
data-composition-variables='[
{"id":"title","type":"string","label":"Titre","default":"Bonjour"},
{"id":"theme","type":"enum","label":"Thème","default":"light","options":[
{"value":"light","label":"Clair"},
{"value":"dark","label":"Sombre"}
]}
]'
>
<body>
<div data-composition-id="root" data-width="1920" data-height="1080">
<h1 id="hero" class="clip" data-start="0" data-duration="3"></h1>
<script>
const { title, theme } = window.__hyperframes.getVariables();
document.getElementById("hero").textContent = title;
document.body.dataset.theme = theme;
</script>
</div>
</body>
</html>
# L'aperçu de développement utilise les valeurs par défaut déclarées
npx hyperframes preview
# Rendu avec remplacements
npx hyperframes render --variables '{"title":"Rapport Q4","theme":"dark"}' --output q4.mp4
# Ou à partir d'un fichier JSON
npx hyperframes render --variables-file ./vars.json
Valeurs par instance de sous-composition : le même getVariables() fonctionne dans les sous-comps chargées via data-composition-src. Chaque élément hôte transmet ses propres valeurs :
<div
data-composition-id="card-pro"
data-composition-src="compositions/card.html"
data-variable-values='{"title":"Pro","price":"29€"}'
></div>
<div
data-composition-id="card-enterprise"
data-composition-src="compositions/card.html"
…
[tronqué pour la traduction]