@dnyantra/dtly 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +135 -2
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +84 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/core/anim.d.ts +3 -0
  7. package/dist/core/anim.js +61 -0
  8. package/dist/core/anim.js.map +1 -0
  9. package/dist/core/color.d.ts +4 -0
  10. package/dist/core/color.js +28 -0
  11. package/dist/core/color.js.map +1 -0
  12. package/dist/core/ease.d.ts +8 -0
  13. package/dist/core/ease.js +90 -0
  14. package/dist/core/ease.js.map +1 -0
  15. package/dist/core/model.d.ts +229 -0
  16. package/dist/core/model.js +4 -0
  17. package/dist/core/model.js.map +1 -0
  18. package/dist/core/text.d.ts +41 -0
  19. package/dist/core/text.js +222 -0
  20. package/dist/core/text.js.map +1 -0
  21. package/dist/index.d.ts +10 -0
  22. package/dist/index.js +13 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/mcp/index.d.ts +1 -0
  25. package/dist/mcp/index.js +2 -0
  26. package/dist/mcp/index.js.map +1 -0
  27. package/dist/mcp/server.d.ts +3 -0
  28. package/dist/mcp/server.js +170 -0
  29. package/dist/mcp/server.js.map +1 -0
  30. package/dist/node.d.ts +4 -0
  31. package/dist/node.js +6 -0
  32. package/dist/node.js.map +1 -0
  33. package/dist/ops/ops.d.ts +38 -0
  34. package/dist/ops/ops.js +246 -0
  35. package/dist/ops/ops.js.map +1 -0
  36. package/dist/ops/store.d.ts +4 -0
  37. package/dist/ops/store.js +22 -0
  38. package/dist/ops/store.js.map +1 -0
  39. package/dist/presets/index.d.ts +14 -0
  40. package/dist/presets/index.js +235 -0
  41. package/dist/presets/index.js.map +1 -0
  42. package/dist/presets/text.d.ts +5 -0
  43. package/dist/presets/text.js +19 -0
  44. package/dist/presets/text.js.map +1 -0
  45. package/dist/render/export.d.ts +43 -0
  46. package/dist/render/export.js +215 -0
  47. package/dist/render/export.js.map +1 -0
  48. package/dist/render/ffmpeg.d.ts +1 -0
  49. package/dist/render/ffmpeg.js +16 -0
  50. package/dist/render/ffmpeg.js.map +1 -0
  51. package/dist/render/node-host.d.ts +18 -0
  52. package/dist/render/node-host.js +168 -0
  53. package/dist/render/node-host.js.map +1 -0
  54. package/dist/render/renderer.d.ts +36 -0
  55. package/dist/render/renderer.js +372 -0
  56. package/dist/render/renderer.js.map +1 -0
  57. package/dist/starter.d.ts +5 -0
  58. package/dist/starter.js +32 -0
  59. package/dist/starter.js.map +1 -0
  60. package/dist/version.d.ts +2 -0
  61. package/dist/version.js +3 -0
  62. package/dist/version.js.map +1 -0
  63. package/fonts/Baloo2.ttf +0 -0
  64. package/fonts/Mukta-Bold.ttf +0 -0
  65. package/fonts/OFL-Baloo2.txt +93 -0
  66. package/fonts/OFL-Mukta.txt +93 -0
  67. package/package.json +80 -4
  68. package/plugin/skills/dtly/SKILL.md +89 -0
package/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DT.ly — from the creator of CustomerPro.in
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ Bundled fonts in fonts/ (Baloo 2, Mukta) are licensed under the SIL Open Font
26
+ License 1.1; see fonts/OFL-Baloo2.txt and fonts/OFL-Mukta.txt.
package/README.md CHANGED
@@ -1,3 +1,136 @@
1
- # Temporary Holding Version
1
+ # DT.ly
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm](https://img.shields.io/npm/v/@dnyantra/dtly)](https://www.npmjs.com/package/@dnyantra/dtly) [![license](https://img.shields.io/npm/l/@dnyantra/dtly)](LICENSE)
4
+
5
+ **After Effects-style motion graphics for code and AI agents.**
6
+ *From the creator of [CustomerPro.in](https://customerpro.in).*
7
+
8
+ DT.ly describes a video as plain JSON — compositions, layers, keyframes with easing curves,
9
+ text animators, masks, track mattes, trim paths, parenting, effects and motion blur — and renders
10
+ it to MP4. An MCP server lets AI agents edit projects keyframe by keyframe and look at the frames
11
+ they make.
12
+
13
+ ![Starter projects](docs/starters.png)
14
+
15
+ - **Fine-grained animation** — any property can be keyframed with AE-style eases, bezier curves or physical springs; `wiggle` included.
16
+ - **Text animators** — per grapheme, word or line, with stagger, order, blur, tracking, colour and masked line reveals. Indian scripts (Devanagari etc.) keep their conjuncts while animating.
17
+ - **Presets** — kinetic typography, logo openers, colour-bar openers, slideshows with Ken Burns, push/whip/wipe/zoom transitions, lower thirds, highlight plates. Presets write ordinary layers, so everything stays editable.
18
+ - **Media** — video, image and audio layers, volume keyframes, music mixing.
19
+ - **Agent-native** — 21 MCP tools plus a skill that teaches motion-design rules.
20
+ - **No browser, no lock-in** — renders with skia-canvas and FFmpeg. MIT licensed.
21
+
22
+ ## Install
23
+
24
+ Requires **Node.js 20+** and **FFmpeg** on your PATH (`brew install ffmpeg`, `sudo apt install ffmpeg`, or `winget install ffmpeg`).
25
+
26
+ ```bash
27
+ npm install @dnyantra/dtly
28
+ ```
29
+
30
+ Try it without installing:
31
+
32
+ ```bash
33
+ npx @dnyantra/dtly init hello.json --starter kinetic --title "Hello"
34
+ npx @dnyantra/dtly render hello.json hello.mp4
35
+ ```
36
+
37
+ ### Use it from Claude Code
38
+
39
+ Install the plugin (MCP server + skill) in one step:
40
+
41
+ ```
42
+ /plugin install dtly --marketplace dnyantra/dtly
43
+ ```
44
+
45
+ Or add the pieces yourself:
46
+
47
+ ```bash
48
+ claude mcp add --scope user dtly -- npx -y @dnyantra/dtly mcp
49
+ npx @dnyantra/dtly skill install
50
+ ```
51
+
52
+ Then ask for things like *"make a 10 second kinetic typography opener for my bakery"* or
53
+ *"ease the call-to-action in with a spring and move it 0.3 s later"*.
54
+
55
+ ## Command line
56
+
57
+ After `npm install -g @dnyantra/dtly` (or via `npx @dnyantra/dtly …`):
58
+
59
+ ```
60
+ dtly init <project.json> [--starter opener|kinetic|bars] [--title "Text"]
61
+ dtly render <project.json> <out.mp4> [--from s] [--to s] [--comp id] [--workers n]
62
+ dtly still <project.json> <time> <out.png> [--scale 0.5]
63
+ dtly sheet <project.json> <out.png> <t1,t2,...>
64
+ dtly presets
65
+ dtly mcp
66
+ dtly skill install [--dir path]
67
+ ```
68
+
69
+ ## Library
70
+
71
+ ```ts
72
+ import {newProject, addLayer, setKeyframe, PRESETS} from '@dnyantra/dtly';
73
+ import {writeProject, renderVideo} from '@dnyantra/dtly/node';
74
+
75
+ const p = newProject({name: 'Promo', duration: 5, background: '#111'});
76
+ addLayer(p, 'main', {
77
+ type: 'text', id: 'title', text: 'Fresh bread daily', size: 120, color: '#ffffff',
78
+ animators: [{unit: 'word', stagger: 0.1, duration: 0.6, ease: 'backOut', from: {opacity: 0, y: 60}}],
79
+ });
80
+ setKeyframe(p, 'title', 'transform.scale', 0, [90, 90], {spring: {stiffness: 180, damping: 12}});
81
+ setKeyframe(p, 'title', 'transform.scale', 1, [100, 100]);
82
+ PRESETS['element.highlightWord'].apply(p, 'main', {layer: 'title', at: 1});
83
+
84
+ writeProject('promo.json', p);
85
+ await renderVideo('promo.json', 'promo.mp4');
86
+ ```
87
+
88
+ Entry points: `@dnyantra/dtly` (model, edits, presets, renderer — browser-safe),
89
+ `@dnyantra/dtly/node` (MP4/PNG rendering, project files), `@dnyantra/dtly/mcp` (MCP server).
90
+ Installing the package also gives you the `dtly` command.
91
+
92
+ ## The model
93
+
94
+ | After Effects | DT.ly |
95
+ |---|---|
96
+ | Composition / precomp | `comps[]`, layer `type: "precomp"` |
97
+ | Layer in/out | `inPoint`, `outPoint` (seconds) |
98
+ | Transform | `transform.anchor/position/scale(%)/rotation(°)/opacity(%)/skew` |
99
+ | Keyframes + easing | `{keyframes: [{t, v, ease}]}` — `easyEase`, `expoOut`, `backOut`, `[x1,y1,x2,y2]`, `{spring: {...}}`, `hold` |
100
+ | `wiggle()` | `{keyframes, wiggle: {freq, amp}}` |
101
+ | Parenting / null | `parent: id`, `type: "null"` |
102
+ | Masks / track mattes | `masks: [...]`, `matte: {layer, mode}` |
103
+ | Trim paths | shape `trim: {start, end}` |
104
+ | Text animators | `animators: [{unit, stagger, duration, ease, from: {...}, clip, mode}]` |
105
+ | Effects | `blur`, `dropShadow`, `glow`, `fill`, `brightness`, `saturate`, `contrast` |
106
+ | Motion blur | `motionBlur: {samples, shutter}` |
107
+
108
+ Layer index 0 is the top of the stack, as in After Effects. See
109
+ [`plugin/skills/dtly/SKILL.md`](plugin/skills/dtly/SKILL.md) for the full guide.
110
+
111
+ ## Development
112
+
113
+ ```bash
114
+ git clone https://github.com/dnyantra/dtly && cd dtly
115
+ npm install # also builds dist/
116
+ npm test # typecheck + end-to-end smoke test (renders + MCP round trip)
117
+ npm run dev -- render examples/kinetic.json out.mp4
118
+ ```
119
+
120
+ Opening Claude Code in the repo loads the local MCP server (`.mcp.json`) and the skill.
121
+
122
+ ## Roadmap
123
+
124
+ - Browser preview and timeline editor on the same renderer
125
+ - Faster rendering: layer-bounds buffers, caching finished layers
126
+ - Templates with brand kits and media slots for fixed 30/60 s videos
127
+ - More AE features: 3D layers and camera, repeaters, path morphing, LUTs
128
+
129
+ ## License
130
+
131
+ MIT for the code. Bundled fonts (Baloo 2, Mukta) are under the SIL Open Font License 1.1.
132
+
133
+ ---
134
+
135
+ DT.ly is made by the creator of [CustomerPro.in](https://customerpro.in) — WhatsApp marketing,
136
+ campaigns and AI agents for small businesses.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { listPresets } from './presets/index.js';
7
+ import { renderContactSheet, renderStill, renderVideo, renderVideoParallel } from './render/export.js';
8
+ import { writeProject } from './ops/store.js';
9
+ import { STARTERS } from './starter.js';
10
+ import { BRAND, VERSION } from './version.js';
11
+ const [cmd, ...rest] = process.argv.slice(2);
12
+ const usage = `${BRAND} v${VERSION}
13
+ After Effects-style motion graphics as JSON, rendered to MP4.
14
+
15
+ dtly init <project.json> [--starter opener|kinetic|bars] [--title "Text"]
16
+ dtly render <project.json> <out.mp4> [--from s] [--to s] [--comp id] [--workers n]
17
+ dtly still <project.json> <time> <out.png> [--scale 0.5] [--comp id]
18
+ dtly sheet <project.json> <out.png> <t1,t2,...> [--comp id]
19
+ dtly presets list text animators, openers, slides, transitions
20
+ dtly mcp run the MCP server (stdio) for AI agents
21
+ dtly skill install [--dir d] copy the agent skill into ~/.claude/skills
22
+
23
+ Docs: https://github.com/dnyantra/dtly`;
24
+ const flag = (name) => { const i = rest.indexOf(`--${name}`); return i >= 0 ? rest[i + 1] : undefined; };
25
+ const num = (s) => (s === undefined ? undefined : Number(s));
26
+ const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
27
+ try {
28
+ if (cmd === 'render' && rest[1]) {
29
+ const t0 = Date.now();
30
+ const r = await renderVideoParallel(rest[0], rest[1], {
31
+ from: num(flag('from')), to: num(flag('to')), comp: flag('comp'), workers: num(flag('workers')),
32
+ onProgress: (d, n) => { if (d % 30 === 0 || d === n)
33
+ process.stderr.write(`\r${d}/${n} frames`); },
34
+ });
35
+ console.error(`\nRendered ${r.out}: ${r.frames} frames, ${r.audioTracks} audio tracks in ${((Date.now() - t0) / 1000).toFixed(1)}s`);
36
+ }
37
+ else if (cmd === 'chunk') {
38
+ // Internal: one parallel-render worker. Reports progress to the parent over IPC.
39
+ const [project, out, from, to, comp, crf] = rest;
40
+ await renderVideo(project, out, { from: Number(from), to: Number(to), comp: comp || undefined, crf: Number(crf), audio: false, onProgress: (d) => process.send?.({ done: d }) });
41
+ }
42
+ else if (cmd === 'still' && rest[2]) {
43
+ console.log(await renderStill(rest[0], Number(rest[1]), rest[2], { scale: num(flag('scale')), comp: flag('comp') }));
44
+ }
45
+ else if (cmd === 'sheet' && rest[2]) {
46
+ console.log(await renderContactSheet(rest[0], rest[2].split(',').map(Number), rest[1], { comp: flag('comp') }));
47
+ }
48
+ else if (cmd === 'init' && rest[0]) {
49
+ const name = flag('starter') ?? 'opener';
50
+ const starter = STARTERS[name];
51
+ if (!starter)
52
+ throw new Error(`Unknown starter "${name}". Options: ${Object.keys(STARTERS).join(', ')}`);
53
+ if (fs.existsSync(rest[0]))
54
+ throw new Error(`${rest[0]} already exists`);
55
+ writeProject(rest[0], starter.build(flag('title') ?? 'DT.ly'));
56
+ console.log(`Created ${rest[0]} (${starter.description})\nNext: dtly render ${rest[0]} ${rest[0].replace(/\.json$/, '')}.mp4`);
57
+ }
58
+ else if (cmd === 'presets') {
59
+ console.log(listPresets());
60
+ }
61
+ else if (cmd === 'mcp') {
62
+ const { startMcpServer } = await import('./mcp/server.js');
63
+ await startMcpServer();
64
+ }
65
+ else if (cmd === 'skill' && rest[0] === 'install') {
66
+ const base = flag('dir') ?? path.join(process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), '.claude'), 'skills');
67
+ const dest = path.join(base, 'dtly');
68
+ fs.mkdirSync(dest, { recursive: true });
69
+ fs.cpSync(path.join(pkgRoot, 'plugin/skills/dtly'), dest, { recursive: true });
70
+ console.log(`Installed the DT.ly skill to ${dest}`);
71
+ }
72
+ else if (cmd === '--version' || cmd === '-v' || cmd === 'version') {
73
+ console.log(VERSION);
74
+ }
75
+ else {
76
+ console.log(usage);
77
+ process.exit(cmd && cmd !== 'help' && cmd !== '--help' ? 1 : 0);
78
+ }
79
+ }
80
+ catch (e) {
81
+ console.error(`dtly: ${e.message}`);
82
+ process.exit(1);
83
+ }
84
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { type Keyframe, type Prop } from './model.js';
2
+ export declare const evalKeyframes: <T>(kfs: Keyframe<T>[], t: number) => T;
3
+ export declare function val<T>(p: Prop<T> | undefined, t: number, fallback: T): T;
@@ -0,0 +1,61 @@
1
+ import { applyEase } from './ease.js';
2
+ import { parseColor, rgbaString } from './color.js';
3
+ import { isAnimated } from './model.js';
4
+ const lerp = (a, b, p) => a + (b - a) * p;
5
+ const interpolate = (a, b, p) => {
6
+ if (typeof a === 'number' && typeof b === 'number')
7
+ return lerp(a, b, p);
8
+ if (Array.isArray(a) && Array.isArray(b))
9
+ return a.map((v, i) => lerp(v, b[i] ?? v, p));
10
+ if (typeof a === 'string' && typeof b === 'string') {
11
+ const ca = parseColor(a), cb = parseColor(b);
12
+ if (ca && cb)
13
+ return rgbaString(ca.map((v, i) => lerp(v, cb[i], p)));
14
+ }
15
+ return p < 1 ? a : b;
16
+ };
17
+ // Smooth 1D value noise for wiggle(); deterministic per seed so renders are repeatable.
18
+ const hash = (n) => {
19
+ const x = Math.sin(n * 127.1 + 311.7) * 43758.5453;
20
+ return x - Math.floor(x);
21
+ };
22
+ const noise = (x, seed) => {
23
+ const i = Math.floor(x), f = x - i;
24
+ const u = f * f * (3 - 2 * f);
25
+ return lerp(hash(i + seed * 1000) * 2 - 1, hash(i + 1 + seed * 1000) * 2 - 1, u);
26
+ };
27
+ const wiggle = (value, t, w) => {
28
+ const off = (dim) => noise(t * w.freq, (w.seed ?? 1) + dim * 17.3) * w.amp;
29
+ if (typeof value === 'number')
30
+ return value + off(0);
31
+ if (Array.isArray(value))
32
+ return value.map((v, i) => v + off(i));
33
+ return value;
34
+ };
35
+ export const evalKeyframes = (kfs, t) => {
36
+ if (kfs.length === 0)
37
+ throw new Error('empty keyframes');
38
+ if (t <= kfs[0].t)
39
+ return kfs[0].v;
40
+ const last = kfs[kfs.length - 1];
41
+ if (t >= last.t)
42
+ return last.v;
43
+ let i = 0;
44
+ while (i < kfs.length - 2 && t >= kfs[i + 1].t)
45
+ i++;
46
+ const a = kfs[i], b = kfs[i + 1];
47
+ const seg = b.t - a.t;
48
+ const p = applyEase(a.ease, seg <= 0 ? 1 : (t - a.t) / seg, seg);
49
+ return interpolate(a.v, b.v, p);
50
+ };
51
+ // Evaluate any property at time t (seconds, in the layer's composition time).
52
+ export function val(p, t, fallback) {
53
+ if (p === undefined || p === null)
54
+ return fallback;
55
+ if (!isAnimated(p))
56
+ return p;
57
+ const sorted = [...p.keyframes].sort((a, b) => a.t - b.t);
58
+ const v = evalKeyframes(sorted, t);
59
+ return (p.wiggle ? wiggle(v, t, p.wiggle) : v);
60
+ }
61
+ //# sourceMappingURL=anim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anim.js","sourceRoot":"","sources":["../../src/core/anim.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export type RGBA = [number, number, number, number];
2
+ export declare const parseColor: (c: string) => RGBA | null;
3
+ export declare const rgbaString: ([r, g, b, a]: RGBA) => string;
4
+ export declare const withAlpha: (c: string, alpha: number) => string;
@@ -0,0 +1,28 @@
1
+ const NAMED = {
2
+ white: '#ffffff', black: '#000000', red: '#ff0000', green: '#008000', blue: '#0000ff',
3
+ yellow: '#ffff00', orange: '#ffa500', transparent: '#00000000', gray: '#808080', grey: '#808080',
4
+ };
5
+ export const parseColor = (c) => {
6
+ const s = (NAMED[c.trim().toLowerCase()] ?? c).trim();
7
+ if (s.startsWith('#')) {
8
+ let h = s.slice(1);
9
+ if (h.length === 3 || h.length === 4)
10
+ h = [...h].map((ch) => ch + ch).join('');
11
+ if (h.length !== 6 && h.length !== 8)
12
+ return null;
13
+ const n = (i) => parseInt(h.slice(i, i + 2), 16);
14
+ return [n(0), n(2), n(4), h.length === 8 ? n(6) / 255 : 1];
15
+ }
16
+ const m = s.match(/^rgba?\(([^)]+)\)$/i);
17
+ if (m) {
18
+ const p = m[1].split(/[\s,\/]+/).filter(Boolean).map(Number);
19
+ return [p[0], p[1], p[2], p[3] ?? 1];
20
+ }
21
+ return null;
22
+ };
23
+ export const rgbaString = ([r, g, b, a]) => `rgba(${Math.round(r)},${Math.round(g)},${Math.round(b)},${Math.max(0, Math.min(1, a)).toFixed(4)})`;
24
+ export const withAlpha = (c, alpha) => {
25
+ const p = parseColor(c);
26
+ return p ? rgbaString([p[0], p[1], p[2], p[3] * alpha]) : c;
27
+ };
28
+ //# sourceMappingURL=color.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.js","sourceRoot":"","sources":["../../src/core/color.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Ease } from './model.js';
2
+ export declare const spring: (elapsed: number, { stiffness, damping, mass }?: {
3
+ damping?: number | undefined;
4
+ mass?: number | undefined;
5
+ stiffness?: number | undefined;
6
+ }) => number;
7
+ export declare const applyEase: (ease: Ease | undefined, x: number, segSeconds?: number) => number;
8
+ export declare const EASE_NAMES: string[];
@@ -0,0 +1,90 @@
1
+ // Cubic-bezier solver (same curve definition as CSS / AE's speed graph handles).
2
+ const bezier = (x1, y1, x2, y2) => {
3
+ const cx = 3 * x1, bx = 3 * (x2 - x1) - cx, ax = 1 - cx - bx;
4
+ const cy = 3 * y1, by = 3 * (y2 - y1) - cy, ay = 1 - cy - by;
5
+ const sx = (t) => ((ax * t + bx) * t + cx) * t;
6
+ const sy = (t) => ((ay * t + by) * t + cy) * t;
7
+ const dx = (t) => (3 * ax * t + 2 * bx) * t + cx;
8
+ return (x) => {
9
+ let t = x;
10
+ for (let i = 0; i < 8; i++) {
11
+ const e = sx(t) - x;
12
+ if (Math.abs(e) < 1e-6)
13
+ return sy(t);
14
+ const d = dx(t);
15
+ if (Math.abs(d) < 1e-6)
16
+ break;
17
+ t -= e / d;
18
+ }
19
+ let lo = 0, hi = 1;
20
+ t = x;
21
+ for (let i = 0; i < 40; i++) {
22
+ const v = sx(t);
23
+ if (Math.abs(v - x) < 1e-6)
24
+ break;
25
+ if (v < x)
26
+ lo = t;
27
+ else
28
+ hi = t;
29
+ t = (lo + hi) / 2;
30
+ }
31
+ return sy(t);
32
+ };
33
+ };
34
+ const CURVES = {
35
+ easeIn: [0.42, 0, 1, 1],
36
+ easeOut: [0, 0, 0.58, 1],
37
+ easyEase: [0.333, 0, 0.667, 1], // AE "Easy Ease" (33% influence both sides)
38
+ quadOut: [0.25, 0.46, 0.45, 0.94],
39
+ cubicOut: [0.215, 0.61, 0.355, 1],
40
+ expoIn: [0.95, 0.05, 0.795, 0.035],
41
+ expoOut: [0.19, 1, 0.22, 1],
42
+ expoInOut: [1, 0, 0, 1],
43
+ backOut: [0.175, 0.885, 0.32, 1.275],
44
+ backIn: [0.6, -0.28, 0.735, 0.045],
45
+ };
46
+ const cache = new Map();
47
+ const elasticOut = (x) => x === 0 || x === 1 ? x : Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * ((2 * Math.PI) / 3)) + 1;
48
+ const bounceOut = (x) => {
49
+ const n = 7.5625, d = 2.75;
50
+ if (x < 1 / d)
51
+ return n * x * x;
52
+ if (x < 2 / d)
53
+ return n * (x -= 1.5 / d) * x + 0.75;
54
+ if (x < 2.5 / d)
55
+ return n * (x -= 2.25 / d) * x + 0.9375;
56
+ return n * (x -= 2.625 / d) * x + 0.984375;
57
+ };
58
+ // Damped spring from 0 to 1; `elapsed` in seconds so the motion is physical, not stretched.
59
+ export const spring = (elapsed, { stiffness = 170, damping = 18, mass = 1 } = {}) => {
60
+ const w0 = Math.sqrt(stiffness / mass);
61
+ const zeta = damping / (2 * Math.sqrt(stiffness * mass));
62
+ if (zeta < 1) {
63
+ const wd = w0 * Math.sqrt(1 - zeta * zeta);
64
+ return 1 - Math.exp(-zeta * w0 * elapsed) * (Math.cos(wd * elapsed) + ((zeta * w0) / wd) * Math.sin(wd * elapsed));
65
+ }
66
+ return 1 - Math.exp(-w0 * elapsed) * (1 + w0 * elapsed);
67
+ };
68
+ // Progress 0..1 (may overshoot) for a segment, given linear progress x and segment length in seconds.
69
+ export const applyEase = (ease, x, segSeconds = 1) => {
70
+ if (!ease || ease === 'linear')
71
+ return x;
72
+ if (ease === 'hold')
73
+ return x >= 1 ? 1 : 0;
74
+ if (ease === 'elasticOut')
75
+ return elasticOut(x);
76
+ if (ease === 'bounceOut')
77
+ return bounceOut(x);
78
+ if (typeof ease === 'object' && !Array.isArray(ease))
79
+ return x >= 1 ? 1 : spring(x * segSeconds, ease.spring);
80
+ const pts = Array.isArray(ease) ? ease : CURVES[ease];
81
+ if (!pts)
82
+ return x;
83
+ const key = pts.join(',');
84
+ let fn = cache.get(key);
85
+ if (!fn)
86
+ cache.set(key, (fn = bezier(...pts)));
87
+ return fn(x);
88
+ };
89
+ export const EASE_NAMES = ['linear', 'hold', ...Object.keys(CURVES), 'elasticOut', 'bounceOut'];
90
+ //# sourceMappingURL=ease.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ease.js","sourceRoot":"","sources":["../../src/core/ease.ts"],"names":[],"mappings":"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"}