Motion Playground
Animate two scenes with keyframes, easing, translation, rotation, scaling, and sequential playback. A fixed random seed keeps the rendered preview repeatable.

Complete app
app.star
load("animation.star", "animation")
load("random.star", "random")
load("render.star", "render")
def main():
random.seed(7)
colors = ["#22d3ee", "#a78bfa", "#fb7185"]
color = colors[random.number(0, len(colors) - 1)]
travel = animation.Transformation(
child = render.Box(width = 8, height = 8, color = color),
duration = 36,
width = 64,
height = 32,
origin = animation.Origin(0.5, 0.5),
direction = "alternate",
keyframes = [
animation.Keyframe(
percentage = 0.0,
transforms = [
animation.Translate(3, 12),
animation.Rotate(0),
animation.Scale(0.75, 0.75),
],
),
animation.Keyframe(
percentage = 0.5,
transforms = [
animation.Translate(28, 5),
animation.Rotate(180),
animation.Scale(1.5, 1.5),
],
curve = "ease_in_out",
),
animation.Keyframe(
percentage = 1.0,
transforms = [
animation.Translate(53, 12),
animation.Rotate(360),
animation.Scale(0.75, 0.75),
],
curve = "ease_out",
),
],
)
pulse = animation.Transformation(
child = render.Circle(diameter = 10, color = "#fbbf24"),
duration = 24,
width = 64,
height = 32,
origin = animation.Origin(0.5, 0.5),
direction = "alternate",
keyframes = [
animation.Keyframe(
percentage = 0.0,
transforms = [
animation.Translate(27, 11),
animation.Scale(0.5, 0.5),
],
),
animation.Keyframe(
percentage = 1.0,
transforms = [
animation.Translate(27, 11),
animation.Scale(2, 2),
],
curve = "ease_in_out",
),
],
)
return render.Root(
delay = 50,
child = render.Sequence(children = [travel, pulse]),
)
How it works
- Each
Keyframedescribes the transform state at one point in the timeline. Origin(0.5, 0.5)makes scaling and rotation happen around the child's center.direction = "alternate"plays each transformation forward and backward.Sequencefinishes the moving square before starting the pulsing circle.Root.delaysets a 50 ms frame delay for the animated result.