What is PinePaper? A professional web app for creating animated text, graphics, diagrams, and data visualizations on an infinite canvas. Powered by Paper.js vector math — everything is resolution-independent and exportable.
How AI agents use it: Via MCP tools (
@pinepaper.studio/mcp-server) or the Agent Mode JS API (window.PinePaperAgent).
Create items with app.create(type, params). All items are vector-based and infinitely scalable.
TypeKey Parameterstext``content, fontSize, fontFamily, fontWeight, color, x, y``circle``radius, fillColor, strokeColor, x, y``rectangle``width, height, fillColor, cornerRadius, x, y``ellipse``radiusX, radiusY, fillColor, x, y``triangle``radius, fillColor, x, y``star``radius, points, innerRadius, fillColor, x, y``polygon``radius, sides, fillColor, x, y``line``from, to, strokeColor, strokeWidth``path``segments, strokeColor, closed``arc``from, through, to, strokeColor
text
x, y — Position
fillColor — Fill (hex, rgba, or gradient object)
strokeColor — Stroke color
strokeWidth — Stroke width
opacity — 0-1
rotation — Degrees
scale / scaleX/Y — Uniform or axis scale
shadowColor — Shadow color
shadowBlur — Shadow blur radius
shadowOffset — [dx, dy]
blendMode — CSS blend mode string
label — String or { content, position, fontSize, color }
javascript
fillColor: {
type: 'linear', // or 'radial'
stops: [
{ color: '#ff0000', offset: 0 },
{ color: '#0000ff', offset: 1 }
],
origin: [100, 100],
destination: [500, 500]
}
javascript
app.batchCreate([
{ type: 'text', params: { content: 'Hello', x: 200, y: 200 } },
{ type: 'circle', params: { radius: 50, x: 400, y: 300 } }
]);
Apply via app.animate(item, { animationType, animationSpeed }) or inline during creation.
TypeEffectKey ParamspulseScale breathingspeed, intensity``rotateContinuous spinspeed``bounceVertical bouncespeed, intensity``fadeOpacity pulsespeed, intensity``wobbleSide-to-side rockspeed, intensity``shakeRapid jitterspeed, intensity``swingPendulum swingspeed, intensity``jellyElastic squash/stretchspeed, intensity``typewriterCharacter-by-character revealspeed
javascript
app.create('text', {
content: 'Hello World',
x: 400, y: 300,
animationType: 'bounce',
animationSpeed: 1.2,
animationIntensity: 0.15
});
Relations define persistent inter-item behaviors. Add with app.addRelation(sourceId, targetId, type, params).
RelationDescriptionKey ParamsorbitsCircular motion around targetradius, speed, direction, phase``followsMove toward targetoffset, smoothing, delay``attached_toMove with targetoffset, inherit_rotation``maintains_distanceStay fixed distancedistance, strength``points_atRotate to face targetoffset_angle, smoothing``mirrorsMirror positionaxis, center``parallaxDepth-based movementdepth, origin``bounds_toStay within boundspadding, bounce``spring_followPhysics spring motionstiffness, damping, mass, maxDisplacement
RelationDescriptionKey Paramsgrows_fromScale in from zeroorigin, duration, delay, easing``staggered_withStaggered effectindex, stagger, effect``indicatesHighlight pulsescale, color, duration, repeat``circumscribesDraw shape around targetshape, color, strokeWidth, fadeOut``wave_throughWave distortionamplitude, frequency, direction``morphs_toShape morphduration, easing, morphColor, morphSize
RelationDescriptionKey Paramscamera_followsView tracks itemsmoothing, offset, zoom, deadzone``camera_animatesKeyframe camerakeyframes, duration, loop
RelationDescriptionKey ParamswiggleNoise-driven motionproperty, frequency, amplitude, seed``driven_byProperty linkingsourceProperty, targetProperty, multiplier``time_expressionMath-based motionproperty, expression, baseValue
RelationDescriptionKey Paramspart_ofSemantic childrole (e.g., eye_left, mouth)expressesFacial expressionexpression, interval, duration
javascript
app.addRelation(sourceId, targetId, type, params);
app.removeRelation(sourceId, targetId, type);
app.replaceRelation(sourceId, targetId, type, params);
app.getRelations(itemId);
app.hasRelation(sourceId, targetId, type);
app.addParts(parentId, { eye_left: id1, eye_right: id2 });
Any relation supports startTime, endTime, and autoRemove to limit when it's active during timeline playback.
Full timeline-based animation with per-property easing, spatial paths, and morphing.
javascript
app.create('circle', {
radius: 40, x: 100, y: 300,
animationType: 'keyframe',
keyframes: [
{ time: 0, properties: { x: 100, opacity: 0 }, easing: 'easeOut' },
{ time: 1, properties: { x: 500, opacity: 1 } },
{ time: 2, properties: { x: 500, y: 100, scale: 2 }, easing: 'bounce' }
]
});
x, y, position, scale, scaleX, scaleY, rotation, opacity, fillColor, strokeColor, fontSize, strokeWidth, shadowBlur, shadowOffsetX, shadowOffsetY, segments (path morphing), trimStart, trimEnd, trimOffset
linear, easeIn, easeOut, easeInOut, easeInCubic, easeOutCubic, easeInOutCubic, bounce, elastic, or custom cubic bezier [x1, y1, x2, y2]
javascript
{ time: 1, properties: { x: 500, y: 100 }, propertyEasings: { x: 'bounce', y: 'easeOut' } }
javascript
{ time: 0, properties: { x: 100, y: 300 }, spatialHandles: { out: [50, -100] } },
{ time: 1, properties: { x: 500, y: 300 }, spatialHandles: { in: [-50, -100] } }
javascript
app.create('path', {
segments: [[100, 300], [700, 300]],
strokeColor: '#3b82f6',
animationType: 'keyframe',
keyframes: [
{ time: 0, properties: { trimEnd: 0 } },
{ time: 2, properties: { trimEnd: 1 }, easing: 'easeOut' }
]
});
javascript
app.playKeyframeTimeline(duration, loop);
app.pauseKeyframeTimeline();
app.stopKeyframeTimeline();
app.seekKeyframeTimeline(time);
Import from js/templates/_shared/animations.js:
Reveal: fadeInUp, fadeInDown, fadeInLeft, fadeInRight, fadeIn Scale: popIn, explosivePop, scalePulse, breathe Physics: gravityDrop, rubberBand, swing, elasticSnap Paths: spiralIn, sineFloat, figureEight, circularOrbit Glitch: glitchFlicker, motionBlurSnap, cyberScan Exit: zoomOut, fadeOutDown, hingeDrop, shrinkAway
Break text into individually styled/animated letters:
javascript
const collage = app.letterCollage.create('HELLO', {
style: 'tile', // tile, magazine, paperCut, fold, gradient
palette: 'neon', // wordle, scrabble, candy, neon, rainbow, pastel, etc.
fontSize: 48,
x: 200, y: 150
});
Tile Palettes: wordle, scrabble, candy, neon, rainbow, pastel, cotton, earth, ocean, forest, sunset, corporate, minimal, monochrome, christmas, halloween, valentines, magazine, ransom, newspaper
Gradient Palettes: rainbow, sunset, fire, gold, rose, ocean, ice, forest, midnight, cyberpunk, neonGlow, chrome
javascript
app.letterCollage.applyStaggeredAnimation(collageId, {
effect: 'fadeSlideUp', // fadeSlideDown, popIn, typewriter, wave
staggerDelay: 0.1,
duration: 0.5,
sequence: 'linear' // reverse, random, center, fibonacci, wave
});
javascript
app.createCursiveText('Hello World', {
x: 200, y: 300, scale: 2,
strokeColor: '#f59e0b', strokeWidth: 3,
animate: true, duration: 3
});
Create custom fonts from drawn glyphs:
javascript
app.fontStudio.createGlyph(char, paperPath);
app.fontStudio.downloadFont('otf');
app.fontStudio.loadIntoDocument();
javascript
app.diagramSystem.createShape('process', { position: new Point(x, y), label: 'Step 1' });
app.diagramSystem.createShape('decision', { position: new Point(x, y), label: 'Yes/No?' });
Shape Types: process, decision, terminal, data, document, database, cloud, server, uml-class, uml-usecase
Comment Shapes: speech-bubble, speech-bubble-square, thought-bubble, comment-box, speech-bubble-pointed, double-bubble, quote-bubble, callout-box
javascript
app.diagramSystem.connect(shape1, shape2);
await app.diagramSystem.applyLayout(null, 'hierarchical', { direction: 'TB' });
Layout Algorithms: hierarchical, force-directed, tree, radial, grid
javascript
// Import Mermaid syntax
const result = app.importMermaid(`
graph TD
A[Start] --> B{Decision}
B -->|Yes| C[Action]
B -->|No| D[End]
`);
// Export current diagram as Mermaid
const mermaid = app.exportMermaid({ direction: 'LR' });
javascript
app.arrowSystem.createArrow([point1, point2], {
lineColor: '#333', lineWidth: 2,
headStyle: 'stealth' // classic, stealth, sharp, open, none
});
javascript
const map = await app.mapSystem.loadMap('world', {
projection: 'naturalEarth',
quality: 'standard',
interactive: true,
showOcean: true
});
Available Maps: world, worldHighRes, usa, usaCounties, canada, india, europe
Projections: naturalEarth, mercator, robinson, orthographic, equirectangular, albers, albersUsa, conicEqualArea, winkelTripel
javascript
app.mapSystem.applyDataColors(
{ 'USA': 90, 'CAN': 75, 'MEX': 60 },
{ colorScale: ['#f7fbff', '#08519c'], legend: true, legendTitle: 'Score' }
);
javascript
app.mapSystem.highlightRegions(['USA', 'CAN'], { fill: '#ef4444' });
app.mapSystem.animateReveal('all', { duration: 1, stagger: 0.05, effect: 'fadeIn' });
javascript
app.mapSystem.addMarker(-122.4, 37.8, { shape: 'pin', label: 'San Francisco' });
app.mapSystem.addRegionLabel('USA', { content: 'United States', fontSize: 14 });
javascript
app.animateMapWave({
duration: 10, loop: true,
colors: ['#ef4444', '#fbbf24', '#22c55e', '#3b82f6'],
waveDirection: 'horizontal'
});
javascript
app.mapSystem.exportSVG();
app.exportMapGeoJSON({ includeStyles: true });
app.exportMapRegionDataCSV();
await app.importCustomMap(geoJson);
javascript
app.applyMask(item, 'circle', { radius: 100 });
app.applyMask(item, 'star', { points: 5, radius: 80 });
Mask Types: rectangle, circle, ellipse, star, triangle, hexagon, heart, rounded, custom
Mask Modes: clip (show inside), subtract (show outside), intersect (overlap only)
javascript
app.applyAnimatedMask(item, 'wipeLeft', { duration: 0.8 });
Preset Animations: wipeLeft, wipeRight, wipeUp, wipeDown, iris, irisOut, star, heart, curtainHorizontal, curtainVertical, cinematic, diagonalWipe, revealUp, revealDown
javascript
app.maskingSystem.addMask(item, 'circle', { mode: 'clip' });
app.maskingSystem.addMask(item, 'rectangle', { mode: 'subtract' });
Apply particle/FX effects to items:
javascript
app.applyEffect(item, 'sparkle', { color: '#ffd700', speed: 2 });
app.applyEffect(item, 'confetti', { colors: ['#ff0', '#f0f', '#0ff'], count: 50 });
Effect Types:
EffectKey Paramssparkle``color, speed, size``blast``color, radius, count, interval``fire``color, size, turbulence``smoke``color, size, drift``rain``color, count, speed, angle``snow``color, count, speed, spread``confetti``colors[], count, gravity``ripple``color, maxRadius, ringCount``glow``color, speed, intensity``electric``color, boltCount, length
GPU-accelerated filters for raster images:
javascript
await app.applyImageFilter(raster, 'blur', { radius: 5 });
// Chain multiple filters
await app.applyImageFilterChain(raster, [
{ name: 'brightness', params: { value: 20 } },
{ name: 'contrast', params: { value: 15 } },
{ name: 'vignette', params: { intensity: 0.5 } }
]);
Filters: grayscale, sepia, brightness, contrast, saturation, invert, posterize, hsl, colorTint, vignette, edgeDetect, blur
javascript
await app.applyCutoutStyle(item, 'sticker');
Presets: papercut, postcard, sticker, poster, monochromeCut, edgeSketch, warmGlow, coolTone
javascript
const skeletonId = app.riggingSystem.createSkeleton('character', { x: 400, y: 500 });
const spine = app.riggingSystem.addBone(skeletonId, { name: 'spine', length: 100, angle: -90 });
const head = app.riggingSystem.addBone(skeletonId, { name: 'head', length: 40, angle: -90, parentBoneId: spine });
Apply a full preset skeleton in one call:
javascript
const preset = getRigPreset('humanoid');
const { boneMap, chainIds } = applyRigPreset(app.riggingSystem, skeletonId, preset);
Presets: humanoid (20 bones), quadruped (13), spider (19), bird (15), fish (14), hand (17), centaur (29), simple_arm (3), snake (10), chain (5)
javascript
app.riggingSystem.createIKChain(skeletonId, {
name: 'left_arm', boneIds: [upperArm, lowerArm],
solverType: 'two_bone' // 'fabrik', 'two_bone', 'ccd'
});
javascript
// Save/load poses
const poseId = app.riggingSystem.savePose(skeletonId, 'wave');
app.riggingSystem.loadPose(skeletonId, poseId);
app.riggingSystem.interpolatePoses(poseA, poseB, 0.5);
// Shape keys (blend shapes)
const blinkKey = app.riggingSystem.saveShapeKey(skeletonId, 'blink');
app.riggingSystem.loadShapeKey(skeletonId, blinkKey, 0.8); // weight 0-1
Built-in facial expressions: blink, smile, frown, surprise, wink, talk
javascript
app.addRelation(faceId, null, 'expresses', { expression: 'blink', interval: 3 });
javascript
app.riggingSystem.skinPath(skeletonId, itemId, { maxInfluences: 2 });
javascript
app.blendingSystem.applyPreset(itemId, 'neon');
app.blendingSystem.transitionBlendMode(itemId, 'screen', { duration: 0.5 });
app.blendingSystem.setGroupBlendMode(groupId, 'multiply', { cascadeOpacity: true });
Presets: ghost, neon, shadow, glow, xray, dreamy, vintage
Blend Modes: normal, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity
javascript
app.blendingSystem.addInteractiveBlend(itemId, {
event: 'hover',
modes: ['normal', 'screen'],
opacities: [1.0, 0.8]
});
Render 3D primitives onto the 2D canvas:
javascript
app.createObject3D('cube', { size: 1.5, color: '#3b82f6', rotationY: 45 });
app.createObject3D('sphere', { radius: 1, color: '#ef4444' });
app.createGlossySphere({ x: 400, y: 300, radius: 60, color: '#F97316' });
app.setProjection3D('perspective', { fov: 60 });
app.setCamera3D({ position: { x: 3, y: 2, z: 5 }, target: { x: 0, y: 0, z: 0 } });
Primitives: cube, sphere, cylinder, torus, cone
Projections: perspective, orthographic, isometric, cabinet, cavalier
Procedural animated backgrounds:
javascript
app.executeGenerator('drawBokeh', { circleCount: 30, animated: true });
app.executeGenerator('drawConstellation', { animated: true });
app.executeGenerator('drawWaves', { waveCount: 8, fill: true, animated: true });
Generators:
NameDescriptiondrawBokehDepth-of-field circlesdrawGradientMeshMulti-color gradient blobsdrawOrganicFlowAurora/silk flowing layersdrawWavesFlowing wave linesdrawStarfieldAnimated starfielddrawNeonGridGlowing neon griddrawCircuitCircuit board with pulsesdrawConstellationConnected star pointsdrawGridCustomizable animated griddrawSunburstRadiating raysdrawFlowFieldDirectional flow particlesdrawNoiseTexturePerlin noise texturedrawKaleidoscopeKaleidoscope patterndrawTessellationGeometric tilingdrawSunsetSceneAnimated sunsetdrawCosmosSpaceSpace scenedrawFirefliesFloating firefliesdrawFallingPetalsFalling petalsdrawGeometricAbstractOverlapping shapesdrawMetaballsOrganic blobsdrawGoldenSpiralGolden ratio spiraldrawPerspectiveGrid3D perspective grid
javascript
app.camera.zoomIn(2, 0.5); // Zoom 2x over 0.5s
app.camera.panTo(200, 200, 0.5); // Pan to point
app.camera.moveTo(200, 200, 2, 0.5); // Pan + zoom
app.camera.reset(0.5); // Reset view
// Keyframe camera animation
app.addRelation('camera', null, 'camera_animates', {
duration: 6, loop: true,
keyframes: [
{ time: 0, zoom: 1, center: [400, 300] },
{ time: 3, zoom: 2, center: [600, 300], easing: 'easeInOut' },
{ time: 6, zoom: 1, center: [400, 300] }
]
});
FormatMethodNotesWebMExport UIVP9, best for animationsMP4Export UIH.264 via WebCodecs (Chrome 94+)GIFExport UIVia gif.js Web WorkersAnimated SVGapp.exportAnimatedSVG()SMIL or CSS animationsPNGExport UIUp to 600 DPIPDFapp.exportPDF({ dpi })300/600 DPI, bleed, trim marksLottieapp.exportLottie({ fps, duration })JSON for any Lottie playerGeoJSONapp.exportMapGeoJSON()Map data exportMermaidapp.exportMermaid()Diagram text export
javascript
app.setCanvasSize('instagram-post'); // 1080x1080
app.setCanvasSize('youtube-thumbnail'); // 1280x720
app.setCanvasSize('tiktok'); // 1080x1920
app.setCanvasSize({ width: 1920, height: 1080 }); // Custom
Presets: youtube-thumbnail, instagram-post, instagram-story, tiktok, a4-portrait, letter-portrait, and more.
javascript
app.getCanvasSize(); // { width, height, preset }
app.expandCanvasToIncludeContent(20); // Fit to content + padding
javascript
app.select(item);
app.deselect();
app.selectAll();
app.deselectAll();
app.getSelection();
// Registry
app.itemRegistry.getAll();
app.itemRegistry.getByType('text');
app.itemRegistry.getItem(itemId);
// Relation queries
app.hasRelation(sourceId, targetId, type);
app.queryNotRelation(type); // Items without relation
app.queryIsolatedItems(); // Items with no relations
app.queryCompound([...conditions]);
app.queryRelationChain([...types]);
javascript
app.undo();
app.redo();
app.getHistoryState(); // { canUndo, canRedo, undoCount, redoCount }
app.clearCanvas();
Activate via URL ?agent=1 or ?mode=agent:
javascript
const agent = window.PinePaperAgent;
await agent.configure({ mode: 'agent' });
await agent.waitForReady();
// Create content
const text = app.create('text', { content: 'Hello', x: 400, y: 300, fontSize: 48 });
app.animate(text, { animationType: 'pulse' });
// Export
const result = await agent.quickExport();
// Reset for new canvas
agent.reset({ canvas: 'tiktok' });
javascript
const rec = agent.getExportRecommendation();
// { format: 'webm', reason: 'Has animations, WebM gives best quality' }
The MCP server ( @pinepaper.studio/mcp-server) exposes these tools:
pinepaper_create_item — Create any item typepinepaper_modify_item — Modify item propertiespinepaper_batch_create — Create multiple itemspinepaper_animate — Apply simple animationpinepaper_keyframe_animate — Apply keyframe animationpinepaper_add_relation — Add relation between itemspinepaper_remove_relation — Remove relationpinepaper_create_diagram_shape — Create flowchart shapepinepaper_connect — Connect shapespinepaper_auto_layout — Apply layout algorithmpinepaper_load_map — Load geographic mappinepaper_highlight_regions — Highlight regionspinepaper_apply_data_colors — Choropleth coloringpinepaper_add_marker — Add map markerpinepaper_create_skeleton — Create skeletonpinepaper_add_bone — Add bonepinepaper_attach_item_to_bone — Attach itempinepaper_apply_rig_preset — Apply presetpinepaper_save_pose / pinepaper_load_pose — Pose managementpinepaper_interpolate_poses — Blend posespinepaper_apply_blend_preset — Apply blend presetpinepaper_transition_blend_mode — Animated blend transitionpinepaper_add_interactive_blend — Interactive blendingpinepaper_set_group_blend_mode — Group blendingpinepaper_apply_image_filter — Single GPU filterpinepaper_apply_filter_chain — Multi-filter chainpinepaper_apply_cutout_style — Apply cutout presetpinepaper_get_cutout_styles — List presetspinepaper_lasso_activate — Activate lasso on imagepinepaper_lasso_apply — Extract regionpinepaper_zoom — Set zoom levelpinepaper_pan — Pan viewpinepaper_fit_view — Fit content in viewpinepaper_get_view_state — Get current viewpinepaper_select — Select itempinepaper_select_all — Select allpinepaper_deselect_all — Deselect allpinepaper_get_selection — Get selectionpinepaper_delete_selected — Delete selectedpinepaper_nudge — Move itemspinepaper_flip — Flip itemspinepaper_reorder — Change z-orderpinepaper_set_background — Set backgroundpinepaper_clear_background — Clear backgroundpinepaper_get_background — Get backgroundpinepaper_undo / pinepaper_redo — Undo/redopinepaper_get_history_state — Get history statepinepaper_clear_canvas — Clear canvaspinepaper_query_items — Query items by criteriapinepaper_get_item_by_id — Get specific itempinepaper_hit_test — Hit test at pointpinepaper_is_empty — Check if canvas emptypinepaper_create_precomp — Create compositionpinepaper_add_to_precomp — Add to compositionpinepaper_remove_from_precomp — Remove from compositionpinepaper_play_timeline — Play keyframe timelinepinepaper_stop_timeline — Stop playbackpinepaper_pause_timeline — Pause playbackpinepaper_seek_timeline — Seek to timepinepaper_export_svg — Export as SVGpinepaper_export_training_data — Export training datapinepaper_execute_generator — Run background generatorpinepaper_list_generators — List available generatorspinepaper_font_create_glyph — Create font glyphpinepaper_font_export — Export font filepinepaper_font_status — Get font creation status