Markdy: Like Mermaid Diagrams, but for Motion

surprisetalk1 pts0 comments

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...

face chad kevin actor side ease

Related Articles