This file provides guidance to Claude Code when working with this repository. See .claude/skills/ for detailed instructions on generating rustmotion scenarios.
Tout JSON de scénario généré doit être validé avec rustmotion validate avant d'être présenté à l'utilisateur. Le validateur fait deux passes : schema et geometry (détection de débordement viewport). Les deux doivent passer.
Aucun contenu textuel ne doit dépasser du device. Trois propriétés contrôlent ce comportement :
style.wrap(defaulttrue) surtext: laisse-le àtruepour wrapper sur la largeur du parent.wrap: falseest légitime uniquement si unmax-widthfinit +font-sizeraisonnable garantissent que la ligne tient. Le validateur émetunwrappable_text_overflowsinon.auto_scroll(defaulttrue) surcodeblocketterminal: quand le contenu dépasse la hauteur dusize, le moteur scrolle (clip + translate) sans réduire lafont-size.auto_scroll: false→auto_scroll_disabled_overflow.style.overflow(defaultvisible) sur les conteneurs : sémantique CSS.hiddenclippe au bord du parent. Le validateur ne se plaint que si le contenu sort du viewport, pas d'un parentvisible.
marquee et cursor sont exemptés (leur rôle est de bleed).
CLI :
rustmotion validate -f file.json— schema + geometry--fix— auto-fix sûr (wrap: true,auto_scroll: true)--report r.json— rapport JSON--strict-anim— vérification frame par frame--lenient— warnings au lieu d'errors
- ffmpeg est auto-détecté et utilisé par défaut (10-bit H.264, meilleure qualité sur les gradients sombres)
- Sans ffmpeg, le fallback openh264 intégré encode en 8-bit
- Pour les vidéos avec des gradients sombres, recommander
--codec prorespour une qualité maximale
text, shape, image, icon, svg, video, gif, caption, rich_text, gradient_text
card, flex, grid, div (alias de container), container, positioned
div= layout pur sans décoration visuelle (HTML<div>).card= même chose mais avec fond/border-radius/ombre attendus.
chart— 12 types: bar, line, pie, donut, horizontal_bar, area, stacked_bar, radar, scatter, radial_bar, funnel, waterfall. Supporte axes/grilles/labels.gauge— jauge semi-circulaire pour KPIssparkline— mini-chart inline sans axesstat— carte KPI composite (valeur + label + tendance + sparkline)heatmap— grille colorée type GitHub contributionstreemap— rectangles proportionnels (slice-and-dice)dot_map— carte mondiale en dot-pattern avec points de données, pulse, lat/lngprogress— barre linéaire ou circulairecounter— compteur animé (standalone uniquement, pas dans les cards)table— tableau avec column_widths, column_align, cell_padding, show_borders
badge— pill avec icon, dot indicator, pulse animation, count badgeavatar/avatar_group— avatar circulaire / groupe empilé avec "+N"switch— toggle animé on/off avec toggle_atslider— curseur horizontal animé avec animate_to/animate_atrating— étoiles avec remplissage partiel animékbd— touche clavier visuelle (effet 3D)tooltip— label flottant avec flèche directionnellenotification— toast fade-in/out avec stack push (info/success/warning/error)pill_nav— tabs avec pill indicator animé entre ongletslist— liste bullet/numbered/checklist avec icônesstepper— étapes numérotées connectées avec progression animéecomparison— vue avant/après avec divider animécountdown— timer digital flip-clock stylemarquee— texte défilant continuskeleton— placeholder de chargement avec shimmer (rectangle/circle/text)tag_cloud— nuage de mots avec tailles pondéréescallout— bulle avec flèchedivider— séparateur visuel
codeblock— code syntax-highlighted avec reveal, diff mode (diff: true), state transitionsterminal— terminal avec chrome macOS, reveal typewriter + curseur clignotant
arrow, connector, timeline, line
mockup, lottie, cursor, particle, qrcode
Le moteur utilise un pipeline box_tree → layout_pass → paint_pass inspiré des navigateurs web :
- box_tree (
box_builder.rs) — construit un arbre deBoxNode { css: CssStyle, children, intrinsic }depuis les composants JSON résolus - layout_pass (
engine/layout_pass.rs) — orchestre taffy pour calculer lesBoxLayout { x, y, width, height }de chaque nœud. Les feuilles avec unIntrinsicMeasure(texte, image, codeblock) sont mesurées via unemeasure_fn. - paint_pass (
engine/paint_pass.rs) — descend l'arbre, applique transform/opacity, peint les décorations (background, border, shadow), délègue auPainterdu composant pour le contenu.
Chaque composant implémente le trait Painter :
pub trait Painter {
fn paint_content(&self, canvas: &Canvas, layout: &BoxLayout, props: &AnimatedProperties, ctx: &PaintCtx);
fn intrinsic_size(&self, available: AvailableSize, ctx: &MeasureCtx) -> Option<(f32, f32)> { None }
}PaintCtx contient : time, scene_duration, fps, frame_index, video_width, video_height, stagger_offset.
crates/
├── rustmotion-core/src/
│ ├── css/ # Modèle CSS
│ │ ├── style.rs # CssStyle (propriétés CSS kebab-case)
│ │ ├── units.rs # Length, LengthPercentage (px, %, em, rem, vw, vh)
│ │ ├── cascade.rs # Héritage color/font-* parent → enfant
│ │ ├── taffy_bridge.rs # CssStyle → taffy::Style
│ │ └── animation.rs # Résolution des animations → override CssStyle
│ ├── engine/
│ │ ├── box_tree.rs # BoxNode, BoxKind, IntrinsicMeasure
│ │ ├── layout_pass.rs # Orchestration taffy, BoxLayout résultant
│ │ ├── paint_pass.rs # Walk top-down, décorations, dispatch Painter
│ │ ├── animator.rs # Résolution animations, easing, spring solver
│ │ ├── transition.rs # Transitions entre scènes
│ │ ├── renderer/ # Primitives Skia (colors, fonts, shapes, text)
│ │ └── text/cosmic.rs # Bridge cosmic-text ↔ Skia (mesure + glyphs)
│ ├── schema/ # Modèles de données JSON
│ │ ├── scenario.rs # Scenario, ResolvedScenario, View, Scene, VideoConfig
│ │ ├── style.rs # Specialized types (CardBorder, CardShadow, Fill, etc.)
│ │ ├── background.rs # AnimatedBackground, BackgroundPreset
│ │ ├── animation.rs # EasingType, AnimationPreset, PresetConfig
│ │ ├── codeblock_types.rs # CodeblockChrome, CodeblockState
│ │ └── video.rs # AnimationEffect, Size, ShapeType, Stroke
│ └── traits/
│ ├── painter.rs # Painter trait + PaintCtx + AvailableSize + MeasureCtx
│ ├── animatable.rs # Animatable trait
│ ├── timed.rs # Timed trait + TimingConfig
│ └── styled.rs # Styled trait
│
├── rustmotion-components/src/
│ ├── lib.rs # Enum Component + dispatch (as_painter, as_animatable, etc.)
│ ├── box_builder.rs # build_scene() → BoxBuilderResult, component_size()
│ ├── intrinsic.rs # TextIntrinsic, BadgeIntrinsic, CounterIntrinsic, etc.
│ ├── legacy_dispatch.rs # LegacyPaintDispatcher (bridge NodeId → Painter)
│ ├── chart/ # 10 fichiers (mod + bar/line/pie/radar/scatter/radial/funnel/waterfall/axes)
│ └── *.rs # Un fichier par composant (impl Painter)
│
└── rustmotion-cli/src/
└── commands/ # validate, render, schema, info
- Créer
crates/rustmotion-components/src/mon_composant.rsavec struct serde +impl Painter(paint_content) - Ajouter
rustmotion_core::impl_traits!(MonComposant { Animatable => animation, Timed => timing, Styled => style }); - Ajouter le variant dans l'enum
Componentdanslib.rs - Ajouter les match arms dans les méthodes de dispatch (
as_painter,as_animatable,as_timed,as_styled) - Ajouter
pub mod mon_composant;etpub use mon_composant::MonComposant;danslib.rs - Ajouter un arm dans
box_builder.rs::component_size()si le composant a une taille fixe - Si le composant mesure son propre contenu : ajouter
XxxIntrinsicdansintrinsic.rs
cargo test # 31 tests (layout + variables + smoke)
cargo check # Vérification compilation
rustmotion validate file.json # Validation scénario