sketchmark 2.1.0 → 2.1.2

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 (117) 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/dist/src/builders/index.d.ts +0 -64
  7. package/dist/src/builders/index.js +0 -212
  8. package/dist/src/compounds.d.ts +0 -13
  9. package/dist/src/compounds.js +0 -118
  10. package/dist/src/deck.d.ts +0 -4
  11. package/dist/src/deck.js +0 -91
  12. package/dist/src/export/index.d.ts +0 -8
  13. package/dist/src/export/index.js +0 -15
  14. package/dist/src/kernel.d.ts +0 -8
  15. package/dist/src/kernel.js +0 -68
  16. package/dist/src/motion.d.ts +0 -4
  17. package/dist/src/motion.js +0 -262
  18. package/dist/src/patch.d.ts +0 -5
  19. package/dist/src/patch.js +0 -72
  20. package/dist/src/player/index.d.ts +0 -68
  21. package/dist/src/player/index.js +0 -600
  22. package/dist/src/project.d.ts +0 -11
  23. package/dist/src/project.js +0 -107
  24. package/dist/src/render/raw-three.d.ts +0 -7
  25. package/dist/src/render/raw-three.js +0 -17
  26. package/dist/src/render/three-html.d.ts +0 -2
  27. package/dist/src/render/three-html.js +0 -257
  28. package/dist/src/render/three-preview-svg.d.ts +0 -3
  29. package/dist/src/render/three-preview-svg.js +0 -102
  30. package/dist/src/scenes.d.ts +0 -4
  31. package/dist/src/scenes.js +0 -26
  32. package/dist/src/sequences.d.ts +0 -43
  33. package/dist/src/sequences.js +0 -109
  34. package/dist/src/shapes/builtins.d.ts +0 -2
  35. package/dist/src/shapes/builtins.js +0 -393
  36. package/dist/src/shapes/common.d.ts +0 -9
  37. package/dist/src/shapes/common.js +0 -76
  38. package/dist/src/shapes/geometry.d.ts +0 -22
  39. package/dist/src/shapes/geometry.js +0 -166
  40. package/dist/src/shapes/index.d.ts +0 -2
  41. package/dist/src/shapes/index.js +0 -18
  42. package/dist/src/shapes/registry.d.ts +0 -8
  43. package/dist/src/shapes/registry.js +0 -31
  44. package/dist/src/shapes/types.d.ts +0 -32
  45. package/dist/src/shapes/types.js +0 -2
  46. package/examples/1730642890464.jpg +0 -0
  47. package/examples/app-screen.svg +0 -1
  48. package/examples/app-screen.visual.json +0 -503
  49. package/examples/dashboard-table.svg +0 -1
  50. package/examples/dashboard-table.visual.json +0 -708
  51. package/examples/dev-docs.svg +0 -1
  52. package/examples/dev-docs.visual.json +0 -248
  53. package/examples/explainer.mp4 +0 -0
  54. package/examples/explainer.visual.json +0 -1713
  55. package/examples/group-origin-effects-lab-check.svg +0 -1
  56. package/examples/group-origin-effects-lab.visual.json +0 -1880
  57. package/examples/image-clip-radius.visual.json +0 -271
  58. package/examples/make-app-screen.cjs +0 -368
  59. package/examples/make-dashboard-table.cjs +0 -277
  60. package/examples/make-dev-docs.cjs +0 -233
  61. package/examples/make-explainer.cjs +0 -438
  62. package/examples/make-group-origin-effects-lab.cjs +0 -370
  63. package/examples/make-image-clip-radius.cjs +0 -169
  64. package/examples/make-modal-dialog.cjs +0 -355
  65. package/examples/make-origin-effects-lab.cjs +0 -311
  66. package/examples/make-preset-character-motion.cjs +0 -32
  67. package/examples/make-presets-demo.cjs +0 -30
  68. package/examples/make-pricing.cjs +0 -286
  69. package/examples/make-product-demo.cjs +0 -468
  70. package/examples/make-product-hero.cjs +0 -223
  71. package/examples/make-release-notes.cjs +0 -333
  72. package/examples/make-settings-panel.cjs +0 -435
  73. package/examples/make-split-preview.cjs +0 -248
  74. package/examples/make-storyboard.cjs +0 -215
  75. package/examples/make-transcript.cjs +0 -234
  76. package/examples/make-typography-test.cjs +0 -397
  77. package/examples/make-ui-demo-explainer.cjs +0 -1094
  78. package/examples/make-ui-flow.cjs +0 -762
  79. package/examples/make-walkthrough.cjs +0 -815
  80. package/examples/modal-dialog.svg +0 -1
  81. package/examples/modal-dialog.visual.json +0 -239
  82. package/examples/origin-effects-lab-check.svg +0 -1
  83. package/examples/origin-effects-lab.visual.json +0 -1412
  84. package/examples/preset-character-motion.visual.json +0 -949
  85. package/examples/presets-demo.visual.json +0 -787
  86. package/examples/pricing.svg +0 -1
  87. package/examples/pricing.visual.json +0 -652
  88. package/examples/product-demo.mp4 +0 -0
  89. package/examples/product-demo.visual.json +0 -866
  90. package/examples/product-hero.svg +0 -1
  91. package/examples/product-hero.visual.json +0 -242
  92. package/examples/release-notes.svg +0 -1
  93. package/examples/release-notes.visual.json +0 -467
  94. package/examples/settings-panel.svg +0 -1
  95. package/examples/settings-panel.visual.json +0 -501
  96. package/examples/split-preview.svg +0 -1
  97. package/examples/split-preview.visual.json +0 -124
  98. package/examples/storyboard.svg +0 -1
  99. package/examples/storyboard.visual.json +0 -312
  100. package/examples/transcript.svg +0 -1
  101. package/examples/transcript.visual.json +0 -407
  102. package/examples/typography-indent-check.svg +0 -1
  103. package/examples/typography-lineheight-0.svg +0 -1
  104. package/examples/typography-lineheight-2.svg +0 -1
  105. package/examples/typography-test-check.svg +0 -1
  106. package/examples/typography-test.svg +0 -1
  107. package/examples/typography-test.visual.json +0 -757
  108. package/examples/ui-demo-explainer-billing.svg +0 -1
  109. package/examples/ui-demo-explainer-check.svg +0 -1
  110. package/examples/ui-demo-explainer-save.svg +0 -1
  111. package/examples/ui-demo-explainer-toggle.svg +0 -1
  112. package/examples/ui-demo-explainer.mp4 +0 -0
  113. package/examples/ui-demo-explainer.visual.json +0 -2597
  114. package/examples/ui-flow.mp4 +0 -0
  115. package/examples/ui-flow.visual.json +0 -1211
  116. package/examples/walkthrough.mp4 +0 -0
  117. package/examples/walkthrough.visual.json +0 -1372
@@ -1,1094 +0,0 @@
1
- const fs = require("node:fs");
2
- const path = require("node:path");
3
-
4
- const width = 1280;
5
- const height = 720;
6
- const duration = 24;
7
- const fps = 30;
8
- const bg = "#07111f";
9
- const font = "Roboto, Arial, sans-serif";
10
-
11
- const colors = {
12
- bg,
13
- shell: "#f8fafc",
14
- shellBorder: "#dbe4f0",
15
- topBar: "#ffffff",
16
- sidebar: "#0f172a",
17
- sidebarMuted: "#94a3b8",
18
- sidebarActive: "#eff6ff",
19
- sidebarActiveText: "#2563eb",
20
- pageTitle: "#0f172a",
21
- body: "#475569",
22
- muted: "#64748b",
23
- card: "#ffffff",
24
- cardBorder: "#dbe4f0",
25
- accent: "#2563eb",
26
- accentSoft: "#dbeafe",
27
- success: "#10b981",
28
- successSoft: "#d1fae5",
29
- warning: "#f59e0b",
30
- warningSoft: "#fef3c7",
31
- toggleOff: "#dbe3ee",
32
- toggleKnob: "#ffffff",
33
- divider: "#e5edf6",
34
- chipBg: "#102444",
35
- chipStroke: "#264b87",
36
- chipText: "#93c5fd",
37
- captionBg: "#09192e",
38
- captionBorder: "#173054",
39
- captionText: "#e2e8f0",
40
- captionMuted: "#94a3b8",
41
- cursor: "#0f172a",
42
- cursorRing: "#38bdf8",
43
- focus: "#22d3ee",
44
- ghost: "#cbd5e1"
45
- };
46
-
47
- const curves = {
48
- ease: { type: "cubicBezier", x1: 0.25, y1: 0.1, x2: 0.25, y2: 1 },
49
- easeOut: { type: "cubicBezier", x1: 0, y1: 0, x2: 0.2, y2: 1 },
50
- snap: { type: "cubicBezier", x1: 0.2, y1: 1, x2: 0.2, y2: 1 }
51
- };
52
-
53
- const screen = { x: 0, y: 0, w: 1120, h: 580 };
54
- const contentX = 236;
55
-
56
- const notificationsCard = { x: 618, y: 96, w: 430, h: 198 };
57
- const billingCard = { x: 236, y: 316, w: 400, h: 188 };
58
- const reviewCard = { x: 658, y: 332, w: 390, h: 172 };
59
-
60
- const weeklyToggleCenter = { x: notificationsCard.x + 352, y: notificationsCard.y + 105 };
61
- const annualPillCenter = { x: billingCard.x + 160, y: billingCard.y + 103 };
62
- const saveButtonCenter = { x: reviewCard.x + 94, y: reviewCard.y + 128 };
63
-
64
- const overviewPose = {
65
- x: (width - screen.w * 0.92) / 2,
66
- y: 92,
67
- scale: 0.92
68
- };
69
- const notifPose = cameraPose(notificationsCard.x + notificationsCard.w / 2, notificationsCard.y + notificationsCard.h / 2, 1.23);
70
- const billingPose = cameraPose(billingCard.x + billingCard.w / 2, billingCard.y + billingCard.h / 2, 1.26);
71
- const reviewPose = cameraPose(reviewCard.x + reviewCard.w / 2, reviewCard.y + reviewCard.h / 2, 1.34);
72
-
73
- const elements = [];
74
-
75
- elements.push({
76
- id: "intro-title",
77
- type: "text",
78
- x: width / 2,
79
- y: 92,
80
- text: "Explaining a UI demo with cursor + zoom",
81
- align: "center",
82
- valign: "middle",
83
- fontSize: 42,
84
- fontFamily: font,
85
- weight: 700,
86
- fill: "#f8fafc",
87
- opacity: 0,
88
- timeline: {
89
- tracks: {
90
- opacity: {
91
- keyframes: [
92
- { time: 0, value: 0, out: curves.ease },
93
- { time: 0.8, value: 1 },
94
- { time: 2.8, value: 1, out: curves.ease },
95
- { time: 3.4, value: 0 }
96
- ]
97
- },
98
- y: {
99
- keyframes: [
100
- { time: 0, value: 108, out: curves.easeOut },
101
- { time: 0.8, value: 92 }
102
- ]
103
- }
104
- }
105
- }
106
- });
107
-
108
- elements.push({
109
- id: "intro-subtitle",
110
- type: "text",
111
- x: width / 2,
112
- y: 138,
113
- text: "One clean screen. One action at a time.",
114
- align: "center",
115
- valign: "middle",
116
- fontSize: 18,
117
- fontFamily: font,
118
- weight: 400,
119
- fill: "#93a9c7",
120
- opacity: 0,
121
- timeline: {
122
- tracks: {
123
- opacity: {
124
- keyframes: [
125
- { time: 0.2, value: 0, out: curves.ease },
126
- { time: 1, value: 1 },
127
- { time: 2.8, value: 1, out: curves.ease },
128
- { time: 3.4, value: 0 }
129
- ]
130
- }
131
- }
132
- }
133
- });
134
-
135
- elements.push(captionCard(
136
- "caption_overview",
137
- 2.4,
138
- 7.4,
139
- "Overview",
140
- "Start wide, show the whole product, and establish where the user is."
141
- ));
142
- elements.push(captionCard(
143
- "caption_notifications",
144
- 7.2,
145
- 13,
146
- "Interaction",
147
- "Zoom into the control, move the cursor with intent, and make a single change."
148
- ));
149
- elements.push(captionCard(
150
- "caption_billing",
151
- 12.8,
152
- 18.2,
153
- "Plan change",
154
- "Use camera zoom to spotlight one setting instead of crowding the screen."
155
- ));
156
- elements.push(captionCard(
157
- "caption_save",
158
- 18,
159
- 23.4,
160
- "Confirmation",
161
- "End on the save action and a visible success state so the flow feels complete."
162
- ));
163
-
164
- elements.push(buildAppCamera());
165
-
166
- const doc = {
167
- version: 1,
168
- canvas: { width, height, background: bg, duration, fps },
169
- elements
170
- };
171
-
172
- const outPath = path.join(__dirname, "ui-demo-explainer.visual.json");
173
- fs.writeFileSync(outPath, JSON.stringify(doc, null, 2));
174
- console.log("Written:", outPath);
175
-
176
- function buildAppCamera() {
177
- return {
178
- id: "app-camera",
179
- type: "group",
180
- x: overviewPose.x,
181
- y: overviewPose.y,
182
- scale: overviewPose.scale,
183
- origin: [0, 0],
184
- opacity: 0,
185
- children: [
186
- buildAppShell(),
187
- buildSidebar(),
188
- buildTopBar(),
189
- buildPageHeader(),
190
- buildProfileCard(),
191
- buildNotificationsCard(),
192
- buildBillingCard(),
193
- buildReviewCard(),
194
- buildToast(),
195
- buildFocusOutline("focus_notifications", notificationsCard, 7.2, 12.9),
196
- buildFocusOutline("focus_billing", billingCard, 12.8, 18.1),
197
- buildFocusOutline("focus_review", reviewCard, 18, 22.4),
198
- buildClickRing("click_weekly", weeklyToggleCenter, 8.9),
199
- buildClickRing("click_annual", annualPillCenter, 14.1),
200
- buildClickRing("click_save", saveButtonCenter, 19.6),
201
- buildCursor()
202
- ],
203
- timeline: {
204
- tracks: {
205
- opacity: {
206
- keyframes: [
207
- { time: 1.8, value: 0, out: curves.ease },
208
- { time: 2.6, value: 1 }
209
- ]
210
- },
211
- x: {
212
- keyframes: [
213
- { time: 2.2, value: overviewPose.x, out: curves.ease },
214
- { time: 3, value: overviewPose.x },
215
- { time: 7.1, value: overviewPose.x, out: curves.easeOut },
216
- { time: 8, value: notifPose.x },
217
- { time: 12.6, value: notifPose.x, out: curves.easeOut },
218
- { time: 13.4, value: billingPose.x },
219
- { time: 17.8, value: billingPose.x, out: curves.easeOut },
220
- { time: 18.6, value: reviewPose.x },
221
- { time: 21.4, value: reviewPose.x, out: curves.easeOut },
222
- { time: 22.2, value: overviewPose.x }
223
- ]
224
- },
225
- y: {
226
- keyframes: [
227
- { time: 2.2, value: overviewPose.y, out: curves.ease },
228
- { time: 3, value: overviewPose.y },
229
- { time: 7.1, value: overviewPose.y, out: curves.easeOut },
230
- { time: 8, value: notifPose.y },
231
- { time: 12.6, value: notifPose.y, out: curves.easeOut },
232
- { time: 13.4, value: billingPose.y },
233
- { time: 17.8, value: billingPose.y, out: curves.easeOut },
234
- { time: 18.6, value: reviewPose.y },
235
- { time: 21.4, value: reviewPose.y, out: curves.easeOut },
236
- { time: 22.2, value: overviewPose.y }
237
- ]
238
- },
239
- scale: {
240
- keyframes: [
241
- { time: 2.2, value: 0.88, out: curves.ease },
242
- { time: 3, value: overviewPose.scale },
243
- { time: 7.1, value: overviewPose.scale, out: curves.easeOut },
244
- { time: 8, value: notifPose.scale },
245
- { time: 12.6, value: notifPose.scale, out: curves.easeOut },
246
- { time: 13.4, value: billingPose.scale },
247
- { time: 17.8, value: billingPose.scale, out: curves.easeOut },
248
- { time: 18.6, value: reviewPose.scale },
249
- { time: 21.4, value: reviewPose.scale, out: curves.easeOut },
250
- { time: 22.2, value: overviewPose.scale }
251
- ]
252
- }
253
- }
254
- }
255
- };
256
- }
257
-
258
- function buildAppShell() {
259
- return {
260
- id: "shell",
261
- type: "group",
262
- x: screen.x,
263
- y: screen.y,
264
- children: [
265
- {
266
- id: "shell-bg",
267
- type: "path",
268
- d: roundedRect(0, 0, screen.w, screen.h, 24),
269
- fill: colors.shell,
270
- stroke: colors.shellBorder,
271
- strokeWidth: 1.5
272
- },
273
- {
274
- id: "shell-top",
275
- type: "path",
276
- d: roundedRect(0, 0, screen.w, 58, 24),
277
- fill: colors.topBar,
278
- stroke: colors.shellBorder,
279
- strokeWidth: 1
280
- },
281
- {
282
- id: "shell-sidebar",
283
- type: "path",
284
- d: roundedRect(0, 0, 208, screen.h, 24),
285
- fill: colors.sidebar,
286
- stroke: "none"
287
- },
288
- {
289
- id: "shell-content-bg",
290
- type: "path",
291
- d: roundedRect(208, 58, screen.w - 208, screen.h - 58, 0),
292
- fill: "#eff4fb",
293
- stroke: "none"
294
- }
295
- ]
296
- };
297
- }
298
-
299
- function buildSidebar() {
300
- return {
301
- id: "sidebar",
302
- type: "group",
303
- x: 0,
304
- y: 0,
305
- children: [
306
- text("brand-mark", 32, 24, "sketchmark", {
307
- fontSize: 19,
308
- weight: 700,
309
- fill: "#f8fafc"
310
- }),
311
- text("brand-sub", 32, 46, "workspace demo", {
312
- fontSize: 11,
313
- weight: 500,
314
- fill: "#9fb3cf"
315
- }),
316
- navItem("nav-home", 20, 108, "Overview", false),
317
- navItem("nav-projects", 20, 154, "Projects", false),
318
- navItem("nav-settings", 20, 200, "Team Settings", true),
319
- navItem("nav-billing", 20, 246, "Billing", false),
320
- navItem("nav-audit", 20, 292, "Audit Log", false),
321
- {
322
- id: "sidebar-footer-chip",
323
- type: "group",
324
- x: 28,
325
- y: screen.h - 92,
326
- children: [
327
- {
328
- id: "sidebar-chip-bg",
329
- type: "path",
330
- d: roundedRect(0, 0, 152, 40, 12),
331
- fill: "#112544",
332
- stroke: "#20406f",
333
- strokeWidth: 1
334
- },
335
- text("sidebar-chip-text", 16, 12, "4 teammates online", {
336
- fontSize: 12,
337
- weight: 500,
338
- fill: "#bfdbfe"
339
- })
340
- ]
341
- }
342
- ]
343
- };
344
- }
345
-
346
- function buildTopBar() {
347
- return {
348
- id: "topbar",
349
- type: "group",
350
- x: 232,
351
- y: 12,
352
- children: [
353
- {
354
- id: "search-bg",
355
- type: "path",
356
- d: roundedRect(0, 0, 300, 34, 10),
357
- fill: "#f8fafc",
358
- stroke: "#d7e2f0",
359
- strokeWidth: 1
360
- },
361
- text("search-text", 16, 10, "Search settings, billing, teammates", {
362
- fontSize: 12,
363
- weight: 400,
364
- fill: "#94a3b8"
365
- }),
366
- pillGroup("top-chip", 724, 2, 128, 30, "Preview mode", colors.chipBg, colors.chipStroke, colors.chipText),
367
- avatarGroup("top-avatar", 974, 0, 34, "#c7d2fe", "JD")
368
- ]
369
- };
370
- }
371
-
372
- function buildPageHeader() {
373
- return {
374
- id: "page-header",
375
- type: "group",
376
- x: contentX,
377
- y: 92,
378
- children: [
379
- text("page-eyebrow", 0, 0, "Team workspace", {
380
- fontSize: 12,
381
- weight: 600,
382
- fill: "#2563eb"
383
- }),
384
- text("page-title", 0, 22, "Settings that stay easy to explain", {
385
- fontSize: 30,
386
- weight: 700,
387
- fill: colors.pageTitle
388
- }),
389
- text("page-body", 0, 64, "A demo flow works best when the camera and cursor both guide attention.", {
390
- fontSize: 15,
391
- weight: 400,
392
- fill: colors.body
393
- })
394
- ]
395
- };
396
- }
397
-
398
- function buildProfileCard() {
399
- return cardGroup("profile-card", 236, 96, 358, 198, [
400
- text("profile-title", 24, 20, "Workspace profile", {
401
- fontSize: 17,
402
- weight: 600,
403
- fill: colors.pageTitle
404
- }),
405
- text("profile-body", 24, 44, "Show identity details before you zoom into smaller controls.", {
406
- fontSize: 13,
407
- weight: 400,
408
- fill: colors.body
409
- }),
410
- avatarGroup("profile-avatar", 24, 88, 48, "#bfdbfe", "NS"),
411
- text("profile-name", 86, 90, "Northstar Studio", {
412
- fontSize: 18,
413
- weight: 600,
414
- fill: colors.pageTitle
415
- }),
416
- text("profile-meta", 86, 116, "8 members • EU region", {
417
- fontSize: 12,
418
- weight: 500,
419
- fill: colors.muted
420
- }),
421
- fieldLine("profile-field-1", 24, 152, 150, "Workspace name"),
422
- fieldLine("profile-field-2", 186, 152, 148, "Support email")
423
- ]);
424
- }
425
-
426
- function buildNotificationsCard() {
427
- return cardGroup("notifications-card", notificationsCard.x, notificationsCard.y, notificationsCard.w, notificationsCard.h, [
428
- text("notif-title", 24, 20, "Notifications", {
429
- fontSize: 17,
430
- weight: 600,
431
- fill: colors.pageTitle
432
- }),
433
- text("notif-body", 24, 44, "Zooming here turns a tiny toggle into a clear, narrated moment.", {
434
- fontSize: 13,
435
- weight: 400,
436
- fill: colors.body
437
- }),
438
- toggleRow("notif-weekly", 24, 82, "Weekly summary", "Send one digest every Monday morning.", false, true),
439
- divider("notif-divider-1", 24, 128, notificationsCard.w - 48),
440
- toggleRow("notif-incidents", 24, 140, "Critical incidents", "Always enabled for admins and owners.", true, false)
441
- ]);
442
- }
443
-
444
- function buildBillingCard() {
445
- return cardGroup("billing-card", billingCard.x, billingCard.y, billingCard.w, billingCard.h, [
446
- text("billing-title", 24, 20, "Billing", {
447
- fontSize: 17,
448
- weight: 600,
449
- fill: colors.pageTitle
450
- }),
451
- text("billing-body", 24, 44, "Use a small motion change to make plan selection feel deliberate.", {
452
- fontSize: 13,
453
- weight: 400,
454
- fill: colors.body
455
- }),
456
- {
457
- id: "billing-segment-bg",
458
- type: "path",
459
- d: roundedRect(24, 82, 186, 42, 14),
460
- fill: "#edf3fb",
461
- stroke: "#d8e4f2",
462
- strokeWidth: 1
463
- },
464
- {
465
- id: "billing-segment-highlight",
466
- type: "path",
467
- d: roundedRect(30, 88, 82, 30, 12),
468
- x: 0,
469
- y: 0,
470
- fill: colors.accentSoft,
471
- stroke: "#93c5fd",
472
- strokeWidth: 1,
473
- timeline: {
474
- tracks: {
475
- x: {
476
- keyframes: [
477
- { time: 0, value: 0 },
478
- { time: 14.05, value: 0, out: curves.snap },
479
- { time: 14.25, value: 89 }
480
- ]
481
- }
482
- }
483
- }
484
- },
485
- text("billing-monthly-label", 71, 103, "Monthly", {
486
- align: "center",
487
- valign: "middle",
488
- fontSize: 13,
489
- weight: 600,
490
- fill: colors.accent,
491
- timeline: {
492
- tracks: {
493
- fill: {
494
- keyframes: [
495
- { time: 0, value: colors.accent },
496
- { time: 14.2, value: colors.muted }
497
- ]
498
- }
499
- }
500
- }
501
- }),
502
- text("billing-annual-label", 160, 103, "Annual", {
503
- align: "center",
504
- valign: "middle",
505
- fontSize: 13,
506
- weight: 600,
507
- fill: colors.muted,
508
- timeline: {
509
- tracks: {
510
- fill: {
511
- keyframes: [
512
- { time: 0, value: colors.muted },
513
- { time: 14.2, value: colors.accent }
514
- ]
515
- }
516
- }
517
- }
518
- }),
519
- text("billing-price", 24, 140, "$49 / seat / month", {
520
- fontSize: 28,
521
- weight: 700,
522
- fill: colors.pageTitle,
523
- timeline: {
524
- tracks: {
525
- text: {
526
- keyframes: [
527
- { time: 0, value: "$49 / seat / month" },
528
- { time: 14.2, value: "$39 / seat / month" }
529
- ]
530
- }
531
- }
532
- }
533
- }),
534
- text("billing-price-note", 24, 170, "Annual billing unlocks a 20% savings and a cleaner renewal cycle.", {
535
- fontSize: 12,
536
- weight: 500,
537
- fill: colors.success
538
- })
539
- ]);
540
- }
541
-
542
- function buildReviewCard() {
543
- return cardGroup("review-card", reviewCard.x, reviewCard.y, reviewCard.w, reviewCard.h, [
544
- text("review-title", 24, 20, "Review changes", {
545
- fontSize: 17,
546
- weight: 600,
547
- fill: colors.pageTitle
548
- }),
549
- text("review-body", 24, 44, "End the sequence on a single confident action: save and show success.", {
550
- fontSize: 13,
551
- weight: 400,
552
- fill: colors.body
553
- }),
554
- pillGroup("review-pill-1", 24, 78, 110, 30, "Weekly summary", "#eff6ff", "#bfdbfe", colors.accent),
555
- pillGroup("review-pill-2", 144, 78, 88, 30, "Annual", "#ecfdf5", "#a7f3d0", colors.success),
556
- {
557
- id: "save-button",
558
- type: "group",
559
- x: 24,
560
- y: 106,
561
- origin: [70, 22],
562
- children: [
563
- {
564
- id: "save-button-bg",
565
- type: "path",
566
- d: roundedRect(0, 0, 140, 44, 14),
567
- fill: colors.accent,
568
- stroke: "none"
569
- },
570
- text("save-button-label", 70, 22, "Save changes", {
571
- align: "center",
572
- valign: "middle",
573
- fontSize: 14,
574
- weight: 600,
575
- fill: "#ffffff",
576
- timeline: {
577
- tracks: {
578
- text: {
579
- keyframes: [
580
- { time: 0, value: "Save changes" },
581
- { time: 19.9, value: "Saved" }
582
- ]
583
- }
584
- }
585
- }
586
- })
587
- ],
588
- timeline: {
589
- tracks: {
590
- scale: {
591
- keyframes: [
592
- { time: 19.5, value: 1, out: curves.snap },
593
- { time: 19.7, value: 0.95 },
594
- { time: 19.95, value: 1 }
595
- ]
596
- }
597
- }
598
- }
599
- },
600
- {
601
- id: "ghost-button",
602
- type: "group",
603
- x: 176,
604
- y: 106,
605
- children: [
606
- {
607
- id: "ghost-button-bg",
608
- type: "path",
609
- d: roundedRect(0, 0, 110, 44, 14),
610
- fill: "#ffffff",
611
- stroke: "#d4e0ee",
612
- strokeWidth: 1
613
- },
614
- text("ghost-button-label", 55, 22, "Cancel", {
615
- align: "center",
616
- valign: "middle",
617
- fontSize: 14,
618
- weight: 500,
619
- fill: colors.muted
620
- })
621
- ]
622
- }
623
- ]);
624
- }
625
-
626
- function buildToast() {
627
- return {
628
- id: "toast",
629
- type: "group",
630
- x: 760,
631
- y: 20,
632
- opacity: 0,
633
- children: [
634
- {
635
- id: "toast-bg",
636
- type: "path",
637
- d: roundedRect(0, 0, 292, 62, 16),
638
- fill: colors.card,
639
- stroke: "#bfe9d8",
640
- strokeWidth: 1.5
641
- },
642
- {
643
- id: "toast-dot",
644
- type: "path",
645
- d: circlePath(24, 31, 9),
646
- fill: colors.successSoft,
647
- stroke: "none"
648
- },
649
- text("toast-title", 42, 16, "Changes saved", {
650
- fontSize: 14,
651
- weight: 700,
652
- fill: colors.pageTitle
653
- }),
654
- text("toast-body", 42, 34, "Notifications and billing have been updated.", {
655
- fontSize: 12,
656
- weight: 400,
657
- fill: colors.body
658
- })
659
- ],
660
- timeline: {
661
- tracks: {
662
- opacity: {
663
- keyframes: [
664
- { time: 19.9, value: 0, out: curves.easeOut },
665
- { time: 20.3, value: 1 },
666
- { time: 23.1, value: 1, out: curves.easeOut },
667
- { time: 23.7, value: 0 }
668
- ]
669
- },
670
- y: {
671
- keyframes: [
672
- { time: 19.9, value: 8, out: curves.easeOut },
673
- { time: 20.3, value: 20 }
674
- ]
675
- }
676
- }
677
- }
678
- };
679
- }
680
-
681
- function buildFocusOutline(id, box, start, end) {
682
- return {
683
- id,
684
- type: "path",
685
- d: roundedRect(box.x - 8, box.y - 8, box.w + 16, box.h + 16, 24),
686
- fill: "none",
687
- stroke: colors.focus,
688
- strokeWidth: 2,
689
- dashArray: [8, 6],
690
- opacity: 0,
691
- timeline: {
692
- tracks: {
693
- opacity: {
694
- keyframes: [
695
- { time: start - 0.2, value: 0, out: curves.ease },
696
- { time: start, value: 0.85 },
697
- { time: end, value: 0.85, out: curves.ease },
698
- { time: end + 0.3, value: 0 }
699
- ]
700
- }
701
- }
702
- }
703
- };
704
- }
705
-
706
- function buildClickRing(id, center, start) {
707
- return {
708
- id,
709
- type: "path",
710
- d: circlePath(center.x, center.y, 16),
711
- fill: "none",
712
- stroke: colors.cursorRing,
713
- strokeWidth: 2,
714
- opacity: 0,
715
- origin: [center.x, center.y],
716
- timeline: {
717
- tracks: {
718
- opacity: {
719
- keyframes: [
720
- { time: start, value: 0 },
721
- { time: start + 0.1, value: 0.7 },
722
- { time: start + 0.55, value: 0 }
723
- ]
724
- },
725
- scale: {
726
- keyframes: [
727
- { time: start, value: 0.4, out: curves.easeOut },
728
- { time: start + 0.55, value: 1.5 }
729
- ]
730
- }
731
- }
732
- }
733
- };
734
- }
735
-
736
- function buildCursor() {
737
- return {
738
- id: "cursor",
739
- type: "group",
740
- x: -80,
741
- y: -80,
742
- children: [
743
- {
744
- id: "cursor-arrow",
745
- type: "path",
746
- d: "M 0 0 L 0 22 L 6 18 L 10 28 L 15 26 L 11 16 L 20 16 Z",
747
- fill: colors.cursor,
748
- stroke: "#ffffff",
749
- strokeWidth: 1.6
750
- }
751
- ],
752
- timeline: {
753
- tracks: {
754
- x: {
755
- keyframes: [
756
- { time: 0, value: -80 },
757
- { time: 6.7, value: -80, out: curves.easeOut },
758
- { time: 7.8, value: weeklyToggleCenter.x },
759
- { time: 9.3, value: weeklyToggleCenter.x },
760
- { time: 12.7, value: weeklyToggleCenter.x, out: curves.easeOut },
761
- { time: 13.9, value: annualPillCenter.x },
762
- { time: 14.5, value: annualPillCenter.x },
763
- { time: 17.8, value: annualPillCenter.x, out: curves.easeOut },
764
- { time: 19.1, value: saveButtonCenter.x },
765
- { time: 20.1, value: saveButtonCenter.x },
766
- { time: 22.2, value: saveButtonCenter.x, out: curves.easeOut },
767
- { time: 23.1, value: screen.w + 180 }
768
- ]
769
- },
770
- y: {
771
- keyframes: [
772
- { time: 0, value: -80 },
773
- { time: 6.7, value: -80, out: curves.easeOut },
774
- { time: 7.8, value: weeklyToggleCenter.y },
775
- { time: 9.3, value: weeklyToggleCenter.y },
776
- { time: 12.7, value: weeklyToggleCenter.y, out: curves.easeOut },
777
- { time: 13.9, value: annualPillCenter.y },
778
- { time: 14.5, value: annualPillCenter.y },
779
- { time: 17.8, value: annualPillCenter.y, out: curves.easeOut },
780
- { time: 19.1, value: saveButtonCenter.y },
781
- { time: 20.1, value: saveButtonCenter.y },
782
- { time: 22.2, value: saveButtonCenter.y, out: curves.easeOut },
783
- { time: 23.1, value: screen.h + 140 }
784
- ]
785
- }
786
- }
787
- }
788
- };
789
- }
790
-
791
- function cardGroup(id, x, y, w, h, children) {
792
- return {
793
- id,
794
- type: "group",
795
- x,
796
- y,
797
- children: [
798
- {
799
- id: `${id}-bg`,
800
- type: "path",
801
- d: roundedRect(0, 0, w, h, 20),
802
- fill: colors.card,
803
- stroke: colors.cardBorder,
804
- strokeWidth: 1
805
- },
806
- ...children
807
- ]
808
- };
809
- }
810
-
811
- function pillGroup(id, x, y, w, h, label, fill, stroke, textFill) {
812
- return {
813
- id,
814
- type: "group",
815
- x,
816
- y,
817
- children: [
818
- {
819
- id: `${id}-bg`,
820
- type: "path",
821
- d: roundedRect(0, 0, w, h, h / 2),
822
- fill,
823
- stroke,
824
- strokeWidth: 1
825
- },
826
- text(`${id}-text`, w / 2, h / 2, label, {
827
- align: "center",
828
- valign: "middle",
829
- fontSize: 12,
830
- weight: 600,
831
- fill: textFill
832
- })
833
- ]
834
- };
835
- }
836
-
837
- function avatarGroup(id, x, y, size, fill, label) {
838
- return {
839
- id,
840
- type: "group",
841
- x,
842
- y,
843
- children: [
844
- {
845
- id: `${id}-circle`,
846
- type: "path",
847
- d: circlePath(size / 2, size / 2, size / 2),
848
- fill,
849
- stroke: "none"
850
- },
851
- text(`${id}-label`, size / 2, size / 2, label, {
852
- align: "center",
853
- valign: "middle",
854
- fontSize: Math.max(11, Math.round(size * 0.32)),
855
- weight: 700,
856
- fill: "#1e3a8a"
857
- })
858
- ]
859
- };
860
- }
861
-
862
- function navItem(id, x, y, label, active) {
863
- return {
864
- id,
865
- type: "group",
866
- x,
867
- y,
868
- children: [
869
- {
870
- id: `${id}-bg`,
871
- type: "path",
872
- d: roundedRect(0, 0, 164, 36, 12),
873
- fill: active ? colors.sidebarActive : "none",
874
- stroke: active ? "#bfdbfe" : "none",
875
- strokeWidth: 1
876
- },
877
- text(`${id}-text`, 16, 10, label, {
878
- fontSize: 13,
879
- weight: active ? 700 : 500,
880
- fill: active ? colors.sidebarActiveText : "#dbe5f2"
881
- })
882
- ]
883
- };
884
- }
885
-
886
- function fieldLine(id, x, y, w, label) {
887
- return {
888
- id,
889
- type: "group",
890
- x,
891
- y,
892
- children: [
893
- text(`${id}-label`, 0, 0, label, {
894
- fontSize: 11,
895
- weight: 600,
896
- fill: colors.muted
897
- }),
898
- {
899
- id: `${id}-line`,
900
- type: "path",
901
- d: `M 0 24 L ${w} 24`,
902
- stroke: colors.divider,
903
- strokeWidth: 2,
904
- fill: "none"
905
- }
906
- ]
907
- };
908
- }
909
-
910
- function toggleRow(id, x, y, label, description, on, animatesOn) {
911
- const knobStart = on ? 334 : 308;
912
- const bgStart = on ? colors.accent : colors.toggleOff;
913
- return {
914
- id,
915
- type: "group",
916
- x,
917
- y,
918
- children: [
919
- text(`${id}-label`, 0, 0, label, {
920
- fontSize: 15,
921
- weight: 600,
922
- fill: colors.pageTitle
923
- }),
924
- text(`${id}-body`, 0, 22, description, {
925
- fontSize: 12,
926
- weight: 400,
927
- fill: colors.body
928
- }),
929
- {
930
- id: `${id}-toggle-bg`,
931
- type: "path",
932
- d: roundedRect(300, 8, 56, 30, 15),
933
- fill: bgStart,
934
- stroke: "none",
935
- timeline: animatesOn ? {
936
- tracks: {
937
- fill: {
938
- keyframes: [
939
- { time: 0, value: colors.toggleOff },
940
- { time: 8.95, value: colors.toggleOff, out: curves.snap },
941
- { time: 9.15, value: colors.accent }
942
- ]
943
- }
944
- }
945
- } : undefined
946
- },
947
- {
948
- id: `${id}-toggle-knob`,
949
- type: "path",
950
- d: circlePath(knobStart, 23, 11),
951
- fill: colors.toggleKnob,
952
- stroke: "none",
953
- timeline: animatesOn ? {
954
- tracks: {
955
- x: {
956
- keyframes: [
957
- { time: 0, value: 0 },
958
- { time: 8.95, value: 0, out: curves.snap },
959
- { time: 9.15, value: 26 }
960
- ]
961
- }
962
- }
963
- } : undefined
964
- }
965
- ]
966
- };
967
- }
968
-
969
- function divider(id, x, y, w) {
970
- return {
971
- id,
972
- type: "path",
973
- d: `M ${x} ${y} L ${x + w} ${y}`,
974
- stroke: colors.divider,
975
- strokeWidth: 1,
976
- fill: "none"
977
- };
978
- }
979
-
980
- function captionCard(id, start, end, label, body) {
981
- const boxW = 620;
982
- const boxH = 92;
983
- const chipW = 96;
984
- const x = (width - boxW) / 2;
985
- const y = 584;
986
- return {
987
- id,
988
- type: "group",
989
- x,
990
- y,
991
- opacity: 0,
992
- children: [
993
- {
994
- id: `${id}-bg`,
995
- type: "path",
996
- d: roundedRect(0, 0, boxW, boxH, 22),
997
- fill: colors.captionBg,
998
- stroke: colors.captionBorder,
999
- strokeWidth: 1.5
1000
- },
1001
- {
1002
- id: `${id}-chip`,
1003
- type: "group",
1004
- x: 20,
1005
- y: 18,
1006
- children: [
1007
- {
1008
- id: `${id}-chip-bg`,
1009
- type: "path",
1010
- d: roundedRect(0, 0, chipW, 28, 14),
1011
- fill: "#0b2648",
1012
- stroke: "#1d4c7f",
1013
- strokeWidth: 1
1014
- },
1015
- text(`${id}-chip-text`, chipW / 2, 14, label, {
1016
- align: "center",
1017
- valign: "middle",
1018
- fontSize: 12,
1019
- weight: 700,
1020
- fill: "#7dd3fc"
1021
- })
1022
- ]
1023
- },
1024
- text(`${id}-body`, 20, 56, body, {
1025
- fontSize: 15,
1026
- weight: 400,
1027
- fill: colors.captionText,
1028
- maxWidth: boxW - 40
1029
- })
1030
- ],
1031
- timeline: {
1032
- tracks: {
1033
- opacity: {
1034
- keyframes: [
1035
- { time: start - 0.3, value: 0, out: curves.easeOut },
1036
- { time: start, value: 1 },
1037
- { time: end, value: 1, out: curves.easeOut },
1038
- { time: end + 0.35, value: 0 }
1039
- ]
1040
- },
1041
- y: {
1042
- keyframes: [
1043
- { time: start - 0.3, value: y + 20, out: curves.easeOut },
1044
- { time: start, value: y }
1045
- ]
1046
- }
1047
- }
1048
- }
1049
- };
1050
- }
1051
-
1052
- function text(id, x, y, value, extra) {
1053
- return {
1054
- id,
1055
- type: "text",
1056
- x,
1057
- y,
1058
- text: value,
1059
- align: "left",
1060
- valign: "top",
1061
- fontSize: 14,
1062
- fontFamily: font,
1063
- weight: 400,
1064
- fill: colors.pageTitle,
1065
- ...extra
1066
- };
1067
- }
1068
-
1069
- function cameraPose(targetX, targetY, scale) {
1070
- return {
1071
- x: width / 2 - targetX * scale,
1072
- y: height / 2 - targetY * scale,
1073
- scale
1074
- };
1075
- }
1076
-
1077
- function circlePath(cx, cy, r) {
1078
- return `M ${cx - r} ${cy} a ${r} ${r} 0 1 0 ${r * 2} 0 a ${r} ${r} 0 1 0 ${-r * 2} 0`;
1079
- }
1080
-
1081
- function roundedRect(x, y, w, h, r) {
1082
- return [
1083
- `M ${x + r} ${y}`,
1084
- `L ${x + w - r} ${y}`,
1085
- `Q ${x + w} ${y} ${x + w} ${y + r}`,
1086
- `L ${x + w} ${y + h - r}`,
1087
- `Q ${x + w} ${y + h} ${x + w - r} ${y + h}`,
1088
- `L ${x + r} ${y + h}`,
1089
- `Q ${x} ${y + h} ${x} ${y + h - r}`,
1090
- `L ${x} ${y + r}`,
1091
- `Q ${x} ${y} ${x + r} ${y}`,
1092
- "Z"
1093
- ].join(" ");
1094
- }