Skip to main content

explainer/
explainer.rs

1use motion_canvas_rs::prelude::*;
2use std::time::Duration;
3
4// ── Design Tokens ──────────────────────────────────────────────────────────
5const FONT: &str = "JetBrains Mono";
6const BG: Color = Color::rgb8(0x0e, 0x0e, 0x12);
7const WHITE: Color = Color::rgb8(0xf0, 0xf0, 0xf0);
8const DIM: Color = Color::rgb8(0x55, 0x55, 0x66);
9const ACCENT: Color = Color::rgb8(0x68, 0xab, 0xdf);
10const RED: Color = Color::rgb8(0xe1, 0x32, 0x38);
11const YELLOW: Color = Color::rgb8(0xe6, 0xa7, 0x00);
12const GREEN: Color = Color::rgb8(0x25, 0xc2, 0x81);
13const TEAL: Color = Color::rgb8(0x20, 0xb2, 0xaa);
14
15const CANVAS_W: u32 = 1280;
16const CANVAS_H: u32 = 720;
17const LEFT: f32 = 40.0;
18
19fn ms(n: u64) -> Duration {
20    Duration::from_millis(n)
21}
22fn secs(n: u64) -> Duration {
23    Duration::from_secs(n)
24}
25
26// ── Text Helpers ───────────────────────────────────────────────────────────
27fn title(text: &str, y: f32) -> TextNode {
28    TextNode::default()
29        .with_anchor(Vec2::new(-1.0, -1.0))
30        .with_position(Vec2::new(LEFT, y))
31        .with_text(text)
32        .with_font_size(36.0)
33        .with_fill(ACCENT)
34        .with_font(FONT)
35        .with_opacity(0.0)
36}
37fn h2(text: &str, y: f32) -> TextNode {
38    TextNode::default()
39        .with_anchor(Vec2::new(-1.0, -1.0))
40        .with_position(Vec2::new(LEFT, y))
41        .with_text(text)
42        .with_font_size(22.0)
43        .with_fill(WHITE)
44        .with_font(FONT)
45        .with_opacity(0.0)
46}
47fn body(text: &str, y: f32) -> TextNode {
48    TextNode::default()
49        .with_anchor(Vec2::new(-1.0, -1.0))
50        .with_position(Vec2::new(LEFT, y))
51        .with_text(text)
52        .with_font_size(17.0)
53        .with_fill(Color::rgb8(0xcc, 0xcc, 0xdd))
54        .with_font(FONT)
55        .with_opacity(0.0)
56}
57fn dim(text: &str, x: f32, y: f32) -> TextNode {
58    TextNode::default()
59        .with_anchor(Vec2::new(-1.0, -1.0))
60        .with_position(Vec2::new(x, y))
61        .with_text(text)
62        .with_font_size(13.0)
63        .with_fill(DIM)
64        .with_font(FONT)
65        .with_opacity(0.0)
66}
67fn note(text: &str, y: f32) -> TextNode {
68    TextNode::default()
69        .with_anchor(Vec2::new(-1.0, -1.0))
70        .with_position(Vec2::new(LEFT + 20.0, y))
71        .with_text(text)
72        .with_font_size(14.0)
73        .with_fill(YELLOW)
74        .with_font(FONT)
75        .with_opacity(0.0)
76}
77fn code_block(code: &str, y: f32) -> CodeNode {
78    CodeNode::default()
79        .with_position(Vec2::new(LEFT + 20.0, y))
80        .with_code(code)
81        .with_language("rust")
82        .with_font(FONT)
83        .with_font_size(14.0)
84        .with_opacity(0.0)
85}
86fn hline(y: f32) -> Line {
87    Line::default()
88        .with_start(Vec2::new(LEFT, y))
89        .with_end(Vec2::new(LEFT, y))
90        .with_stroke(Color::rgba8(255, 255, 255, 25), 1.0)
91}
92// Shorthand for show/hide
93fn show(n: &impl HasOpacity, d: Duration) -> Box<dyn Animation> {
94    n.opacity_signal()
95        .to(1.0, d)
96        .ease(easings::cubic_out)
97        .into()
98}
99fn hide(n: &impl HasOpacity, d: Duration) -> Box<dyn Animation> {
100    n.opacity_signal().to(0.0, d).ease(easings::cubic_in).into()
101}
102
103// Trait to unify opacity access across different node types
104trait HasOpacity {
105    fn opacity_signal(&self) -> Signal<f32>;
106}
107impl HasOpacity for TextNode {
108    fn opacity_signal(&self) -> Signal<f32> {
109        self.opacity.clone()
110    }
111}
112impl HasOpacity for Circle {
113    fn opacity_signal(&self) -> Signal<f32> {
114        self.opacity.clone()
115    }
116}
117impl HasOpacity for Rect {
118    fn opacity_signal(&self) -> Signal<f32> {
119        self.opacity.clone()
120    }
121}
122impl HasOpacity for Line {
123    fn opacity_signal(&self) -> Signal<f32> {
124        self.opacity.clone()
125    }
126}
127impl HasOpacity for Polygon {
128    fn opacity_signal(&self) -> Signal<f32> {
129        self.opacity.clone()
130    }
131}
132impl HasOpacity for CodeNode {
133    fn opacity_signal(&self) -> Signal<f32> {
134        self.opacity.clone()
135    }
136}
137impl HasOpacity for GroupNode {
138    fn opacity_signal(&self) -> Signal<f32> {
139        self.opacity.clone()
140    }
141}
142impl HasOpacity for ImageNode {
143    fn opacity_signal(&self) -> Signal<f32> {
144        self.opacity.clone()
145    }
146}
147
148fn main() {
149    let mut project = Project::default()
150        .with_dimensions(CANVAS_W, CANVAS_H)
151        .with_fps(60)
152        .with_title("Explainer")
153        .with_background(BG)
154        .close_on_finish();
155
156    // =====================================================================
157    //  S1: TITLE CARD
158    // =====================================================================
159    let s1_line = hline(100.0);
160    let s1_title = TextNode::default()
161        .with_anchor(Vec2::new(-1.0, -1.0))
162        .with_position(Vec2::new(LEFT, 120.0))
163        .with_text("motion-canvas-rs")
164        .with_font_size(52.0)
165        .with_fill(ACCENT)
166        .with_font(FONT)
167        .with_opacity(0.0);
168    let s1_sub = h2("A GPU-Accelerated Vector Animation Engine", 185.0);
169    let s1_built = body(
170        "Built on Vello + Typst  —  Inspired by Motion Canvas",
171        220.0,
172    );
173    let s1_desc = body(
174        "This animation will teach you how the library works,",
175        280.0,
176    );
177    let s1_desc2 = body(
178        "from struct definitions to the GPU rendering pipeline.",
179        305.0,
180    );
181
182    let s1_logo = ImageNode::default()
183        .with_position(Vec2::new(950.0, 420.0))
184        .with_path("examples/images/motion-canvas-rs.svg")
185        .with_scale(0.3)
186        .with_opacity(0.0);
187
188    for n in [&s1_title, &s1_sub, &s1_built, &s1_desc, &s1_desc2] {
189        project.scene.add(Box::new(n.clone()));
190    }
191    project.scene.add(Box::new(s1_line.clone()));
192    project.scene.add(Box::new(s1_logo.clone()));
193
194    // =====================================================================
195    //  S2: THE 5 STEPS
196    // =====================================================================
197    let s2_h = title("Every program follows 5 steps", 50.0);
198    let steps = [
199        "1. Create a Project       — your canvas settings",
200        "2. Create Nodes           — shapes, text, images",
201        "3. Add Nodes to Scene     — what gets drawn",
202        "4. Animate the Timeline   — how things move",
203        "5. Show or Export         — live window or video",
204    ];
205    let s2_texts: Vec<TextNode> = steps
206        .iter()
207        .enumerate()
208        .map(|(i, s)| body(s, 120.0 + i as f32 * 35.0))
209        .collect();
210
211    project.scene.add(Box::new(s2_h.clone()));
212    for t in &s2_texts {
213        project.scene.add(Box::new(t.clone()));
214    }
215
216    // =====================================================================
217    //  S3: WHAT IS A STRUCT?  +  The Project struct
218    // =====================================================================
219    let s3_h = title("What is a 'struct'?", 50.0);
220    let s3_explain = h2(
221        "A struct is a container that groups related data together.",
222        95.0,
223    );
224    let s3_analogy = body(
225        "Think of it like a class in Python/JS, but it only holds data.",
226        130.0,
227    );
228
229    let s3_code = code_block(
230        "pub struct Project {
231    pub width: u32,          // Canvas width in pixels
232    pub height: u32,         // Canvas height in pixels
233    pub fps: u32,            // Frames per second
234    pub title: String,       // Window title
235    pub scene: BaseScene,    // Holds nodes + timelines
236    pub background_color: Color,
237    pub close_on_finish: bool,
238}",
239        175.0,
240    );
241
242    let s3_note = note(
243        "^ This is the actual Project struct from the library.",
244        420.0,
245    );
246    let s3_note2 = body(
247        "'pub' means public — anyone can read/write these fields.",
248        455.0,
249    );
250    let s3_note3 = body(
251        "u32 = unsigned 32-bit integer,  String = text,  bool = true/false",
252        485.0,
253    );
254
255    for n in [
256        &s3_h,
257        &s3_explain,
258        &s3_analogy,
259        &s3_note,
260        &s3_note2,
261        &s3_note3,
262    ] {
263        project.scene.add(Box::new(n.clone()));
264    }
265    project.scene.add(Box::new(s3_code.clone()));
266
267    // =====================================================================
268    //  S4: WHAT IS impl?  +  Builder pattern
269    // =====================================================================
270    let s4_h = title("What is 'impl'?", 50.0);
271    let s4_explain = h2("impl adds methods (functions) to a struct.", 95.0);
272    let s4_analogy = body(
273        "Like adding methods to a class. Separated from the data.",
274        130.0,
275    );
276
277    let s4_code = code_block(
278        "impl Project {
279    pub fn with_fps(mut self, fps: u32) -> Self {
280        self.fps = fps;   // set the value
281        self              // return yourself (builder pattern)
282    }
283    pub fn with_title(mut self, title: &str) -> Self {
284        self.title = title.to_string();
285        self
286    }
287}",
288        175.0,
289    );
290
291    let s4_usage = body("Usage — chain calls to configure:", 430.0);
292    let s4_usage_code = code_block(
293        "let project = Project::default()
294    .with_fps(60)
295    .with_title(\"My Animation\")
296    .with_dimensions(800, 600)
297    .close_on_finish();",
298        460.0,
299    );
300
301    let s4_note = note(
302        "Each .with_*() returns 'self', so you can chain them.",
303        590.0,
304    );
305
306    for n in [&s4_h, &s4_explain, &s4_analogy, &s4_usage, &s4_note] {
307        project.scene.add(Box::new(n.clone()));
308    }
309    project.scene.add(Box::new(s4_code.clone()));
310    project.scene.add(Box::new(s4_usage_code.clone()));
311
312    // =====================================================================
313    //  S5: WHAT IS A TRAIT?  +  The Node trait
314    // =====================================================================
315    let s5_h = title("What is a 'trait'?", 50.0);
316    let s5_explain = h2(
317        "A trait is a contract — like an interface in Java/TypeScript.",
318        95.0,
319    );
320    let s5_analogy = body(
321        "Any type that implements a trait promises to provide those methods.",
322        130.0,
323    );
324
325    let s5_code = code_block(
326        "pub trait Node: Send + Sync + 'static {
327    fn render(&self, scene: &mut Scene,
328              parent_transform: Affine,
329              parent_opacity: f32);
330    fn update(&mut self, dt: Duration);
331    fn state_hash(&self) -> u64;
332    fn clone_node(&self) -> Box<dyn Node>;
333}",
334        175.0,
335    );
336
337    let s5_r = note(
338        "render()     — draw yourself using current signal values",
339        410.0,
340    );
341    let s5_u = note(
342        "update(dt)   — called every frame (for per-frame logic)",
343        435.0,
344    );
345    let s5_s = note(
346        "state_hash() — returns a number that changes when you change",
347        460.0,
348    );
349    let s5_c = note("clone_node() — make a deep copy of yourself", 485.0);
350    let s5_every = body(
351        "Circle, Rect, Line, TextNode, Polygon all implement Node.",
352        530.0,
353    );
354
355    for n in [
356        &s5_h,
357        &s5_explain,
358        &s5_analogy,
359        &s5_r,
360        &s5_u,
361        &s5_s,
362        &s5_c,
363        &s5_every,
364    ] {
365        project.scene.add(Box::new(n.clone()));
366    }
367    project.scene.add(Box::new(s5_code.clone()));
368
369    // =====================================================================
370    //  S6: NODE GALLERY  (visual demo)
371    // =====================================================================
372    let s6_h = title("The Built-in Nodes", 50.0);
373    let s6_sub = body(
374        "Each node uses the builder pattern and stores properties as Signals.",
375        90.0,
376    );
377
378    let demo_c = Circle::default()
379        .with_position(Vec2::new(120.0, 230.0))
380        .with_radius(40.0)
381        .with_fill(RED)
382        .with_opacity(0.0);
383    let demo_r = Rect::default()
384        .with_position(Vec2::new(293.0, 230.0))
385        .with_size(Vec2::new(80.0, 80.0))
386        .with_fill(ACCENT)
387        .with_radius(8.0)
388        .with_opacity(0.0);
389    let demo_l = Line::default()
390        .with_start(Vec2::new(420.0, 200.0))
391        .with_end(Vec2::new(510.0, 270.0))
392        .with_stroke(WHITE, 3.0)
393        .with_opacity(0.0);
394    let demo_p = Polygon::regular(5, 40.0)
395        .with_position(Vec2::new(606.0, 230.0))
396        .with_fill(YELLOW)
397        .with_opacity(0.0);
398    let demo_t = TextNode::default()
399        .with_position(Vec2::new(765.0, 230.0))
400        .with_text("Abc")
401        .with_font_size(36.0)
402        .with_fill(GREEN)
403        .with_font(FONT)
404        .with_opacity(0.0);
405
406    let lc = dim("Circle", 95.0, 285.0);
407    let lr = dim("Rect", 280.0, 285.0);
408    let ll = dim("Line", 445.0, 285.0);
409    let lp = dim("Polygon", 580.0, 285.0);
410    let lt = dim("TextNode", 735.0, 285.0);
411
412    let s6_box_h = h2("Why Box<dyn Node>?", 340.0);
413    let s6_box1 = body("The scene stores different node types in one list:", 375.0);
414    let s6_box_code = code_block(
415        "pub struct BaseScene {
416    pub nodes: Vec<Box<dyn Node>>,  // a list of \"any Node\"
417}
418// 'Box' = heap-allocated,  'dyn Node' = any type implementing Node
419// Like List<INode> in Java or Array<Node> in TypeScript
420project.scene.add(Box::new(circle));  // wrap + add",
421        405.0,
422    );
423
424    for n in [&s6_h, &s6_sub, &lc, &lr, &ll, &lp, &lt, &s6_box_h, &s6_box1] {
425        project.scene.add(Box::new(n.clone()));
426    }
427    project.scene.add(Box::new(demo_c.clone()));
428    project.scene.add(Box::new(demo_r.clone()));
429    project.scene.add(Box::new(demo_l.clone()));
430    project.scene.add(Box::new(demo_p.clone()));
431    project.scene.add(Box::new(demo_t.clone()));
432    project.scene.add(Box::new(s6_box_code.clone()));
433
434    // =====================================================================
435    //  S7: SIGNALS — The Reactive Core
436    // =====================================================================
437    let s7_h = title("Signals — The Reactive Core", 50.0);
438    let s7_sub = h2("Every animatable property is a Signal<T>.", 95.0);
439
440    let s7_code = code_block(
441        "pub struct Signal<T> {
442    pub data: Arc<Mutex<SignalData<T>>>,
443}
444pub struct SignalData<T> {
445    pub value: T,  // the actual value (f32, Vec2, Color...)
446}",
447        140.0,
448    );
449
450    let s7_arc = note("Arc = shared pointer. Multiple owners, same data.", 310.0);
451    let s7_mutex = note(
452        "Mutex = lock. Only one thread reads/writes at a time.",
453        335.0,
454    );
455    let s7_why = body(
456        "Why? A node and its animation both need the same property:",
457        380.0,
458    );
459
460    let s7_diagram_code = code_block(
461        "let circle = Circle::default().with_radius(50.0);
462// circle.radius is a Signal<f32>
463
464circle.radius.to(100.0, Duration::from_secs(1));
465// Creates a SignalTween with a CLONE of circle.radius
466// Both point to the SAME underlying value (via Arc)
467
468// The animation WRITES new values each frame
469// The node READS them when rendering",
470        420.0,
471    );
472
473    // Live demo circle
474    let sig_demo = Circle::default()
475        .with_position(Vec2::new(1000.0, 400.0))
476        .with_radius(50.0)
477        .with_fill(RED)
478        .with_stroke(Color::rgba8(255, 255, 255, 50), 2.0)
479        .with_opacity(0.0);
480    let sig_lbl = dim("Live Signal demo", 900.0, 150.0);
481
482    for n in [&s7_h, &s7_sub, &s7_arc, &s7_mutex, &s7_why, &sig_lbl] {
483        project.scene.add(Box::new(n.clone()));
484    }
485    project.scene.add(Box::new(s7_code.clone()));
486    project.scene.add(Box::new(s7_diagram_code.clone()));
487    project.scene.add(Box::new(sig_demo.clone()));
488
489    // =====================================================================
490    //  S8: SIGNAL TWEEN — How animations work per-frame
491    // =====================================================================
492    let s8_h = title("SignalTween — The Animation Engine", 50.0);
493    let s8_sub = body(
494        ".to() creates a SignalTween that interpolates over time:",
495        90.0,
496    );
497
498    let s8_code = code_block(
499        "pub struct SignalTween<T> {
500    data: Arc<Mutex<SignalData<T>>>,  // shared ref to signal
501    start_value: Option<T>,           // captured on FIRST update (lazy!)
502    target_value: Option<T>,          // where we're going
503    duration: Duration,               // how long
504    elapsed: Duration,                // how much time passed
505    easing: fn(f32) -> f32,           // curve function
506}",
507        125.0,
508    );
509
510    let s8_how = h2("Each frame update:", 340.0);
511    let s8_steps = [
512        "1. elapsed += dt",
513        "2. t_linear = elapsed / duration          (0.0 to 1.0)",
514        "3. t_eased  = easing(t_linear)            (curved)",
515        "4. value    = lerp(start, target, t)      (interpolate)",
516        "5. Write value into Signal                (node sees it)",
517        "6. If elapsed >= duration: finished!      (return leftover dt)",
518    ];
519    let s8_step_texts: Vec<TextNode> = s8_steps
520        .iter()
521        .enumerate()
522        .map(|(i, s)| body(s, 370.0 + i as f32 * 28.0))
523        .collect();
524
525    let s8_lazy = note(
526        "start_value is captured lazily — so chained tweens read the",
527        570.0,
528    );
529    let s8_lazy2 = note(
530        "correct value at their actual start time, not creation time.",
531        590.0,
532    );
533
534    // Progress bar
535    let prog_bg = Rect::default()
536        .with_position(Vec2::new(895.0, 150.0))
537        .with_size(Vec2::new(400.0, 16.0))
538        .with_fill(Color::rgba8(255, 255, 255, 15))
539        .with_radius(8.0)
540        .with_opacity(0.0);
541    let prog_fill = Rect::default()
542        .with_position(Vec2::new(695.0, 154.0))
543        .with_anchor(Vec2::new(-1.0, 0.5))
544        .with_size(Vec2::new(0.0, 16.0))
545        .with_fill(ACCENT)
546        .with_radius(8.0)
547        .with_opacity(0.0);
548    let plbl0 = dim("t=0", 700.0, 172.0);
549    let plbl1 = dim("t=1", 1070.0, 172.0);
550    let tween_ball = Circle::default()
551        .with_position(Vec2::new(900.0, 430.0))
552        .with_radius(30.0)
553        .with_fill(RED)
554        .with_opacity(0.0);
555    let tween_lbl = dim("radius animating: 30 -> 80", 760.0, 220.0);
556
557    for n in [
558        &s8_h, &s8_sub, &s8_how, &s8_lazy, &s8_lazy2, &plbl0, &plbl1, &tween_lbl,
559    ] {
560        project.scene.add(Box::new(n.clone()));
561    }
562    project.scene.add(Box::new(s8_code.clone()));
563    for t in &s8_step_texts {
564        project.scene.add(Box::new(t.clone()));
565    }
566    project.scene.add(Box::new(prog_bg.clone()));
567    project.scene.add(Box::new(prog_fill.clone()));
568    project.scene.add(Box::new(tween_ball.clone()));
569
570    // =====================================================================
571    //  S9: TWEENABLE + EASINGS
572    // =====================================================================
573    let s9_h = title("Tweenable — What Can Be Animated", 50.0);
574    let s9_code = code_block(
575        "pub trait Tweenable: Clone + Send + Sync {
576    fn interpolate(a: &Self, b: &Self, t: f32) -> Self;
577    fn state_hash(&self) -> u64;
578}
579// Implemented for: f32, Vec2, Color, String, Affine, Vec<Vec2>
580//   f32:    lerp(a, b, t) = a + (b-a)*t
581//   Vec2:   lerp x and y independently
582//   Color:  lerp R,G,B,A channels independently
583//   String: snap — returns 'a' until t>=1, then 'b'",
584        95.0,
585    );
586
587    let s9_easing_h = h2("Easing functions curve the linear t:", 310.0);
588    let s9_easing_desc = body("Same distance, same duration — different feel.", 340.0);
589
590    let enames = [
591        "linear",
592        "cubic_in_out",
593        "elastic_out",
594        "bounce_out",
595        "back_out",
596    ];
597    let ecolors = [WHITE, ACCENT, RED, YELLOW, GREEN];
598    let mut eballs: Vec<Circle> = Vec::new();
599    let mut elabels: Vec<TextNode> = Vec::new();
600    for (i, name) in enames.iter().enumerate() {
601        let y = 390.0 + i as f32 * 55.0;
602        let b = Circle::default()
603            .with_position(Vec2::new(250.0, y))
604            .with_radius(12.0)
605            .with_fill(ecolors[i])
606            .with_opacity(0.0);
607        let l = dim(name, LEFT, y - 5.0);
608        project.scene.add(Box::new(b.clone()));
609        project.scene.add(Box::new(l.clone()));
610        eballs.push(b);
611        elabels.push(l);
612    }
613    project.scene.add(Box::new(s9_h.clone()));
614    project.scene.add(Box::new(s9_code.clone()));
615    project.scene.add(Box::new(s9_easing_h.clone()));
616    project.scene.add(Box::new(s9_easing_desc.clone()));
617
618    // =====================================================================
619    //  S10: FLOW CONTROLS
620    // =====================================================================
621    let s10_h = title("Flow Controls — Composing Animations", 50.0);
622    let s10_sub = body(
623        "Individual tweens are simple. Power comes from composing them.",
624        90.0,
625    );
626
627    // chain
628    let s10_chain_h = h2("chain![ ] — one after another", 140.0);
629    let chain_d: Vec<Circle> = (0..3)
630        .map(|i| {
631            Circle::default()
632                .with_position(Vec2::new(LEFT + 30.0 + i as f32 * 60.0, 200.0))
633                .with_radius(18.0)
634                .with_fill([RED, ACCENT, YELLOW][i])
635                .with_opacity(0.0)
636        })
637        .collect();
638
639    // all
640    let s10_all_h = h2("all![ ] — all at the same time", 270.0);
641    let all_d: Vec<Circle> = (0..3)
642        .map(|i| {
643            Circle::default()
644                .with_position(Vec2::new(LEFT + 30.0 + i as f32 * 60.0, 330.0))
645                .with_radius(18.0)
646                .with_fill([RED, ACCENT, YELLOW][i])
647                .with_opacity(0.0)
648        })
649        .collect();
650
651    // sequence
652    let s10_seq_h = h2("sequence![ ] — staggered starts", 400.0);
653    let seq_d: Vec<Circle> = (0..3)
654        .map(|i| {
655            Circle::default()
656                .with_position(Vec2::new(LEFT + 30.0 + i as f32 * 60.0, 460.0))
657                .with_radius(18.0)
658                .with_fill([RED, ACCENT, YELLOW][i])
659                .with_opacity(0.0)
660        })
661        .collect();
662
663    let s10_code = code_block(
664        "chain![ a, b, c ]           // a then b then c
665all![ a, b, c ]             // a + b + c together
666sequence![ 200ms, a, b, c ] // staggered
667delay![ 500ms, a ]          // wait then play
668wait(1s)                    // pause
669any![ a, b ]                // race: first wins
670loop_anim![ a, 3 ]          // repeat 3 times",
671        510.0,
672    );
673
674    project.scene.add(Box::new(s10_h.clone()));
675    project.scene.add(Box::new(s10_sub.clone()));
676    project.scene.add(Box::new(s10_chain_h.clone()));
677    project.scene.add(Box::new(s10_all_h.clone()));
678    project.scene.add(Box::new(s10_seq_h.clone()));
679    project.scene.add(Box::new(s10_code.clone()));
680    for d in &chain_d {
681        project.scene.add(Box::new(d.clone()));
682    }
683    for d in &all_d {
684        project.scene.add(Box::new(d.clone()));
685    }
686    for d in &seq_d {
687        project.scene.add(Box::new(d.clone()));
688    }
689
690    // =====================================================================
691    //  S11: TIMELINE + RENDERING
692    // =====================================================================
693    let s11_h = title("The Timeline — Animation Queue", 50.0);
694    let s11_code = code_block(
695        "pub struct Timeline {
696    pub animations: Vec<Box<dyn Animation>>,
697}
698impl Timeline {
699    fn update(&mut self, mut dt: Duration) {
700        while !self.animations.is_empty() {
701            let (finished, leftover) = self.animations[0].update(dt);
702            if finished {
703                self.animations.remove(0); // pop front
704                dt = leftover;             // pass leftover to next!
705            } else { break; }
706        }
707    }
708}",
709        95.0,
710    );
711
712    let s11_leftover = note(
713        "leftover propagation: if A finishes mid-frame, the remaining",
714        390.0,
715    );
716    let s11_leftover2 = note(
717        "dt is immediately given to B. No 'lost frames' at transitions.",
718        415.0,
719    );
720
721    let s11_render_h = h2("Rendering Pipeline (per frame):", 470.0);
722    let s11_steps = [
723        "1. Timeline.update(dt)  =>  SignalTween writes to Signals",
724        "2. Node.render()        =>  reads signals, draws shapes",
725        "3. Vello GPU            =>  compiles scene => wgpu => pixels",
726        "4. state_hash()         =>  seahash for hashing scene state, skip if unchanged",
727    ];
728    let s11_render_texts: Vec<TextNode> = s11_steps
729        .iter()
730        .enumerate()
731        .map(|(i, s)| body(s, 505.0 + i as f32 * 28.0))
732        .collect();
733
734    project.scene.add(Box::new(s11_h.clone()));
735    project.scene.add(Box::new(s11_code.clone()));
736    for n in [&s11_leftover, &s11_leftover2, &s11_render_h] {
737        project.scene.add(Box::new(n.clone()));
738    }
739    for t in &s11_render_texts {
740        project.scene.add(Box::new(t.clone()));
741    }
742
743    // =====================================================================
744    //  S12: EVENT LOOP — Why an infinite loop?
745    // =====================================================================
746    let s12_h = title("Why an Infinite Loop? — The Event Loop", 50.0);
747    let s12_sub = body(
748        "GPU rendering requires a persistent event loop (winit + wgpu).",
749        90.0,
750    );
751    let s12_code = code_block(
752        "event_loop.run(|event, elwt| {
753    match event {
754        Resumed => {           // GPU surface ready
755            renderer.resume(&window);
756        }
757        AboutToWait => {       // run every frame
758            scene.update(dt);  // advance animations
759            let hash = scene.state_hash();
760            if hash != last_hash {    // dirty?
761                window.request_redraw();
762            }
763        }
764        RedrawRequested => {   // GPU draw call
765            renderer.render(&scene, w, h);
766        }
767    }
768});",
769        130.0,
770    );
771    let s12_why = note(
772        "The window stays open because the GPU surface is tied to",
773        490.0,
774    );
775    let s12_why2 = note(
776        "the OS event loop. Without it, the surface is immediately dropped.",
777        510.0,
778    );
779    let s12_hash = body(
780        "state_hash() skips re-rendering unchanged frames (dirty-checking).",
781        550.0,
782    );
783
784    for n in [&s12_h, &s12_sub, &s12_why, &s12_why2, &s12_hash] {
785        project.scene.add(Box::new(n.clone()));
786    }
787    project.scene.add(Box::new(s12_code.clone()));
788
789    // =====================================================================
790    //  S13: HEADLESS EXPORT — GPU without a window
791    // =====================================================================
792    let s13_h = title("Headless Export: GPU -> PNG -> FFmpeg", 50.0);
793    let s13_sub = body(
794        "Same GPU rendering, but without a window — output to files.",
795        90.0,
796    );
797    let s13_code = code_block(
798        "pub struct Exporter {
799    texture: wgpu::Texture,       // GPU-side image
800    output_buffer: wgpu::Buffer,  // CPU-readable copy
801    renderer: Renderer,           // Vello
802}
803fn export_frame(&mut self, scene) -> Vec<u8> {
804    scene.render(&mut self.scene);           // 1. build shapes
805    renderer.render_to_texture(..);          // 2. GPU draws
806    encoder.copy_texture_to_buffer(..);      // 3. GPU -> CPU
807    output_buffer.map_async(Read, ..);       // 4. read pixels
808    return pixels;                           // 5. raw RGBA
809}",
810        130.0,
811    );
812    let s13_cache = note(
813        "Cache: state_hash per frame. If unchanged, skip GPU entirely.",
814        420.0,
815    );
816    let s13_ffmpeg = note(
817        "FFmpeg: raw pixels piped to stdin -> libx264 -> .mkv video.",
818        445.0,
819    );
820    let s13_parallel = body(
821        "PNG saving runs on a background thread. Export is pipelined.",
822        485.0,
823    );
824
825    for n in [&s13_h, &s13_sub, &s13_cache, &s13_ffmpeg, &s13_parallel] {
826        project.scene.add(Box::new(n.clone()));
827    }
828    project.scene.add(Box::new(s13_code.clone()));
829
830    // =====================================================================
831    //  S14: ENGINE UTILITIES
832    // =====================================================================
833    let s14_h = title("Under the Hood: Utility Modules", 50.0);
834    let s14_sub = body(
835        "Helper systems that power the engine behind the scenes.",
836        90.0,
837    );
838    let s14_code = code_block(
839        "// src/engine/util/
840font_manager.rs    // Lazy-loads system fonts via Typst
841                   // Global HashMap cache with lazy_static
842
843image_manager.rs   // Loads PNG + SVG (via resvg)
844                   // Caches decoded images as Arc<Image>
845
846code_tokenizer.rs  // Syntax highlighting via Syntect
847                   // Parses code -> colored spans for CodeNode
848
849hash.rs            // SeaHash: fast, deterministic fingerprints
850                   // Position-aware combination
851                   // Powers Rayon parallel state hashing
852
853export.rs          // FFmpeg pipe: rawvideo -> libx264
854                   // Audio merging with filter_complex
855                   // Title sanitization for filenames",
856        130.0,
857    );
858    let s14_lazy = note(
859        "lazy_static + Mutex = global singleton, created once, cached forever.",
860        495.0,
861    );
862    let s14_arc = body(
863        "Arc<Image> lets multiple nodes share one decoded image without copies.",
864        530.0,
865    );
866    let s14_hash = note(
867        "Rayon + SeaHash = Deterministic fingerprints across runs & threads.",
868        565.0,
869    );
870
871    for n in [&s14_h, &s14_sub, &s14_lazy, &s14_arc, &s14_hash] {
872        project.scene.add(Box::new(n.clone()));
873    }
874    project.scene.add(Box::new(s14_code.clone()));
875
876    // =====================================================================
877    //  S15: FINALE
878    // =====================================================================
879    let fin = TextNode::default()
880        .with_anchor(Vec2::new(-1.0, -1.0))
881        .with_position(Vec2::new(LEFT, 200.0))
882        .with_text("That's how it works!")
883        .with_font_size(48.0)
884        .with_fill(ACCENT)
885        .with_font(FONT)
886        .with_opacity(0.0);
887    let fin_steps = [
888        "1.  struct          — data container",
889        "2.  impl            — methods / builder pattern",
890        "3.  trait Node      — interface contract",
891        "4.  Box<dyn Node>   — type-erased heap allocation",
892        "5.  Signal<T>       — Arc<Mutex> shared reactive state",
893        "6.  SignalTween     — per-frame lerp interpolation",
894        "7.  Timeline        — sequential queue + leftover dt",
895        "8.  Event Loop      — winit + wgpu infinite loop",
896        "9.  Exporter        — headless GPU -> PNG/FFmpeg",
897        "10. Utilities       — font/image cache, syntax highlight",
898    ];
899    let fin_texts: Vec<TextNode> = fin_steps
900        .iter()
901        .enumerate()
902        .map(|(i, s)| body(s, 270.0 + i as f32 * 28.0))
903        .collect();
904    let fin_hint = dim("cargo run --example getting_started", LEFT, 570.0);
905
906    project.scene.add(Box::new(fin.clone()));
907    for t in &fin_texts {
908        project.scene.add(Box::new(t.clone()));
909    }
910    project.scene.add(Box::new(fin_hint.clone()));
911
912    // =====================================================================
913    //  ANIMATION TIMELINE
914    // =====================================================================
915    // Helper: hide_all takes a vec of opacity signals and fades them out
916    let hide_dur = ms(200);
917
918    project.scene.video_timeline.add(chain![
919        // ── S1: TITLE ──
920        s1_line
921            .end
922            .to(Vec2::new(500.0, 100.0), ms(500))
923            .ease(easings::cubic_out),
924        sequence![
925            ms(120),
926            show(&s1_title, ms(500)),
927            show(&s1_sub, ms(500)),
928            show(&s1_built, ms(500)),
929            show(&s1_logo, ms(600)),
930            show(&s1_desc, ms(500)),
931            show(&s1_desc2, ms(500)),
932        ],
933        wait(secs(5)),
934        all![
935            hide(&s1_title, hide_dur),
936            hide(&s1_sub, hide_dur),
937            hide(&s1_built, hide_dur),
938            hide(&s1_desc, hide_dur),
939            hide(&s1_desc2, hide_dur),
940            hide(&s1_logo, hide_dur),
941            s1_line.end.to(Vec2::new(LEFT, 100.0), hide_dur)
942        ],
943        wait(ms(150)),
944        // ── S2: FIVE STEPS ──
945        show(&s2_h, ms(500)),
946        wait(ms(400)),
947        sequence![
948            ms(250),
949            show(&s2_texts[0], ms(400)),
950            show(&s2_texts[1], ms(400)),
951            show(&s2_texts[2], ms(400)),
952            show(&s2_texts[3], ms(400)),
953            show(&s2_texts[4], ms(400)),
954        ],
955        wait(secs(8)),
956        all![
957            hide(&s2_h, hide_dur),
958            hide(&s2_texts[0], hide_dur),
959            hide(&s2_texts[1], hide_dur),
960            hide(&s2_texts[2], hide_dur),
961            hide(&s2_texts[3], hide_dur),
962            hide(&s2_texts[4], hide_dur)
963        ],
964        wait(ms(150)),
965        // ── S3: STRUCT ──
966        sequence![
967            ms(120),
968            show(&s3_h, ms(500)),
969            show(&s3_explain, ms(400)),
970            show(&s3_analogy, ms(400))
971        ],
972        wait(ms(500)),
973        show(&s3_code, ms(500)),
974        wait(secs(6)),
975        sequence![
976            ms(300),
977            show(&s3_note, ms(400)),
978            show(&s3_note2, ms(400)),
979            show(&s3_note3, ms(400))
980        ],
981        wait(secs(6)),
982        all![
983            hide(&s3_h, hide_dur),
984            hide(&s3_explain, hide_dur),
985            hide(&s3_analogy, hide_dur),
986            hide(&s3_code, hide_dur),
987            hide(&s3_note, hide_dur),
988            hide(&s3_note2, hide_dur),
989            hide(&s3_note3, hide_dur)
990        ],
991        wait(ms(150)),
992        // ── S4: IMPL / BUILDER ──
993        sequence![
994            ms(120),
995            show(&s4_h, ms(500)),
996            show(&s4_explain, ms(400)),
997            show(&s4_analogy, ms(400))
998        ],
999        wait(ms(500)),
1000        show(&s4_code, ms(500)),
1001        wait(secs(7)),
1002        show(&s4_usage, ms(400)),
1003        show(&s4_usage_code, ms(500)),
1004        wait(secs(2)),
1005        show(&s4_note, ms(400)),
1006        wait(secs(5)),
1007        all![
1008            hide(&s4_h, hide_dur),
1009            hide(&s4_explain, hide_dur),
1010            hide(&s4_analogy, hide_dur),
1011            hide(&s4_code, hide_dur),
1012            hide(&s4_usage, hide_dur),
1013            hide(&s4_usage_code, hide_dur),
1014            hide(&s4_note, hide_dur)
1015        ],
1016        wait(ms(150)),
1017        // ── S5: TRAIT / NODE ──
1018        sequence![
1019            ms(120),
1020            show(&s5_h, ms(500)),
1021            show(&s5_explain, ms(400)),
1022            show(&s5_analogy, ms(400))
1023        ],
1024        wait(ms(500)),
1025        show(&s5_code, ms(500)),
1026        wait(secs(6)),
1027        sequence![
1028            ms(300),
1029            show(&s5_r, ms(400)),
1030            show(&s5_u, ms(400)),
1031            show(&s5_s, ms(400)),
1032            show(&s5_c, ms(400))
1033        ],
1034        wait(secs(2)),
1035        show(&s5_every, ms(400)),
1036        wait(secs(4)),
1037        all![
1038            hide(&s5_h, hide_dur),
1039            hide(&s5_explain, hide_dur),
1040            hide(&s5_analogy, hide_dur),
1041            hide(&s5_code, hide_dur),
1042            hide(&s5_r, hide_dur),
1043            hide(&s5_u, hide_dur),
1044            hide(&s5_s, hide_dur),
1045            hide(&s5_c, hide_dur),
1046            hide(&s5_every, hide_dur)
1047        ],
1048        wait(ms(150)),
1049        // ── S6: NODE GALLERY ──
1050        sequence![ms(120), show(&s6_h, ms(500)), show(&s6_sub, ms(400))],
1051        wait(ms(400)),
1052        sequence![
1053            ms(200),
1054            all![show(&demo_c, ms(400)), show(&lc, ms(400))],
1055            all![show(&demo_r, ms(400)), show(&lr, ms(400))],
1056            all![show(&demo_l, ms(400)), show(&ll, ms(400))],
1057            all![show(&demo_p, ms(400)), show(&lp, ms(400))],
1058            all![show(&demo_t, ms(400)), show(&lt, ms(400))],
1059        ],
1060        wait(secs(2)),
1061        sequence![
1062            ms(200),
1063            show(&s6_box_h, ms(400)),
1064            show(&s6_box1, ms(400)),
1065            show(&s6_box_code, ms(500))
1066        ],
1067        wait(secs(7)),
1068        all![
1069            hide(&s6_h, hide_dur),
1070            hide(&s6_sub, hide_dur),
1071            hide(&demo_c, hide_dur),
1072            hide(&demo_r, hide_dur),
1073            hide(&demo_l, hide_dur),
1074            hide(&demo_p, hide_dur),
1075            hide(&demo_t, hide_dur),
1076            hide(&lc, hide_dur),
1077            hide(&lr, hide_dur),
1078            hide(&ll, hide_dur),
1079            hide(&lp, hide_dur),
1080            hide(&lt, hide_dur),
1081            hide(&s6_box_h, hide_dur),
1082            hide(&s6_box1, hide_dur),
1083            hide(&s6_box_code, hide_dur)
1084        ],
1085        wait(ms(150)),
1086        // ── S7: SIGNALS ──
1087        sequence![ms(120), show(&s7_h, ms(500)), show(&s7_sub, ms(400))],
1088        wait(ms(400)),
1089        show(&s7_code, ms(500)),
1090        wait(secs(5)),
1091        sequence![ms(300), show(&s7_arc, ms(400)), show(&s7_mutex, ms(400))],
1092        wait(secs(4)),
1093        show(&s7_why, ms(400)),
1094        show(&s7_diagram_code, ms(500)),
1095        wait(secs(6)),
1096        // Live demo
1097        all![show(&sig_demo, ms(300)), show(&sig_lbl, ms(300))],
1098        chain![
1099            sig_demo.radius.to(80.0, ms(700)).ease(easings::elastic_out),
1100            sig_demo.fill_color.to(TEAL, ms(500)),
1101            sig_demo
1102                .position
1103                .to(Vec2::new(950.0, 350.0), ms(500))
1104                .ease(easings::cubic_out),
1105            wait(ms(300)),
1106            all![
1107                sig_demo.radius.to(50.0, ms(400)),
1108                sig_demo.fill_color.to(RED, ms(400)),
1109                sig_demo.position.to(Vec2::new(900.0, 300.0), ms(400))
1110            ],
1111        ],
1112        wait(secs(3)),
1113        all![
1114            hide(&s7_h, hide_dur),
1115            hide(&s7_sub, hide_dur),
1116            hide(&s7_code, hide_dur),
1117            hide(&s7_arc, hide_dur),
1118            hide(&s7_mutex, hide_dur),
1119            hide(&s7_why, hide_dur),
1120            hide(&s7_diagram_code, hide_dur),
1121            hide(&sig_demo, hide_dur),
1122            hide(&sig_lbl, hide_dur)
1123        ],
1124        wait(ms(150)),
1125        // ── S8: SIGNAL TWEEN ──
1126        sequence![ms(120), show(&s8_h, ms(500)), show(&s8_sub, ms(400))],
1127        wait(ms(400)),
1128        show(&s8_code, ms(500)),
1129        wait(secs(6)),
1130        show(&s8_how, ms(300)),
1131        sequence![
1132            ms(100),
1133            show(&s8_step_texts[0], ms(250)),
1134            show(&s8_step_texts[1], ms(250)),
1135            show(&s8_step_texts[2], ms(250)),
1136            show(&s8_step_texts[3], ms(250)),
1137            show(&s8_step_texts[4], ms(250)),
1138            show(&s8_step_texts[5], ms(250)),
1139        ],
1140        wait(ms(500)),
1141        sequence![ms(100), show(&s8_lazy, ms(300)), show(&s8_lazy2, ms(300))],
1142        wait(ms(500)),
1143        // Progress bar demo
1144        all![
1145            show(&prog_bg, ms(200)),
1146            show(&prog_fill, ms(200)),
1147            show(&plbl0, ms(200)),
1148            show(&plbl1, ms(200)),
1149            show(&tween_ball, ms(200)),
1150            show(&tween_lbl, ms(200))
1151        ],
1152        all![
1153            prog_fill
1154                .size
1155                .to(Vec2::new(400.0, 16.0), secs(2))
1156                .ease(easings::cubic_in_out),
1157            tween_ball
1158                .radius
1159                .to(80.0, secs(2))
1160                .ease(easings::cubic_in_out),
1161        ],
1162        wait(secs(3)),
1163        all![
1164            hide(&s8_h, hide_dur),
1165            hide(&s8_sub, hide_dur),
1166            hide(&s8_code, hide_dur),
1167            hide(&s8_how, hide_dur),
1168            hide(&s8_lazy, hide_dur),
1169            hide(&s8_lazy2, hide_dur),
1170            hide(&prog_bg, hide_dur),
1171            hide(&prog_fill, hide_dur),
1172            hide(&plbl0, hide_dur),
1173            hide(&plbl1, hide_dur),
1174            hide(&tween_ball, hide_dur),
1175            hide(&tween_lbl, hide_dur),
1176            hide(&s8_step_texts[0], hide_dur),
1177            hide(&s8_step_texts[1], hide_dur),
1178            hide(&s8_step_texts[2], hide_dur),
1179            hide(&s8_step_texts[3], hide_dur),
1180            hide(&s8_step_texts[4], hide_dur),
1181            hide(&s8_step_texts[5], hide_dur)
1182        ],
1183        wait(ms(150)),
1184        // ── S9: TWEENABLE + EASINGS ──
1185        show(&s9_h, ms(500)),
1186        show(&s9_code, ms(500)),
1187        wait(secs(4)),
1188        sequence![
1189            ms(60),
1190            show(&s9_easing_h, ms(300)),
1191            show(&s9_easing_desc, ms(300))
1192        ],
1193        sequence![
1194            ms(50),
1195            all![show(&eballs[0], ms(200)), show(&elabels[0], ms(200))],
1196            all![show(&eballs[1], ms(200)), show(&elabels[1], ms(200))],
1197            all![show(&eballs[2], ms(200)), show(&elabels[2], ms(200))],
1198            all![show(&eballs[3], ms(200)), show(&elabels[3], ms(200))],
1199            all![show(&eballs[4], ms(200)), show(&elabels[4], ms(200))],
1200        ],
1201        wait(ms(300)),
1202        // Race!
1203        all![
1204            eballs[0]
1205                .position
1206                .to(Vec2::new(1050.0, 390.0), secs(2))
1207                .ease(easings::linear),
1208            eballs[1]
1209                .position
1210                .to(Vec2::new(1050.0, 445.0), secs(2))
1211                .ease(easings::cubic_in_out),
1212            eballs[2]
1213                .position
1214                .to(Vec2::new(1050.0, 500.0), secs(2))
1215                .ease(easings::elastic_out),
1216            eballs[3]
1217                .position
1218                .to(Vec2::new(1050.0, 555.0), secs(2))
1219                .ease(easings::bounce_out),
1220            eballs[4]
1221                .position
1222                .to(Vec2::new(1050.0, 610.0), secs(2))
1223                .ease(easings::back_out),
1224        ],
1225        wait(ms(500)),
1226        all![
1227            eballs[0]
1228                .position
1229                .to(Vec2::new(250.0, 390.0), secs(2))
1230                .ease(easings::linear),
1231            eballs[1]
1232                .position
1233                .to(Vec2::new(250.0, 445.0), secs(2))
1234                .ease(easings::cubic_in_out),
1235            eballs[2]
1236                .position
1237                .to(Vec2::new(250.0, 500.0), secs(2))
1238                .ease(easings::elastic_out),
1239            eballs[3]
1240                .position
1241                .to(Vec2::new(250.0, 555.0), secs(2))
1242                .ease(easings::bounce_out),
1243            eballs[4]
1244                .position
1245                .to(Vec2::new(250.0, 610.0), secs(2))
1246                .ease(easings::back_out),
1247        ],
1248        wait(ms(500)),
1249        all![
1250            hide(&s9_h, hide_dur),
1251            hide(&s9_code, hide_dur),
1252            hide(&s9_easing_h, hide_dur),
1253            hide(&s9_easing_desc, hide_dur),
1254            hide(&eballs[0], hide_dur),
1255            hide(&eballs[1], hide_dur),
1256            hide(&eballs[2], hide_dur),
1257            hide(&eballs[3], hide_dur),
1258            hide(&eballs[4], hide_dur),
1259            hide(&elabels[0], hide_dur),
1260            hide(&elabels[1], hide_dur),
1261            hide(&elabels[2], hide_dur),
1262            hide(&elabels[3], hide_dur),
1263            hide(&elabels[4], hide_dur)
1264        ],
1265        wait(ms(150)),
1266        // ── S10: FLOW CONTROLS ──
1267        sequence![ms(120), show(&s10_h, ms(500)), show(&s10_sub, ms(400))],
1268        wait(ms(400)),
1269        // chain demo
1270        show(&s10_chain_h, ms(400)),
1271        all![
1272            show(&chain_d[0], ms(300)),
1273            show(&chain_d[1], ms(300)),
1274            show(&chain_d[2], ms(300))
1275        ],
1276        wait(ms(300)),
1277        chain![
1278            chain_d[0]
1279                .position
1280                .to(Vec2::new(700.0, 200.0), ms(500))
1281                .ease(easings::cubic_out),
1282            chain_d[1]
1283                .position
1284                .to(Vec2::new(800.0, 200.0), ms(500))
1285                .ease(easings::cubic_out),
1286            chain_d[2]
1287                .position
1288                .to(Vec2::new(900.0, 200.0), ms(500))
1289                .ease(easings::cubic_out),
1290        ],
1291        wait(secs(1)),
1292        // all demo
1293        show(&s10_all_h, ms(400)),
1294        all![
1295            show(&all_d[0], ms(300)),
1296            show(&all_d[1], ms(300)),
1297            show(&all_d[2], ms(300))
1298        ],
1299        wait(ms(300)),
1300        all![
1301            all_d[0]
1302                .position
1303                .to(Vec2::new(700.0, 330.0), ms(500))
1304                .ease(easings::cubic_out),
1305            all_d[1]
1306                .position
1307                .to(Vec2::new(800.0, 330.0), ms(500))
1308                .ease(easings::cubic_out),
1309            all_d[2]
1310                .position
1311                .to(Vec2::new(900.0, 330.0), ms(500))
1312                .ease(easings::cubic_out),
1313        ],
1314        wait(secs(1)),
1315        // sequence demo
1316        show(&s10_seq_h, ms(400)),
1317        all![
1318            show(&seq_d[0], ms(300)),
1319            show(&seq_d[1], ms(300)),
1320            show(&seq_d[2], ms(300))
1321        ],
1322        wait(ms(300)),
1323        sequence![
1324            ms(250),
1325            seq_d[0]
1326                .position
1327                .to(Vec2::new(700.0, 460.0), ms(500))
1328                .ease(easings::cubic_out),
1329            seq_d[1]
1330                .position
1331                .to(Vec2::new(800.0, 460.0), ms(500))
1332                .ease(easings::cubic_out),
1333            seq_d[2]
1334                .position
1335                .to(Vec2::new(900.0, 460.0), ms(500))
1336                .ease(easings::cubic_out),
1337        ],
1338        wait(secs(1)),
1339        show(&s10_code, ms(500)),
1340        wait(secs(8)),
1341        all![
1342            hide(&s10_h, hide_dur),
1343            hide(&s10_sub, hide_dur),
1344            hide(&s10_chain_h, hide_dur),
1345            hide(&s10_all_h, hide_dur),
1346            hide(&s10_seq_h, hide_dur),
1347            hide(&s10_code, hide_dur),
1348            hide(&chain_d[0], hide_dur),
1349            hide(&chain_d[1], hide_dur),
1350            hide(&chain_d[2], hide_dur),
1351            hide(&all_d[0], hide_dur),
1352            hide(&all_d[1], hide_dur),
1353            hide(&all_d[2], hide_dur),
1354            hide(&seq_d[0], hide_dur),
1355            hide(&seq_d[1], hide_dur),
1356            hide(&seq_d[2], hide_dur)
1357        ],
1358        wait(ms(150)),
1359        // ── S11: TIMELINE + RENDERING ──
1360        show(&s11_h, ms(500)),
1361        wait(ms(400)),
1362        show(&s11_code, ms(500)),
1363        wait(secs(7)),
1364        sequence![
1365            ms(200),
1366            show(&s11_leftover, ms(400)),
1367            show(&s11_leftover2, ms(400))
1368        ],
1369        wait(secs(4)),
1370        show(&s11_render_h, ms(400)),
1371        sequence![
1372            ms(200),
1373            show(&s11_render_texts[0], ms(350)),
1374            show(&s11_render_texts[1], ms(350)),
1375            show(&s11_render_texts[2], ms(350)),
1376            show(&s11_render_texts[3], ms(350)),
1377        ],
1378        wait(secs(7)),
1379        all![
1380            hide(&s11_h, hide_dur),
1381            hide(&s11_code, hide_dur),
1382            hide(&s11_leftover, hide_dur),
1383            hide(&s11_leftover2, hide_dur),
1384            hide(&s11_render_h, hide_dur),
1385            hide(&s11_render_texts[0], hide_dur),
1386            hide(&s11_render_texts[1], hide_dur),
1387            hide(&s11_render_texts[2], hide_dur),
1388            hide(&s11_render_texts[3], hide_dur)
1389        ],
1390        wait(ms(300)),
1391        // ── S12: EVENT LOOP ──
1392        sequence![ms(120), show(&s12_h, ms(500)), show(&s12_sub, ms(400))],
1393        wait(ms(500)),
1394        show(&s12_code, ms(500)),
1395        wait(secs(8)),
1396        sequence![
1397            ms(200),
1398            show(&s12_why, ms(400)),
1399            show(&s12_why2, ms(400)),
1400            show(&s12_hash, ms(400))
1401        ],
1402        wait(secs(5)),
1403        all![
1404            hide(&s12_h, hide_dur),
1405            hide(&s12_sub, hide_dur),
1406            hide(&s12_code, hide_dur),
1407            hide(&s12_why, hide_dur),
1408            hide(&s12_why2, hide_dur),
1409            hide(&s12_hash, hide_dur)
1410        ],
1411        wait(ms(150)),
1412        // ── S13: HEADLESS EXPORT ──
1413        sequence![ms(120), show(&s13_h, ms(500)), show(&s13_sub, ms(400))],
1414        wait(ms(500)),
1415        show(&s13_code, ms(500)),
1416        wait(secs(8)),
1417        sequence![
1418            ms(200),
1419            show(&s13_cache, ms(400)),
1420            show(&s13_ffmpeg, ms(400)),
1421            show(&s13_parallel, ms(400))
1422        ],
1423        wait(secs(5)),
1424        all![
1425            hide(&s13_h, hide_dur),
1426            hide(&s13_sub, hide_dur),
1427            hide(&s13_code, hide_dur),
1428            hide(&s13_cache, hide_dur),
1429            hide(&s13_ffmpeg, hide_dur),
1430            hide(&s13_parallel, hide_dur)
1431        ],
1432        wait(ms(150)),
1433        // ── S14: UTILITIES ──
1434        sequence![ms(120), show(&s14_h, ms(500)), show(&s14_sub, ms(400))],
1435        wait(ms(500)),
1436        show(&s14_code, ms(500)),
1437        wait(secs(8)),
1438        sequence![ms(200), show(&s14_lazy, ms(400)), show(&s14_arc, ms(400))],
1439        wait(secs(5)),
1440        all![
1441            hide(&s14_h, hide_dur),
1442            hide(&s14_sub, hide_dur),
1443            hide(&s14_code, hide_dur),
1444            hide(&s14_lazy, hide_dur),
1445            hide(&s14_arc, hide_dur)
1446        ],
1447        wait(ms(300)),
1448        // ── S15: FINALE ──
1449        show(&fin, ms(700)),
1450        wait(ms(500)),
1451        sequence![
1452            ms(150),
1453            show(&fin_texts[0], ms(350)),
1454            show(&fin_texts[1], ms(350)),
1455            show(&fin_texts[2], ms(350)),
1456            show(&fin_texts[3], ms(350)),
1457            show(&fin_texts[4], ms(350)),
1458            show(&fin_texts[5], ms(350)),
1459            show(&fin_texts[6], ms(350)),
1460            show(&fin_texts[7], ms(350)),
1461            show(&fin_texts[8], ms(350)),
1462            show(&fin_texts[9], ms(350)),
1463        ],
1464        wait(secs(2)),
1465        show(&fin_hint, ms(400)),
1466        wait(secs(6)),
1467    ]);
1468
1469    #[cfg(feature = "audio")]
1470    project
1471        .scene
1472        .audio_timeline
1473        .add(play!(AudioNode::new("background.mp3").with_volume(0.3)));
1474
1475    project.show().expect("Failed to render");
1476}