animot-presenter 0.5.5 → 0.5.7

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.
@@ -0,0 +1,348 @@
1
+ /**
2
+ * Svelte action: animates a text element's content via JS RAF.
3
+ *
4
+ * Implemented modes:
5
+ * • fade-letters — wraps each character in a <span> and fades them in with stagger
6
+ * • bounce-in — same wrapping, scale+translate pop per character
7
+ * • handwriting — re-renders the text inside an <svg><text> with stroke + transparent
8
+ * fill and animates `stroke-dashoffset` so the text appears to be
9
+ * drawn left-to-right (works best with cursive/script fonts)
10
+ *
11
+ * Other modes (instant, typewriter, fade-words) are handled elsewhere by the
12
+ * /present render path — this action no-ops for those.
13
+ *
14
+ * The action registers a restart fn into `window.__svgAnimRestart` so the
15
+ * server-side video export pipeline can reset all animations under the
16
+ * virtual clock at the start of the slide hold (same pattern as FlowMarkers
17
+ * and arrowClipDraw).
18
+ */
19
+ export function textAnimate(node, params) {
20
+ let raf = 0;
21
+ let originalContent = null;
22
+ let activeMode = null;
23
+ function clearAnim() {
24
+ if (raf)
25
+ cancelAnimationFrame(raf);
26
+ raf = 0;
27
+ }
28
+ function restore() {
29
+ clearAnim();
30
+ // Replace whatever we injected with the original content. Doing this on
31
+ // every (re)start keeps the DOM clean when the user toggles modes.
32
+ if (originalContent !== null)
33
+ node.textContent = originalContent;
34
+ }
35
+ function effectiveDuration() {
36
+ const requested = Math.max(50, params.duration || 800);
37
+ if (params.slideDuration && params.slideDuration > 0 && params.loop) {
38
+ return params.slideDuration / Math.max(1, Math.round(params.slideDuration / requested));
39
+ }
40
+ return requested;
41
+ }
42
+ /** Wraps each character of `text` in a <span class="ta-char">. Spaces are
43
+ * also wrapped (with a special class) so the per-letter animation runs
44
+ * uniformly across word boundaries while still preserving spacing. */
45
+ function wrapChars(text) {
46
+ const frag = document.createElement('span');
47
+ frag.className = 'ta-wrap';
48
+ for (const ch of text) {
49
+ const s = document.createElement('span');
50
+ s.className = ch === ' ' ? 'ta-char ta-space' : 'ta-char';
51
+ s.textContent = ch === ' ' ? ' ' : ch; // nbsp so spaces render as fixed-width in inline-block
52
+ s.style.display = 'inline-block';
53
+ frag.appendChild(s);
54
+ }
55
+ return frag;
56
+ }
57
+ function runFadeLetters() {
58
+ // Source the text from params.content, not node.textContent — the host
59
+ // template intentionally renders nothing when an action mode is active
60
+ // (so we don't get a flash of unstyled text), which means the DOM is
61
+ // empty when this runs. Reading textContent here would yield ''.
62
+ originalContent = params.content ?? '';
63
+ node.innerHTML = '';
64
+ const wrap = wrapChars(originalContent);
65
+ node.appendChild(wrap);
66
+ const chars = Array.from(wrap.querySelectorAll('.ta-char'));
67
+ const stagger = params.stagger ?? Math.max(20, Math.min(60, 600 / Math.max(1, chars.length)));
68
+ const perLetter = 280;
69
+ const total = stagger * chars.length + perLetter;
70
+ const dur = effectiveDuration();
71
+ const totalEffective = params.loop ? dur : total;
72
+ for (const c of chars)
73
+ c.style.opacity = '0';
74
+ const start = performance.now();
75
+ function step(now) {
76
+ const elapsed = (now - start) % (params.loop ? totalEffective : Number.POSITIVE_INFINITY);
77
+ let allDone = true;
78
+ for (let i = 0; i < chars.length; i++) {
79
+ const localStart = i * stagger;
80
+ const t = Math.min(1, Math.max(0, (elapsed - localStart) / perLetter));
81
+ if (t < 1)
82
+ allDone = false;
83
+ chars[i].style.opacity = String(t);
84
+ }
85
+ if (params.loop || !allDone)
86
+ raf = requestAnimationFrame(step);
87
+ else
88
+ raf = 0;
89
+ }
90
+ raf = requestAnimationFrame(step);
91
+ }
92
+ function runBounceIn() {
93
+ // Source the text from params.content, not node.textContent — the host
94
+ // template intentionally renders nothing when an action mode is active
95
+ // (so we don't get a flash of unstyled text), which means the DOM is
96
+ // empty when this runs. Reading textContent here would yield ''.
97
+ originalContent = params.content ?? '';
98
+ node.innerHTML = '';
99
+ const wrap = wrapChars(originalContent);
100
+ node.appendChild(wrap);
101
+ const chars = Array.from(wrap.querySelectorAll('.ta-char'));
102
+ const stagger = params.stagger ?? Math.max(30, Math.min(80, 800 / Math.max(1, chars.length)));
103
+ const perLetter = 380;
104
+ for (const c of chars) {
105
+ c.style.opacity = '0';
106
+ c.style.transform = 'translateY(0.4em) scale(0.6)';
107
+ c.style.transformOrigin = '50% 80%';
108
+ }
109
+ // `let` because the loop branch resets `start = now` to begin a new cycle.
110
+ let start = performance.now();
111
+ function step(now) {
112
+ const elapsed = now - start;
113
+ let allDone = true;
114
+ for (let i = 0; i < chars.length; i++) {
115
+ const t = Math.min(1, Math.max(0, (elapsed - i * stagger) / perLetter));
116
+ if (t < 1)
117
+ allDone = false;
118
+ // Spring-ish easing — overshoots slightly then settles.
119
+ const eased = 1 - Math.pow(1 - t, 3);
120
+ const overshoot = Math.sin(t * Math.PI) * 0.1;
121
+ const scale = 0.6 + (1 - 0.6) * eased + overshoot;
122
+ const translate = (1 - eased) * 0.4;
123
+ chars[i].style.opacity = String(eased);
124
+ chars[i].style.transform = `translateY(${translate}em) scale(${scale})`;
125
+ }
126
+ if (!allDone)
127
+ raf = requestAnimationFrame(step);
128
+ else if (params.loop) {
129
+ // Reset and loop — a small pause at the end keeps it readable.
130
+ const totalCycle = effectiveDuration();
131
+ if (now - start >= totalCycle) {
132
+ for (const c of chars) {
133
+ c.style.opacity = '0';
134
+ c.style.transform = 'translateY(0.4em) scale(0.6)';
135
+ }
136
+ start = now;
137
+ }
138
+ raf = requestAnimationFrame(step);
139
+ }
140
+ else {
141
+ raf = 0;
142
+ }
143
+ }
144
+ raf = requestAnimationFrame(step);
145
+ }
146
+ function runHandwriting() {
147
+ // Real per-character handwriting: each glyph is its own <text> element
148
+ // with its own stroke-dasharray. We measure char positions from a single
149
+ // hidden <text> first (the only reliable way to get kerning/spacing
150
+ // matching what the browser would draw) then build per-glyph elements
151
+ // laid out in absolute SVG coordinates. Each glyph is revealed in turn
152
+ // with a small overlap so the effect reads like a continuous pen, not
153
+ // a typewriter.
154
+ originalContent = params.content ?? '';
155
+ node.innerHTML = '';
156
+ if (!originalContent)
157
+ return;
158
+ const color = params.color ?? '#ffffff';
159
+ const fs = params.fontSize ?? 48;
160
+ const ff = params.fontFamily ?? 'inherit';
161
+ const fw = params.fontWeight ?? 400;
162
+ const fst = params.fontStyle ?? 'normal';
163
+ const align = params.textAlign ?? 'left';
164
+ const svgNS = 'http://www.w3.org/2000/svg';
165
+ const svg = document.createElementNS(svgNS, 'svg');
166
+ svg.setAttribute('width', '100%');
167
+ svg.setAttribute('height', '100%');
168
+ const w = Math.max(1, node.clientWidth);
169
+ const h = Math.max(1, node.clientHeight);
170
+ svg.setAttribute('viewBox', `0 0 ${w} ${h}`);
171
+ svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
172
+ svg.style.overflow = 'visible';
173
+ node.appendChild(svg);
174
+ // Step 1 — measure char positions from a hidden full-string render.
175
+ const measure = document.createElementNS(svgNS, 'text');
176
+ measure.setAttribute('x', '0');
177
+ measure.setAttribute('y', '50%');
178
+ measure.setAttribute('dominant-baseline', 'middle');
179
+ measure.setAttribute('text-anchor', 'start');
180
+ measure.setAttribute('visibility', 'hidden');
181
+ measure.style.fontSize = `${fs}px`;
182
+ measure.style.fontFamily = ff;
183
+ measure.style.fontWeight = String(fw);
184
+ measure.style.fontStyle = fst;
185
+ measure.textContent = originalContent;
186
+ svg.appendChild(measure);
187
+ const charPositions = [];
188
+ const charLengths = [];
189
+ let totalWidth = 0;
190
+ try {
191
+ const t = measure;
192
+ totalWidth = t.getComputedTextLength();
193
+ for (let i = 0; i < originalContent.length; i++) {
194
+ const pt = t.getStartPositionOfChar(i);
195
+ charPositions.push(pt.x);
196
+ const nextX = i + 1 < originalContent.length
197
+ ? t.getStartPositionOfChar(i + 1).x
198
+ : pt.x + (totalWidth - pt.x);
199
+ charLengths.push(Math.max(1, nextX - pt.x));
200
+ }
201
+ }
202
+ catch {
203
+ // Fallback: even spacing across host width.
204
+ totalWidth = w * 0.9;
205
+ const stepW = totalWidth / Math.max(1, originalContent.length);
206
+ for (let i = 0; i < originalContent.length; i++) {
207
+ charPositions.push(i * stepW);
208
+ charLengths.push(stepW);
209
+ }
210
+ }
211
+ // Honor textAlign by shifting the whole row in absolute coordinates.
212
+ const baseX = align === 'center' ? (w - totalWidth) / 2
213
+ : align === 'right' ? (w - totalWidth)
214
+ : 0;
215
+ svg.removeChild(measure);
216
+ // Step 2 — build per-character <text> elements.
217
+ const glyphs = [];
218
+ for (let i = 0; i < originalContent.length; i++) {
219
+ const ch = originalContent[i];
220
+ const t = document.createElementNS(svgNS, 'text');
221
+ t.setAttribute('x', String(baseX + charPositions[i]));
222
+ t.setAttribute('y', '50%');
223
+ t.setAttribute('dominant-baseline', 'middle');
224
+ t.setAttribute('text-anchor', 'start');
225
+ t.setAttribute('fill', 'transparent');
226
+ t.setAttribute('stroke', color);
227
+ t.setAttribute('stroke-width', '1');
228
+ t.setAttribute('stroke-linecap', 'round');
229
+ t.setAttribute('stroke-linejoin', 'round');
230
+ t.style.fontSize = `${fs}px`;
231
+ t.style.fontFamily = ff;
232
+ t.style.fontWeight = String(fw);
233
+ t.style.fontStyle = fst;
234
+ t.textContent = ch;
235
+ svg.appendChild(t);
236
+ // Spaces have no visible stroke — skip dashoffset math, they reveal
237
+ // instantly. For everything else, we don't know the exact outline
238
+ // length per glyph, so we pick a generous upper bound that comfortably
239
+ // exceeds any realistic glyph contour at this font size, then use an
240
+ // explicit *huge* gap so the dash pattern can't repeat. Without the
241
+ // huge gap, complex letters (g, B, &, ampersand-heavy scripts) whose
242
+ // outline is longer than our `len` estimate would show a second dash
243
+ // cycle peeking through before the animation reaches them.
244
+ const len = ch === ' ' ? 0 : Math.max(40, fs * 4 + charLengths[i] * 2.5);
245
+ const gap = len * 4;
246
+ t.style.strokeDasharray = `${len || 1} ${gap || 1}`;
247
+ t.style.strokeDashoffset = String(len || 1);
248
+ glyphs.push({ el: t, len });
249
+ }
250
+ const dur = effectiveDuration();
251
+ // Per-char duration with overlap. overlap=0.6 means each char starts after
252
+ // the previous is 60% drawn — looks like a continuous pen rather than a
253
+ // typewriter (overlap=1) or strict per-letter sequence (overlap=0).
254
+ const n = originalContent.length;
255
+ const overlap = 0.6;
256
+ // Solve: stagger * (n - 1) + perChar = dur, where stagger = perChar * overlap.
257
+ // → perChar * (overlap * (n - 1) + 1) = dur.
258
+ const perChar = dur / Math.max(1, overlap * (n - 1) + 1);
259
+ const stagger = perChar * overlap;
260
+ let start = performance.now();
261
+ function step(now) {
262
+ const elapsed = now - start;
263
+ let allDone = true;
264
+ for (let i = 0; i < glyphs.length; i++) {
265
+ const g = glyphs[i];
266
+ if (g.len === 0)
267
+ continue; // space — already invisible
268
+ const localStart = i * stagger;
269
+ const t = Math.min(1, Math.max(0, (elapsed - localStart) / perChar));
270
+ if (t < 1)
271
+ allDone = false;
272
+ const eased = 1 - Math.pow(1 - t, 3);
273
+ g.el.style.strokeDashoffset = String(g.len * (1 - eased));
274
+ // Cross-fade fill in over the last 30% of the glyph's draw so the
275
+ // finished letter reads cleanly. Otherwise thin script fonts look
276
+ // hollow and don't match the rest of the slide's text.
277
+ if (t > 0.7) {
278
+ const fillT = (t - 0.7) / 0.3;
279
+ g.el.setAttribute('fill', color);
280
+ g.el.setAttribute('fill-opacity', String(fillT));
281
+ }
282
+ }
283
+ if (!allDone) {
284
+ raf = requestAnimationFrame(step);
285
+ }
286
+ else if (params.loop) {
287
+ for (const g of glyphs) {
288
+ g.el.style.strokeDashoffset = String(g.len || 1);
289
+ g.el.setAttribute('fill', 'transparent');
290
+ g.el.removeAttribute('fill-opacity');
291
+ }
292
+ start = now;
293
+ raf = requestAnimationFrame(step);
294
+ }
295
+ else {
296
+ raf = 0;
297
+ }
298
+ }
299
+ raf = requestAnimationFrame(step);
300
+ }
301
+ function run() {
302
+ restore();
303
+ activeMode = params.mode;
304
+ if (!params.enabled)
305
+ return;
306
+ switch (params.mode) {
307
+ case 'fade-letters':
308
+ runFadeLetters();
309
+ break;
310
+ case 'bounce-in':
311
+ runBounceIn();
312
+ break;
313
+ case 'handwriting':
314
+ runHandwriting();
315
+ break;
316
+ default: /* other modes handled elsewhere */ break;
317
+ }
318
+ }
319
+ queueMicrotask(run);
320
+ if (typeof window !== 'undefined') {
321
+ const reg = (window.__svgAnimRestart ||= []);
322
+ reg.push(run);
323
+ node.__svgAnimRestart = run;
324
+ }
325
+ let lastKey = params.key;
326
+ return {
327
+ update(p) {
328
+ const keyChanged = p.key !== lastKey || p.mode !== activeMode;
329
+ params = p;
330
+ if (keyChanged) {
331
+ lastKey = p.key;
332
+ queueMicrotask(run);
333
+ }
334
+ },
335
+ destroy() {
336
+ restore();
337
+ if (typeof window !== 'undefined') {
338
+ const reg = window.__svgAnimRestart;
339
+ const fn = node.__svgAnimRestart;
340
+ if (reg && fn) {
341
+ const idx = reg.indexOf(fn);
342
+ if (idx >= 0)
343
+ reg.splice(idx, 1);
344
+ }
345
+ }
346
+ }
347
+ };
348
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Parse a video URL into an embed descriptor. Supports YouTube and Vimeo.
3
+ * Returns null for URLs we don't recognize (the caller should treat those as
4
+ * raw `<video src>` files). Used by VideoElement renderers to switch between
5
+ * `<video>` and `<iframe>` rendering.
6
+ *
7
+ * Important: embed URLs cannot be captured by the server-side export pipeline
8
+ * (cross-origin iframe content isn't accessible to Puppeteer's `__tick` virtual
9
+ * clock and YouTube's player ignores any external time signal). We surface
10
+ * this as a warning in the properties panel.
11
+ */
12
+ export interface VideoEmbed {
13
+ provider: 'youtube' | 'vimeo';
14
+ /** ID of the video on the provider (`dQw4w9WgXcQ` for YouTube, `123456` for Vimeo). */
15
+ id: string;
16
+ /** Fully-formed src for an `<iframe>`. */
17
+ embedSrc: string;
18
+ /** A poster image we can probe for from the provider. */
19
+ thumbnail?: string;
20
+ }
21
+ export interface ParseEmbedOptions {
22
+ autoplay?: boolean;
23
+ loop?: boolean;
24
+ muted?: boolean;
25
+ startTime?: number;
26
+ endTime?: number;
27
+ showControls?: boolean;
28
+ }
29
+ export declare function parseEmbedUrl(url: string, opts?: ParseEmbedOptions): VideoEmbed | null;
30
+ export declare function isEmbedUrl(url: string): boolean;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Parse a video URL into an embed descriptor. Supports YouTube and Vimeo.
3
+ * Returns null for URLs we don't recognize (the caller should treat those as
4
+ * raw `<video src>` files). Used by VideoElement renderers to switch between
5
+ * `<video>` and `<iframe>` rendering.
6
+ *
7
+ * Important: embed URLs cannot be captured by the server-side export pipeline
8
+ * (cross-origin iframe content isn't accessible to Puppeteer's `__tick` virtual
9
+ * clock and YouTube's player ignores any external time signal). We surface
10
+ * this as a warning in the properties panel.
11
+ */
12
+ const YOUTUBE_RE = /(?:youtube\.com\/(?:watch\?v=|embed\/|shorts\/|live\/)|youtu\.be\/)([A-Za-z0-9_-]{6,16})/;
13
+ const VIMEO_RE = /vimeo\.com\/(?:video\/)?(\d{6,12})/;
14
+ export function parseEmbedUrl(url, opts = {}) {
15
+ if (!url)
16
+ return null;
17
+ if (url.startsWith('data:'))
18
+ return null;
19
+ const ytMatch = url.match(YOUTUBE_RE);
20
+ if (ytMatch) {
21
+ const id = ytMatch[1];
22
+ // YouTube's embed parameters: muted+autoplay = playsinline-on-mobile-friendly,
23
+ // loop requires both `loop=1` AND `playlist=<same id>` (quirk of YouTube's API).
24
+ const params = [];
25
+ if (opts.autoplay)
26
+ params.push('autoplay=1');
27
+ if (opts.muted || opts.autoplay)
28
+ params.push('mute=1'); // browsers block unmuted autoplay
29
+ if (opts.loop) {
30
+ params.push('loop=1');
31
+ params.push(`playlist=${id}`);
32
+ }
33
+ if (opts.startTime)
34
+ params.push(`start=${Math.round(opts.startTime)}`);
35
+ if (opts.endTime)
36
+ params.push(`end=${Math.round(opts.endTime)}`);
37
+ if (!opts.showControls)
38
+ params.push('controls=0');
39
+ params.push('rel=0', 'modestbranding=1', 'playsinline=1');
40
+ return {
41
+ provider: 'youtube',
42
+ id,
43
+ embedSrc: `https://www.youtube.com/embed/${id}?${params.join('&')}`,
44
+ thumbnail: `https://i.ytimg.com/vi/${id}/maxresdefault.jpg`
45
+ };
46
+ }
47
+ const vimMatch = url.match(VIMEO_RE);
48
+ if (vimMatch) {
49
+ const id = vimMatch[1];
50
+ const params = [];
51
+ if (opts.autoplay)
52
+ params.push('autoplay=1');
53
+ if (opts.muted || opts.autoplay)
54
+ params.push('muted=1');
55
+ if (opts.loop)
56
+ params.push('loop=1');
57
+ if (!opts.showControls)
58
+ params.push('controls=0');
59
+ params.push('byline=0', 'portrait=0', 'title=0');
60
+ return {
61
+ provider: 'vimeo',
62
+ id,
63
+ embedSrc: `https://player.vimeo.com/video/${id}?${params.join('&')}`
64
+ };
65
+ }
66
+ return null;
67
+ }
68
+ export function isEmbedUrl(url) {
69
+ return parseEmbedUrl(url) !== null;
70
+ }
package/package.json CHANGED
@@ -1,84 +1,84 @@
1
- {
2
- "name": "animot-presenter",
3
- "version": "0.5.5",
4
- "description": "Embed animated presentations anywhere. Works with vanilla JS, React, Vue, Angular, Svelte, and any frontend framework. Morphing animations, code highlighting, charts, particles, and more.",
5
- "type": "module",
6
- "svelte": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "svelte": "./dist/index.js",
11
- "types": "./dist/index.d.ts",
12
- "default": "./dist/index.js"
13
- },
14
- "./element": {
15
- "import": "./dist/cdn/animot-presenter.esm.js",
16
- "require": "./dist/cdn/animot-presenter.min.js"
17
- },
18
- "./cdn": {
19
- "import": "./dist/cdn/animot-presenter.esm.js",
20
- "require": "./dist/cdn/animot-presenter.min.js"
21
- },
22
- "./styles": "./dist/styles/presenter.css"
23
- },
24
- "files": [
25
- "dist",
26
- "!dist/**/*.test.*",
27
- "!dist/**/*.spec.*"
28
- ],
29
- "scripts": {
30
- "dev": "vite dev",
31
- "build": "npm run build:svelte && npm run build:element",
32
- "build:svelte": "svelte-kit sync && svelte-package -o dist",
33
- "build:element": "vite build --config vite.element.config.ts",
34
- "package": "npm run build",
35
- "preview": "vite preview",
36
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
37
- "prepublishOnly": "npm run build"
38
- },
39
- "peerDependencies": {
40
- "svelte": "^5.0.0"
41
- },
42
- "peerDependenciesMeta": {
43
- "svelte": {
44
- "optional": true
45
- }
46
- },
47
- "devDependencies": {
48
- "@sveltejs/adapter-auto": "^3.0.0",
49
- "@sveltejs/kit": "^2.0.0",
50
- "@sveltejs/package": "^2.0.0",
51
- "@sveltejs/vite-plugin-svelte": "^4.0.0",
52
- "@types/canvas-confetti": "^1.9.0",
53
- "svelte": "^5.0.0",
54
- "svelte-check": "^4.0.0",
55
- "typescript": "^5.0.0",
56
- "vite": "^5.0.0"
57
- },
58
- "dependencies": {
59
- "@animotion/motion": "^2.0.1",
60
- "canvas-confetti": "^1.9.4",
61
- "shiki": "^1.0.0"
62
- },
63
- "keywords": [
64
- "svelte",
65
- "react",
66
- "vue",
67
- "angular",
68
- "web-component",
69
- "animation",
70
- "presentation",
71
- "slides",
72
- "morphing",
73
- "animot",
74
- "code-animation",
75
- "typewriter",
76
- "charts",
77
- "particles"
78
- ],
79
- "license": "BUSL-1.1",
80
- "repository": {
81
- "type": "git",
82
- "url": "https://github.com/beeblock/animot-presenter"
83
- }
84
- }
1
+ {
2
+ "name": "animot-presenter",
3
+ "version": "0.5.7",
4
+ "description": "Embed animated presentations anywhere. Works with vanilla JS, React, Vue, Angular, Svelte, and any frontend framework. Morphing animations, code highlighting, charts, particles, and more.",
5
+ "type": "module",
6
+ "svelte": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "svelte": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./element": {
15
+ "import": "./dist/cdn/animot-presenter.esm.js",
16
+ "require": "./dist/cdn/animot-presenter.min.js"
17
+ },
18
+ "./cdn": {
19
+ "import": "./dist/cdn/animot-presenter.esm.js",
20
+ "require": "./dist/cdn/animot-presenter.min.js"
21
+ },
22
+ "./styles": "./dist/styles/presenter.css"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "!dist/**/*.test.*",
27
+ "!dist/**/*.spec.*"
28
+ ],
29
+ "scripts": {
30
+ "dev": "vite dev",
31
+ "build": "npm run build:svelte && npm run build:element",
32
+ "build:svelte": "svelte-kit sync && svelte-package -o dist",
33
+ "build:element": "vite build --config vite.element.config.ts",
34
+ "package": "npm run build",
35
+ "preview": "vite preview",
36
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
37
+ "prepublishOnly": "npm run build"
38
+ },
39
+ "peerDependencies": {
40
+ "svelte": "^5.0.0"
41
+ },
42
+ "peerDependenciesMeta": {
43
+ "svelte": {
44
+ "optional": true
45
+ }
46
+ },
47
+ "devDependencies": {
48
+ "@sveltejs/adapter-auto": "^3.0.0",
49
+ "@sveltejs/kit": "^2.0.0",
50
+ "@sveltejs/package": "^2.0.0",
51
+ "@sveltejs/vite-plugin-svelte": "^4.0.0",
52
+ "@types/canvas-confetti": "^1.9.0",
53
+ "svelte": "^5.0.0",
54
+ "svelte-check": "^4.0.0",
55
+ "typescript": "^5.0.0",
56
+ "vite": "^5.0.0"
57
+ },
58
+ "dependencies": {
59
+ "@animotion/motion": "^2.0.1",
60
+ "canvas-confetti": "^1.9.4",
61
+ "shiki": "^1.0.0"
62
+ },
63
+ "keywords": [
64
+ "svelte",
65
+ "react",
66
+ "vue",
67
+ "angular",
68
+ "web-component",
69
+ "animation",
70
+ "presentation",
71
+ "slides",
72
+ "morphing",
73
+ "animot",
74
+ "code-animation",
75
+ "typewriter",
76
+ "charts",
77
+ "particles"
78
+ ],
79
+ "license": "BUSL-1.1",
80
+ "repository": {
81
+ "type": "git",
82
+ "url": "https://github.com/beeblock/animot-presenter"
83
+ }
84
+ }