Markdy β Open-Source Animation DSL Engineπ¬"> Key Features<br>π¦ Zero Dependencies<br>Pure TypeScript parser β no DOM, no runtime deps.
π Web-Native Renderer<br>Web Animations API + CSS transforms. No Canvas, no GSAP.
π€ AI-Agent Friendly<br>Structured DSL that LLMs can generate, validate, and iterate.
π§© Framework Agnostic<br>Works anywhere. Astro island component included.
Ecosystem Lightweight Packages<br>Ship only what you need. Tiny bundles, zero bloat.
@markdy/core<br>Parser + AST types β zero runtime deps<br>~12 KBTypeScript<br>@markdy/renderer-dom<br>Web Animations API renderer<br>~22 KBDOM<br>@markdy/astro<br>Astro island component β viewport hydration<br>~2 KBAstro
Interactive Playground<br>Edit the code and watch it animate. Pick an example or write your own.
Full StoryHello WorldFight SceneLove StoryDance Party<br>Editor Ctrl+Space autocomplete Β· Cmd+Enter run Run βΆ
scene width=800 height=400 fps=30 bg=#fafafa
var skin_a = #c68642<br>var skin_b = #8d5524<br>var skin_g = #fad4c0
def fighter(skin, face) {<br>figure(${skin}, m, ${face})
def girl(skin, face) {<br>figure(${skin}, f, ${face})
seq hit(side, power) {<br>@+0.0: $.punch(side=${side}, dur=0.3)<br>@+0.05: $.shake(intensity=${power}, dur=0.3)
actor act1 = caption("I. Boys See Girl") at top opacity 0<br>actor act2 = caption("II. Boys Fight") at top opacity 0<br>actor act3 = caption("III. Girl Says No") at top opacity 0<br>actor act4 = caption("IV. Girl Picks the Loser") at top opacity 0
actor chad = fighter(${skin_a}, π) at (640, 180)<br>actor kevin = fighter(${skin_b}, π€) at (100, 180)<br>actor mia = girl(${skin_g}, π§) at (370, 180) opacity 0<br>actor bush = text("πͺ¨") at (50, 285) size 80 opacity 0
scene "rivals" {<br>@+0.0: act1.fade_in(dur=0.3)<br>@+0.1: mia.fade_in(dur=0.5)<br>@+0.0: chad.enter(from=right, dur=0.6)<br>@+0.0: kevin.enter(from=left, dur=0.6)<br>@+0.2: chad.wave(side=left, dur=0.5)<br>@+0.0: kevin.wave(side=right, dur=0.5)<br>@+0.2: chad.face("π")<br>@+0.0: kevin.face("π")<br>@+0.2: chad.say("Hey gorgeous π", dur=1.3)<br>@+0.3: kevin.say("Back off, she's mine!", dur=1.3)<br>@+0.6: act1.fade_out(dur=0.3)<br>@+0.1: mia.face("π³")<br>@+0.2: mia.say("β¦do I get a say?", dur=1.2)<br>@+0.4: mia.move(to=(600, 180), dur=0.5, ease=out)
scene "duel" {<br>@+0.0: act2.fade_in(dur=0.3)<br>@+0.3: chad.face("π ")<br>@+0.0: kevin.face("π ")<br>@+0.1: chad.move(to=(400, 180), dur=0.5, ease=in)<br>@+0.0: kevin.move(to=(320, 180), dur=0.5, ease=in)<br>@+0.3: act2.fade_out(dur=0.3)<br>@+0.2: kevin.say("En garde, bro!", dur=1.0)<br>@+0.3: chad.play(hit, side=left, power=7)<br>@+0.0: camera.shake(intensity=6, dur=0.3)<br>@+0.1: kevin.play(hit, side=right, power=7)<br>@+0.2: kevin.kick(side=right, dur=0.3)<br>@+0.0: chad.shake(intensity=9, dur=0.3)<br>@+0.2: chad.say("Is that all?", dur=0.9)<br>@+0.9: chad.punch(side=left, dur=0.25)<br>@+0.0: camera.shake(intensity=16, dur=0.5)<br>@+0.0: kevin.face("π΅")<br>@+0.0: bush.fade_in(dur=0.2)<br>@+0.1: kevin.move(to=(110, 275), dur=0.55, ease=out)<br>@+0.0: kevin.rotate(to=-80, dur=0.55, ease=out)<br>@+0.4: bush.shake(intensity=7, dur=0.4)<br>@+0.0: bush.fade_out(dur=0.3)<br>@+0.0: kevin.face("π€")<br>@+0.3: chad.face("π")<br>@+0.2: chad.say("Too easy. She's mine!", dur=1.4)
scene "rejection" {<br>@+0.0: act3.fade_in(dur=0.3)<br>@+0.3: chad.face("π₯°")<br>@+0.0: chad.move(to=(520, 180), dur=0.6, ease=out)<br>@+0.5: chad.say("My queen π", dur=1.1)<br>@+0.3: mia.face("π ")<br>@+0.2: mia.say("Hard pass.", dur=1.1)<br>@+0.0: mia.shake(intensity=5, dur=0.4)<br>@+0.4: act3.fade_out(dur=0.3)<br>@+0.1: chad.face("π¨")<br>@+0.2: chad.say("Butβ¦ I WON??", dur=1.3)<br>@+0.4: mia.face("π")<br>@+0.2: mia.say("That's the problem.", dur=1.4)<br>@+0.6: chad.face("π")<br>@+0.4: chad.exit(to=right, dur=0.8)
scene "aftermath" {<br>@+0.0: act4.fade_in(dur=0.3)<br>@+0.2: mia.face("π₯Ί")<br>@+0.1: mia.move(to=(230, 210), dur=1.2, ease=out)<br>@+0.0: camera.pan(to=(100, 345), dur=1.2, ease=out)<br>@+0.0: camera.zoom(to=1.3, dur=1.2, ease=out)<br>@+0.6: act4.fade_out(dur=0.3)<br>@+0.3: mia.face("π")<br>@+0.2: mia.say("You okay down there?", dur=1.6)<br>@+0.2: kevin.face("π₯²")<br>@+0.2: kevin.face("π₯Ή")<br>@+0.2: kevin.say("Mission failedβ¦", dur=1.3)<br>@+0.2: kevin.say("β¦successfully. π", dur=1.6)<br>@+0.2: mia.face("π")<br>Preview Play<br>Share
Step-by-Step Learn MarkdyScript<br>Follow this guide from basics to advanced. Each card builds on the last.
Scene Setup
Every animation starts by defining the canvas. This must be the first line in your .markdy file.<br>scene width=800 height=400 fps=30 bg=#fafafa<br>PropertyDefaultDescription width800Canvas width in pixels height400Canvas height in pixels fps30Frame rate for the rendering engine bgwhiteAny CSS color value (e.g. #1a1a2e) durationautoOverride total length in seconds<br>Actors & Positioning
Actors are the objects in your scene. Place them with at (x, y) and add optional modifiers.<br>actor a = figure(#c68642, m, π) at (100, 200) scale 1.2<br>actor b = text("Hello World") at (50, 50) size 24<br>actor c = box() at (300, 200)<br>TypeArgumentsDescription text"quoted string"Plain text label. Customise with size. boxβ100x100 solid box. Great for prototyping. figureskinColor, gender, faceStick figure with articulatable limbs. spriteasset...