@algorisys/tinyfly
v0.98.0
Published
Lightweight, API-driven animation engine and editor. The engine is framework-agnostic and runs in browsers, Web Workers, and Node.
Maintainers
Readme
tinyfly
A lightweight, API-driven animation engine and visual editor for creating high-performance, embeddable animations.
Try it: studio · interactive course · examples · npm install @algorisys/tinyfly
GSAP-level power with Excalidraw-level simplicity.
Beta. tinyfly is in beta testing. Watch out for breaking changes: we keep them to a minimum, and when a release has any, its release notes say what changed and how to upgrade.
Features
Core Engine
- Framework-agnostic - Works in any JavaScript environment (browser, Node.js, Web Workers)
- JSON-first - Animations are fully serializable, perfect for AI generation and persistence
- Deterministic - Same input always produces the same output
- Lightweight - No heavy dependencies, minimal footprint
Animation Capabilities
- Timeline-based - Orchestrate multiple tracks with precise timing
- Rich easing - Linear, quad, cubic, custom cubic-bezier, and parametric
elastic/bounce/back/stepseases that stay one small, exact keyframe in JSON - Interpolation - Numbers, colors, and arrays
- Playback control - Play, pause, stop, seek, reverse, speed adjustment
- Looping - Finite loops, infinite loops, ping-pong (alternate), and
repeatDelaybetween iterations - Springs - Physical spring tracks, integrated at a fixed timestep from t=0 so they stay deterministic and serializable (the animation is the parameters, not a baked curve); scale-invariant, so
scale: 0→1andx: 0→100settle identically; authored in the editor with feel presets and sliders - Runtime stagger - One track fans across many targets (
each/amount/from), producing exactly what baking N tracks would — and ~15x smaller in JSON (measured: 100 letters is 11.7 KB baked, 0.8 KB as one track) - Track scheduling - Per-track
delayandendDelaywithout rewriting keyframes - Track queries -
getTracks(filter),removeTracks(filter), andfindConflicts()to surface overlapping writes - Compile-time values -
"+=100"and"random(-50, 50)"resolve at authoring time from a recorded seed, so the JSON holds plain numbers and replays identically
Scroll & Interaction
- Scroll-driven playback -
ScrollDriverscrubs a timeline against scroll position with GSAP-style trigger strings ('top bottom','top top+=500'); the geometry is a pure, unit-tested function - Play when visible -
VisibilityDriverplays on appearance (once/repeat/reset) via IntersectionObserver - Drag & pointer input -
Observernormalises pointer/touch/wheel;Draggablesupports bounds, axis lock, grid/edge snapping (shared with the editor stage), and drag-to-scrub - FLIP transitions -
flip()measures a layout change in the DOM layer and emits ordinary keyframes, keeping the engine free of live layout reads
Teaching Animations
- Markers and captions in the JSON - Named steps (
pause,questionfor predict-then-reveal) and captions per language, kept out of the tracks so translation never touches timing - A player that teaches - Shows a real frame on load, steps between markers (
next/prev/stepMode), respectsprefers-reduced-motion, and pauses off screen - One-script embeds -
tinyfly-embed.iife.jswithdata-tinyfly-automounts every[data-tinyfly-embed]figure with step controls, announced captions and an accessible SVG, with no per-post JavaScript - Full screen -
data-fullscreen="true"adds a button that fills the screen with the figure, through the Fullscreen API or, on iPhone, an overlay; the drawing scales to the screen whatever the page's figure CSS says - Scenarios - Several timelines on one figure, and the reader chooses which plays: a group of options, a stepped slider, or clickable parts of the SVG (
data-tinyfly-choose). Switching undoes the previous scenario's drawing and keeps the reader at the same step - Authoring kit -
lesson()step builder and diagram primitives (array cells, pointer, stack, queue / channel, table, pipeline) in@algorisys/tinyfly/teach - Build tools -
npx @algorisys/tinyfly validatecatches broken figures in CI;renderwrites a frame to static SVG for RSS, email and print
Video from Code
- Headless MP4 -
npx tinyfly video scene.mjsrenders a scene to H.264 MP4 in Node (no browser), frame by frame at exact times, with an optional soundtrack muxed in;--stillswrites one PNG mid-way through each caption line (or marker step) for layout checks - Code-drawn targets - A
customcanvas target draws with code (characters, charts, props) while the timeline animates its position, opacity and its ownprops, so drawing stays code and timing stays JSON - Immediate mode too - A scene's
backgroundanddraw(ctx, { time })functions paint each frame directly, Cairo/Processing-style, and mix freely with timeline targets - Timing from narration -
planNarration()lays spoken lines out from their clip lengths (lead, gap, tail) into cues, scene spans and markers;voiceNarration()measures recorded clips with ffmpeg and writes the sample-aligned narration WAV - Characters - A poseable stick figure (
@algorisys/tinyfly/characters): named poses, facial expressions (brows, eyes that widen, close and look around, mouth shapes), squash and stretch, rubber-hose limbs, blending, walk cycles and talking, all driven by timeline tracks. Characters v2: one skeleton in 3D drawn flat, turning front to side to back, resting on whatever touches the ground, reaching for points, in clean, pencil or silhouette looks, and a 🧍 Character element in the editor you pose and keyframe. Character appearance: 34 hairstyles, 20 facial-hair styles, glasses, hats and ears as plain data on the head (they turn with it through all eight views), builds from toddler to tall, a recurring cast (castMember), and 30 expressions with blush, tears and sweat (guide) - Dance and flips - The stick figure dances disco, hip hop, breaking toprock, jazz, K-pop, Bollywood, Bhangra, Bharatanatyam (with mudras), the Charleston, tap (foot strikes as data, for tap sounds) and popping (side glide, moonwalk, forward glide, which travel with the planted foot staying put), and does front, back, layout, scissor and side flips, cartwheels, back handsprings, split leaps, toe touches and full splits. Wrists, ankles, foot turn-out, whole-body
spinandriseare pose fields; moves are keyed in beats, so any tempo plays them; styles, grooves and routines are plain JSON (danceFrame(),danceTracks(),flipPose(),flipTracks()). In the editor a 🧍 Character dances from the playhead, in time with the music: an Audio element's tempo is detected (detectTempo()) or tapped, the timeline shows its beats and bars, and keyframes snap to them. See docs/dance.md - Maps - Animated maps on OpenStreetMap tiles or an offline world outline (also in pencil): the camera flies in, pins drop, routes draw themselves while a marker travels them; a 🗺 Map element in the editor with a one-click trip
- Pencil sketch style - Hand-drawn strokes with line boil (
sketchstyle,sketchPen) for a pencil-test, Pencilmation-like look, on the stick figure and on canvas rect / circle / line / path shapes (also from the editor's Properties panel); adrawOntrack draws any of those shapes on; the wobble is seeded by time, so renders stay deterministic - The drawing hand -
drawnPathTarget()draws a sketched stroke on from adrawtrack while a cartoon hand holds the pencil at its end in a writing grip; the same hand can hold the eraser.handAt()gives one hand for a whole scene: it follows each stroke, lifts and glides to the next, and enters and leaves the page - Erasing - An eraser rubs out part of a target (
erasable(), animated by anerasetrack) or of anything drawn in code (withErased()); clip-based, so it works in the browser and headless alike - Cartoon hands - A hand rig in 3D, drawn flat: thumb and fingers posed by numbers (
index.curl,thumb.across,spread,turn), ready-made shapes (fist, point, thumbs-up, peace, OK, pinch, wave, pencil grip…), left and right, four or five fingers, in every look; the animator's hand and (opt-in) character gloves are built on it. See docs/cartoon-hands.md - Cartoon acting -
actTracks()turns the same key poses into acted motion: wind-ups, overshoot that settles or wobbles, joints that overlap (hips lead, wrists drag), eyes that dart ahead and blink on head turns, moving holds and jump squash, infull,snappyorlimitedstyles. Gags come as data (gag('take'), double take, wind-up, tremble). A line of action (bend) curves the spine, and gaits have personality (bouncy, double bounce, sneak, strut, tired, run). Lip-sync works from text, Latin or Devanagari.scriptTracks()compiles a story written as beats ({ do: 'walk', to: 640 },{ do: 'take' },{ do: 'say', say: '…' }) into all of it.cameraTracks()films it with push-ins, cuts, shakes and follows. The cartoon zip ({ do: 'zip' }) has the legs wheel in place before it is gone. Figures can act on code, Animator vs Animation style:codePanel()lays a listing out so every line and word is a place in the scene, and beats leap onto lines, point at them and swipe them away ({ do: 'swipe', target: code.line(7) }), with the line wiped, knocked off the panel or blurred out of focus on the frames the hand crosses it. Words come loose too: a figure grabsvarand throws it, kicks another out of a loop, carries a stray;to where it belongs, pushes a word along its line (the text closing up behind it), and writesmutinto a line by hand. A figure standing on a line rides it when a gap above closes (code.ride()). Code is one surface;whiteboard()is another, a board or chalkboard of texts and hand-drawn circles, boxes, underlines and arrows, so a figure can work through a lesson that is not code,chart()a third, bar and line charts whose bars a figure points at, highlights and stands on as they grow, andpropSurface()makes any prop one, so a figure opens a car's door or switches a house's lights on. Places on a surface have names (line:7,token:4:Println,term:eq:+ 4), andsurfaceScript()takes a whole scene as JSON: beats aim at named places and say what the surface does in answer, at the moment the hand touches or lets go. Props give the story a world: cars, trucks, buses, tractors, carts, train carriages, bikes, motorbikes, trees, houses, a helicopter and a plane, a horse, dogs, cats, cows and birds are 3D rigs drawn with the figures' pens, so they turn toward the camera and boil in pencil, or as line art beside stick figures. They act from beats through the same principles (they rock back before they go, dip on the stop, skid, bump, sway, take off and loop; animals walk, trot and gallop with their feet keeping pace, sit and jump; birds hop, peck, flap and fly up to a perch), with an exaggeration dial, and figures can ride them. See docs/props.md. Your own behaviours are plain values:defineAction()(from beats or timed pose steps),defineGait(), andpersona()for a recurring character's look, acting style, gait, usual face and stance. In the editor, a Character's Acting section acts its keyed poses in a style and adds gags, walks in a gait and lip-synced lines, with drawing on twos as a timeline setting. Characters come in builds (slim, kid, broad, curvy, stocky), and the v2 human has its own line of action, gaits and gags, posed in 3D so they read from every view. It also adds speed lines from earlier frames (drawStickSmear()), dust puffs, impact stars, and drawing on twos for video (drawingRate). Output is plain keyframes. See docs/acting.md - Pencil story example -
examples/pencil-story-hindi/index.htmltells a short Hindi story (प्यासा राहगीर, the thirsty traveller) Pencilmation-style with the browser bundle alone: the hand draws the scene and writes the narration, then rubs out the pot - Captions -
toSRT()/toWebVTT()from narration cues or from a timeline's markers
Render Adapters
- DOM - CSS transforms, opacity, colors, clip-path reveal, filters, shine, transform-origin, perspective
- Canvas - Shapes with position, size, rotation, colors, clip reveal, filters, transform-origin pivot
- SVG - Attributes, transforms, clip-path reveal, filters, transform-origin, perspective
- WebGL - Minimal GPU target: textured/solid quads with transform, opacity, and tint
- Clip/mask reveal - Animatable clip-inset (
clipTop/Right/Bottom/Left) wipes elements into view, consistently across all three renderers - Filters - Animatable
blur,glow, and drop-shadow, composed identically across DOM, SVG, and Canvas
Text Animation
- Split text into letters - Break a text element into per-letter elements (Animate-style "break apart"), positioned to match the original layout
- Staggered animation - Fan any preset across the letters (or a multi-selection) with a per-letter delay — the primitive behind Animate-style drop, cascade, and wave effects
- Per-letter presets - Drop & Bounce, Cascade Up, Wave, Assemble, and Pop In, tuned to shine when staggered
- Typewriter reveal - Character-by-character typing with an optional blinking cursor that steps along; the timeline auto-extends to fit
- Scramble & type-on text tracks - Select a text element → Text Animation to scramble it into new words or type it on; a text-track inspector edits the words, character set, reveal delay, refresh rate and timing. Seeded, so previews and exports replay identically
- Filters - Animatable blur, glow, and drop-shadow (Blur In, Glow Pulse, Drop Shadow presets)
- Shine sweep - A highlight sweeps across the text, clipped to the glyphs, on all three renderers
- All JSON - A stagger, typewriter, or filter is just keyframe tracks, so it serializes, persists, and plays anywhere the engine runs
Playback & Sync
- Standalone player -
TinyflyPlayerloads animation JSON (and animates embedded symbol instances via their nested timelines) and plays it onto DOM elements; ships as an ESM/UMD/IIFE bundle for npm or CDN - Audio/video sync -
MediaSync/player.attachMedia()locks an audio or video element to the timeline clock (play/pause/seek/rate), correcting drift as it plays - Audio & video in the editor - Add an Audio or Video element (source, start time, volume, mute, loop; video also has object-fit); it plays in sync while you scrub and preview the timeline
- Media in embeds - Exported/embedded HTML carries the media, and the player auto-discovers and syncs it (
[data-tinyfly-media]), so audio/video play in time wherever the animation is embedded
Visual Editor
- AI prompt → animation - Describe an animation in plain language ("a title that fades up with a shine", "three cards sliding in one after another") and generate a fully editable timeline. Bring your own API key for OpenAI, Google Gemini, or Anthropic — keys stay in your browser and are sent directly to the provider. The model emits tinyfly's JSON schema, which loads through the same path as the editable examples, so generated animations are ordinary keyframes you can tweak
- Any canvas / aspect ratio - The preview artboard follows the project canvas (DOM, Canvas, and SVG renderers) and fits-to-view, so a vertical 9:16 promo or any custom size previews at true proportions; samples can declare their own canvas size
- Device-frame preset - One click stamps a device mockup (rounded body, camera/notch, and a rounded video "screen") sized to the canvas, in Phone / Landscape / Tablet variants — drop a screen-recording into the screen's source for an app promo
- Timeline view - Visual keyframe editing with drag-and-drop
- Camera - Animate a pan/zoom/rotate over the whole stage (a reserved
Cameralayer driven by ordinary tracks). One-click add, a 🎥 Camera inspector (Pan/Zoom/Rotation keyframed at the playhead), on-stage ✋ Pan dragging, and a dedicated 🎥 lane at the top of the timeline; applies in every preview, export, and embed. Ready-made Push In / Pan Across / Orbit Reveal samples - Polygon & star shapes - ⬡ Polygon and ★ Star elements are parametric paths — edit sides / points / inner ratio in Properties and they regenerate and rescale; they render and export like any path
- Pen tool - ✒️ Draw custom bezier paths: click for corners, click-drag for curves (Alt for a cusp), drag existing anchors/handles to adjust, snap points to grid/guides, click the first point or Enter to finish
- Loop check -
tinyfly video scene.mjs --loop-checkrenders five frames and says whether a video loops: whether the frame at the duration matches the first, and whether the last → first seam looks like any other step (exit 1 when it does not).--frames 0,79,88renders stills at chosen frames. See docs/video-rendering.md - Bloom -
bloom: trueon a video scene adds a deterministic glow around bright things (bright pass, blur, add; noctx.filter), and 3D materials takeemissivelight that shines in the dark and feeds it.applyBloom(ctx)works on any 2D canvas. See docs/video-rendering.md - Light trails -
trailSamples+drawTraildraw comets and light streaks on any 2D canvas: a tapering, fading band with a round head, smooth at any speed, and deterministic (the trail is where the thing was, asked of the timeline, not a frame history). 3D scenes getlineandtrailobjects whose segments sort by depth among the meshes. See docs/video-rendering.md and docs/scene-3d.md - 3D scenes -
@algorisys/tinyfly/scene-3d: cameras, lights and meshes (box, sphere, cylinder, cone, torus, floor, any SVG path extruded) as JSON in metres, animated by ordinary tracks (stage/box), cut between cameras with one keyframe, drawn on a plain canvas with flat, smooth or toon shading, ink outlines and fog: in the browser, in Workers, and to MP4 in Node. Characters stand in scenes and dance, seen by any camera, in their pen looks (clean, pencil) or as solid figures (characterObjects); props (cars, houses, trees, animals, birds) stand in them by preset name (propObjects), sorted a column at a time so figures walk round them rightly, and lit by the scene's lights (a sunset, a night with lit windows). Both are scripted in world metres (propScript3D,characterScript3D): drive or walk to a point or along a path, wheels and feet keeping pace with the ground. In the editor, a 🧊 3D Scene element builds them without code: add shapes, characters and lights, key them, dance from the playhead, cut between cameras. No WebGL needed (about 7 KB gzipped, its own entry and CDN script); for big or tangled scenes the optional WebGL2 renderer (@algorisys/tinyfly/scene-3d/webgl) adds a depth buffer and light per pixel. See docs/scene-3d.md - 3D transforms - Cards, images, text and shapes turn in 3D (
rotateX,rotateY,z,perspective,backfaceVisibility) and land on the same pixels in DOM, SVG, Canvas (true perspective, also headless) and WebGL, checked against each browser's own CSS. One documented transform order everywhere; Tilt / Turn / Depth / Perspective fields in the editor; 3D Card Flip and Cover Flow samples. Quaternion tracks withinterpolation: "slerp"turn without gimbal lock; the engine'svec3,quatandmat4helpers are plain arrays. The first steps of 3D support. See docs/3d-rotations.md - Shape morph - 🌀 Tween one path into another over the timeline (engine-level path interpolation); plays in every renderer, export, and embed
- Grid, snapping & guides - ▦ 20px grid, 🧲 snapping of drag/resize to grid/element-edges/artboard with live alignment guides, and 📏 rulers with draggable guides
- Onion skinning - 🧅 Faint ghost frames before/after the playhead (Canvas renderer) to see the arc of a move while editing one frame
- Curve editor - Switch the timeline between the Dope Sheet (keyframes & timing) and a Curves graph view where each numeric track is a value-over-time curve with the real easing drawn between keyframes; drag points in 2D (time + value), drag the easing handles to shape the cubic-bezier, double-click a lane to add a keyframe, Ctrl/Cmd-click to multi-select
- Timeline zoom & scroll - Zoom the timeline with Ctrl/⌘+scroll or the −/+ control and pan with the scrollbar or Shift+scroll (shared across both views)
- Scene duration you can author - Click the
current / durationreadout to type an exact length in seconds. The timeline auto-extends when a keyframe is added or dragged past the end, so late keyframes always play; the end of the scene is drawn as a dashed marker with the unreachable region dimmed, and a Fit button snaps the duration back out to the last keyframe after a manual trim - Box-select & curve overlay - Rubber-band-select keyframes in either timeline view; in Curves, toggle Overlay to compare all tracks on one shared axis. Scene tabs show live per-scene thumbnails
- Symbols & Library - Bundle elements into a reusable symbol (Convert to Symbol), place instances across scenes, edit in place (double-click an instance), give a symbol its own timeline for nested animation, and swap which symbol an instance shows over time (lip-sync) — all JSON-serialized
- Multi-select keyframes - Ctrl/Cmd-click to select many keyframes; copy/paste (at the playhead) and delete them together
- Property panel - Edit keyframe values and easing
- Track management - Add, remove, and organize animation tracks
- Per-letter stagger - Toggle in the preset panel to split text and fan a preset across its letters
- Playback controls - Preview animations in real-time
- Undo/Redo - Unified history across the timeline and the scene: one Ctrl+Z reverses the last change of either kind (element add/move/resize/group/property edits or keyframe/track edits); drags collapse to a single step
- Multiple scenes - Organize animations into separate scenes with independent elements and timelines
- Scene transitions - Configurable transitions between scenes (fade, slide)
- Multi-scene sequencer - Play all scenes in order with transitions
- My Animations gallery - Every project you make is saved and browsable in a thumbnail grid; open one to keep editing, duplicate, or delete it. Persisted to IndexedDB (LocalStorage projects are migrated automatically on first run), so you can accumulate many animations without the old single-slot limit
- Collapsible panels - Hide the Elements/Tracks and Properties/Presets columns to give the canvas more room; a slim tab brings each back
- Rename & save - Double-click the project title to rename it inline; a Save button shows a clear Save / Saving… / Saved ✓ status (handy on touch devices) on top of continuous auto-save
- Project management - Auto-save to IndexedDB (with LocalStorage fallback)
- Export/Import - JSON file support
- Named exports - Choose the download filename in the Export dialog for every format; it defaults to the project name and is sanitized for any filesystem
- MP4 export - Encode the animation to a real MP4 (H.264) via WebCodecs, with a hand-written muxer and no dependencies. Frame-by-frame rather than real-time, so it's faster than playback and reproducible; MediaRecorder (WebM) is the fallback where WebCodecs is missing
- Animated GIF export - Median-cut colour quantization with optional Floyd–Steinberg dithering, per-frame palettes, and transparency
- Animated WebP export - Roughly 3× smaller than GIF at true colour, with full alpha
- Sprite-sheet export - Every frame packed into one PNG grid plus a JSON metadata file (frame size, columns/rows, count, fps) — ready for game engines or a custom
<canvas>player; alpha kept when transparent - Rich raster export options - Resolution multiplier (2x by default, so text and edges stay crisp), FPS, background colour or transparency, progress and cancel. Image and video layers are composited too — a device screen's recording is captured with
object-fitcover/contain and rounded corners, seeked in sync - Resizable preview - Drag the splitter between the preview and the timeline to resize (double-click to reset)
- Stroke write-on - Draw a shape's outline on: paths in every renderer, and rectangles, circles and lines on Canvas (a
drawOntrack); one-click "Write On" preset, which pairs with Pencil Sketch - Embed code - Generate copy-paste code for websites (single scene or full sequence)
- Landing page -
/introduces tinyfly with tinyfly itself: a masked headline, a pointer-led canvas, a live code playground that shows the JSON its code compiles to, a pinned feature story, a gallery and a copyable script tag, with a reduced-motion mode. It loads without the editor, which lives at/studio(lazy-loaded, as are Examples, Showcase and Docs) - Learn - An interactive course at
/learn: short steps with live code, a preview you can scrub, and checks that read what your code compiled to. Nine modules, 28 lessons, 67 steps: Foundations (animation as JSON), the GSAP-style API, the Editor (build in the studio, Copy JSON, compare with code), Motion craft (timing, anticipation, springs), Text and SVG, Interaction (hover, quickTo, drag and throw, Flip, canvas), Scroll (reveals, scrub, pinning, velocity, smooth scrolling with parallax), Accessibility and performance (a real reduced-motion mode, keyboard parity, transforms, pausing off-screen work), and a Capstone that rebuilds the Agency Landing Page section by section, from the pointer-lit hero canvas to the magnetic button. Mistakes that would fail silently (a selector that matches nothing,drawSVGon a non-shape, a spring on a colour) show as plain-language warnings beside the code, and the last step of each module offers Copy as page and, for JSON lessons, Open in editor. The course is also inllms-full.txt - Examples - One page (
/examples, the Examples toolbar button), and a shareable page for every example at/examples/<id>for every ready-made animation: editable examples open in the editor as a new project, code examples show their timeline JSON and HTML to copy. Includes a full-page Agency Landing Page showcase and a GSAP-style section of 41 runnablelive.to()demos (Flip layouts and shared elements, pinned horizontal scroll, line mask reveals, spring release, canvas from object tweens, motion paths, orbits, shape and menu morphs, scramble text, draggable throws, swipe cards, 3D card flips, magnetic button, proximity grid, marquee, split text, SVG draw…), and every card has Copy code for a complete standalone HTML page. Hover-to-play previews, search, and filters for kind and category (GSAP-style, Showcase, Basics, Motion, Text, UI, Loaders, Effects, Data, Camera, Scroll, and Algorisys product demos)
Documentation
- Getting Started — Installation, setup, and your first animation
- Building Animations with an LLM — The capability catalog,
describeTarget, did-you-mean checks for beats and tracks, rendering stills to look at, and the agent skill - Editor Guide — Complete guide to the visual editor (elements, timeline, scenes, presets, shortcuts)
- API Reference — Full engine, player, adapter, and export API documentation
- File Format — The tinyfly JSON format (animation documents, timelines, projects, sequences) for integrations
- Examples — Code examples for common animation patterns
- Scroll Animation — Scroll-driven and visibility-triggered playback via drivers
- GSAP Compatibility — The GSAP-flavoured API, the mapping table, and what we deliberately don't do
- Rendering Video from Code —
tinyfly video, custom code-drawn targets, narration timing, captions and stills, without a browser - Teaching Animations — Step-through figures: markers, captions, the stepping player, controls, declarative embeds, scenarios the reader chooses,
tinyfly/teach, validate and render - Extending tinyfly — Writing adapters, custom eases and stagger offsets, adding a track kind, contributing examples
- Deployment — Hosting, Docker, and CDN configuration
- 2D Animation Roadmap — Adobe Animate gap analysis and phased plan (symbols/library, camera, onion skinning, …)
Feature guides: Character appearance · Symbols & Library · Camera · Polygon & Star · Pen tool · Shape morph · Grid & snapping · Onion skinning · Sprite-sheet export
Docs for LLMs: the repo root has llms.txt, an llmstxt.org index of these docs, and llms-full.txt: every doc, the course, and the capability catalog in one file. Both ship in the npm package with the docs and an agent skill (skills/tinyfly/SKILL.md), so an assistant finds them in node_modules at the installed version. npx @algorisys/tinyfly capabilities prints the catalog: every pose, expression, gag, gait, beat action, canvas property and code-panel edit, read from the library, so a name in it is accepted and a name not in it is rejected with the name probably meant (tinyfly check beats.json, checkTracks). The built editor also serves /llms.txt, /llms-full.txt and each page as raw markdown at /docs/<page>.md.
Installation
npm install @algorisys/tinyflyOr use it with no build step from the GitHub CDN.
It also installs a tinyfly command for build pipelines:
npx tinyfly validate figure.json --markup figure.svg # fails the build on broken figures
npx tinyfly render figure.json figure.svg --at end > end.svg # a static frame for RSS, email, print
npx tinyfly video scene.mjs -o scene.mp4 --srt scene.srt # a whole video, no browser (needs ffmpeg + @napi-rs/canvas)The package ships these entry points. Each is tree-shakeable, so you pay only for what you import:
| Import | What it is | Environments |
|---|---|---|
| @algorisys/tinyfly | The engine — Timeline, tracks, easing, JSON | Browser, Web Worker, Node |
| @algorisys/tinyfly/player | TinyflyPlayer, MediaSync, sequencer — plays editor JSON on the DOM | Browser |
| @algorisys/tinyfly/export | exportToCSS, exportToLottie, GIF / WebP / MP4 / sprite-sheet export, SRT / WebVTT captions | Browser (CSS, Lottie and captions anywhere) |
| @algorisys/tinyfly/adapters | DOMAdapter, CanvasAdapter, SVGAdapter, WebGLAdapter — apply timeline state to a render target | Browser |
| @algorisys/tinyfly/gsap-compat | GSAP-style live.to() / timeline(), plus the compiling tf facade | Browser (tf anywhere) |
| @algorisys/tinyfly/drivers | ScrollDriver, VisibilityDriver | Browser |
| @algorisys/tinyfly/interaction | Observer, Draggable | Browser |
| @algorisys/tinyfly/embed | Teaching embeds: the player with step controls, captions, scenarios and one-script [data-tinyfly-embed] mounting; validateEmbed, validateScenarios, renderFrame | Browser (tools anywhere) |
| @algorisys/tinyfly/teach | lesson() step builder and diagram primitives (cells, pointer, stack, queue, table, pipeline) | Anywhere |
| @algorisys/tinyfly/react, @algorisys/tinyfly/vue, @algorisys/tinyfly/svelte, @algorisys/tinyfly/solid | useTinyfly hooks, a Svelte action and a Solid primitive: live animations scoped to a component and reverted on unmount | Browser (frameworks are optional peer dependencies) |
| @algorisys/tinyfly/maps | Animated maps: OpenStreetMap tiles or an offline world outline, places, routes that draw on, a camera that flies (mapTarget, mapFlyTracks, fitView, WORLD_CITIES) | Anywhere with a Canvas 2D context (tiles need a network) |
| @algorisys/tinyfly/characters | Poseable stick figure: stickFigureTarget, poseTracks, POSES, blendPose, walkPose, drawStickFigure; stickFigureJoints and layers for costumes, hair and props; turn, sit and ground contact; an organic look by default (classic keeps the old one); characters v2: character() / characterTarget(), turnarounds front to back, contact, reaching, clean / pencil / silhouette looks; hair, facialHair, glasses, hat, ears, build, outfit as data, castMember(), HUMAN_EXPRESSIONS with face marks | Anywhere with a Canvas 2D context |
| @algorisys/tinyfly/headless | renderVideo, renderStills, FrameRenderer: render a scene to MP4 or PNGs (see Rendering Video from Code) | Node (@napi-rs/canvas optional peer, ffmpeg) |
| @algorisys/tinyfly/browser | One script-tag bundle (tinyfly.iife.js): the engine, player, drivers, interaction, the GSAP-style live / tf facades, teaching-embed controls and mounting, and the characters stick figure. Not included: export, adapters, teach, the framework hooks, headless | Browser |
// The framework-agnostic engine (browser, Web Worker, or Node)
import { Timeline, createTrack } from '@algorisys/tinyfly'
// The DOM player + media sync (browser)
import { TinyflyPlayer, MediaSync } from '@algorisys/tinyfly/player'
// Render adapters: apply timeline state to DOM, Canvas, SVG or WebGL (browser)
import { DOMAdapter } from '@algorisys/tinyfly/adapters'
// GSAP-style animation of real elements (browser)
import { live } from '@algorisys/tinyfly/gsap-compat'
live.to('.box', { x: 200, duration: 1, ease: 'power2.out' })// React: everything the setup creates is reverted when the component unmounts
import { useRef } from 'react'
import { useTinyfly } from '@algorisys/tinyfly/react'
function Hero() {
const root = useRef(null)
useTinyfly((live) => live.from('.title', { y: 40, opacity: 0 }), { scope: root })
return <section ref={root}><h1 className="title">Hello</h1></section>
}TypeScript declarations ship with every entry point. Works with any framework or none; the engine has no dependencies. See framework hooks for Vue, Svelte and Solid.
Use from a <script> tag (no build step)
Every release publishes browser bundles to the cdn/ folder of
algorisys-oss/tinyfly, tagged with
its version, and jsDelivr serves them straight from
GitHub. No npm required.
tinyfly.iife.js (~53 KB gzipped) puts everything on one tinyfly global, with
GSAP-shaped functions at the top level. Teaching embeds are included: add
data-tinyfly-auto to the script tag and every [data-tinyfly-embed] figure mounts
itself.
<script src="https://cdn.jsdelivr.net/gh/algorisys-oss/[email protected]/cdn/tinyfly.iife.js"></script>
<script>
tinyfly.to('.box', { x: 200, rotate: 90, duration: 1, ease: 'power2.out' })
tinyfly.timeline({ repeat: -1, yoyo: true })
.fromTo('.dot', { opacity: 0, y: 20 }, { opacity: 1, y: 0, duration: 0.4, stagger: 0.1 })
.to('.title', { scale: 1.1, duration: 0.3 })
// The player, engine, drivers and interaction are on the same global
const player = new tinyfly.TinyflyPlayer('#stage')
player.load('animation.json').then(() => player.play())
</script>| File | What |
|---|---|
| cdn/tinyfly.iife.js | Everything, on a tinyfly global, including teaching embeds |
| cdn/tinyfly.umd.js | The same, as UMD |
| cdn/tinyfly.esm.js | The same, as an ES module: import { live } from '…/cdn/tinyfly.esm.js' |
| cdn/tinyfly-player.iife.js | Player only (~17 KB gzipped), for playing editor exports |
| cdn/tinyfly-embed.iife.js | Only teaching figures (~19 KB gzipped): player, step controls, auto-mount |
Replace @v0.71.0 with the version you want. Pin a version in production:
a tag's files never change, and each release's cdn/README.md lists an SRI hash
for integrity=. @main follows the latest release, which jsDelivr caches for up
to a day. Loading more than one bundle is safe: they add to the same tinyfly
global.
A teaching figure needs no code at all:
<figure data-tinyfly-embed data-options='{"stepMode": true}'>
<svg viewBox="0 0 720 200">…<rect data-tinyfly="cell-3" …/>…</svg>
<script type="application/json" data-tinyfly-timeline>{ …timeline JSON with markers… }</script>
<figcaption>Appending to a full slice</figcaption>
</figure>
<script src="https://cdn.jsdelivr.net/gh/algorisys-oss/[email protected]/cdn/tinyfly.iife.js" data-tinyfly-auto></script>See Teaching Animations.
Every card on the Examples page has Copy code, which gives you a complete HTML page already using these URLs.
Build the distributable libraries
npm run build:libs # all bundles + type declarations -> lib/This produces:
lib/engine/tinyfly-engine.js(ESM) and.umd.cjs— the enginelib/player/tinyfly-player.{es,umd,iife}.js— the standalone DOM playerlib/addons/{adapters,export,gsap-compat,drivers,interaction,teach}.js— the optional entry pointslib/frameworks/{react,vue,svelte,solid}.js— the framework hookslib/embed/tinyfly-embed.{iife.js,js}— teaching embeds;lib/cli/tools.js— thetinyflycommandlib/browser/tinyfly.{iife,umd}.jsandtinyfly.js— the all-in-one bundlelib/types/**— TypeScript declarations
Quick Start
Using the Editor
# Clone the repository
git clone https://github.com/algorisys-oss/tinyfly.git
cd tinyfly
# Install dependencies
npm install
# Start the development server
npm run devOpen http://localhost:5173 for the landing page; the visual editor is at /studio.
Using the Engine (API)
import { Timeline, createTrack } from '@algorisys/tinyfly';
// Create a timeline
const timeline = new Timeline({
id: 'my-animation',
config: { duration: 2000, loop: -1 }
});
// Add a track
timeline.addTrack(createTrack({
id: 'opacity',
target: 'box',
property: 'opacity',
keyframes: [
{ time: 0, value: 0 },
{ time: 1000, value: 1, easing: 'ease-out' },
{ time: 2000, value: 0, easing: 'ease-in' }
]
}));
// Play the animation
timeline.play();
// Animation loop
function animate() {
timeline.tick(16.67); // ~60fps
const state = timeline.getStateAtTime(timeline.currentTime);
// Apply state to your elements...
requestAnimationFrame(animate);
}
animate();Embedding Animations
With a bundler (Vite, webpack, Next.js…):
import { play } from '@algorisys/tinyfly/player'
// Load from a JSON file (e.g. exported from the studio)
play('#animation', './animation.json', { loop: -1 })
// Or pass the JSON directly
play('#animation', { id: 'my-animation', config: { duration: 1000 }, tracks: [/* … */] })Without a build step, the player bundle puts the same functions on a tinyfly global:
<div id="animation">
<div data-tinyfly="box" style="width: 60px; height: 60px; background: #4a9eff;"></div>
</div>
<script src="https://cdn.jsdelivr.net/gh/algorisys-oss/[email protected]/cdn/tinyfly-player.iife.js"></script>
<script>
tinyfly.play('#animation', './animation.json', { loop: -1 })
</script>The player automatically finds target elements by:
data-tinyfly="name"attribute- Class name
.name - ID
#name
API Reference
Timeline
const timeline = new Timeline({
id: string,
name?: string,
config?: {
duration?: number, // Total duration in ms
loop?: number, // -1 for infinite, 0 for none, n for n times
alternate?: boolean, // Ping-pong effect
speed?: number // Playback speed multiplier
}
});
timeline.play();
timeline.pause();
timeline.stop();
timeline.seek(timeMs);
timeline.reverse();
timeline.tick(deltaMs);
timeline.getStateAtTime(timeMs);Track
const track = createTrack({
id: string,
target: string, // Element identifier
property: string, // Property to animate
keyframes: [
{
time: number, // Time in ms
value: number | string, // Value at this keyframe
easing?: EasingType // Easing to next keyframe
}
]
});Easing Types
Built-in easing types:
linearease-in,ease-out,ease-in-outease-in-quad,ease-out-quad,ease-in-out-quadease-in-cubic,ease-out-cubic,ease-in-out-cubic
Custom cubic-bezier easing:
{
type: 'cubic-bezier',
points: [0.42, 0, 0.58, 1] // [cp1x, cp1y, cp2x, cp2y]
}Parametric eases, evaluated exactly when played:
{ type: 'elastic', mode: 'out', amplitude: 1, period: 0.3 }
{ type: 'bounce', mode: 'in-out' }
{ type: 'back', mode: 'out', overshoot: 1.70158 }
{ type: 'steps', count: 5, position: 'end' } // CSS steps() jump positionsPlayer
import { TinyflyPlayer, play, create } from '@algorisys/tinyfly/player';
// Quick play
const player = await play('#container', 'animation.json', options);
// Manual control
const player = new TinyflyPlayer('#container', options);
await player.load('animation.json');
player.play();
player.pause();
player.seek(500);
player.setSpeed(2);
player.destroy();
// Teaching: markers, steps and captions
const lessonPlayer = new TinyflyPlayer('#figure', { stepMode: true, playWhenVisible: true });
await lessonPlayer.load('slice.json'); // shows the first frame straight away
lessonPlayer.next(); // animate to the next marker
lessonPlayer.prev(); // jump back a step
lessonPlayer.caption(); // the current step's caption, in the page's languageOr skip the code entirely with declarative embeds (see Teaching Animations):
<figure data-tinyfly-embed data-options='{"stepMode": true}'>
<svg viewBox="0 0 720 200">…</svg>
<script type="application/json" data-tinyfly-timeline>{ …timeline JSON… }</script>
</figure>
<script src="https://cdn.jsdelivr.net/gh/algorisys-oss/[email protected]/cdn/tinyfly-embed.iife.js" data-tinyfly-auto></script>Audio / Video Sync
Attach a media element so it stays locked to the timeline clock (the timeline stays the source of truth; the media follows play/pause/seek/rate and drift is corrected as it plays).
const player = new TinyflyPlayer('#container');
await player.load('animation.json');
const audio = document.querySelector('audio');
player.attachMedia(audio, { offset: 0 }); // start media at timeline t=0
player.play(); // audio plays in sync
player.seek(2000); // audio jumps to 2s
player.detachMedia();
// Or use the primitive directly with any { currentTime, paused, play, pause }:
import { MediaSync } from '@algorisys/tinyfly/player';
const sync = new MediaSync(audio, { driftTolerance: 0.15 });
sync.update(timelineMs, isPlaying);Sequencer (Multi-Scene)
import { TinyflySequencer, playSequence } from '@algorisys/tinyfly/player';
// Quick play all scenes in sequence
const sequencer = await playSequence('#container', 'sequence.json', {
loop: -1,
autoplay: true,
onSceneChange: (index) => console.log(`Scene ${index}`),
});
// Manual control
const sequencer = new TinyflySequencer('#container', options);
await sequencer.load('sequence.json');
sequencer.play();
sequencer.pause();
sequencer.goToScene(2);
sequencer.destroy();
// Properties
sequencer.currentSceneIndex; // Current scene index
sequencer.sceneCount; // Total number of scenes
sequencer.isPlaying; // Playback stateTransition types: none, fade, slide-left, slide-right, slide-up, slide-down
Scroll, springs, and GSAP-style authoring
Three optional entry points sit outside the engine. Each is opt-in and tree-shakeable, so an embed that only plays an animation pays nothing for them.
import { ScrollDriver, VisibilityDriver } from '@algorisys/tinyfly/drivers'
import { Observer, Draggable } from '@algorisys/tinyfly/interaction'
import { timeline, quickPlay } from '@algorisys/tinyfly/gsap-compat'Scroll-driven animation is a driver, not a special timeline. The engine is a pure function of time; a driver is what decides which time to hand it. So any existing animation becomes scroll-driven without changing it — and the editor's ⇅ Scroll preview runs that same driver against a real scroll container, so triggers you tune there behave identically on your page:
new ScrollDriver({
timeline,
trigger: document.querySelector('#panel')!,
start: 'top bottom',
end: 'bottom top',
scrub: true,
}).start()Springs are a track kind, integrated at a fixed timestep from t=0. That is what lets them be physical and deterministic and serializable at once — the animation is the parameters, so it exports like any other track:
timeline.addTrack({
id: 'pop', target: 'box', property: 'scale',
kind: 'spring',
spring: { from: 0, to: 1, stiffness: 200, damping: 12 },
})Flip: live.flip('.item', () => reorder()) animates any layout change — reorders, class toggles, filters, resizes — from where elements were to where they land, and interrupts smoothly.
Inertia and dragging: throw elements with live.draggable(el, { bounds, inertia: { end: slots } }) or the inertia tween option. Friction is exact closed-form decay, snapping lands precisely, and it all serializes like any track.
Text animation types, backspaces and scrambles text (text and scrambleText), deterministically: scramble characters are seeded, so scrubbing and exports replay exactly.
Shape morphing turns any path into any other (morphSVG in live and tf): subpaths are paired, the start point and direction are chosen so nothing twists, and corners stay sharp.
Motion paths follow SVG path data, points, or (with live) an SVG element on the page — align lays the path over the element where it is drawn and autoRotate turns the follower to face along it. The path parser handles everything design tools export, and followers move at an even speed.
Award-site motion on the same live API, all compiled to ordinary tracks:
scrollTrigger— scrub (exact or smoothed),pin(the sticky recipe automated),toggleActions,once, velocity inonUpdate; scrolling does no layout readslive.splitText()— characters, words and rendered lines, with clipping masks and accessible labelsdrawSVG— stroke drawing by length or segment ('20% 80%')spring— presets or stiffness/damping/mass, carrying the momentum of whatever it interrupts or a drag's release velocity- Flip shared elements —
data-flip-idgrows a thumbnail into a different hero element - Plain-object targets and
live.ticker— drive canvas, Three.js or shader uniforms on the same frame as the DOM - Survives resizes and breakpoints — function values,
invalidateOnRefresh,splitTextautoSplit,live.matchMedia()(a real reduced-motion mode) andlive.context()cleanup live.utils(clamp, mapRange, interpolate, wrap, snap, seeded random, distribute, pipe…),"random(…)"values,repeatRefresh,live.getProperty; native elastic / bounce / back / steps eases- Teaching animations (guide): markers and captions in the JSON, a player that steps, respects reduced motion and pauses off screen, step controls with predict-then-reveal questions, one-script declarative embeds,
@algorisys/tinyfly/teachdiagram primitives, andnpx @algorisys/tinyfly validate/render - Timeline callbacks and control:
tl.call,tl.addPause,tl.tweenTo/tweenFromTo,onRepeat,onReverseComplete, tween callbacks inside timelines,live.delayedCall,live.killTweensOf live.quickTofor pointer and scroll-driven values;snap,markersandcontainerAnimationon scroll triggerslive.smoothScrolleased wheel scrolling withdata-speed/data-lagparallax, on the real scroll position so triggers and pins keep workinglive.imageSequencescroll-scrubbed frame sequences,live.pageTransitionroute changes with shared elements, andCustomEase/CustomBounce/CustomWiggle
See the Agency Landing Page showcase on the Examples page (/showcase/agency-landing).
GSAP-flavoured authoring desugars a familiar API into ordinary tracks:
const tl = timeline()
tl.fromTo('box', { x: 0, opacity: 0 }, { x: 200, opacity: 1, duration: 1, ease: 'power2.out' })
tl.to(['l1', 'l2', 'l3'], { y: 0, duration: 0.5, stagger: 0.08 }, '-=0.25')
quickPlay({ timeline: tl.timeline, targets: { box: '#box', l1: '#l1', l2: '#l2', l3: '#l3' } })For a page that just wants things to move, live plays straight onto
elements — CSS selectors, elements or node lists, no target map, no loop:
import { live } from '@algorisys/tinyfly/gsap-compat'
live.to('.card', { y: -20, opacity: 1, duration: 0.6, stagger: 0.08, ease: 'power3.out' })
live.to('#logo', { rotate: 360, duration: 2, repeat: -1 })
live.timeline({ repeat: -1, yoyo: true })
.to('.a', { x: 120, duration: 0.5 })
.to('.b', { scale: 1.4, duration: 0.5 }, '<')Separate live animations on the same element compose (an x tween and a
rotate tween both apply) because they share one frame loop and one adapter.
It is familiar, not compatible — GSAP code will not run unchanged. Anything authored through it is ordinary tinyfly JSON that opens in the editor. Read docs/gsap-compat.md for the mapping table and, more importantly, for what we deliberately don't do and why.
Architecture
tinyfly/
├── src/
│ ├── engine/ # Framework-agnostic core
│ │ ├── core/ # Timeline, Track, Clock
│ │ ├── interpolation/# Easing and interpolators
│ │ └── serialization/# JSON import/export
│ ├── adapters/ # Render adapters
│ │ ├── dom/ # DOM/CSS adapter
│ │ ├── canvas/ # Canvas 2D adapter
│ │ ├── svg/ # SVG adapter
│ │ └── webgl/ # WebGL adapter
│ ├── editor/ # Visual editor (SolidJS)
│ │ ├── components/ # UI components
│ │ └── stores/ # State management
│ └── player/ # Lightweight embed playerRoadmap
Coming Soon
- [x] Multiple preview elements (shapes, images, text)
- [x] More animatable properties (colors, borders, shadows)
- [x] Copy/paste keyframes
- [x] Multi-select keyframes
- [x] Visual curve editor for custom easing
- [x] npm engine package + CDN player build (
npm run build:libs) - [x] All-in-one
<script>bundle with a GSAP-shapedtinyflyglobal, andlive.to()that plays on real elements - [x] Publish to npm as
@algorisys/tinyfly
Future
- [x] Scene transitions (fade, slide between scenes)
- [x] Multi-scene player/sequencer
- [x] Per-letter text animation (split + stagger)
- [x] Typewriter reveal (char-by-char + blinking cursor)
- [x] Clip/mask reveal (wipe presets, all adapters)
- [x] Animatable filters (blur, glow, drop-shadow)
- [x] Shine sweep (highlight clipped to glyphs, all renderers)
- [x] Audio/video sync (
MediaSync/player.attachMedia()) - [x] WebGL adapter (minimal: textured/solid quads with transform, opacity, tint)
- [x] Scroll-driven playback + visibility triggers (
@algorisys/tinyfly/drivers) - [x] Drag / pointer interaction layer (
@algorisys/tinyfly/interaction) - [x] Deterministic spring tracks
- [x] FLIP layout transitions
- [x] GSAP-flavoured compat facade (
@algorisys/tinyfly/gsap-compat) - [x] Scroll-scrub preview in the editor (runs the real driver, not a simulation)
- [x] Overlapping-track warnings in the Tracks panel
- [x] Spring curves in the graph editor
- [x] Spring parameter editing in the editor UI (presets + sliders, auto-extending scene)
- [x] Spring presets in the Presets panel (Pop, Drop, Slide, Wobble, Settle)
- [ ] Export-time collapse of baked staggers into runtime stagger tracks
Planned — Phase 27 (closing on GSAP):
- [x] Cross-browser checks (
npm run e2e): Chromium, Firefox and WebKit pass - [x] Performance benchmark against GSAP (results — ~3x slower per frame; the DOM adapter is 75% of our cost, not the engine)
- [ ] Exercise the WebGL adapter against a real GL context (only its maths is tested)
- [ ] Load-time value resolution + responsive variants (the serializable answer to function values and
matchMedia) - [x] Inertia / throw as an
inertiatrack kind - [x] Text tracks: type-on and scramble text
- [x] CustomEase, CustomBounce, CustomWiggle
- [x] Framework wrappers (
@algorisys/tinyfly/react,/vue,/svelte,/solid) - [x] Scroll pinning (
scrollTrigger: { pin }), split text, drawSVG, springs and shared-element Flip onlive(Phase 28) - [x] Interactive tutorial at
/learn— 9 modules from keyframes to an award-style landing page, checked in three browsers (Phase 29) - [x] GSAP parity: native eases,
live.utils, keyframes, grid staggers, rotation dragging, scroll batch / scroll-to, smooth scrolling, timeline callbacks (Phase 29E) - [x] Teaching embeds: markers, captions, stepping player, controls, declarative mounting,
tinyfly/teach, validate / render (Phase 30) - [ ] Editor UI for markers and captions
- [ ] Nested timelines at runtime, explicit track priority
- [ ] React Native adapter
- [ ] Collaborative editing
Development
# Install dependencies
npm install
# Start dev server
npm run dev
# Run tests
npm test
# Cross-browser checks in Chromium, Firefox and WebKit (see e2e/README.md)
npx playwright-core install firefox webkit # once
npm run e2e
# Build for production
npm run buildPublishing to npm
npm login # once
npm run release:npm # npm publishrelease:npm runs npm publish. npm then runs the prepublishOnly script,
which runs the unit tests and npm run build:libs, so a failing test stops the
publish. npm will not publish the same version twice, so bump version in
package.json (and add a note under release-notes/) before you publish.
Cross-browser checks
npm run e2e runs tinyfly in real Chromium, Firefox and WebKit and measures the
results rather than just looking for errors:
- Engine: the path parser against each browser's own SVG geometry, determinism,
and
structuredClone. - Adapters: transforms, shine clipped to the glyphs (
background-clip: text), SVG rotating in place (transform-box), and canvas drawing, all checked by the pixels and boxes actually rendered. - Exports: GIF, WebP and MP4 (WebCodecs).
- Demos: every GSAP-style demo animating under real mouse input, plus Flip and motion-path precision.
- Editor: adding elements, playing in all three renderers, and IndexedDB persistence across a reload.
- Site: the landing page, docs, every example page, the showcases (pins, smooth scrolling, parallax, reduced motion) and every course step.
- Embeds: keyboard stepping in the focused figure only, captions in the page language, SVG paint, labelled images, play-when-visible and reduced motion.
Latest run: Chromium 150, Firefox 153 and WebKit 26.5 pass every check. In WebKit
on Linux, MP4 export is reported as a note: Playwright's WebKit build crashes while
starting its bundled GStreamer, before tinyfly's code runs. WebKit needs libavif16
on Linux hosts, and the runner removes the GTK/GIO variables a snap-installed
terminal (VS Code from the Snap Store) exports, which otherwise break every page
load.
Test Coverage
npm test runs 2,197 unit tests in 127 files, all passing:
| Area | Tests |
|---|---|
| Engine (src/engine: timeline, easing, paths, text, exports, serialization) | 521 |
| Editor (src/editor: stores, utils, presets, AI, samples) | 409 |
| GSAP-style API (src/compat/gsap) | 332 |
| Examples page, GSAP-style demos and showcases (src/examples) | 302 |
| Render adapters (src/adapters) | 134 |
| Interactive course (src/learn) | 211 |
| Drivers and interaction (src/drivers, src/interaction) | 136 |
| Player, media sync, sequencer, teaching and scenarios (src/player) | 103 |
| Docs viewer and llms.txt (src/docs) | 16 |
| Embeds, teaching kit, framework hooks, landing (src/embed, src/teach, src/frameworks, src/landing) | 33 |
Contributing
Contributions are welcome! For anything larger than a small fix, please open an issue first to discuss it, then send a pull request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
MIT © 2026 Algorisys OSS Team. Use it in personal, commercial and SaaS projects, modify it and redistribute it; keep the copyright and license notice with copies of the source.
SDD by Rajesh Pillai at Algorisys Technologies
