@make-pictures/kits 0.0.0-stage → 2.0.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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +49 -2
  3. package/dist/chunk-3SUIELJV.mjs +601 -0
  4. package/dist/chunk-4Z3G6FPZ.mjs +485 -0
  5. package/dist/chunk-AHA23PHC.mjs +678 -0
  6. package/dist/chunk-CW623YVB.mjs +1124 -0
  7. package/dist/chunk-NNA7XXQB.mjs +1059 -0
  8. package/dist/chunk-S4CHTWOS.mjs +1237 -0
  9. package/dist/chunk-WAA7P5FT.mjs +912 -0
  10. package/dist/data-storytelling/AnimatedBarRace.d.ts +37 -0
  11. package/dist/data-storytelling/MetricComparisonCard.d.ts +29 -0
  12. package/dist/data-storytelling/TimelineTrack.d.ts +29 -0
  13. package/dist/data-storytelling/index.cjs +1224 -0
  14. package/dist/data-storytelling/index.d.ts +3 -0
  15. package/dist/data-storytelling/index.mjs +10 -0
  16. package/dist/index.cjs +6010 -0
  17. package/dist/index.d.ts +7 -0
  18. package/dist/index.mjs +108 -0
  19. package/dist/isometric-3d/IsometricStudioEnvironment.d.ts +8 -0
  20. package/dist/isometric-3d/Stylized3DCharacter.d.ts +29 -0
  21. package/dist/isometric-3d/index.cjs +611 -0
  22. package/dist/isometric-3d/index.d.ts +2 -0
  23. package/dist/isometric-3d/index.mjs +8 -0
  24. package/dist/kinetic-typography/AnimatedMetricCounter.d.ts +36 -0
  25. package/dist/kinetic-typography/ImpactKeywordCard.d.ts +31 -0
  26. package/dist/kinetic-typography/WordByWordStage.d.ts +47 -0
  27. package/dist/kinetic-typography/index.cjs +665 -0
  28. package/dist/kinetic-typography/index.d.ts +3 -0
  29. package/dist/kinetic-typography/index.mjs +14 -0
  30. package/dist/motion-diagrams-2d/CameraRig.d.ts +34 -0
  31. package/dist/motion-diagrams-2d/CodePayloadToast.d.ts +14 -0
  32. package/dist/motion-diagrams-2d/DataFlowEdge.d.ts +24 -0
  33. package/dist/motion-diagrams-2d/NodeBox.d.ts +65 -0
  34. package/dist/motion-diagrams-2d/SystemNode.d.ts +21 -0
  35. package/dist/motion-diagrams-2d/index.cjs +1137 -0
  36. package/dist/motion-diagrams-2d/index.d.ts +5 -0
  37. package/dist/motion-diagrams-2d/index.mjs +28 -0
  38. package/dist/paper-diorama-3d/ImageCutout3D.d.ts +47 -0
  39. package/dist/paper-diorama-3d/PaperCutout3D.d.ts +62 -0
  40. package/dist/paper-diorama-3d/PaperPopUpBook3D.d.ts +51 -0
  41. package/dist/paper-diorama-3d/PaperStageLighting3D.d.ts +12 -0
  42. package/dist/paper-diorama-3d/index.cjs +1087 -0
  43. package/dist/paper-diorama-3d/index.d.ts +4 -0
  44. package/dist/paper-diorama-3d/index.mjs +34 -0
  45. package/dist/product-frames/BrowserFrame.d.ts +28 -0
  46. package/dist/product-frames/PhoneFrame.d.ts +23 -0
  47. package/dist/product-frames/ScreenSpotlightOverlay.d.ts +34 -0
  48. package/dist/product-frames/frameMedia.d.ts +24 -0
  49. package/dist/product-frames/index.cjs +507 -0
  50. package/dist/product-frames/index.d.ts +8 -0
  51. package/dist/product-frames/index.mjs +12 -0
  52. package/dist/vfx/CinematicLensOverlay.d.ts +29 -0
  53. package/dist/vfx/ParticleField.d.ts +18 -0
  54. package/dist/vfx/SceneTransition.d.ts +24 -0
  55. package/dist/vfx/StagePostProcessing3D.d.ts +44 -0
  56. package/dist/vfx/index.cjs +933 -0
  57. package/dist/vfx/index.d.ts +4 -0
  58. package/dist/vfx/index.mjs +14 -0
  59. package/package.json +95 -4
@@ -0,0 +1,678 @@
1
+ // packages/kits/src/kinetic-typography/WordByWordStage.tsx
2
+ import { useMemo } from "react";
3
+ import {
4
+ AbsoluteFill,
5
+ interpolate,
6
+ spring,
7
+ useCurrentFrame,
8
+ useVideoConfig
9
+ } from "remotion";
10
+ import {
11
+ FONTS,
12
+ SPRING_PRESETS,
13
+ STUDIO_THEME,
14
+ THEME,
15
+ useStudioFontsReady
16
+ } from "@make-pictures/core";
17
+ import { getSceneVoiceover } from "@make-pictures/core";
18
+ import { jsx, jsxs } from "react/jsx-runtime";
19
+ function buildFallbackWords(text, fps, durationInFrames) {
20
+ const tokens = text.trim().split(/\s+/).filter(Boolean);
21
+ if (tokens.length === 0) return [];
22
+ const usableStart = Math.min(12, Math.floor(durationInFrames * 0.08));
23
+ const usableSpan = Math.max(tokens.length * 6, durationInFrames - usableStart - 16);
24
+ const perWord = usableSpan / tokens.length;
25
+ return tokens.map((token, idx) => {
26
+ const sf = Math.round(usableStart + idx * perWord);
27
+ const ef = Math.max(sf + 4, Math.round(usableStart + (idx + 0.86) * perWord));
28
+ return {
29
+ word: token,
30
+ normalizedWord: token.replace(/^[.,!?;:"'()[\]{}—–-]+|[.,!?;:"'()[\]{}—–-]+$/g, "").toLowerCase(),
31
+ start: sf / fps,
32
+ end: ef / fps,
33
+ startFrame: sf,
34
+ endFrame: ef
35
+ };
36
+ });
37
+ }
38
+ function groupWordsIntoBeats(words, wordsPerBeat = 4, fps = 60) {
39
+ if (!words.length) return [];
40
+ const maxWords = Math.max(2, Math.min(7, Math.round(wordsPerBeat)));
41
+ const pauseThresholdFrames = Math.round(fps * 0.36);
42
+ const beats = [];
43
+ let bucket = [];
44
+ const flush = () => {
45
+ if (!bucket.length) return;
46
+ beats.push({
47
+ words: bucket,
48
+ startFrame: bucket[0].startFrame,
49
+ endFrame: bucket[bucket.length - 1].endFrame
50
+ });
51
+ bucket = [];
52
+ };
53
+ for (let i = 0; i < words.length; i++) {
54
+ const curr = words[i];
55
+ const prev = bucket[bucket.length - 1];
56
+ if (prev && curr.startFrame - prev.endFrame >= pauseThresholdFrames && bucket.length >= 2) {
57
+ flush();
58
+ }
59
+ bucket.push(curr);
60
+ const endsSentence = /[.!?;:—]$/.test(curr.word);
61
+ const endsClause = /[,–]$/.test(curr.word) && bucket.length >= 3;
62
+ if (bucket.length >= maxWords || endsSentence && bucket.length >= 2 || endsClause) {
63
+ flush();
64
+ }
65
+ }
66
+ flush();
67
+ return beats;
68
+ }
69
+ var WordByWordStage = ({
70
+ words,
71
+ sceneId,
72
+ voiceoverManifest,
73
+ fallbackText,
74
+ sceneStartFrame = 0,
75
+ wordsPerBeat = 4,
76
+ accentColor = STUDIO_THEME.colors.brand[600],
77
+ variant = "transparent",
78
+ eyebrow,
79
+ fontSize,
80
+ safeBox9x16 = true,
81
+ enterFrame,
82
+ startFrame,
83
+ style
84
+ }) => {
85
+ const frame = useCurrentFrame();
86
+ const { fps, width, height, durationInFrames } = useVideoConfig();
87
+ useStudioFontsReady();
88
+ const resolvedWords = useMemo(() => {
89
+ if (words && words.length > 0) return words;
90
+ if (sceneId) {
91
+ const vo = getSceneVoiceover(sceneId, voiceoverManifest);
92
+ if (vo && vo.words.length > 0) return vo.words;
93
+ if (vo?.text) return buildFallbackWords(vo.text, fps, durationInFrames);
94
+ }
95
+ if (fallbackText) {
96
+ return buildFallbackWords(fallbackText, fps, durationInFrames);
97
+ }
98
+ return [];
99
+ }, [words, sceneId, voiceoverManifest, fallbackText, fps, durationInFrames]);
100
+ const beats = useMemo(
101
+ () => groupWordsIntoBeats(resolvedWords, wordsPerBeat, fps),
102
+ [resolvedWords, wordsPerBeat, fps]
103
+ );
104
+ const stageEnter = spring({
105
+ frame: frame - Math.max(2, startFrame ?? enterFrame ?? 2),
106
+ fps,
107
+ config: SPRING_PRESETS.snappy
108
+ });
109
+ if (stageEnter <= 5e-3 || beats.length === 0) {
110
+ return null;
111
+ }
112
+ const localFrame = frame - sceneStartFrame;
113
+ let activeBeatIndex = 0;
114
+ for (let i = 0; i < beats.length; i++) {
115
+ const b = beats[i];
116
+ const nextBeat = beats[i + 1];
117
+ if (localFrame >= b.startFrame - 4) {
118
+ activeBeatIndex = i;
119
+ }
120
+ if (nextBeat && localFrame < nextBeat.startFrame - 4) {
121
+ break;
122
+ }
123
+ }
124
+ const activeBeat = beats[activeBeatIndex];
125
+ const beatEnter = spring({
126
+ frame: Math.max(0, localFrame - (activeBeat.startFrame - 4)),
127
+ fps,
128
+ config: SPRING_PRESETS.snappy
129
+ });
130
+ let activeWordIdx = -1;
131
+ for (let i = 0; i < activeBeat.words.length; i++) {
132
+ const w = activeBeat.words[i];
133
+ if (localFrame >= w.startFrame - 1 && localFrame <= w.endFrame + 8) {
134
+ activeWordIdx = i;
135
+ }
136
+ }
137
+ const isVertical = height > width;
138
+ const isLight = variant === "light";
139
+ const textPrimary = isLight ? STUDIO_THEME.colors.text.slate900 : THEME.colors.textPrimary;
140
+ const resolvedFontSize = fontSize ?? (isVertical ? 64 : 74);
141
+ const containerPaddingStyle = isVertical && safeBox9x16 ? {
142
+ paddingLeft: Math.round(44 * (width / 1080)),
143
+ paddingRight: Math.round((1080 - 906) * (width / 1080)),
144
+ paddingTop: Math.round(230 * (height / 1920)),
145
+ paddingBottom: Math.round((1920 - 1370) * (height / 1920))
146
+ } : {
147
+ padding: "72px 120px"
148
+ };
149
+ const beatProgress = interpolate(
150
+ localFrame,
151
+ [activeBeat.startFrame, Math.max(activeBeat.startFrame + 1, activeBeat.endFrame)],
152
+ [0, 1],
153
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" }
154
+ );
155
+ return /* @__PURE__ */ jsx(
156
+ AbsoluteFill,
157
+ {
158
+ style: {
159
+ backgroundColor: variant === "dark" ? THEME.colors.bgPrimary : variant === "light" ? STUDIO_THEME.colors.bgCanvas : "transparent",
160
+ display: "flex",
161
+ flexDirection: "column",
162
+ alignItems: "center",
163
+ justifyContent: "center",
164
+ boxSizing: "border-box",
165
+ opacity: stageEnter,
166
+ pointerEvents: "none",
167
+ ...containerPaddingStyle,
168
+ ...style
169
+ },
170
+ children: /* @__PURE__ */ jsxs(
171
+ "div",
172
+ {
173
+ style: {
174
+ maxWidth: isVertical ? 840 : 1420,
175
+ width: "100%",
176
+ display: "flex",
177
+ flexDirection: "column",
178
+ alignItems: "center",
179
+ gap: 24,
180
+ transform: `translateY(${interpolate(beatEnter, [0, 1], [16, 0]).toFixed(2)}px) scale(${interpolate(stageEnter, [0, 1], [0.95, 1]).toFixed(4)})`
181
+ },
182
+ children: [
183
+ eyebrow && /* @__PURE__ */ jsx(
184
+ "div",
185
+ {
186
+ style: {
187
+ fontFamily: FONTS.mono,
188
+ fontSize: isVertical ? 18 : 19,
189
+ fontWeight: 700,
190
+ letterSpacing: "0.08em",
191
+ textTransform: "uppercase",
192
+ padding: "8px 18px",
193
+ borderRadius: 9999,
194
+ color: isLight ? accentColor : "#FFFFFF",
195
+ backgroundColor: isLight ? `${accentColor}16` : `${accentColor}33`,
196
+ border: `1.5px solid ${accentColor}66`
197
+ },
198
+ children: eyebrow
199
+ }
200
+ ),
201
+ /* @__PURE__ */ jsx(
202
+ "div",
203
+ {
204
+ style: {
205
+ display: "flex",
206
+ flexWrap: "wrap",
207
+ justifyContent: "center",
208
+ alignItems: "center",
209
+ gap: isVertical ? "14px 16px" : "16px 22px",
210
+ textAlign: "center"
211
+ },
212
+ children: activeBeat.words.map((w, idx) => {
213
+ const isCurrent = idx === activeWordIdx;
214
+ const isPast = localFrame > w.endFrame + 8;
215
+ const wordPop = spring({
216
+ frame: Math.max(0, localFrame - w.startFrame),
217
+ fps,
218
+ config: SPRING_PRESETS.micro
219
+ });
220
+ const scale = isCurrent ? interpolate(wordPop, [0, 1], [0.96, 1.07]) : 1;
221
+ const yShift = isCurrent || isPast ? 0 : interpolate(wordPop, [0, 1], [6, 0]);
222
+ const wordOpacity = isCurrent ? 1 : isPast ? 0.94 : 0.38;
223
+ return /* @__PURE__ */ jsx(
224
+ "span",
225
+ {
226
+ style: {
227
+ display: "inline-block",
228
+ fontFamily: FONTS.display,
229
+ fontSize: resolvedFontSize,
230
+ fontWeight: 800,
231
+ lineHeight: 1.15,
232
+ letterSpacing: "-0.025em",
233
+ padding: isCurrent ? "6px 20px" : "6px 8px",
234
+ borderRadius: 18,
235
+ color: isCurrent ? "#FFFFFF" : textPrimary,
236
+ backgroundColor: isCurrent ? accentColor : "transparent",
237
+ boxShadow: isCurrent ? `0 14px 36px ${accentColor}66, 0 0 0 2px rgba(255,255,255,0.22) inset` : "none",
238
+ opacity: wordOpacity,
239
+ transform: `translateY(${yShift.toFixed(2)}px) scale(${scale.toFixed(4)})`,
240
+ textShadow: !isCurrent && variant === "transparent" ? "0 4px 22px rgba(9, 10, 15, 0.55)" : "none"
241
+ },
242
+ children: w.word
243
+ },
244
+ `${activeBeatIndex}-${idx}-${w.startFrame}`
245
+ );
246
+ })
247
+ }
248
+ ),
249
+ /* @__PURE__ */ jsx(
250
+ "div",
251
+ {
252
+ style: {
253
+ width: isVertical ? 220 : 280,
254
+ height: 5,
255
+ borderRadius: 9999,
256
+ backgroundColor: isLight ? "rgba(15, 23, 42, 0.10)" : "rgba(255, 255, 255, 0.14)",
257
+ overflow: "hidden"
258
+ },
259
+ children: /* @__PURE__ */ jsx(
260
+ "div",
261
+ {
262
+ style: {
263
+ width: `${Math.round(beatProgress * 100)}%`,
264
+ height: "100%",
265
+ borderRadius: 9999,
266
+ backgroundColor: accentColor
267
+ }
268
+ }
269
+ )
270
+ }
271
+ )
272
+ ]
273
+ }
274
+ )
275
+ }
276
+ );
277
+ };
278
+
279
+ // packages/kits/src/kinetic-typography/ImpactKeywordCard.tsx
280
+ import { Sparkles } from "lucide-react";
281
+ import {
282
+ interpolate as interpolate2,
283
+ spring as spring2,
284
+ useCurrentFrame as useCurrentFrame2,
285
+ useVideoConfig as useVideoConfig2
286
+ } from "remotion";
287
+ import {
288
+ FONTS as FONTS2,
289
+ SPRING_PRESETS as SPRING_PRESETS2,
290
+ STUDIO_THEME as STUDIO_THEME2,
291
+ THEME as THEME2,
292
+ useStudioFontsReady as useStudioFontsReady2
293
+ } from "@make-pictures/core";
294
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
295
+ var ImpactKeywordCard = ({
296
+ keyword,
297
+ eyebrow,
298
+ sublabel,
299
+ badge,
300
+ icon,
301
+ accentColor = STUDIO_THEME2.colors.brand[600],
302
+ variant = "dark",
303
+ enterFrame,
304
+ startFrame,
305
+ exitFrame,
306
+ safeBox9x16 = true,
307
+ style
308
+ }) => {
309
+ const frame = useCurrentFrame2();
310
+ const { fps, width, height } = useVideoConfig2();
311
+ useStudioFontsReady2();
312
+ const effectiveStart = Math.max(2, startFrame ?? enterFrame ?? 2);
313
+ const enter = spring2({
314
+ frame: frame - effectiveStart,
315
+ fps,
316
+ config: SPRING_PRESETS2.snappy
317
+ });
318
+ const exit = typeof exitFrame === "number" ? spring2({
319
+ frame: frame - exitFrame,
320
+ fps,
321
+ config: SPRING_PRESETS2.smooth
322
+ }) : 0;
323
+ const visibility = Math.max(0, enter * (1 - exit));
324
+ if (visibility <= 5e-3) {
325
+ return null;
326
+ }
327
+ const isVertical = height > width;
328
+ const isLight = variant === "light";
329
+ const translateY = interpolate2(enter, [0, 1], [28, 0]) - exit * 18;
330
+ const scale = interpolate2(visibility, [0, 1], [0.93, 1]);
331
+ const accentBarScale = spring2({
332
+ frame: frame - effectiveStart - 4,
333
+ fps,
334
+ config: SPRING_PRESETS2.smooth
335
+ });
336
+ const resolvedIcon = icon === void 0 ? /* @__PURE__ */ jsx2(Sparkles, { size: isVertical ? 30 : 34, color: "#FFFFFF", strokeWidth: 2.4 }) : icon;
337
+ return /* @__PURE__ */ jsxs2(
338
+ "div",
339
+ {
340
+ style: {
341
+ position: "relative",
342
+ display: "inline-flex",
343
+ alignItems: "center",
344
+ gap: isVertical ? 22 : 28,
345
+ maxWidth: isVertical && safeBox9x16 ? 830 : 1080,
346
+ padding: isVertical ? "28px 34px" : "32px 42px",
347
+ borderRadius: 26,
348
+ boxSizing: "border-box",
349
+ backgroundColor: isLight ? STUDIO_THEME2.colors.bgSurfaceGlass : "rgba(13, 15, 26, 0.86)",
350
+ border: isLight ? `1.5px solid ${STUDIO_THEME2.colors.border.subtle}` : `1.5px solid rgba(255, 255, 255, 0.14)`,
351
+ boxShadow: isLight ? STUDIO_THEME2.shadows.cardElevated : THEME2.shadows.glow(accentColor, 0.9),
352
+ backdropFilter: "blur(16px)",
353
+ opacity: visibility,
354
+ transform: `translate3d(0, ${translateY.toFixed(2)}px, 0) scale(${scale.toFixed(4)})`,
355
+ overflow: "hidden",
356
+ ...style
357
+ },
358
+ children: [
359
+ /* @__PURE__ */ jsx2(
360
+ "div",
361
+ {
362
+ style: {
363
+ position: "absolute",
364
+ left: 0,
365
+ top: 0,
366
+ bottom: 0,
367
+ width: 8,
368
+ backgroundColor: accentColor,
369
+ transformOrigin: "top center",
370
+ transform: `scaleY(${accentBarScale.toFixed(4)})`
371
+ }
372
+ }
373
+ ),
374
+ resolvedIcon && /* @__PURE__ */ jsx2(
375
+ "div",
376
+ {
377
+ style: {
378
+ width: isVertical ? 64 : 72,
379
+ height: isVertical ? 64 : 72,
380
+ borderRadius: 20,
381
+ flexShrink: 0,
382
+ display: "flex",
383
+ alignItems: "center",
384
+ justifyContent: "center",
385
+ background: `linear-gradient(135deg, ${accentColor} 0%, ${accentColor}CC 100%)`,
386
+ boxShadow: `0 12px 28px ${accentColor}55`
387
+ },
388
+ children: resolvedIcon
389
+ }
390
+ ),
391
+ /* @__PURE__ */ jsxs2(
392
+ "div",
393
+ {
394
+ style: {
395
+ display: "flex",
396
+ flexDirection: "column",
397
+ gap: 6,
398
+ minWidth: 0
399
+ },
400
+ children: [
401
+ (eyebrow || badge) && /* @__PURE__ */ jsxs2(
402
+ "div",
403
+ {
404
+ style: {
405
+ display: "flex",
406
+ alignItems: "center",
407
+ gap: 12,
408
+ flexWrap: "wrap"
409
+ },
410
+ children: [
411
+ eyebrow && /* @__PURE__ */ jsx2(
412
+ "span",
413
+ {
414
+ style: {
415
+ fontFamily: FONTS2.mono,
416
+ fontSize: 15,
417
+ fontWeight: 700,
418
+ letterSpacing: "0.08em",
419
+ textTransform: "uppercase",
420
+ color: isLight ? STUDIO_THEME2.colors.text.muted : THEME2.colors.textSecondary
421
+ },
422
+ children: eyebrow
423
+ }
424
+ ),
425
+ badge && /* @__PURE__ */ jsx2(
426
+ "span",
427
+ {
428
+ style: {
429
+ fontFamily: FONTS2.mono,
430
+ fontSize: 14,
431
+ fontWeight: 700,
432
+ padding: "3px 11px",
433
+ borderRadius: 9999,
434
+ color: "#FFFFFF",
435
+ backgroundColor: accentColor
436
+ },
437
+ children: badge
438
+ }
439
+ )
440
+ ]
441
+ }
442
+ ),
443
+ /* @__PURE__ */ jsx2(
444
+ "div",
445
+ {
446
+ style: {
447
+ fontFamily: FONTS2.display,
448
+ fontSize: isVertical ? 44 : 52,
449
+ fontWeight: 800,
450
+ lineHeight: 1.1,
451
+ letterSpacing: "-0.025em",
452
+ color: isLight ? STUDIO_THEME2.colors.text.slate900 : THEME2.colors.textPrimary
453
+ },
454
+ children: keyword
455
+ }
456
+ ),
457
+ sublabel && /* @__PURE__ */ jsx2(
458
+ "div",
459
+ {
460
+ style: {
461
+ fontFamily: FONTS2.sans,
462
+ fontSize: isVertical ? 20 : 22,
463
+ fontWeight: 500,
464
+ color: isLight ? STUDIO_THEME2.colors.text.secondary : THEME2.colors.textSecondary
465
+ },
466
+ children: sublabel
467
+ }
468
+ )
469
+ ]
470
+ }
471
+ )
472
+ ]
473
+ }
474
+ );
475
+ };
476
+
477
+ // packages/kits/src/kinetic-typography/AnimatedMetricCounter.tsx
478
+ import {
479
+ Easing,
480
+ interpolate as interpolate3,
481
+ spring as spring3,
482
+ useCurrentFrame as useCurrentFrame3,
483
+ useVideoConfig as useVideoConfig3
484
+ } from "remotion";
485
+ import {
486
+ FONTS as FONTS3,
487
+ SPRING_PRESETS as SPRING_PRESETS3,
488
+ STUDIO_THEME as STUDIO_THEME3,
489
+ THEME as THEME3,
490
+ useStudioFontsReady as useStudioFontsReady3
491
+ } from "@make-pictures/core";
492
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
493
+ function formatTabularMetric(val, decimals = 0, thousandSeparator = ",") {
494
+ const safeDecimals = Math.max(0, Math.min(6, Math.round(decimals)));
495
+ const fixed = val.toFixed(safeDecimals);
496
+ const [intPart, decPart] = fixed.split(".");
497
+ const sign = intPart.startsWith("-") ? "-" : "";
498
+ const digits = sign ? intPart.slice(1) : intPart;
499
+ const grouped = thousandSeparator ? digits.replace(/\B(?=(\d{3})+(?!\d))/g, thousandSeparator) : digits;
500
+ return decPart !== void 0 ? `${sign}${grouped}.${decPart}` : `${sign}${grouped}`;
501
+ }
502
+ var AnimatedMetricCounter = ({
503
+ value,
504
+ fromValue = 0,
505
+ prefix = "",
506
+ suffix = "",
507
+ decimals = 0,
508
+ startFrame = 2,
509
+ durationInFrames = 54,
510
+ label,
511
+ badge,
512
+ accentColor = STUDIO_THEME3.colors.accents.emerald,
513
+ variant = "dark",
514
+ thousandSeparator = ",",
515
+ fontSize,
516
+ style
517
+ }) => {
518
+ const frame = useCurrentFrame3();
519
+ const { fps, width, height } = useVideoConfig3();
520
+ useStudioFontsReady3();
521
+ const effectiveStart = Math.max(2, startFrame);
522
+ const dur = Math.max(6, durationInFrames);
523
+ const enter = spring3({
524
+ frame: frame - effectiveStart,
525
+ fps,
526
+ config: SPRING_PRESETS3.snappy
527
+ });
528
+ const countProgress = interpolate3(
529
+ frame,
530
+ [effectiveStart, effectiveStart + dur],
531
+ [0, 1],
532
+ {
533
+ easing: Easing.out(Easing.cubic),
534
+ extrapolateLeft: "clamp",
535
+ extrapolateRight: "clamp"
536
+ }
537
+ );
538
+ const currentVal = fromValue + (value - fromValue) * countProgress;
539
+ const formatted = `${prefix}${formatTabularMetric(
540
+ currentVal,
541
+ decimals,
542
+ thousandSeparator
543
+ )}${suffix}`;
544
+ if (variant === "inline") {
545
+ if (frame === 0 && startFrame >= 1) {
546
+ return null;
547
+ }
548
+ return /* @__PURE__ */ jsx3(
549
+ "span",
550
+ {
551
+ style: {
552
+ fontFamily: FONTS3.display,
553
+ fontVariantNumeric: "tabular-nums",
554
+ fontWeight: 800,
555
+ letterSpacing: "-0.03em",
556
+ fontSize: fontSize ?? 56,
557
+ opacity: enter,
558
+ ...style
559
+ },
560
+ children: formatted
561
+ }
562
+ );
563
+ }
564
+ if (enter <= 5e-3) {
565
+ return null;
566
+ }
567
+ const isVertical = height > width;
568
+ const isLight = variant === "light";
569
+ const resolvedFontSize = fontSize ?? (isVertical ? 68 : 76);
570
+ return /* @__PURE__ */ jsxs3(
571
+ "div",
572
+ {
573
+ style: {
574
+ display: "inline-flex",
575
+ flexDirection: "column",
576
+ alignItems: "flex-start",
577
+ gap: 10,
578
+ padding: isVertical ? "26px 32px" : "30px 38px",
579
+ borderRadius: 24,
580
+ boxSizing: "border-box",
581
+ backgroundColor: isLight ? STUDIO_THEME3.colors.bgSurfaceGlass : "rgba(13, 15, 26, 0.86)",
582
+ border: isLight ? `1.5px solid ${STUDIO_THEME3.colors.border.subtle}` : "1.5px solid rgba(255, 255, 255, 0.12)",
583
+ boxShadow: isLight ? STUDIO_THEME3.shadows.cardElevated : THEME3.shadows.glow(accentColor, 0.75),
584
+ opacity: enter,
585
+ transform: `translateY(${interpolate3(enter, [0, 1], [22, 0]).toFixed(2)}px) scale(${interpolate3(enter, [0, 1], [0.94, 1]).toFixed(4)})`,
586
+ ...style
587
+ },
588
+ children: [
589
+ /* @__PURE__ */ jsxs3(
590
+ "div",
591
+ {
592
+ style: {
593
+ display: "flex",
594
+ alignItems: "baseline",
595
+ gap: 14
596
+ },
597
+ children: [
598
+ /* @__PURE__ */ jsx3(
599
+ "span",
600
+ {
601
+ style: {
602
+ fontFamily: FONTS3.display,
603
+ fontVariantNumeric: "tabular-nums",
604
+ fontSize: resolvedFontSize,
605
+ fontWeight: 800,
606
+ lineHeight: 1,
607
+ letterSpacing: "-0.035em",
608
+ color: isLight ? STUDIO_THEME3.colors.text.slate900 : THEME3.colors.textPrimary
609
+ },
610
+ children: formatted
611
+ }
612
+ ),
613
+ badge && /* @__PURE__ */ jsx3(
614
+ "span",
615
+ {
616
+ style: {
617
+ fontFamily: FONTS3.mono,
618
+ fontSize: 15,
619
+ fontWeight: 700,
620
+ padding: "5px 12px",
621
+ borderRadius: 9999,
622
+ color: "#FFFFFF",
623
+ backgroundColor: accentColor
624
+ },
625
+ children: badge
626
+ }
627
+ )
628
+ ]
629
+ }
630
+ ),
631
+ label && /* @__PURE__ */ jsx3(
632
+ "div",
633
+ {
634
+ style: {
635
+ fontFamily: FONTS3.sans,
636
+ fontSize: isVertical ? 20 : 21,
637
+ fontWeight: 600,
638
+ color: isLight ? STUDIO_THEME3.colors.text.secondary : THEME3.colors.textSecondary
639
+ },
640
+ children: label
641
+ }
642
+ ),
643
+ /* @__PURE__ */ jsx3(
644
+ "div",
645
+ {
646
+ style: {
647
+ width: "100%",
648
+ height: 5,
649
+ marginTop: 4,
650
+ borderRadius: 9999,
651
+ backgroundColor: isLight ? "rgba(15, 23, 42, 0.08)" : "rgba(255, 255, 255, 0.12)",
652
+ overflow: "hidden"
653
+ },
654
+ children: /* @__PURE__ */ jsx3(
655
+ "div",
656
+ {
657
+ style: {
658
+ width: `${Math.round(countProgress * 100)}%`,
659
+ height: "100%",
660
+ borderRadius: 9999,
661
+ backgroundColor: accentColor
662
+ }
663
+ }
664
+ )
665
+ }
666
+ )
667
+ ]
668
+ }
669
+ );
670
+ };
671
+
672
+ export {
673
+ groupWordsIntoBeats,
674
+ WordByWordStage,
675
+ ImpactKeywordCard,
676
+ formatTabularMetric,
677
+ AnimatedMetricCounter
678
+ };