Étape 1/2 — Créer le scénario-storyboard JSON d'une vidéo. Contient la chorégraphie complète frame-by-frame.
Tu crées src/scenarios/$ARGUMENTS[0].json — un vrai storyboard avec la chorégraphie complète de chaque élément.
src/lib/BIBLE.md — principes de motion design (TOUT lire)src/lib/toolkit.tsx — techniques de texte (exports)src/lib/visuals.tsx — composants visuels (exports)src/lib/effects.tsx — effets post-processsrc/designs/$ARGUMENTS[2].md → palette, typo, style d'imagesreferences/pinterest/analyses/ un JSON qui correspond au template. LIS-LE EN ENTIER. C'est la source de vérité pour les proportions, positions, tailles, et patterns visuels.❌ Un inventaire d'éléments ("il y a un cercle, une image, du texte") ❌ Une description vague ("le personnage apparaît au centre") ❌ Une liste de props sans timing ("AnimatedImage width=400 Z2")
✅ Un storyboard temporel — chaque élément a un moment d'entrée, de vie, de sortie ✅ Une chorégraphie de mouvements — "entre par la droite en 8 frames, ralentit, s'arrête à x=540" ✅ Des interactions entre éléments — "le texte apparaît QUAND l'image atteint sa position finale" ✅ Des directions visuelles précises — positions en pixels, tailles exactes, couleurs hex
Le user fournit : transcription, sous-titres, ou sujet.
Transformation → storyboard VISUAL-FIRST :
{
"id": "nom-kebab",
"title": "Titre descriptif",
"format": { "width": 1080, "height": 1920, "fps": 24, "durationInFrames": 240 },
"designTemplate": "nom-du-template|null",
"palette": {
"bg": "#0a0a0a",
"bgAlt": "#f5f5f5",
"accent": "#d4382c",
"text": "#ffffff",
"textAlt": "#1a1a1a",
"subtle": "#888888"
},
"typography": {
"primary": "Inter",
"primaryWeights": [300, 400, 900],
"secondary": "PlayfairDisplay",
"secondaryWeights": [700]
},
"imageAssets": [
{
"id": "hero-nebula",
"category": "3d-icon|photo-moody|flat-illustration|product|texture|scene-narrative",
"prompt": "...",
"filename": "hero-nebula.png",
"removeBackground": true,
"usage": "Scene 1 hero — occupies 35-50% of frame, center-right"
}
],
"scenes": [
{
"id": "S1-hook",
"startFrame": 0,
"endFrame": 80,
"background": { "type": "gradient", "from": "#0a0a0a", "to": "#1a1a1a" },
"camera": {
"enter": {
"from": { "scale": 1.15, "x": -60, "y": 30, "rotation": 1.5, "blur": 12 },
"to": { "scale": 1, "x": 0, "y": 0, "rotation": 0, "blur": 0 },
"spring": { "stiffness": 50, "damping": 20, "mass": 1.3 }
},
"wander": { "intensity": 12, "speed": 0.015, "rotation": 0.5 },
"exit": {
"direction": "right-up",
"translate": 350,
"scale": 1.15,
"blur": 20,
"frames": 8
},
"shake": { "triggerFrame": 14, "intensity": 8, "decay": 10 }
},
"storyboard": [
{
"frame": 0,
"description": "Scene opens. Camera zooms out from 1.15x, blurred. Background gradient fades in."
},
{
"frame": 2,
"description": "Ghost text 'INFINI' appears deep in Z0, very blurred, scrolling slowly right. Dot grid Z1 fades in at 4% opacity."
},
{
"frame": 4,
"description": "Hero image (nebula-orb) starts scale-rotate entrance from center. Size: 400×400px at x=340, y=450. Spring: stiffness 80, damping 18. Shadow enabled."
},
{
"frame": 8,
"description": "Setup text 'l'espace' fades in at y=980 (below hero). Inter 300, 32px, white, letterSpacing 0.08em. Slides up 30px over 6 frames."
},
{
"frame": 12,
"description": "Small decorative line draws from left (x=200) to right (x=880) at y=1020. Stroke white 0.5px, opacity 0.15. Duration: 8 frames."
},
{
"frame": 14,
"description": "ACCENT 'INFINI' explodes in at y=1060. Bebas Neue 130px, gold (#F5D442). Scale 2.2→1 spring (stiffness 220, damping 13). ChromaticAberration 0.7→0. Screen shake 8px decay over 10 frames. textShadow glow: '0 0 60px accent50, 0 0 120px accent25'."
},
{
"frame": 16,
"description": "ExpandingRing burst from accent word center (x=540, y=1060). Max radius 250px, 4 frames, opacity 0.3→0."
},
{
"frame": 20,
"description": "Support text 'sans fin' types in (Typewriter) at y=1140. Inter 300, 24px, subtle gray. Cursor color=accent."
},
{
"frame": 30,
"description": "Secondary visual: IconTarget at x=820, y=520, size=120px. Draw-on entrance over 10 frames. Sits next to hero image."
},
{
"frame": 72,
"description": "Exit begins. Camera accelerates right-up (350px, -200px) over 8 frames. Blur increases to 20px. All elements drift with parallax speeds."
}
],
"elements": [
{
"id": "hero-nebula",
"type": "AnimatedImage",
"layer": "Z2",
"position": { "x": 340, "y": 450 },
"size": { "width": 400, "height": 400 },
"entrance": "scale-rotate",
"entranceDelay": 4,
"exitFrame": 70,
"exitDuration": 8,
"props": { "shadow": true },
"motion": {
"during": "Gentle floating: translateY oscillates ±8px via sine(fl*0.04). Subtle rotation ±1° via noise2D."
}
},
{
"id": "setup-text",
"type": "text",
"content": "l'espace",
"role": "setup",
"position": { "x": "center", "y": 980 },
"style": { "fontFamily": "primary", "fontWeight": 300, "fontSize": 32, "color": "text", "letterSpacing": "0.08em" },
"reveal": "fade-slide",
"revealDelay": 8,
"motion": {
"enter": "Slides up 30px over 6 frames with eOut easing, opacity 0→1"
}
},
{
"id": "accent-text",
"type": "text",
"content": "INFINI",
"role": "accent",
"position": { "x": "center", "y": 1060 },
"style": { "fontFamily": "secondary", "fontWeight": 700, "fontSize": 130, "color": "accent" },
"reveal": "scale-explode",
"revealDelay": 14,
"motion": {
"enter": "Scale 2.2→1 spring (220/13/1.1). Blur 12→0. ChromaticAberration 0.7→0. Color flash white→accent at frame 16. textShadow glow activates at flash."
},
"impact": {
"screenShake": { "intensity": 8, "decay": 10 },
"expandingRing": { "delay": 2, "maxRadius": 250 },
"chromaticAberration": { "peak": 0.7, "decay": "0.12→0.4→0" }
}
},
{
"id": "support-text",
"type": "text",
"content": "sans fin",
"role": "support",
"position": { "x": "center", "y": 1140 },
"style": { "fontFamily": "primary", "fontWeight": 300, "fontSize": 24, "color": "subtle" },
"reveal": "Typewriter",
"revealDelay": 20
},
{
"id": "decorative-line",
"type": "svg-line",
"layer": "Z2",
"from": { "x": 200, "y": 1020 },
"to": { "x": 880, "y": 1020 },
"style": { "stroke": "text", "strokeWidth": 0.5, "opacity": 0.15 },
"drawDelay": 12,
"drawDuration": 8
},
{
"id": "icon-target",
"type": "IconTarget",
"layer": "Z2",
"position": { "x": 820, "y": 520 },
"size": 120,
"entrance": "draw-on",
"entranceDelay": 30,
"exitFrame": 70
}
],
"decorative": {
"ghost": { "text": "INFINI", "fontSize": 400, "x": -80, "y": 200, "rotation": -4 },
"grid": { "type": "standard", "opacity": 0.04 },
"dots": { "count": 18, "speed": 0.3 },
"bokeh": { "count": 6 }
},
"postProcess": ["grain(0.04)", "vignette(0.7)", "edgeBlur", "lightLeak(accent)", "scanLineFlash"]
}
]
}
storyboard EST OBLIGATOIREC'est la timeline narrative de la scène. Il décrit frame par frame ce qui se passe visuellement. C'est ce qui transforme un inventaire d'éléments en une chorégraphie.
Règles du storyboard :
motionLe champ motion décrit comment l'élément bouge, pas juste où il est. Trois phases :
"motion": {
"enter": "Scale 0.5→1 over 8 frames, spring stiffness 80 damping 18. Comes from x=1200 (right edge), slides to x=540. Rotation -15°→0°.",
"during": "Gentle sine oscillation: y ±8px at 0.04 speed. Subtle rotation ±1° via noise2D. Shadow pulse via sine.",
"exit": "Scales down 1→0.8 over 6 frames. Opacity 1→0. Slides left 100px."
}
BAD (vague) :
"motion": { "enter": "appears", "during": "floats" }
GOOD (précis) :
"motion": {
"enter": "Scale-rotate from 0.3x with 15° CCW rotation. Spring: stiffness 80, damping 18, mass 1.2. Starts at frame START+4. Takes ~12 frames to settle.",
"during": "translateY: sine(fl * 0.04) * 8px. Rotation: noise2D('hero-rot', fl*0.01, 0) * 1.5°. Shadow opacity pulses: 0.3 + sin(fl*0.08)*0.1.",
"exit": "Begins at frame END-10. Scale 1→0.85 over 8 frames (eAccel). Opacity 1→0 clamped. Blur 0→6px."
}
Si le template a une référence dans references/pinterest/analyses/, LIS-LA et respecte :
Personnage plein pied : height ≥ 1056px (55% de 1920)
Hero centré (image/shape) : width ≥ 594px (55% de 1080)
Numbered cards : 180-270px
DataCard : width ≥ 756px (70% de 1080)
Globe/cercle : diamètre ≥ 400px
Photo plein cadre : couvre 40-80% de la frame
❌ "position": { "x": "center", "y": "middle" }
✅ "position": { "x": 340, "y": 450 } avec "size": { "width": 400, "height": 400 }
Le centre du cadre est (540, 960). Pour centrer un élément de 400px :
x = 540 - 200 = 340y = 960 - 200 = 760 (mais souvent plus haut pour laisser le texte en bas)┌─ 1080 ──────────────────────────────────┐
│ │ y=0
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ y=120 (safe top)
│ │
│ ┌──────────────────┐ │ y=300-500
│ │ HERO VISUAL │ │ (hero starts here)
│ │ 400-600px │ │
│ │ │ │
│ └──────────────────┘ │ y=700-900
│ │
│ setup text 32px │ y=950-1000
│ ───────────────── │ y=1020 (line)
│ ACCENT 120-140px │ y=1050-1100
│ support 24px │ y=1150-1200
│ │
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ y=1800 (safe bottom)
│ │ y=1920
└──────────────────────────────────────────┘
| Axe | S1 | S2 | S3 | Règle |
|---|---|---|---|---|
| Text reveal | CharStagger | SplitReveal | TextScramble | AUCUN doublon |
| Hero visuel | image 3D | SVG globe | image photo | Pas 2x le même type |
| Hero entrance | scale-rotate | arc-entrance | mask-circle | Pas 2x la même |
| Layout | centered | split-right | fullscreen | Alterner |
| Background | dark | light | dark | Alterner |
| Camera enter | zoom-out | swing-right | drop-bottom | Varier direction |
| Camera exit | right-up | left-down | fade | Varier direction |
Avant de créer, lis src/scenarios/ pour tout scénario du même sujet/préfixe. Remplis :
V1 utilise: layout=[centered,split,centered] camera=[zoom-out,swing,drop]
reveals=[CharStagger,TextScramble,SplitReveal] images=[brain,desk,man]
→ V2 DOIT utiliser des choix DIFFÉRENTS sur au moins 5/7 axes
continuity au top level :"continuity": {
"threadElement": {
"id": "growing-circle",
"description": "Un cercle qui commence petit en S1 (80px), grossit en S2 (300px), explose en particules en S3",
"scenes": ["S1", "S2", "S3"],
"evolution": {
"S1": { "type": "circle", "size": 80, "position": {"x": 540, "y": 960}, "color": "accent" },
"S2": { "type": "circle", "size": 300, "position": {"x": 540, "y": 600}, "color": "accent", "addRings": true },
"S3": { "type": "particles", "count": 30, "origin": {"x": 540, "y": 600}, "color": "accent" }
}
},
"transformations": [
{ "frame": 70, "from": "small-circle", "to": "medium-circle", "type": "scale-morph", "duration": 8 },
{ "frame": 142, "from": "medium-circle", "to": "particle-burst", "type": "explode", "duration": 6 }
],
"interactions": [
{ "frame": 14, "trigger": "accent-text-lands", "effect": "circle-shakes", "description": "Quand ÉCHOUENT atterrit, le cercle vibre 4 frames" },
{ "frame": 26, "trigger": "image-reaches-center", "effect": "bars-start-growing", "description": "Quand l'image atteint y=500, les barres commencent à pousser" },
{ "frame": 50, "trigger": "circle-reaches-300px", "effect": "text-gets-pushed-down", "description": "Le cercle grandissant pousse le texte vers le bas de 50px" }
],
"sceneTransitions": [
{ "from": "S1", "to": "S2", "type": "morph-through", "description": "Le cercle de S1 grandit et DEVIENT le fond de S2. Le bg shift graduel." },
{ "from": "S2", "to": "S3", "type": "explosion", "description": "Le cercle de S2 explose, les fragments deviennent les particules de S3." }
]
}
| Fil rouge | Description | Exemple |
|---|---|---|
| Objet qui grandit | Un shape qui évolue frame par frame | Cercle 80→300→explosion |
| Personnage qui agit | Une silhouette qui change de pose | Assis → debout → marche |
| Graphique qui se construit | Data qui s'accumule scène après scène | 1 barre → 3 barres → courbe complète |
| Compteur qui monte | Un chiffre qui progresse | 0% → 50% → 100% |
| Path qui se dessine | Une trajectoire continue | Ligne qui traverse les 3 scènes |
| Élément qui se fragmente | Un objet qui se décompose | Objet entier → fissuré → explosé |
| Type | Mécanisme | Implémentation |
|---|---|---|
| Push | A déplace B | Position de B = f(progress de A) |
| Squash | A écrase B | Scale Y de B diminue quand A arrive |
| Attract | A attire B | Position de B interpole vers A |
| Explode | A disperse B[] | Positions de B[] = rayon croissant depuis A |
| Morph | A devient B | borderRadius/path/color interpolés |
| Trigger | A déclenche B | B commence quand A atteint un seuil |
| React | A secoue quand B impacte | Shake/pulse de A quand B land |
AVANT de finaliser le JSON, lire la section "Elements & Assets" du template .md.
Chaque template a des styles visuels signature (pas des objets fixes — des TYPES visuels).
"signatureElements": {
"required": ["globe-lumineux", "silhouette-mystérieuse"],
"optional": ["coins-metallic", "news-card", "chart-card"],
"present": {
"S1": ["globe-lumineux", "silhouette-mystérieuse"],
"S2": ["chart-card"],
"S3": ["globe-lumineux"]
}
}
Règles :
Le template dit "globe lumineux" ? Si la transcription parle de lumière → globe = source de lumière. Si elle parle de réseau → globe = réseau mondial. Le STYLE reste, le SENS s'adapte.
Avant de valider le JSON, vérifier la distribution verticale de CHAQUE scène.
"verticalAudit": {
"Q1_0-480": ["ghost-text", "header-deco"],
"Q2_480-960": ["hero-globe-600px", "silhouette"],
"Q3_960-1440": ["accent-text", "support-text", "data-viz"],
"Q4_1440-1920": ["light-rain", "micro-label", "separator-line"]
}
Règles :
Chaque scène DOIT spécifier son layout type dans le JSON :
"layoutType": "center-dominant|split-horizontal|split-vertical|text-hero-stacked|hero-background|diagonal"
Règle : aucun layoutType ne peut être répété dans une animation 3 scènes.
center-dominant → S2: split-horizontal → S3: text-hero-stacked ✓center-dominant → S2: center-dominant → S3: center-dominant ✗ REJETÉAvant de lister les postProcess, lire la section "Effects Stack" / "Do not use" du template .md.
"postProcess": ["grain(0.04)", "vignette(0.7)", "edgeBlur"],
"forbiddenEffects": ["LightLeak", "ChromaticAberration", "ScanLineFlash"],
"accentTreatment": "gold-glow-scale"
Règles :
forbiddenEffects"accentTreatment": "gold-glow-scale" (textShadow multi-layer + scale explosion)forbiddenEffects — NE JAMAIS importer/utiliser ces effetsstoryboard de 8-12 entréesmotion avec enter/during/exit détaillécontinuity.threadElement défini — 1 élément traverse ≥2 scènescontinuity.transformations — ≥2 morphs dans l'animationcontinuity.interactions — ≥3 interactions causales (A→B)signatureElements — ≥2 éléments signature du template, dans ≥2 scènesverticalAudit — ≥3 quarts sur 4 occupés par scène, aucune zone >400px videlayoutType — 3 layouts DIFFÉRENTS pour 3 scènesforbiddenEffects — effets interdits du template listés✓ Storyboard "$ARGUMENTS[0]" créé : src/scenarios/$ARGUMENTS[0].json
Résumé chorégraphie :
S1 (0-80): [hero] nebula 400px scale-rotate → [texte] "INFINI" scale-explode f14 → [icon] target draw-on f30
S2 (76-156): [hero] telescope arc-entrance → [texte] "ÉTOILES" SplitReveal f18 → [ring] expanding f22
S3 (152-240): [hero] planet mask-circle → [texte] "MONDES" Typewriter f20 → [bars] stagger f24
Divergence ✓ : reveals=[scale-explode, SplitReveal, Typewriter] entrances=[scale-rotate, arc, mask-circle]
→ /motion-animate $ARGUMENTS[0] NomDuComposant