Code/Demos/AnimationShowcase/Sections/ChoreographySection.cs
using Goo;
using Goo.Animation;
using Sandbox.UI;
using static Sandbox.DemoTokens;

namespace Sandbox.AnimationShowcase;

// one Timeline drives three properties on one element; caller dispatches on sample.SegmentIndex to route the value, each property holding its last value between phases. stateless: opacity/xOffset/scale come straight from Sample at Build time, no cached fields, no manual reset on replay.
public class ChoreographySection
{
    const float TrackW  = 380f;
    const float TrackH  = 100f;
    const float Slide   = 260f;   // px of MarginLeft travel during phase 1
    const float SizeMin = 40f;
    const float SizeMax = 80f;

    static readonly Timeline Choreography = Timeline.Sequence(
        new Tween( Easing.EaseInOut, duration: 0.5f ),   // seg 0 -> opacity
        new Tween( Easing.EaseInOut, duration: 0.5f ),   // seg 1 -> x offset
        new Tween( Easing.EaseInOut, duration: 0.5f ) ); // seg 2 -> scale

    TimelineAnimator _anim = new( Choreography ) { Elapsed = 999f };   // start past-end (settled)

    public void Update( float dt ) => _anim.Update( dt );

    public Container Build()
    {
        var s        = _anim.Sample;
        float opacity = PhaseValue( s, 0 );
        float xOff    = PhaseValue( s, 1 ) * Slide;
        float size    = SizeMin + (SizeMax - SizeMin) * PhaseValue( s, 2 );

        return Section.Build(
            "Choreography - one sequence across 3 properties: Fade --> Slide --> Grow",
            new Container
            {
                FlexDirection = FlexDirection.Column,
                Gap           = Space3,
                AlignItems    = Align.FlexStart,
                Children =
                {
                    new Container
                    {
                        Width           = TrackW,
                        Height          = TrackH,
                        BackgroundColor = BgCardHi,
                        BorderRadius    = Radius2,
                        FlexDirection   = FlexDirection.Row,
                        AlignItems      = Align.Center,
                        Padding         = Space2,
                        Children =
                        {
                            new Container
                            {
                                MarginLeft      = xOff,
                                Width           = size,
                                Height          = size,
                                Opacity         = opacity,
                                BackgroundColor = Accent,
                                BorderRadius    = Radius2,
                            },
                        },
                    },
                    CtrlButton.Build( "replay", _ => _anim.Restart() ),
                },
            } );
    }

    // Property N is at 0 until its segment runs, animates with sample.Value while
    // active, and stays at the segment's end value (1f) once a later segment runs.
    static float PhaseValue( TimelineSample s, int phase )
    {
        if ( s.SegmentIndex < phase ) return 0f;
        if ( s.SegmentIndex == phase ) return s.Value;
        return 1f;
    }
}