D0 HyperFrame – Création vidéo
Workflow complet pour réaliser des vidéos de marque D0 avec HyperFrames HTML + GSAP. Ce document est la seule référence — polices, couleurs, mise en page, animations, règles de transition sont définies ici.
La spécification typographique du skill
d0-brandfait autorité ; ce skill ne fait que réécrire les règles complémentaires liées à la production vidéo.
Mots déclencheurs
- "Fais une vidéo D0" / "d0 hyperframe"
- "Utilise hyperframes pour faire une vidéo" / "/d0-hyperframe"
Étape 0 — Initialisation du projet et des assets
Recommandé : utiliser le script d'initialisation fourni avec le skill
Après avoir installé le dépôt, exécutez le script contenu dans ce skill. Il initialise un projet HyperFrames et copie le logo D0, l'avatar D0 ainsi que toutes les polices nécessaires :
bash "${CODEX_HOME:-$HOME/.codex}/skills/d0-hyperframe/scripts/bootstrap_project.sh" .
Si le projet est déjà initialisé, synchronisez uniquement les assets de marque :
bash "${CODEX_HOME:-$HOME/.codex}/skills/d0-hyperframe/scripts/bootstrap_project.sh" . --assets-only
Le script nécessite Node.js 18+ et npx. La CLI HyperFrames est exécutée à la demande via npx hyperframes@latest, aucun paquet tiers n'est copié dans le dépôt du skill.
Optionnel : écraser D0_AVATAR et le logo depuis le HTML source
Extraire D0_AVATAR et le logo depuis le HTML source
Le HTML source de l'affiche contient généralement deux types d'images base64 :
| Asset | Type | Caractéristique | Usage |
|---|---|---|---|
| Logo D0 | PNG, environ 12–16 KB | .brand img ou PNG la plus courte | En haut à gauche de toutes les scènes |
| D0_AVATAR | JPEG, environ 100 KB | .av ou JPEG la plus longue | Avatar des composants TG |
python3 - << 'EOF'
import re, base64, os
with open('SOURCE.html', 'r') as f:
src = f.read()
os.makedirs('assets', exist_ok=True)
# Logo (PNG la plus courte)
pngs = re.findall(r'(data:image/png;base64,[A-Za-z0-9+/=]+)', src)
logo_b64 = min(pngs, key=len)
with open('assets/d0-logo.png', 'wb') as f:
f.write(base64.b64decode(logo_b64.split(',')[1]))
# D0_AVATAR (JPEG la plus longue)
jpegs = re.findall(r'data:image/jpeg;base64,([A-Za-z0-9+/=]+)', src)
avatar = max(jpegs, key=len)
with open('assets/d0-avatar.jpg', 'wb') as f:
f.write(base64.b64decode(avatar))
print("assets/ prêt :", os.listdir('assets'))
EOF
Polices (méthode manuelle)
mkdir -p fonts
cp /chemin/vers/d0-hyperframe/assets/fonts/*.woff2 fonts/
# Nécessite : InstrumentSerif-Regular.woff2, InstrumentSerif-Italic.woff2
# OpenSans-400/600/700/800-normal.woff2
Initialisation du projet (méthode manuelle)
npx hyperframes@latest init . --non-interactive --example blank --skill=d0-hyperframe
Étape 1 — Tokens de marque (CSS)
:root {
--bg: #06060d;
--pu2: #b3a8f7;
--white: #f2f2f5;
--t2: #8d8d98;
--t3: #5a5a66;
--green: #3ddc84;
--red: #f87171;
--gold: #f7a93a;
--bdr: #23232c;
--bdr2: #2e2e39;
--grad: linear-gradient(120deg, #5468F5 0%, #9B4FD6 34%, #C25C9A 62%, #DD8B57 100%);
}
@font-face { font-family:'Instrument Serif'; src:url('fonts/InstrumentSerif-Regular.woff2') format('woff2'); font-weight:400; font-style:normal; }
@font-face { font-family:'Instrument Serif'; src:url('fonts/InstrumentSerif-Italic.woff2') format('woff2'); font-weight:400; font-style:italic; }
@font-face { font-family:'Open Sans'; src:url('fonts/OpenSans-400-normal.woff2') format('woff2'); font-weight:400; }
@font-face { font-family:'Open Sans'; src:url('fonts/OpenSans-600-normal.woff2') format('woff2'); font-weight:600; }
@font-face { font-family:'Open Sans'; src:url('fonts/OpenSans-700-normal.woff2') format('woff2'); font-weight:700; }
@font-face { font-family:'Open Sans'; src:url('fonts/OpenSans-800-normal.woff2') format('woff2'); font-weight:800; }
Étape 2 — Structure du stage (calque persistant + couche composant)
Modèle central : un seul calque d'arrière-plan fixe, le contenu est découpé en composants. L'arrière-plan + les éléments fixes (logo/footer/rail supérieur) appartiennent au calque persistant, immobile tout au long de la vidéo ; les titres, données, panneaux TG, graphiques, etc., sont chacun des composants
.blkindépendants, qui entrent et sortent individuellement. Les transitions se produisent au niveau des composants, pas au niveau de l'écran entier — c'est la clé pour se débarrasser des coupures brutales style PPT.
Squelette HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D0 · [Titre de la vidéo]</title>
<style>/* tokens + CSS de la scène */</style>
</head>
<body>
<div id="main"
data-composition-id="main"
data-start="0"
data-duration="[Durée totale en secondes]"
data-width="1920"
data-height="1080">
<!-- ── Calque persistant : immobile, aucune animation tl (voir Étape 3) ── -->
<div class="bg"></div>
<div class="rail"></div>
<div class="logo"></div>
<div class="foot-tag">Agentic Trading for Every Market.</div>
<div class="foot-url">getdonut.ai</div>
<!-- ── Couche contenu : groupée par beat, composants entrent/sortent en décalé (voir Étape 6) ── -->
<!-- beat 1 -->
<div class="blk" id="b1-title">...</div>
<div class="blk" id="b1-sub">...</div>
<!-- beat 2 -->
<div class="blk" id="b2-stat">...</div>
<div class="blk" id="b2-tg">...</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<script>/* Timeline GSAP */</script>
</body>
</html>
CSS du stage (calque persistant + couche contenu)
/* Conteneur du stage */
#main {
position: absolute; inset: 0; overflow: hidden;
font-family: 'Open Sans', sans-serif; color: var(--white);
}
/* Arrière-plan persistant — fixe tout au long de la vidéo, jamais dans l'animation tl */
.bg {
position: absolute; inset: 0; width: 1920px; height: 1080px; z-index: 0;
background: var(--bg);
background-image:
radial-gradient(ellipse 46% 58% at 76% 50%, rgba(150,128,255,.16) 0%, transparent 60%),
radial-gradient(ellipse 62% 50% at 8% 4%, rgba(110,100,255,.10) 0%, transparent 56%);
}
/* Rail dégradé supérieur — fixe, un seul pour toute la vidéo */
.rail {
position: absolute; top: 0; left: 0; right: 0; height: 5px; z-index: 10;
background: var(--grad);
}
/* Composant contenu — unité minimale d'animation, caché par défaut, entre et sort individuellement */
.blk { position: absolute; opacity: 0; z-index: 2; }
L'ancien modèle avec chaque écran
.scenecomplet, arrière-plan inclu, glissait en bloc → effet PPT + arrêts brusques. Le nouveau modèle n'a qu'un seul arrière-plan fixe, le contenu est découpé en composants.blk, les transitions se produisent au niveau des composants (voir Étape 6).
Étape 3 — Spécifications des éléments fixes (non négociables)
Les trois éléments suivants restent identiques dans toutes les scènes et n'ont pas d'animation d'entrée ou de sortie :
| Élément | Position | Taille police | Description |
|---|---|---|---|
| Logo Donut | En haut à gauche top:40px left:72px | 200×76px | CSS background, pas de <img> |
| Agentic Trading for Every Market. | En bas à gauche bottom:48px left:72px | 27px | Instrument Serif italic, --pu2 |
| getdonut.ai | En bas à droite bottom:50px right:72px | 14px | Open Sans, --t3 |
/* Logo — CSS background, pas d'opacity:0 */
.logo {
position: absolute; top: 40px; left: 72px; z-index: 10;
width: 200px; height: 76px;
background: url('assets/d0-logo.png') left center / contain no-repeat;
}
/* Footer — directement visible, sans animation */
.foot-tag {
position: absolute; bottom: 48px; left: 72px; z-index: 10;
font-family: 'Instrument Serif', serif; font-style: italic;
font-size: 27px; color: var(--pu2);
}
.foot-url {
position: absolute; bottom: 50px; right: 72px; z-index: 10;
font-size: 14px; font-weight: 700; letter-spacing: .06em; color: var(--t3); opacity: 0;
}
Les éléments fixes appartiennent au calque persistant, visibles dès l'image 0, aucune animation tl n'est écrite.
foot-tagest directement visible ;foot-urlpeut conserver un léger fondu unique, mais ce n'est pas obligatoire.
Étape 4 — Principes de mise en page
Équilibre gauche-droite, éliminer le vide central
- Priorité à la disposition à deux colonnes : texte / données à gauche, composant TG / image à droite
- Avec une image de fond de personnage à droite : positionnée en absolu pour remplir la droite, superposée d'un masque dégradé de gauche à droite
.scene-char {
position: absolute; top:0; right:0; bottom:0; width:62%;
background: url('assets/bg-char.png') right center / cover no-repeat;
z-index: 0;
}
.scene-char::after {
content:''; position:absolute; inset:0;
background: linear-gradient(to right,
var(--bg) 0%, rgba(6,6,13,.82) 18%, rgba(6,6,13,.45) 45%, transparent 100%);
}
- La zone de texte à gauche doit avoir
position: relative; z-index: 2pour être superposée à l'image de fond
Taille de police minimale
Sur la base de la vidéo D0 Thorp Engine, ne pas descendre en dessous de :
| Niveau | Taille minimale | Police |
|---|---|---|
| Titre principal | 80px | Instrument Serif |
| Sous-titre de scène | 56px | Instrument Serif |
| Kick label / eyebrow | 18px | Open Sans 700 uppercase |
| Corps de texte / feature bullet | 22px | Open Sans |
| Point d'ancrage de donnée (stat-val) | 48px | Instrument Serif |
| Footer tag | 27px | Instrument Serif italic |
| Texte dans les composants TG | 13px (minimal acceptable) | Open Sans |
Étape 5 — Spécifications des composants TG
Avatar : doit utiliser D0_AVATAR
/* Grand avatar (header, 40px) */
.av { width:40px; height:40px; border-radius:50%; flex:none;
background: url('assets/d0-avatar.jpg') center/cover no-repeat; }
/* Petit avatar (ligne de message, 28px) */
.mav { width:28px; height:28px; border-radius:50%; flex:none; margin-top:2px;
background: url('assets/d0-avatar.jpg') center/cover no-repeat; }
Ne pas utiliser de balise <img> pour éviter que HyperFrames ne détecte des nœuds media en double (avertissement lint).
Ne pas utiliser de dégradé de repli, le fichier D0_AVATAR réel est obligatoire.
Structure du panneau TG
<div id="tg-panel">
<div class="tg-chrome">
<div class="dot r"></div><div class="dot y"></div><div class="dot g"></div>
<div class="tg-url">t.me / D0_Bot</div>
</div>
<div class="tg-hdr">
<div class="av"></div>
<div>
<div class="tg-nm">D0</div>
<div class="tg-st">bot · online</div>
</div>
</div>
<div class="tg-body">
<div class="msg-user">prompt utilisateur</div>
<div class="msg-bot">
<div class="mav"></div>
<div class="bbubble">contenu de la réponse de D0</div>
</div>
</div>
</div>
Étape 6 — Règles de transition (non négociables)
Principe central : l'arrière-plan ne bouge pas, ce sont les composants qui bougent. L'"effet PPT" vient du défilement complet de la scène (y compris l'arrière-plan) — le spectateur perd son point d'ancrage stable ; les "arrêts brusques" viennent de l'enchaînement bout à bout de l'animation de sortie et de l'animation d'entrée — il y a un instant de silence total entre les deux. La solution aux deux problèmes est la même phrase : arrière-plan fixe + entrées/sorties de composants décalées et chevauchées.
Transition par défaut : entrées/sorties de composants en décalé (element cross-stagger)
L'arrière-plan + les éléments fixes (logo/footer/rail) restent immobiles. Chaque changement de beat ne fait bouger que les composants de contenu :
- Les composants de l'ancien beat sortent l'un après l'autre (stagger, easing
.in, léger déplacement + fondu) - Les composants du nouveau beat entrent l'un après l'autre (stagger, easing
.out) - Les deux sont chevauchement d'environ 0,2–0,3 s — l'ancien n'a pas fini de sortir que le nouveau commence déjà à entrer. C'est la clé pour éliminer les arrêts brusques.
// Changement de beat : sortie des anciens en décalé, entrée des nouveaux en décalé, avec chevauchement (pas bout à bout)
function beat(outEls, inEls, t) {
outEls.forEach((el, i) =>
tl.to(el, { opacity:0, y:-30, duration:0.45, ease:'power2.in' }, t + i*0.06));
inEls.forEach((el, i) =>
tl.fromTo(el, { opacity:0, y:40 },
{ opacity:1, y:0, duration:0.6, ease:'expo.out' }, t + 0.2 + i*0.08)); // +0.2 = chevauchement
}
// Utilisation : beat(['#b1-title','#b1-sub'], ['#b2-stat','#b2-tg'], 4.0);
- Le déplacement est faible (20–40 px) ; la direction peut être uniforme (par exemple, tous vers le haut) pour créer une "sensation de courant d'air", mais l'arrière-plan ne participe jamais.
- Quand il y a beaucoup de composants, le stagger maintient une action continue à l'écran, évitant "entrée simultanée → tout gelé".
Quand utiliser une transition d'écran complet
Uniquement pour les grandes sections (par exemple, carte titre de marque → section données), et seulement la couche contenu est poussée, pas l'arrière-plan — les composants de cette section sont regroupés dans un conteneur #grp puis poussés :
// Push-slide (ne pousse que le conteneur contenu, l'arrière-plan .bg ne bouge pas)
tl.to('#grpA', { x:-1920, duration:0.55, ease:'power3.inOut' }, t);
tl.fromTo('#grpB', { x:1920, opacity:1 }, { x:0, duration:0.55, ease:'power3.inOut' }, t);
// Blur crossfade (transition émotionnelle, généralement pour la dernière transition)
tl.to('#grpC', { filter:'blur(20px)', opacity:0, duration:0.65, ease:'power2.in' }, t);
tl.fromTo('#grpD', { filter:'blur(20px)', opacity:0 },
{ filter:'blur(0px)', opacity:1, duration:0.65, ease:'power2.out' }, t);
❌ Interdit : scène entière (y compris l'arrière-plan) qui glisse, zoom dans un coin, zoom d'écran entier, enchaînement out/in bout à bout sans chevauchement.
Étape 7 — Règles d'animation GSAP
Obligations
const tl = gsap.timeline({ paused: true });
// ... tweens ...
window.__timelines['main'] = tl;
- Utiliser
tl.fromTo()plutôt quetl.from()(pour garantir la détermination du seek HyperFrames) - Pour les animations répétées, utiliser
Math.floor(duration / cycle) - 1, interdiction derepeat: -1 - Pour plusieurs groupes d'animations sur le même élément, ajouter
overwrite: 'auto' - Utiliser l'easing
.outpour l'entrée et l'easing.inpour la sortie
Mise en page d'abord, animation ensuite
Écrire d'abord l'état final de l'élément (positionnement CSS), puis utiliser fromTo pour décrire le chemin d'entrée :
// ✅ Correct
tl.fromTo('#title', { opacity:0, y:40 }, { opacity:1, y:0, duration:0.8, ease:'expo.out' }, 0.3);
Pas d'animation sur le calque persistant
.bg, .rail, .logo, .foot-tag, .foot-url sont fixes tout au long de la vidéo, les animations ne concernent que les composants .blk. Dès que l'arrière-plan participe à un déplacement, on retombe dans l'effet PPT — les transitions ne font bouger que la couche contenu (voir Étape 6 beat()).
Étape 8 — Lint & Aperçu
npx hyperframes lint # 0 erreurs obligatoire
npx hyperframes preview # http://localhost:300x, rechargement à chaud
| Avertissement courant | Traitement |
|---|---|
overlapping_gsap_tweens | Ajouter overwrite:'auto' |
duplicate_media_discovery_risk | Remplacer <img> par background:url() en CSS |
composition_file_too_large | Ignorer si fichier unique <500 lignes |
Dépendances et sources des assets
- Skill
d0-brand: autorité ultime pour les polices, tailles et principes de marque, doit être installé avec ce skill. - CLI HyperFrames : obtenue à la demande via
npx hyperframes@latest. - GSAP : version CDN
3.14.2utilisée dans l'exemple ; en cas de mise à niveau, relancer lint et validation seek. assets/fonts/: Instrument Serif et Open Sans ; les fichiers de licence se trouvent dans le répertoire de police correspondant.assets/brand/: Logo D0 et avatar D0, destinés uniquement aux membres autorisés de l'équipe Donut pour le contenu de marque D0.