sketchmark 2.0.0 → 2.1.0

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 (132) hide show
  1. package/ANIMATABLE_MATRIX.md +177 -0
  2. package/KERNEL_SPEC.md +412 -0
  3. package/PACKS.md +81 -0
  4. package/PRESETS.md +182 -0
  5. package/README.md +274 -188
  6. package/bin/editor-ui.cjs +2285 -0
  7. package/bin/preview-ui.cjs +74 -0
  8. package/bin/sketchmark.cjs +648 -2008
  9. package/dist/src/animatable.d.ts +21 -0
  10. package/dist/src/animatable.js +439 -0
  11. package/dist/src/builders/index.d.ts +1 -11
  12. package/dist/src/builders/index.js +1 -19
  13. package/dist/src/diagnostics.js +1 -64
  14. package/dist/src/edit.d.ts +27 -0
  15. package/dist/src/edit.js +162 -0
  16. package/dist/src/index.d.ts +4 -13
  17. package/dist/src/index.js +4 -13
  18. package/dist/src/keyframes.d.ts +48 -0
  19. package/dist/src/keyframes.js +182 -0
  20. package/dist/src/motion.d.ts +4 -0
  21. package/dist/src/motion.js +262 -0
  22. package/dist/src/normalize.js +120 -151
  23. package/dist/src/presets/characters.d.ts +15 -0
  24. package/dist/src/presets/characters.js +113 -0
  25. package/dist/src/presets/compose.d.ts +5 -0
  26. package/dist/src/presets/compose.js +80 -0
  27. package/dist/src/presets/effects.d.ts +40 -0
  28. package/dist/src/presets/effects.js +79 -0
  29. package/dist/src/presets/helpers.d.ts +33 -0
  30. package/dist/src/presets/helpers.js +165 -0
  31. package/dist/src/presets/index.d.ts +9 -0
  32. package/dist/src/presets/index.js +48 -0
  33. package/dist/src/presets/motions.d.ts +33 -0
  34. package/dist/src/presets/motions.js +75 -0
  35. package/dist/src/presets/scenes.d.ts +35 -0
  36. package/dist/src/presets/scenes.js +134 -0
  37. package/dist/src/presets/shapes.d.ts +71 -0
  38. package/dist/src/presets/shapes.js +96 -0
  39. package/dist/src/presets/transitions.d.ts +29 -0
  40. package/dist/src/presets/transitions.js +113 -0
  41. package/dist/src/presets/types.d.ts +34 -0
  42. package/dist/src/presets/types.js +2 -0
  43. package/dist/src/render/html.js +1 -4
  44. package/dist/src/render/svg.d.ts +2 -2
  45. package/dist/src/render/svg.js +86 -82
  46. package/dist/src/render/three-html.js +67 -113
  47. package/dist/src/scenes.js +1 -0
  48. package/dist/src/schema.js +218 -280
  49. package/dist/src/shapes/builtins.js +11 -47
  50. package/dist/src/shapes/common.js +12 -11
  51. package/dist/src/shapes/registry.d.ts +0 -1
  52. package/dist/src/shapes/registry.js +0 -4
  53. package/dist/src/shapes/types.d.ts +1 -3
  54. package/dist/src/types.d.ts +57 -288
  55. package/dist/src/utils.d.ts +2 -11
  56. package/dist/src/utils.js +13 -70
  57. package/dist/src/validate.js +321 -275
  58. package/dist/tests/run.js +576 -510
  59. package/examples/1730642890464.jpg +0 -0
  60. package/examples/app-screen.svg +1 -0
  61. package/examples/app-screen.visual.json +503 -0
  62. package/examples/dashboard-table.svg +1 -0
  63. package/examples/dashboard-table.visual.json +708 -0
  64. package/examples/dev-docs.svg +1 -0
  65. package/examples/dev-docs.visual.json +248 -0
  66. package/examples/explainer.mp4 +0 -0
  67. package/examples/explainer.visual.json +1713 -0
  68. package/examples/group-origin-effects-lab-check.svg +1 -0
  69. package/examples/group-origin-effects-lab.visual.json +1880 -0
  70. package/examples/image-clip-radius.visual.json +271 -0
  71. package/examples/make-app-screen.cjs +368 -0
  72. package/examples/make-dashboard-table.cjs +277 -0
  73. package/examples/make-dev-docs.cjs +233 -0
  74. package/examples/make-explainer.cjs +438 -0
  75. package/examples/make-group-origin-effects-lab.cjs +370 -0
  76. package/examples/make-image-clip-radius.cjs +169 -0
  77. package/examples/make-modal-dialog.cjs +355 -0
  78. package/examples/make-origin-effects-lab.cjs +311 -0
  79. package/examples/make-preset-character-motion.cjs +32 -0
  80. package/examples/make-presets-demo.cjs +30 -0
  81. package/examples/make-pricing.cjs +286 -0
  82. package/examples/make-product-demo.cjs +468 -0
  83. package/examples/make-product-hero.cjs +223 -0
  84. package/examples/make-release-notes.cjs +333 -0
  85. package/examples/make-settings-panel.cjs +435 -0
  86. package/examples/make-split-preview.cjs +248 -0
  87. package/examples/make-storyboard.cjs +215 -0
  88. package/examples/make-transcript.cjs +234 -0
  89. package/examples/make-typography-test.cjs +397 -0
  90. package/examples/make-ui-demo-explainer.cjs +1094 -0
  91. package/examples/make-ui-flow.cjs +762 -0
  92. package/examples/make-walkthrough.cjs +815 -0
  93. package/examples/modal-dialog.svg +1 -0
  94. package/examples/modal-dialog.visual.json +239 -0
  95. package/examples/origin-effects-lab-check.svg +1 -0
  96. package/examples/origin-effects-lab.visual.json +1412 -0
  97. package/examples/preset-character-motion.visual.json +949 -0
  98. package/examples/presets-demo.visual.json +787 -0
  99. package/examples/pricing.svg +1 -0
  100. package/examples/pricing.visual.json +652 -0
  101. package/examples/product-demo.mp4 +0 -0
  102. package/examples/product-demo.visual.json +866 -0
  103. package/examples/product-hero.svg +1 -0
  104. package/examples/product-hero.visual.json +242 -0
  105. package/examples/release-notes.svg +1 -0
  106. package/examples/release-notes.visual.json +467 -0
  107. package/examples/settings-panel.svg +1 -0
  108. package/examples/settings-panel.visual.json +501 -0
  109. package/examples/split-preview.svg +1 -0
  110. package/examples/split-preview.visual.json +124 -0
  111. package/examples/storyboard.svg +1 -0
  112. package/examples/storyboard.visual.json +312 -0
  113. package/examples/transcript.svg +1 -0
  114. package/examples/transcript.visual.json +407 -0
  115. package/examples/typography-indent-check.svg +1 -0
  116. package/examples/typography-lineheight-0.svg +1 -0
  117. package/examples/typography-lineheight-2.svg +1 -0
  118. package/examples/typography-test-check.svg +1 -0
  119. package/examples/typography-test.svg +1 -0
  120. package/examples/typography-test.visual.json +757 -0
  121. package/examples/ui-demo-explainer-billing.svg +1 -0
  122. package/examples/ui-demo-explainer-check.svg +1 -0
  123. package/examples/ui-demo-explainer-save.svg +1 -0
  124. package/examples/ui-demo-explainer-toggle.svg +1 -0
  125. package/examples/ui-demo-explainer.mp4 +0 -0
  126. package/examples/ui-demo-explainer.visual.json +2597 -0
  127. package/examples/ui-flow.mp4 +0 -0
  128. package/examples/ui-flow.visual.json +1211 -0
  129. package/examples/walkthrough.mp4 +0 -0
  130. package/examples/walkthrough.visual.json +1372 -0
  131. package/package.json +52 -52
  132. package/schema/visual.schema.json +1086 -930
@@ -0,0 +1,370 @@
1
+ const fs = require("node:fs");
2
+ const path = require("node:path");
3
+
4
+ const width = 1280;
5
+ const height = 760;
6
+ const duration = 6;
7
+ const fps = 30;
8
+ const bg = "#07111f";
9
+ const font = "Roboto, Arial, sans-serif";
10
+
11
+ const colors = {
12
+ panel: "#0d1a2e",
13
+ panelStroke: "#193252",
14
+ title: "#f8fafc",
15
+ body: "#94a3b8",
16
+ marker: "#fde047",
17
+ markerStroke: "#0f172a",
18
+ grid: "#173054",
19
+ rowLabel: "#7dd3fc",
20
+ frame: "#31537f",
21
+ fills: ["#818cf8", "#38bdf8", "#22c55e", "#f59e0b"]
22
+ };
23
+
24
+ const curves = {
25
+ ease: { type: "cubicBezier", x1: 0.25, y1: 0.1, x2: 0.25, y2: 1 },
26
+ easeOut: { type: "cubicBezier", x1: 0, y1: 0, x2: 0.2, y2: 1 }
27
+ };
28
+
29
+ const groupW = 180;
30
+ const groupH = 120;
31
+ const cellW = 270;
32
+ const cellH = 240;
33
+ const gridX = 70;
34
+ const gridGap = 25;
35
+ const scaleRowY = 150;
36
+ const rotateRowY = 435;
37
+ const groupOffsetX = 50;
38
+ const groupOffsetY = 92;
39
+
40
+ const originVariants = [
41
+ { key: "top_left", title: "Top-left origin", note: "[0, 0]", local: [0, 0] },
42
+ { key: "center", title: "Center origin", note: "[90, 60]", local: [groupW / 2, groupH / 2] },
43
+ { key: "bottom_right", title: "Bottom-right origin", note: "[180, 120]", local: [groupW, groupH] },
44
+ { key: "outside_right", title: "Outside-right origin", note: "[220, 60]", local: [groupW + 40, groupH / 2] }
45
+ ];
46
+
47
+ const elements = [];
48
+
49
+ elements.push({
50
+ id: "lab_title",
51
+ type: "text",
52
+ x: 70,
53
+ y: 52,
54
+ text: "Group Origin Transform Lab",
55
+ fontSize: 38,
56
+ fontFamily: font,
57
+ weight: 700,
58
+ fill: colors.title
59
+ });
60
+
61
+ elements.push({
62
+ id: "lab_body",
63
+ type: "text",
64
+ x: 70,
65
+ y: 96,
66
+ text: "Same multi-child group, same motion, different origins.",
67
+ fontSize: 16,
68
+ fontFamily: font,
69
+ weight: 400,
70
+ fill: colors.body
71
+ });
72
+
73
+ elements.push({
74
+ id: "scale_row_label",
75
+ type: "text",
76
+ x: 70,
77
+ y: scaleRowY - 38,
78
+ text: "Scale tests",
79
+ fontSize: 18,
80
+ fontFamily: font,
81
+ weight: 700,
82
+ fill: colors.rowLabel
83
+ });
84
+
85
+ elements.push({
86
+ id: "rotate_row_label",
87
+ type: "text",
88
+ x: 70,
89
+ y: rotateRowY - 38,
90
+ text: "Rotation tests",
91
+ fontSize: 18,
92
+ fontFamily: font,
93
+ weight: 700,
94
+ fill: colors.rowLabel
95
+ });
96
+
97
+ originVariants.forEach((variant, index) => {
98
+ const cellX = gridX + index * (cellW + gridGap);
99
+ const fill = colors.fills[index % colors.fills.length];
100
+ addPanel(cellX, scaleRowY, `scale_${variant.key}`, variant.title, variant.note);
101
+ addScaleDemo(cellX, scaleRowY, variant, fill);
102
+ addPanel(cellX, rotateRowY, `rotate_${variant.key}`, variant.title, variant.note);
103
+ addRotateDemo(cellX, rotateRowY, variant, fill);
104
+ });
105
+
106
+ const doc = {
107
+ version: 1,
108
+ canvas: { width, height, background: bg, duration, fps },
109
+ elements
110
+ };
111
+
112
+ const outPath = path.join(__dirname, "group-origin-effects-lab.visual.json");
113
+ fs.writeFileSync(outPath, JSON.stringify(doc, null, 2));
114
+ console.log("Written:", outPath);
115
+
116
+ function addPanel(x, y, id, title, note) {
117
+ elements.push({
118
+ id: `${id}_panel`,
119
+ type: "path",
120
+ d: roundedRect(x, y, cellW, cellH, 22),
121
+ fill: colors.panel,
122
+ stroke: colors.panelStroke,
123
+ strokeWidth: 1.5
124
+ });
125
+
126
+ elements.push({
127
+ id: `${id}_title`,
128
+ type: "text",
129
+ x: x + 20,
130
+ y: y + 18,
131
+ text: title,
132
+ fontSize: 16,
133
+ fontFamily: font,
134
+ weight: 700,
135
+ fill: colors.title
136
+ });
137
+
138
+ elements.push({
139
+ id: `${id}_note`,
140
+ type: "text",
141
+ x: x + 20,
142
+ y: y + 42,
143
+ text: note,
144
+ fontSize: 12,
145
+ fontFamily: font,
146
+ weight: 500,
147
+ fill: colors.body
148
+ });
149
+
150
+ elements.push({
151
+ id: `${id}_guide_h`,
152
+ type: "path",
153
+ d: `M ${x + 20} ${y + groupOffsetY + groupH / 2} L ${x + cellW - 20} ${y + groupOffsetY + groupH / 2}`,
154
+ fill: "none",
155
+ stroke: colors.grid,
156
+ strokeWidth: 1,
157
+ dashArray: [6, 6]
158
+ });
159
+
160
+ elements.push({
161
+ id: `${id}_guide_v`,
162
+ type: "path",
163
+ d: `M ${x + groupOffsetX + groupW / 2} ${y + 68} L ${x + groupOffsetX + groupW / 2} ${y + cellH - 20}`,
164
+ fill: "none",
165
+ stroke: colors.grid,
166
+ strokeWidth: 1,
167
+ dashArray: [6, 6]
168
+ });
169
+ }
170
+
171
+ function addScaleDemo(cellX, cellY, variant, fill) {
172
+ const groupX = cellX + groupOffsetX;
173
+ const groupY = cellY + groupOffsetY;
174
+ const originAbs = absoluteOrigin(groupX, groupY, variant.local);
175
+
176
+ elements.push(groupFrame(`scale_${variant.key}_frame`, groupX, groupY));
177
+ elements.push(originMarker(`scale_${variant.key}_origin`, originAbs[0], originAbs[1]));
178
+
179
+ elements.push({
180
+ id: `scale_${variant.key}_group`,
181
+ type: "group",
182
+ x: groupX,
183
+ y: groupY,
184
+ width: groupW,
185
+ height: groupH,
186
+ origin: originAbs,
187
+ children: buildDemoGroup(`scale_${variant.key}`, fill),
188
+ timeline: {
189
+ tracks: {
190
+ scale: {
191
+ keyframes: [
192
+ { time: 0, value: 1, out: curves.easeOut },
193
+ { time: 1.3, value: 1.45, out: curves.ease },
194
+ { time: 2.6, value: 0.72, out: curves.ease },
195
+ { time: 4, value: 1.18, out: curves.ease },
196
+ { time: 5.4, value: 1 }
197
+ ]
198
+ }
199
+ }
200
+ }
201
+ });
202
+ }
203
+
204
+ function addRotateDemo(cellX, cellY, variant, fill) {
205
+ const groupX = cellX + groupOffsetX;
206
+ const groupY = cellY + groupOffsetY;
207
+ const originAbs = absoluteOrigin(groupX, groupY, variant.local);
208
+
209
+ elements.push(groupFrame(`rotate_${variant.key}_frame`, groupX, groupY));
210
+ elements.push(originMarker(`rotate_${variant.key}_origin`, originAbs[0], originAbs[1]));
211
+
212
+ elements.push({
213
+ id: `rotate_${variant.key}_group`,
214
+ type: "group",
215
+ x: groupX,
216
+ y: groupY,
217
+ width: groupW,
218
+ height: groupH,
219
+ origin: originAbs,
220
+ children: buildDemoGroup(`rotate_${variant.key}`, fill),
221
+ timeline: {
222
+ tracks: {
223
+ rotation: {
224
+ keyframes: [
225
+ { time: 0, value: 0, out: curves.easeOut },
226
+ { time: 1.5, value: 42, out: curves.ease },
227
+ { time: 3, value: -28, out: curves.ease },
228
+ { time: 4.5, value: 18, out: curves.ease },
229
+ { time: 5.8, value: 0 }
230
+ ]
231
+ }
232
+ }
233
+ }
234
+ });
235
+ }
236
+
237
+ function buildDemoGroup(prefix, fill) {
238
+ return [
239
+ {
240
+ id: `${prefix}_main`,
241
+ type: "path",
242
+ d: roundedRect(0, 18, groupW, 102, 18),
243
+ fill,
244
+ stroke: "#dbeafe",
245
+ strokeWidth: 2
246
+ },
247
+ {
248
+ id: `${prefix}_tag`,
249
+ type: "path",
250
+ d: roundedRect(12, 0, 66, 20, 10),
251
+ fill: "#0f172a",
252
+ stroke: "#7dd3fc",
253
+ strokeWidth: 1.5
254
+ },
255
+ {
256
+ id: `${prefix}_tag_text`,
257
+ type: "text",
258
+ x: 45,
259
+ y: 10,
260
+ text: "GROUP",
261
+ align: "center",
262
+ valign: "middle",
263
+ fontSize: 10,
264
+ fontFamily: font,
265
+ weight: 700,
266
+ fill: "#7dd3fc"
267
+ },
268
+ {
269
+ id: `${prefix}_accent`,
270
+ type: "path",
271
+ d: roundedRect(128, 38, 34, 34, 10),
272
+ fill: "#0f172a",
273
+ stroke: "#ffffff",
274
+ strokeWidth: 1.5
275
+ },
276
+ {
277
+ id: `${prefix}_bar_1`,
278
+ type: "path",
279
+ d: roundedRect(16, 50, 78, 10, 5),
280
+ fill: "rgba(255,255,255,0.9)",
281
+ stroke: "none"
282
+ },
283
+ {
284
+ id: `${prefix}_bar_2`,
285
+ type: "path",
286
+ d: roundedRect(16, 70, 112, 10, 5),
287
+ fill: "rgba(255,255,255,0.65)",
288
+ stroke: "none"
289
+ },
290
+ {
291
+ id: `${prefix}_footer`,
292
+ type: "path",
293
+ d: roundedRect(16, 92, 92, 12, 6),
294
+ fill: "rgba(255,255,255,0.35)",
295
+ stroke: "none"
296
+ }
297
+ ];
298
+ }
299
+
300
+ function groupFrame(id, x, y) {
301
+ return {
302
+ id,
303
+ type: "path",
304
+ d: roundedRect(x, y, groupW, groupH, 18),
305
+ fill: "none",
306
+ stroke: colors.frame,
307
+ strokeWidth: 1,
308
+ dashArray: [8, 6]
309
+ };
310
+ }
311
+
312
+ function originMarker(id, x, y) {
313
+ return {
314
+ id,
315
+ type: "group",
316
+ x: x - 16,
317
+ y: y - 16,
318
+ children: [
319
+ {
320
+ id: `${id}_cross_h`,
321
+ type: "path",
322
+ d: "M 0 16 L 32 16",
323
+ fill: "none",
324
+ stroke: colors.marker,
325
+ strokeWidth: 2,
326
+ strokeCap: "round"
327
+ },
328
+ {
329
+ id: `${id}_cross_v`,
330
+ type: "path",
331
+ d: "M 16 0 L 16 32",
332
+ fill: "none",
333
+ stroke: colors.marker,
334
+ strokeWidth: 2,
335
+ strokeCap: "round"
336
+ },
337
+ {
338
+ id: `${id}_dot`,
339
+ type: "path",
340
+ d: circlePath(16, 16, 6),
341
+ fill: colors.marker,
342
+ stroke: colors.markerStroke,
343
+ strokeWidth: 1.5
344
+ }
345
+ ]
346
+ };
347
+ }
348
+
349
+ function absoluteOrigin(groupX, groupY, localOrigin) {
350
+ return [groupX + localOrigin[0], groupY + localOrigin[1]];
351
+ }
352
+
353
+ function circlePath(cx, cy, r) {
354
+ return `M ${cx - r} ${cy} a ${r} ${r} 0 1 0 ${r * 2} 0 a ${r} ${r} 0 1 0 ${-r * 2} 0`;
355
+ }
356
+
357
+ function roundedRect(x, y, w, h, r) {
358
+ return [
359
+ `M ${x + r} ${y}`,
360
+ `L ${x + w - r} ${y}`,
361
+ `Q ${x + w} ${y} ${x + w} ${y + r}`,
362
+ `L ${x + w} ${y + h - r}`,
363
+ `Q ${x + w} ${y + h} ${x + w - r} ${y + h}`,
364
+ `L ${x + r} ${y + h}`,
365
+ `Q ${x} ${y + h} ${x} ${y + h - r}`,
366
+ `L ${x} ${y + r}`,
367
+ `Q ${x} ${y} ${x + r} ${y}`,
368
+ "Z"
369
+ ].join(" ");
370
+ }
@@ -0,0 +1,169 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const width = 960;
5
+ const height = 540;
6
+ const duration = 4;
7
+ const fps = 30;
8
+
9
+ const image = {
10
+ id: "photo",
11
+ type: "image",
12
+ src: svgDataUri(`
13
+ <svg xmlns="http://www.w3.org/2000/svg" width="900" height="560" viewBox="0 0 900 560">
14
+ <defs>
15
+ <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
16
+ <stop offset="0" stop-color="#0f766e"/>
17
+ <stop offset="0.52" stop-color="#2563eb"/>
18
+ <stop offset="1" stop-color="#7c3aed"/>
19
+ </linearGradient>
20
+ </defs>
21
+ <rect width="900" height="560" fill="url(#bg)"/>
22
+ <circle cx="180" cy="150" r="86" fill="#fef3c7" opacity="0.9"/>
23
+ <circle cx="710" cy="120" r="120" fill="#bae6fd" opacity="0.42"/>
24
+ <circle cx="640" cy="415" r="180" fill="#f0abfc" opacity="0.35"/>
25
+ <path d="M 0 410 C 160 340 260 470 430 390 C 590 315 720 395 900 330 L 900 560 L 0 560 Z" fill="#022c22" opacity="0.45"/>
26
+ <path d="M 0 450 C 170 390 330 510 500 425 C 660 350 760 470 900 405 L 900 560 L 0 560 Z" fill="#020617" opacity="0.38"/>
27
+ <text x="60" y="488" font-family="Arial, sans-serif" font-size="48" font-weight="800" fill="#ffffff">clip.d radius animation</text>
28
+ </svg>
29
+ `),
30
+ x: 210,
31
+ y: 116,
32
+ width: 540,
33
+ height: 336,
34
+ clip: {
35
+ type: "path",
36
+ d: roundedRectClipPath(210, 116, 540, 336, 0)
37
+ },
38
+ timeline: {
39
+ tracks: {
40
+ "clip.d": {
41
+ keyframes: sampledRadiusKeyframes({
42
+ x: 210,
43
+ y: 116,
44
+ width: 540,
45
+ height: 336,
46
+ duration,
47
+ fps: 12,
48
+ radiusAt: (time) => {
49
+ const loop = pingPong(time / duration);
50
+ return 120 * easeInOut(loop);
51
+ }
52
+ })
53
+ },
54
+ opacity: {
55
+ keyframes: [
56
+ { time: 0, value: 0, out: curve("ease-out") },
57
+ { time: 0.35, value: 1 }
58
+ ]
59
+ }
60
+ }
61
+ }
62
+ };
63
+
64
+ const doc = {
65
+ version: 1,
66
+ canvas: {
67
+ width,
68
+ height,
69
+ background: "#f8fafc",
70
+ duration,
71
+ fps
72
+ },
73
+ elements: [
74
+ {
75
+ id: "title",
76
+ type: "text",
77
+ text: "Image radius is compiled to clip.d keyframes",
78
+ x: width / 2,
79
+ y: 54,
80
+ align: "center",
81
+ valign: "middle",
82
+ fontSize: 30,
83
+ weight: 800,
84
+ fill: "#0f172a"
85
+ },
86
+ {
87
+ id: "note",
88
+ type: "text",
89
+ text: "The kernel only sees an image plus animated clip path data.",
90
+ x: width / 2,
91
+ y: 494,
92
+ align: "center",
93
+ valign: "middle",
94
+ fontSize: 18,
95
+ fill: "#475569"
96
+ },
97
+ image
98
+ ]
99
+ };
100
+
101
+ const outPath = path.join(__dirname, "image-clip-radius.visual.json");
102
+ fs.writeFileSync(outPath, JSON.stringify(doc, null, 2) + "\n", "utf8");
103
+ console.log(`Wrote ${outPath}`);
104
+
105
+ function sampledRadiusKeyframes({ x, y, width, height, duration, fps, radiusAt }) {
106
+ const frameCount = Math.max(1, Math.round(duration * fps));
107
+ const frames = [];
108
+ for (let index = 0; index <= frameCount; index += 1) {
109
+ const time = Number(((index / frameCount) * duration).toFixed(4));
110
+ const radius = radiusAt(time);
111
+ frames.push({
112
+ time,
113
+ value: roundedRectClipPath(x, y, width, height, radius)
114
+ });
115
+ }
116
+ return frames;
117
+ }
118
+
119
+ function roundedRectClipPath(x, y, width, height, radius) {
120
+ const left = finite(x);
121
+ const top = finite(y);
122
+ const w = Math.max(0, finite(width));
123
+ const h = Math.max(0, finite(height));
124
+ const r = Math.min(Math.max(0, finite(radius)), w / 2, h / 2);
125
+ const right = left + w;
126
+ const bottom = top + h;
127
+
128
+ if (r <= 0) return `M ${left} ${top} H ${right} V ${bottom} H ${left} Z`;
129
+
130
+ return [
131
+ `M ${round(left + r)} ${top}`,
132
+ `H ${round(right - r)}`,
133
+ `Q ${right} ${top} ${right} ${round(top + r)}`,
134
+ `V ${round(bottom - r)}`,
135
+ `Q ${right} ${bottom} ${round(right - r)} ${bottom}`,
136
+ `H ${round(left + r)}`,
137
+ `Q ${left} ${bottom} ${left} ${round(bottom - r)}`,
138
+ `V ${round(top + r)}`,
139
+ `Q ${left} ${top} ${round(left + r)} ${top}`,
140
+ "Z"
141
+ ].join(" ");
142
+ }
143
+
144
+ function pingPong(value) {
145
+ const t = Math.max(0, Math.min(1, value));
146
+ return t <= 0.5 ? t * 2 : (1 - t) * 2;
147
+ }
148
+
149
+ function easeInOut(t) {
150
+ return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
151
+ }
152
+
153
+ function curve(name) {
154
+ if (name === "ease-out") return { type: "cubicBezier", x1: 0, y1: 0, x2: 0.58, y2: 1 };
155
+ return { type: "graph", points: [[0, 0], [1, 1]] };
156
+ }
157
+
158
+ function svgDataUri(svg) {
159
+ return `data:image/svg+xml,${encodeURIComponent(svg.replace(/\s+/g, " ").trim())}`;
160
+ }
161
+
162
+ function finite(value) {
163
+ const number = Number(value);
164
+ return Number.isFinite(number) ? number : 0;
165
+ }
166
+
167
+ function round(value) {
168
+ return Number(value.toFixed(2));
169
+ }