sketchmark 2.1.0 → 2.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 (77) hide show
  1. package/package.json +1 -7
  2. package/ANIMATABLE_MATRIX.md +0 -177
  3. package/KERNEL_SPEC.md +0 -412
  4. package/PACKS.md +0 -81
  5. package/PRESETS.md +0 -182
  6. package/examples/1730642890464.jpg +0 -0
  7. package/examples/app-screen.svg +0 -1
  8. package/examples/app-screen.visual.json +0 -503
  9. package/examples/dashboard-table.svg +0 -1
  10. package/examples/dashboard-table.visual.json +0 -708
  11. package/examples/dev-docs.svg +0 -1
  12. package/examples/dev-docs.visual.json +0 -248
  13. package/examples/explainer.mp4 +0 -0
  14. package/examples/explainer.visual.json +0 -1713
  15. package/examples/group-origin-effects-lab-check.svg +0 -1
  16. package/examples/group-origin-effects-lab.visual.json +0 -1880
  17. package/examples/image-clip-radius.visual.json +0 -271
  18. package/examples/make-app-screen.cjs +0 -368
  19. package/examples/make-dashboard-table.cjs +0 -277
  20. package/examples/make-dev-docs.cjs +0 -233
  21. package/examples/make-explainer.cjs +0 -438
  22. package/examples/make-group-origin-effects-lab.cjs +0 -370
  23. package/examples/make-image-clip-radius.cjs +0 -169
  24. package/examples/make-modal-dialog.cjs +0 -355
  25. package/examples/make-origin-effects-lab.cjs +0 -311
  26. package/examples/make-preset-character-motion.cjs +0 -32
  27. package/examples/make-presets-demo.cjs +0 -30
  28. package/examples/make-pricing.cjs +0 -286
  29. package/examples/make-product-demo.cjs +0 -468
  30. package/examples/make-product-hero.cjs +0 -223
  31. package/examples/make-release-notes.cjs +0 -333
  32. package/examples/make-settings-panel.cjs +0 -435
  33. package/examples/make-split-preview.cjs +0 -248
  34. package/examples/make-storyboard.cjs +0 -215
  35. package/examples/make-transcript.cjs +0 -234
  36. package/examples/make-typography-test.cjs +0 -397
  37. package/examples/make-ui-demo-explainer.cjs +0 -1094
  38. package/examples/make-ui-flow.cjs +0 -762
  39. package/examples/make-walkthrough.cjs +0 -815
  40. package/examples/modal-dialog.svg +0 -1
  41. package/examples/modal-dialog.visual.json +0 -239
  42. package/examples/origin-effects-lab-check.svg +0 -1
  43. package/examples/origin-effects-lab.visual.json +0 -1412
  44. package/examples/preset-character-motion.visual.json +0 -949
  45. package/examples/presets-demo.visual.json +0 -787
  46. package/examples/pricing.svg +0 -1
  47. package/examples/pricing.visual.json +0 -652
  48. package/examples/product-demo.mp4 +0 -0
  49. package/examples/product-demo.visual.json +0 -866
  50. package/examples/product-hero.svg +0 -1
  51. package/examples/product-hero.visual.json +0 -242
  52. package/examples/release-notes.svg +0 -1
  53. package/examples/release-notes.visual.json +0 -467
  54. package/examples/settings-panel.svg +0 -1
  55. package/examples/settings-panel.visual.json +0 -501
  56. package/examples/split-preview.svg +0 -1
  57. package/examples/split-preview.visual.json +0 -124
  58. package/examples/storyboard.svg +0 -1
  59. package/examples/storyboard.visual.json +0 -312
  60. package/examples/transcript.svg +0 -1
  61. package/examples/transcript.visual.json +0 -407
  62. package/examples/typography-indent-check.svg +0 -1
  63. package/examples/typography-lineheight-0.svg +0 -1
  64. package/examples/typography-lineheight-2.svg +0 -1
  65. package/examples/typography-test-check.svg +0 -1
  66. package/examples/typography-test.svg +0 -1
  67. package/examples/typography-test.visual.json +0 -757
  68. package/examples/ui-demo-explainer-billing.svg +0 -1
  69. package/examples/ui-demo-explainer-check.svg +0 -1
  70. package/examples/ui-demo-explainer-save.svg +0 -1
  71. package/examples/ui-demo-explainer-toggle.svg +0 -1
  72. package/examples/ui-demo-explainer.mp4 +0 -0
  73. package/examples/ui-demo-explainer.visual.json +0 -2597
  74. package/examples/ui-flow.mp4 +0 -0
  75. package/examples/ui-flow.visual.json +0 -1211
  76. package/examples/walkthrough.mp4 +0 -0
  77. package/examples/walkthrough.visual.json +0 -1372
@@ -1,762 +0,0 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
-
4
- const width = 1280;
5
- const height = 720;
6
- const duration = 18;
7
- const fps = 30;
8
- const bg = "#f8fafc";
9
- const font = "Inter, system-ui, sans-serif";
10
-
11
- const colors = {
12
- text: "#0f172a",
13
- textMuted: "#64748b",
14
- panelBg: "#ffffff",
15
- panelBorder: "#e2e8f0",
16
- inputBg: "#f8fafc",
17
- inputBorder: "#cbd5e1",
18
- btnPrimary: "#2563eb",
19
- btnPrimaryText: "#ffffff",
20
- btnSecondary: "#ffffff",
21
- btnSecondaryText: "#475569",
22
- btnSecondaryBorder: "#e2e8f0",
23
- accent: "#2563eb",
24
- success: "#10b981",
25
- successBg: "#ecfdf5",
26
- cursor: "#0f172a",
27
- cursorRing: "#3b82f6",
28
- backdrop: "#0f172a"
29
- };
30
-
31
- const curves = {
32
- ease: { type: "cubicBezier", x1: 0.4, y1: 0, x2: 0.2, y2: 1 },
33
- easeOut: { type: "cubicBezier", x1: 0, y1: 0, x2: 0.2, y2: 1 },
34
- gentle: { type: "cubicBezier", x1: 0.25, y1: 0.1, x2: 0.25, y2: 1 }
35
- };
36
-
37
- const elements = [];
38
-
39
- // Layout constants
40
- const cardW = 520;
41
- const cardH = 400;
42
- const cardX = (width - cardW) / 2;
43
- const cardY = (height - cardH) / 2;
44
-
45
- // Button positions (for cursor targeting)
46
- const uploadBtnX = cardX + cardW / 2;
47
- const uploadBtnY = cardY + 260;
48
- const fileBtnX = cardX + cardW / 2;
49
- const fileBtnY = cardY + 200;
50
- const confirmBtnX = cardX + cardW - 90;
51
- const confirmBtnY = cardY + cardH - 52;
52
-
53
- // === Card background (persistent) ===
54
- elements.push({
55
- id: "main-card-bg",
56
- type: "path",
57
- d: roundedRect(cardX, cardY, cardW, cardH, 16),
58
- fill: colors.panelBg,
59
- stroke: colors.panelBorder,
60
- strokeWidth: 1,
61
- opacity: 0,
62
- timeline: {
63
- tracks: {
64
- opacity: {
65
- keyframes: [
66
- { time: 0, value: 0, out: curves.ease },
67
- { time: 0.6, value: 1 },
68
- { time: 17, value: 1, out: curves.ease },
69
- { time: 17.5, value: 0 }
70
- ]
71
- }
72
- }
73
- }
74
- });
75
-
76
- // Card title (persistent)
77
- elements.push({
78
- id: "card-title",
79
- type: "text",
80
- x: cardX + 32,
81
- y: cardY + 32,
82
- text: "Upload Document",
83
- align: "left",
84
- valign: "top",
85
- fontSize: 20,
86
- fontFamily: font,
87
- weight: 600,
88
- fill: colors.text,
89
- opacity: 0,
90
- timeline: {
91
- tracks: {
92
- opacity: {
93
- keyframes: [
94
- { time: 0, value: 0, out: curves.ease },
95
- { time: 0.6, value: 1 },
96
- { time: 17, value: 1, out: curves.ease },
97
- { time: 17.5, value: 0 }
98
- ]
99
- }
100
- }
101
- }
102
- });
103
-
104
- // Card description (persistent until success)
105
- elements.push({
106
- id: "card-desc",
107
- type: "text",
108
- x: cardX + 32,
109
- y: cardY + 62,
110
- text: "Add a file to get started with your project.",
111
- align: "left",
112
- valign: "top",
113
- fontSize: 14,
114
- fontFamily: font,
115
- weight: 400,
116
- fill: colors.textMuted,
117
- opacity: 0,
118
- timeline: {
119
- tracks: {
120
- opacity: {
121
- keyframes: [
122
- { time: 0, value: 0, out: curves.ease },
123
- { time: 0.6, value: 1 },
124
- { time: 13.5, value: 1, out: curves.ease },
125
- { time: 14, value: 0 }
126
- ]
127
- }
128
- }
129
- }
130
- });
131
-
132
- // === SCENE 1: Empty state (0-6s) - upload zone ===
133
- elements.push({
134
- id: "upload-zone",
135
- type: "group",
136
- x: cardX + 32,
137
- y: cardY + 100,
138
- children: [
139
- {
140
- id: "upload-zone-bg",
141
- type: "path",
142
- d: roundedRect(0, 0, cardW - 64, 140, 12),
143
- fill: colors.inputBg,
144
- stroke: colors.inputBorder,
145
- strokeWidth: 2,
146
- dashArray: [8, 4]
147
- },
148
- {
149
- id: "upload-icon",
150
- type: "path",
151
- d: "M 0 12 L 0 4 A 4 4 0 0 1 4 0 L 20 0 A 4 4 0 0 1 24 4 L 24 12 M 12 20 L 12 6 M 6 12 L 12 6 L 18 12",
152
- x: (cardW - 64) / 2 - 12,
153
- y: 40,
154
- fill: "none",
155
- stroke: colors.textMuted,
156
- strokeWidth: 2,
157
- strokeCap: "round",
158
- strokeJoin: "round"
159
- },
160
- {
161
- id: "upload-text",
162
- type: "text",
163
- x: (cardW - 64) / 2,
164
- y: 90,
165
- text: "Drop files here or click to browse",
166
- align: "center",
167
- valign: "top",
168
- fontSize: 13,
169
- fontFamily: font,
170
- weight: 400,
171
- fill: colors.textMuted
172
- }
173
- ],
174
- opacity: 0,
175
- timeline: {
176
- tracks: {
177
- opacity: {
178
- keyframes: [
179
- { time: 0.3, value: 0, out: curves.ease },
180
- { time: 0.8, value: 1 },
181
- // Fade out when file is selected
182
- { time: 5.8, value: 1, out: curves.ease },
183
- { time: 6.3, value: 0 }
184
- ]
185
- }
186
- }
187
- }
188
- });
189
-
190
- // Upload button (in empty state)
191
- elements.push({
192
- id: "upload-btn",
193
- type: "group",
194
- x: cardX + cardW / 2 - 70,
195
- y: cardY + 280,
196
- children: [
197
- {
198
- id: "upload-btn-bg",
199
- type: "path",
200
- d: roundedRect(0, 0, 140, 40, 8),
201
- fill: colors.btnPrimary,
202
- stroke: "none"
203
- },
204
- {
205
- id: "upload-btn-text",
206
- type: "text",
207
- x: 70,
208
- y: 20,
209
- text: "Choose File",
210
- align: "center",
211
- valign: "middle",
212
- fontSize: 14,
213
- fontFamily: font,
214
- weight: 600,
215
- fill: colors.btnPrimaryText
216
- }
217
- ],
218
- opacity: 0,
219
- origin: [70, 20],
220
- timeline: {
221
- tracks: {
222
- opacity: {
223
- keyframes: [
224
- { time: 0.5, value: 0, out: curves.ease },
225
- { time: 1, value: 1 },
226
- // Fade out with upload zone
227
- { time: 5.8, value: 1, out: curves.ease },
228
- { time: 6.3, value: 0 }
229
- ]
230
- },
231
- scale: {
232
- keyframes: [
233
- { time: 4.8, value: 1, out: curves.ease },
234
- { time: 4.95, value: 0.95 },
235
- { time: 5.1, value: 1 }
236
- ]
237
- }
238
- }
239
- }
240
- });
241
-
242
- // === SCENE 2: File selected state (6-14s) ===
243
- elements.push({
244
- id: "file-item",
245
- type: "group",
246
- x: cardX + 32,
247
- y: cardY + 100,
248
- children: [
249
- {
250
- id: "file-bg",
251
- type: "path",
252
- d: roundedRect(0, 0, cardW - 64, 64, 10),
253
- fill: colors.panelBg,
254
- stroke: colors.accent,
255
- strokeWidth: 2
256
- },
257
- // File icon
258
- {
259
- id: "file-icon-bg",
260
- type: "path",
261
- d: roundedRect(16, 14, 36, 36, 6),
262
- fill: colors.successBg,
263
- stroke: "none"
264
- },
265
- {
266
- id: "file-icon",
267
- type: "path",
268
- d: "M 28 24 L 28 40 M 28 32 L 22 32 L 28 26 L 34 32 L 28 32",
269
- fill: "none",
270
- stroke: colors.success,
271
- strokeWidth: 2,
272
- strokeCap: "round",
273
- strokeJoin: "round"
274
- },
275
- {
276
- id: "file-name",
277
- type: "text",
278
- x: 68,
279
- y: 22,
280
- text: "quarterly-report.pdf",
281
- align: "left",
282
- valign: "top",
283
- fontSize: 14,
284
- fontFamily: font,
285
- weight: 500,
286
- fill: colors.text
287
- },
288
- {
289
- id: "file-size",
290
- type: "text",
291
- x: 68,
292
- y: 42,
293
- text: "2.4 MB · PDF Document",
294
- align: "left",
295
- valign: "top",
296
- fontSize: 12,
297
- fontFamily: font,
298
- weight: 400,
299
- fill: colors.textMuted
300
- },
301
- // Checkmark
302
- {
303
- id: "file-check",
304
- type: "path",
305
- d: "M 0 0 m -10 0 a 10 10 0 1 1 20 0 a 10 10 0 1 1 -20 0",
306
- x: cardW - 64 - 32,
307
- y: 32,
308
- fill: colors.success,
309
- stroke: "none"
310
- },
311
- {
312
- id: "file-check-mark",
313
- type: "path",
314
- d: "M -4 0 L -1 3 L 4 -3",
315
- x: cardW - 64 - 32,
316
- y: 32,
317
- fill: "none",
318
- stroke: "#ffffff",
319
- strokeWidth: 2,
320
- strokeCap: "round",
321
- strokeJoin: "round"
322
- }
323
- ],
324
- opacity: 0,
325
- timeline: {
326
- tracks: {
327
- opacity: {
328
- keyframes: [
329
- // Fade in after upload zone fades out
330
- { time: 6.3, value: 0, out: curves.ease },
331
- { time: 6.8, value: 1 },
332
- // Stay visible until success state
333
- { time: 13.5, value: 1, out: curves.ease },
334
- { time: 14, value: 0 }
335
- ]
336
- },
337
- y: {
338
- keyframes: [
339
- { time: 6.3, value: cardY + 110, out: curves.ease },
340
- { time: 6.8, value: cardY + 100 }
341
- ]
342
- }
343
- }
344
- }
345
- });
346
-
347
- // Status text - ready
348
- elements.push({
349
- id: "status-text",
350
- type: "text",
351
- x: cardX + cardW / 2,
352
- y: cardY + 190,
353
- text: "File ready to upload",
354
- align: "center",
355
- valign: "top",
356
- fontSize: 13,
357
- fontFamily: font,
358
- weight: 500,
359
- fill: colors.success,
360
- opacity: 0,
361
- timeline: {
362
- tracks: {
363
- opacity: {
364
- keyframes: [
365
- { time: 7, value: 0, out: curves.ease },
366
- { time: 7.4, value: 1 },
367
- // Fade out when uploading starts
368
- { time: 10.5, value: 1, out: curves.ease },
369
- { time: 11, value: 0 }
370
- ]
371
- }
372
- }
373
- }
374
- });
375
-
376
- // Confirm button
377
- const confirmBtnW = 140;
378
- const confirmBtnH = 42;
379
- elements.push({
380
- id: "confirm-btn",
381
- type: "group",
382
- x: cardX + cardW - 32 - confirmBtnW,
383
- y: cardY + cardH - 32 - confirmBtnH,
384
- children: [
385
- {
386
- id: "confirm-btn-bg",
387
- type: "path",
388
- d: roundedRect(0, 0, confirmBtnW, confirmBtnH, 8),
389
- fill: colors.btnPrimary,
390
- stroke: "none"
391
- },
392
- {
393
- id: "confirm-btn-text",
394
- type: "text",
395
- x: confirmBtnW / 2,
396
- y: confirmBtnH / 2,
397
- text: "Upload Now",
398
- align: "center",
399
- valign: "middle",
400
- fontSize: 14,
401
- fontFamily: font,
402
- weight: 600,
403
- fill: colors.btnPrimaryText
404
- }
405
- ],
406
- opacity: 0,
407
- origin: [confirmBtnW / 2, confirmBtnH / 2],
408
- timeline: {
409
- tracks: {
410
- opacity: {
411
- keyframes: [
412
- { time: 7.2, value: 0, out: curves.ease },
413
- { time: 7.6, value: 1 },
414
- // Fade out when upload starts
415
- { time: 10.5, value: 1, out: curves.ease },
416
- { time: 11, value: 0 }
417
- ]
418
- },
419
- scale: {
420
- keyframes: [
421
- { time: 9.8, value: 1, out: curves.ease },
422
- { time: 9.95, value: 0.95 },
423
- { time: 10.1, value: 1 }
424
- ]
425
- }
426
- }
427
- }
428
- });
429
-
430
- // Cancel button
431
- elements.push({
432
- id: "cancel-btn",
433
- type: "group",
434
- x: cardX + 32,
435
- y: cardY + cardH - 32 - confirmBtnH,
436
- children: [
437
- {
438
- id: "cancel-btn-bg",
439
- type: "path",
440
- d: roundedRect(0, 0, 100, confirmBtnH, 8),
441
- fill: colors.btnSecondary,
442
- stroke: colors.btnSecondaryBorder,
443
- strokeWidth: 1
444
- },
445
- {
446
- id: "cancel-btn-text",
447
- type: "text",
448
- x: 50,
449
- y: confirmBtnH / 2,
450
- text: "Cancel",
451
- align: "center",
452
- valign: "middle",
453
- fontSize: 14,
454
- fontFamily: font,
455
- weight: 500,
456
- fill: colors.btnSecondaryText
457
- }
458
- ],
459
- opacity: 0,
460
- timeline: {
461
- tracks: {
462
- opacity: {
463
- keyframes: [
464
- { time: 7.2, value: 0, out: curves.ease },
465
- { time: 7.6, value: 1 },
466
- // Fade out when upload starts
467
- { time: 10.5, value: 1, out: curves.ease },
468
- { time: 11, value: 0 }
469
- ]
470
- }
471
- }
472
- }
473
- });
474
-
475
- // === SCENE 3: Uploading state (11-14s) ===
476
- elements.push({
477
- id: "uploading-text",
478
- type: "text",
479
- x: cardX + cardW / 2,
480
- y: cardY + 190,
481
- text: "Uploading...",
482
- align: "center",
483
- valign: "top",
484
- fontSize: 13,
485
- fontFamily: font,
486
- weight: 500,
487
- fill: colors.accent,
488
- opacity: 0,
489
- timeline: {
490
- tracks: {
491
- opacity: {
492
- keyframes: [
493
- { time: 11, value: 0, out: curves.ease },
494
- { time: 11.4, value: 1 },
495
- { time: 13.5, value: 1, out: curves.ease },
496
- { time: 14, value: 0 }
497
- ]
498
- }
499
- }
500
- }
501
- });
502
-
503
- // Progress bar track
504
- elements.push({
505
- id: "progress-track",
506
- type: "path",
507
- d: roundedRect(cardX + 80, cardY + 220, cardW - 160, 8, 4),
508
- fill: colors.inputBg,
509
- stroke: colors.inputBorder,
510
- strokeWidth: 1,
511
- opacity: 0,
512
- timeline: {
513
- tracks: {
514
- opacity: {
515
- keyframes: [
516
- { time: 11, value: 0, out: curves.ease },
517
- { time: 11.4, value: 1 },
518
- { time: 13.5, value: 1, out: curves.ease },
519
- { time: 14, value: 0 }
520
- ]
521
- }
522
- }
523
- }
524
- });
525
-
526
- // Progress bar fill (using drawEnd for animation)
527
- elements.push({
528
- id: "progress-fill",
529
- type: "path",
530
- d: `M ${cardX + 82} ${cardY + 222} L ${cardX + cardW - 82} ${cardY + 222} L ${cardX + cardW - 82} ${cardY + 226} L ${cardX + 82} ${cardY + 226} Z`,
531
- fill: colors.accent,
532
- stroke: "none",
533
- opacity: 0,
534
- drawEnd: 0,
535
- timeline: {
536
- tracks: {
537
- opacity: {
538
- keyframes: [
539
- { time: 11.2, value: 0, out: curves.ease },
540
- { time: 11.5, value: 1 },
541
- { time: 13.5, value: 1, out: curves.ease },
542
- { time: 14, value: 0 }
543
- ]
544
- },
545
- drawEnd: {
546
- keyframes: [
547
- { time: 11.5, value: 0, out: curves.gentle },
548
- { time: 13.5, value: 1 }
549
- ]
550
- }
551
- }
552
- }
553
- });
554
-
555
- // === SCENE 4: Success state (14-18s) ===
556
- elements.push({
557
- id: "success-overlay",
558
- type: "group",
559
- x: cardX + cardW / 2,
560
- y: cardY + cardH / 2 - 20,
561
- children: [
562
- // Large checkmark circle
563
- {
564
- id: "success-circle",
565
- type: "path",
566
- d: "M 0 0 m -40 0 a 40 40 0 1 1 80 0 a 40 40 0 1 1 -80 0",
567
- fill: colors.successBg,
568
- stroke: colors.success,
569
- strokeWidth: 2
570
- },
571
- {
572
- id: "success-check",
573
- type: "path",
574
- d: "M -14 0 L -4 10 L 14 -10",
575
- fill: "none",
576
- stroke: colors.success,
577
- strokeWidth: 3,
578
- strokeCap: "round",
579
- strokeJoin: "round"
580
- },
581
- {
582
- id: "success-title",
583
- type: "text",
584
- x: 0,
585
- y: 70,
586
- text: "Upload Complete",
587
- align: "center",
588
- valign: "top",
589
- fontSize: 18,
590
- fontFamily: font,
591
- weight: 600,
592
- fill: colors.text
593
- },
594
- {
595
- id: "success-desc",
596
- type: "text",
597
- x: 0,
598
- y: 98,
599
- text: "Your document has been uploaded successfully.",
600
- align: "center",
601
- valign: "top",
602
- fontSize: 13,
603
- fontFamily: font,
604
- weight: 400,
605
- fill: colors.textMuted
606
- }
607
- ],
608
- opacity: 0,
609
- origin: [0, 0],
610
- timeline: {
611
- tracks: {
612
- opacity: {
613
- keyframes: [
614
- { time: 14, value: 0, out: curves.ease },
615
- { time: 14.6, value: 1 },
616
- { time: 17, value: 1, out: curves.ease },
617
- { time: 17.5, value: 0 }
618
- ]
619
- },
620
- scale: {
621
- keyframes: [
622
- { time: 14, value: 0.8, out: curves.ease },
623
- { time: 14.6, value: 1 }
624
- ]
625
- }
626
- }
627
- }
628
- });
629
-
630
- // === CURSOR (added last to render on top) ===
631
-
632
- // Calculated positions:
633
- // Upload button: x = cardX + cardW/2 = 640, y = cardY + 280 + 20 = 460
634
- // Confirm button: x = cardX + cardW - 32 - confirmBtnW/2 = 798, y = cardY + cardH - 32 - confirmBtnH/2 = 507
635
- const uploadBtnCenter = { x: cardX + cardW / 2, y: cardY + 280 + 20 };
636
- const confirmBtnCenter = { x: cardX + cardW - 32 - confirmBtnW / 2, y: cardY + cardH - 32 - confirmBtnH / 2 };
637
-
638
- // Click ring
639
- elements.push({
640
- id: "click-ring",
641
- type: "path",
642
- d: "M 0 0 m -18 0 a 18 18 0 1 1 36 0 a 18 18 0 1 1 -36 0",
643
- x: uploadBtnCenter.x,
644
- y: uploadBtnCenter.y,
645
- fill: "none",
646
- stroke: colors.cursorRing,
647
- strokeWidth: 2,
648
- opacity: 0,
649
- origin: [0, 0],
650
- timeline: {
651
- tracks: {
652
- opacity: {
653
- keyframes: [
654
- // Click 1: Upload button
655
- { time: 4.8, value: 0 },
656
- { time: 4.9, value: 0.5 },
657
- { time: 5.3, value: 0 },
658
- // Click 2: Confirm button
659
- { time: 9.8, value: 0 },
660
- { time: 9.9, value: 0.5 },
661
- { time: 10.3, value: 0 }
662
- ]
663
- },
664
- scale: {
665
- keyframes: [
666
- { time: 4.8, value: 0.5, out: curves.easeOut },
667
- { time: 5.3, value: 1.8 },
668
- { time: 9.8, value: 0.5, out: curves.easeOut },
669
- { time: 10.3, value: 1.8 }
670
- ]
671
- },
672
- x: {
673
- keyframes: [
674
- { time: 4.8, value: uploadBtnCenter.x },
675
- { time: 9.8, value: confirmBtnCenter.x }
676
- ]
677
- },
678
- y: {
679
- keyframes: [
680
- { time: 4.8, value: uploadBtnCenter.y },
681
- { time: 9.8, value: confirmBtnCenter.y }
682
- ]
683
- }
684
- }
685
- }
686
- });
687
-
688
- // Cursor
689
- elements.push({
690
- id: "cursor",
691
- type: "group",
692
- x: -50,
693
- y: height / 2,
694
- children: [
695
- {
696
- id: "cursor-arrow",
697
- type: "path",
698
- d: "M 0 0 L 0 18 L 4.5 14.5 L 7.5 22 L 11 20.5 L 8 13 L 13.5 13 Z",
699
- fill: colors.cursor,
700
- stroke: "#ffffff",
701
- strokeWidth: 1.5
702
- }
703
- ],
704
- timeline: {
705
- tracks: {
706
- x: {
707
- keyframes: [
708
- // Enter from left
709
- { time: 2, value: -50, out: curves.ease },
710
- { time: 3, value: uploadBtnCenter.x },
711
- // Stay on upload button, click at 4.8
712
- { time: 5.5, value: uploadBtnCenter.x },
713
- // Move to confirm button
714
- { time: 7.5, value: uploadBtnCenter.x, out: curves.ease },
715
- { time: 8.5, value: confirmBtnCenter.x },
716
- // Stay on confirm button, click at 9.8
717
- { time: 10.5, value: confirmBtnCenter.x },
718
- // Exit
719
- { time: 13, value: confirmBtnCenter.x, out: curves.ease },
720
- { time: 14, value: width + 50 }
721
- ]
722
- },
723
- y: {
724
- keyframes: [
725
- { time: 2, value: height / 2, out: curves.ease },
726
- { time: 3, value: uploadBtnCenter.y },
727
- { time: 5.5, value: uploadBtnCenter.y },
728
- { time: 7.5, value: uploadBtnCenter.y, out: curves.ease },
729
- { time: 8.5, value: confirmBtnCenter.y },
730
- { time: 10.5, value: confirmBtnCenter.y },
731
- { time: 13, value: confirmBtnCenter.y, out: curves.ease },
732
- { time: 14, value: height + 50 }
733
- ]
734
- }
735
- }
736
- }
737
- });
738
-
739
- function roundedRect(x, y, w, h, r) {
740
- return [
741
- `M ${x + r} ${y}`,
742
- `L ${x + w - r} ${y}`,
743
- `Q ${x + w} ${y} ${x + w} ${y + r}`,
744
- `L ${x + w} ${y + h - r}`,
745
- `Q ${x + w} ${y + h} ${x + w - r} ${y + h}`,
746
- `L ${x + r} ${y + h}`,
747
- `Q ${x} ${y + h} ${x} ${y + h - r}`,
748
- `L ${x} ${y + r}`,
749
- `Q ${x} ${y} ${x + r} ${y}`,
750
- "Z"
751
- ].join(" ");
752
- }
753
-
754
- const doc = {
755
- version: 1,
756
- canvas: { width, height, background: bg, duration, fps },
757
- elements
758
- };
759
-
760
- const outPath = path.join(__dirname, "ui-flow.visual.json");
761
- fs.writeFileSync(outPath, JSON.stringify(doc, null, 2));
762
- console.log("Written:", outPath);