MCPcopy Index your code
hub / github.com/HoangYell/markdy-com

github.com/HoangYell/markdy-com @v0.7.6

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.7.6 ↗ · + Follow
94 symbols 216 edges 24 files 15 documented · 16%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Markdy

An open-source animation DSL engine.

Write MarkdyScript → get animated scenes in the browser.

✨ Try the Interactive Playground

CI npm version Bundle Size Open in StackBlitz MIT License


What is Markdy?

Markdy is a framework-agnostic Animation DSL. Write animations like Markdown.

It is like Mermaid but for motion. Define actors, timelines, and interactions in a simple, readable DSL — the engine handles rendering with the Web Animations API. No Canvas, no GSAP, no bloated dependencies.

scene width=600 height=300 bg=white

actor label = text("Hello World") at (50, 130) size 40 opacity 0

@0.3: label.fade_in(dur=0.6)
@1.2: label.move(to=(200, 130), dur=0.8, ease=out)

Key Features

Feature Detail
Zero-dep parser @markdy/core is pure TypeScript — no DOM, no runtime deps
Web-native renderer Web Animations API + CSS transforms. No Canvas, no GSAP
Stick-figure actors Emoji-based figure type with articulatable limbs, face expressions
Chapters + cameras scene "title" { ... } blocks, camera.pan/zoom/shake, @+N: relative time
Captions, imports, presets First-class caption actors, import "..." as ns composition, parse-time preset <name>(...) macros
Forgiving by default, strict on demand Unknown actions soft-warn; prefix with ! to hard-fail (e.g. hero.!shake(...))
Language-first design var, def, seq let users build character systems and choreographies without engine changes
Astro-ready <Markdy /> island that hydrates on viewport entry
AI-agent friendly Structured DSL that LLMs can generate, validate, and iterate on (Agent Guide)

Packages

Package Description Size
@markdy/core Parser + AST types (zero runtime deps) ~12 KB
@markdy/renderer-dom Web Animations API renderer ~22 KB
@markdy/astro Astro island component ~2 KB

Quick Start

Vanilla JS / TypeScript

pnpm add @markdy/core @markdy/renderer-dom
import { createPlayer } from "@markdy/renderer-dom";

const player = createPlayer({
  container: document.getElementById("scene")!,
  code: `
    scene width=600 height=300 bg=white
    actor label = text("Hello") at (50, 130) size 40 opacity 0
    @0.3: label.fade_in(dur=0.6)
  `,
  autoplay: true,
});

// Control playback
player.pause();
player.seek(1.5);   // jump to 1.5 s
player.play();
player.destroy();    // clean up

Astro / MDX

pnpm add @markdy/astro
---
import { Markdy } from "@markdy/astro";

const code = `
  scene width=800 height=400 bg=#fff5f9
  actor hero = figure(#c68642, m, 😎) at (300, 200)
  @0.5: hero.enter(from=left, dur=0.8)
  @1.5: hero.say("Hello!", dur=1.0)
`;
---

<Markdy code={code} width={800} height={400} bg="#fff5f9" autoplay />

Parser Only (Node.js / Edge)

import { parse, ParseError } from "@markdy/core";

try {
  const ast = parse(source);
  console.log(ast.actors);  // { hero: { type: "figure", ... } }
  console.log(ast.events);  // [{ time: 0.5, actor: "hero", action: "enter", ... }]
} catch (e) {
  if (e instanceof ParseError) {
    console.error(`Line ${e.line}: ${e.message}`);
  }
}

DSL at a Glance

Full reference: docs/SYNTAX.md · Step-by-step tutorial: docs/TUTORIAL.md · AI agent guide: docs/AGENT.md

Scene + Actors + Timeline

scene width=800 height=400 bg=white

asset flower = image("/flower.svg")

actor hero  = figure(#c68642, m, 😎) at (100, 200)
actor label = text("Watch this") at (400, 50) size 32 opacity 0

@0.0: hero.enter(from=left, dur=0.8)
@1.0: hero.say("Hi!", dur=1.2)
@2.5: hero.face("😄")
@3.0: label.fade_in(dur=0.5)

Variables + Templates + Sequences

var skin = #c68642

def fighter(skin, face) {
  figure(${skin}, m, ${face})
}

seq punch_combo(side) {
  @+0.0: $.punch(side=${side}, dur=0.3)
  @+0.3: $.shake(intensity=5, dur=0.2)
}

actor bruno = fighter(${skin}, 😏) at (200, 200)

@0.5: bruno.enter(from=left, dur=0.8)
@2.0: bruno.play(punch_combo, side=right)

Chapters, Camera, Captions

scene width=900 height=500 bg=#101424

actor title = caption("ROUND 1") at top
actor hero  = figure(#c68642, m, 😎) at (300, 260)

scene "intro" {
  @+0.0: title.fade_in(dur=0.3)
  @+0.3: hero.enter(from=left, dur=0.7)
}

scene "beat" {
  @+0.2: camera.zoom(to=1.3, dur=0.5)
  @+0.1: hero.punch(side=right, dur=0.3)
  @+0.0: camera.shake(intensity=10, dur=0.3)
}

@+0.5: hero.exit(to=right, dur=0.5)

Namespaced Imports + Presets

# One-liner using a shipped preset macro:
preset meme("when the bug is finally fixed", "it was a typo")
# Compose across files — host resolves "as chars" → ast
import "./characters.markdy" as chars

actor hero = chars.fighter(${chars.skin_warm}, 😎) at (200, 200)
@0.0: hero.enter(from=left, dur=0.6)

Actions Reference

Action Description Key Parameters
enter Slide in from offscreen + fade from, dur, ease
exit Slide off-screen + fade out to, dur, ease
move Translate to position to=(x,y), dur, ease
fade_in / fade_out Opacity transitions dur
scale Animate scale to, dur, ease
rotate Animate rotation to (degrees), dur
shake Horizontal oscillation intensity, dur
say Speech bubble "text", dur
throw Projectile to target asset, to, dur
punch / kick Limb strike (figure only) side
rotate_part Rotate body part (figure only) part, to, dur
pose Set multiple parts at once (figure only) arm_left, arm_right, etc.
wave Wave gesture (figure only) side, dur
nod Head nod gesture (figure only) dur
jump Jump with squash/stretch (figure only) height, dur
bounce Diminishing vertical bounce (figure only) intensity, count, dur
face Swap emoji expression (figure only) "emoji"
camera.pan Pan scene to center on (x, y) to=(x,y), dur, ease
camera.zoom Zoom scene content to, dur
camera.shake Camera-level shake intensity, dur

Easing values: linear (default), in, out, inout.


API Reference

parse(source: string, opts?: ParseOptions): SceneAST

Parses MarkdyScript source into a typed AST. Throws ParseError with line numbers on structural errors. Pure function with no side effects — runs in Node.js, Deno, edge runtimes, or the browser.

interface ParseOptions {
  // Host-resolved ASTs for `import "..." as ns`. Namespaces whose ASTs are
  // supplied have their vars/defs/seqs merged under `ns.<name>` and can be
  // referenced from the parent. Unresolved namespaces emit a soft warning.
  imports?: Record<string, SceneAST>;
}

SceneAST includes ast.warnings[] (soft parse warnings like unknown-action, import-unresolved), ast.chapters[], and ast.imports[]. See docs/AGENT.md for the full shape.

createPlayer(options: PlayerOptions): Player

Creates a DOM-based animation player.

interface PlayerOptions {
  container: HTMLElement;    // Mount point
  code: string;             // MarkdyScript source
  assets?: Record<string, string>;  // Asset URL overrides
  imports?: Record<string, SceneAST>;  // Namespaces for `import "..." as ns`
  autoplay?: boolean;       // Start immediately (default: true)
  loop?: boolean;           // Loop at end (default: true)
  copyright?: boolean;      // "Powered by Markdy" badge (default: true)
  progressBar?: boolean;    // Rainbow border progress bar (default: true)
  onWarning?: (w: ParseWarning) => void;  // Surface soft parse warnings
}

interface Player {
  play(): void;             // Start / resume
  pause(): void;            // Pause at current position
  seek(seconds: number): void;  // Jump to time
  destroy(): void;          // Remove DOM + cancel animations
}

<Markdy /> (Astro Component)

Prop Type Default Description
code string (required) MarkdyScript source
width number 800 Placeholder width (px)
height number 400 Placeholder height (px)
bg string "white" Placeholder background colour
assets Record<string, string> {} Asset URL overrides
autoplay boolean true Auto-play when fully visible in viewport
loop boolean true Loop the animation when it ends
copyright boolean true Show a "Powered by Markdy" badge below the animation
progressBar boolean true Show a rainbow progress bar around the viewport border
class string CSS class for outer wrapper

Architecture

See docs/ARCHITECTURE.md for technical details.

  MarkdyScript source
        │
        ▼
  ┌─────────────┐
  │ @markdy/core │  parse() → SceneAST
  │  (parser)    │  Pure TS, zero deps
  └──────┬──────┘
         │ SceneAST
         ▼
  ┌──────────────────┐
  │ @markdy/renderer  │  createPlayer() → Player
  │  -dom             │  WAAPI + rAF loop
  └──────┬───────────┘
         │ Player
         ▼
  ┌──────────────────┐
  │ @markdy/astro     │  <Markdy /> island
  │  (optional)       │  SSR placeholder + IntersectionObserver
  └──────────────────┘

All WAAPI animations are permanently paused. A requestAnimationFrame loop manually sets anim.currentTime = sceneMs each frame. This avoids browser-specific quirks with startTime-based resumption and enables reliable seek().


Development

git clone https://github.com/HoangYell/markdy-com.git
cd markdy-com
pnpm install
pnpm build
pnpm test

Project Structure

packages/
  core/              @markdy/core         — Parser + AST types (zero deps)
  renderer-dom/      @markdy/renderer-dom — WAAPI renderer
  astro/             @markdy/astro        — Astro island component
website/               Official markdy.com playground & website (Astro)
docs/
  SYNTAX.md          Full DSL reference
  TUTORIAL.md        Step-by-step human tutorial
  AGENT.md           Guide for AI agents / LLMs
  ARCHITECTURE.md    Technical deep dive

Scripts

Command Description
pnpm build Build all packages and website
pnpm test Run all tests (vitest)
pnpm typecheck Type-check all packages
pnpm clean Remove all dist/ directories
pnpm run release <version> Bump version, commit, tag, and push to trigger CI/CD

Deployment (Cloudflare)

The project is deployed via Cloudflare Pages (Workers Assets). - Project Name: markdy-com - Build command: pnpm build - Deploy command: cd website && npx wrangler deploy - Path: / (repo root)


Documentation

Document Audience Description
SYNTAX.md All users Complete DSL language reference
TUTORIAL.md Humans Step-by-step guide from zero to animated scenes
AGENT.md AI agents / LLMs Structured prompt-ready reference for code generation
ARCHITECTURE.md Contributors Technical design, renderer internals, AST shape
CONTRIBUTING.md Contributors Dev setup, code style, PR guidelines

Contributing

See CONTRIBUTING.md for development setup and guidelines.


License

MIT © Hoang Yell

Extension points exported contracts — how you extend this code

ParseOptions (Interface)
(no doc)
packages/core/src/parser.ts
CameraState (Interface)
(no doc)
packages/renderer-dom/src/animations.ts
Feature (Interface)
(no doc)
scripts/regenerate-all.ts
Failure (Interface)
(no doc)
scripts/verify-examples.ts
MarkdyState (Interface)
(no doc)
website/src/editor/markdy-language.ts
PlayerOptions (Interface)
(no doc)
packages/renderer-dom/src/player.ts
Player (Interface)
(no doc)
packages/renderer-dom/src/player.ts
ArmGeometry (Interface)
(no doc)
packages/renderer-dom/src/figure.ts

Core symbols most depended-on inside this repo

parse
called by 119
packages/core/src/parser.ts
quote
called by 20
packages/core/src/presets.ts
cameraTx
called by 10
packages/renderer-dom/src/animations.ts
splitByComma
called by 6
packages/core/src/parser.ts
readRotation
called by 6
packages/renderer-dom/src/figure.ts
interpolate
called by 5
packages/core/src/parser.ts
round3
called by 4
packages/core/src/parser.ts
parseActionParams
called by 3
packages/core/src/parser.ts

Shape

Function 77
Interface 10
Method 5
Class 2

Languages

TypeScript100%

Modules by API surface

packages/core/src/parser.ts23 symbols
packages/renderer-dom/src/player.ts17 symbols
packages/renderer-dom/src/animations.ts10 symbols
scripts/regenerate-all.ts8 symbols
packages/compat/src/gate.ts7 symbols
packages/renderer-dom/src/types.ts6 symbols
packages/renderer-dom/src/figure.ts6 symbols
website/src/editor/markdy-language.ts5 symbols
packages/renderer-dom/tests/render.extended.test.ts5 symbols
scripts/verify-examples.ts4 symbols
packages/renderer-dom/src/actors.ts1 symbols
packages/core/tests/parser.test.ts1 symbols

For agents

$ claude mcp add markdy-com \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact