@bendyline/squisq 2.11.8 → 2.11.10

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 (31) hide show
  1. package/dist/{Doc-CynsGqL4.d.ts → Doc-BWtfp7dT.d.ts} +218 -3
  2. package/dist/{ImageEditDoc-BVZe_5y4.d.ts → ImageEditDoc-CYRQK8so.d.ts} +1 -1
  3. package/dist/{chunk-YNO6JV36.js → chunk-2SRF5III.js} +4 -4
  4. package/dist/{chunk-VU4T74LI.js → chunk-7WN7DDMI.js} +1 -1
  5. package/dist/{chunk-5FBI3ZW4.js → chunk-BVQZCKXN.js} +2 -2
  6. package/dist/{chunk-JM2QARY2.js → chunk-CPQCZUBW.js} +311 -19
  7. package/dist/{chunk-JJO6JFNZ.js → chunk-HVJ6VFDX.js} +2 -2
  8. package/dist/{chunk-Y4D6TDZT.js → chunk-PFH3WV6J.js} +145 -3
  9. package/dist/{chunk-ZEONJCP3.js → chunk-PWCIJHZH.js} +1 -1
  10. package/dist/{chunk-KFZOCWPK.js → chunk-RV5OOBSV.js} +2 -2
  11. package/dist/{chunk-RMCUZDW5.js → chunk-TYW5VMNP.js} +366 -111
  12. package/dist/doc/index.d.ts +108 -7
  13. package/dist/doc/index.js +31 -5
  14. package/dist/fence/index.d.ts +1 -1
  15. package/dist/generate/index.d.ts +1 -1
  16. package/dist/imageEdit/index.d.ts +3 -3
  17. package/dist/index.d.ts +5 -5
  18. package/dist/index.js +49 -9
  19. package/dist/jsonForm/index.d.ts +1 -1
  20. package/dist/jsonForm/index.js +4 -4
  21. package/dist/{materializePageSection-CAJoa8fh.d.ts → materializePageSection-Db7pABI0.d.ts} +6 -3
  22. package/dist/mediaEdit/index.d.ts +2 -2
  23. package/dist/narration/index.d.ts +1 -1
  24. package/dist/narration/index.js +4 -4
  25. package/dist/schemas/index.d.ts +4 -4
  26. package/dist/schemas/index.js +17 -3
  27. package/dist/{themeLibrary-z7t3x5CU.d.ts → themeLibrary-Bphz1CRp.d.ts} +1 -1
  28. package/dist/timing/index.d.ts +1 -1
  29. package/dist/transform/index.d.ts +2 -2
  30. package/dist/transform/index.js +3 -3
  31. package/package.json +1 -1
@@ -26,6 +26,7 @@ import {
26
26
  DEFAULT_THEME,
27
27
  FRONTMATTER_CUSTOM_TEMPLATES_KEY,
28
28
  FRONTMATTER_CUSTOM_THEMES_KEY,
29
+ MOTION_PROFILES,
29
30
  VIEWPORT_PRESETS,
30
31
  cloneData,
31
32
  createTemplateContext,
@@ -35,13 +36,14 @@ import {
35
36
  isTemplateBlock,
36
37
  oklchDarken,
37
38
  pickContrastingText,
39
+ readFrontmatterMotion,
38
40
  relativeLuminance,
39
41
  resolveFontFamily,
40
42
  scaledFontSize2 as scaledFontSize,
41
43
  validateCustomTemplateDefinition,
42
44
  validateTheme,
43
45
  withAlpha
44
- } from "./chunk-Y4D6TDZT.js";
46
+ } from "./chunk-PFH3WV6J.js";
45
47
  import {
46
48
  mediaEditValuesFromAttributes,
47
49
  mediaEditValuesFromParams,
@@ -461,6 +463,27 @@ function themedEntrance(context, layerType, fallback) {
461
463
  if (!type || type === "slowZoom" || type === fallback.type) return fallback;
462
464
  return { ...fallback, type };
463
465
  }
466
+ function getMotionProfile(context) {
467
+ return context.motion ?? MOTION_PROFILES.calm;
468
+ }
469
+ function motionEntrance(context, fallback, layerType = "text") {
470
+ const themed = themedEntrance(context, layerType, fallback);
471
+ const profile = getMotionProfile(context);
472
+ if (profile.name === "calm") return themed;
473
+ if (themed.type === "typewriter" || themed.type === "none") return themed;
474
+ const { textEntrance } = profile;
475
+ return {
476
+ ...themed,
477
+ type: textEntrance.type,
478
+ duration: textEntrance.duration,
479
+ ...themed.delay != null ? { delay: motionDelay(context, themed.delay) } : {},
480
+ ...textEntrance.easing ? { easing: textEntrance.easing } : {}
481
+ };
482
+ }
483
+ function motionDelay(context, delay) {
484
+ const scale = getMotionProfile(context).textEntrance.delayScale;
485
+ return scale === 1 ? delay : Number((delay * scale).toFixed(3));
486
+ }
464
487
  function shouldUseShadow(context) {
465
488
  return context.theme.style.textShadow ?? true;
466
489
  }
@@ -676,7 +699,7 @@ function titleBlock(input, context) {
676
699
  width: layout.maxTextWidth,
677
700
  height: `${px(titleH)}%`
678
701
  },
679
- animation: themedEntrance(context, "text", { type: "fadeIn", duration: 2 })
702
+ animation: motionEntrance(context, { type: "fadeIn", duration: 2 })
680
703
  }
681
704
  );
682
705
  if (subtitle && subtitleFit) {
@@ -1052,7 +1075,7 @@ function sectionHeader(input, context) {
1052
1075
  width: layout.maxTextWidth,
1053
1076
  height: `${px(titleH)}%`
1054
1077
  },
1055
- animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1.5 })
1078
+ animation: motionEntrance(context, { type: "fadeIn", duration: 1.5 })
1056
1079
  });
1057
1080
  return layers;
1058
1081
  }
@@ -1184,7 +1207,7 @@ function bigText(input, context) {
1184
1207
  width: `${BIG_TEXT_WIDTH_FRACTION * 100}%`,
1185
1208
  height: `${BIG_TEXT_HEIGHT_FRACTION * 100}%`
1186
1209
  },
1187
- animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1 })
1210
+ animation: motionEntrance(context, { type: "fadeIn", duration: 1 })
1188
1211
  });
1189
1212
  return layers;
1190
1213
  }
@@ -2215,6 +2238,59 @@ function contentBlock(input, context) {
2215
2238
  return layers;
2216
2239
  }
2217
2240
 
2241
+ // src/doc/utils/countUp.ts
2242
+ var STAT_PATTERN = /^([^\d]*?)([-−]?\d[\d,]*(?:\.\d+)?)(.*)$/s;
2243
+ function parseStatNumber(text) {
2244
+ const match = STAT_PATTERN.exec(text.trim());
2245
+ if (!match) return null;
2246
+ const [, prefix, numeric, suffix] = match;
2247
+ const normalized = numeric.replace(/,/g, "").replace("\u2212", "-");
2248
+ const value = Number(normalized);
2249
+ if (!Number.isFinite(value)) return null;
2250
+ const decimals = normalized.includes(".") ? normalized.split(".")[1].length : 0;
2251
+ const bare = /^-?\d{4}$/.test(normalized);
2252
+ const looksLikeYear = bare && Math.abs(value) >= 1e3 && Math.abs(value) <= 2999 && !/^[$€£¥]/.test(prefix);
2253
+ if (looksLikeYear && !/^[%KMB]/i.test(suffix.trim())) return null;
2254
+ return { prefix, value, decimals, grouping: numeric.includes(","), suffix };
2255
+ }
2256
+ function countUpSpecFor(text) {
2257
+ const parsed = parseStatNumber(text);
2258
+ if (!parsed) return null;
2259
+ return {
2260
+ from: 0,
2261
+ to: parsed.value,
2262
+ decimals: parsed.decimals,
2263
+ grouping: parsed.grouping,
2264
+ prefix: parsed.prefix,
2265
+ suffix: parsed.suffix
2266
+ };
2267
+ }
2268
+ function easeOutCubic(t) {
2269
+ const u = 1 - t;
2270
+ return 1 - u * u * u;
2271
+ }
2272
+ function formatCountValue(value, decimals, grouping) {
2273
+ const fixed = Math.abs(value).toFixed(decimals);
2274
+ const [whole, fraction] = fixed.split(".");
2275
+ const grouped = grouping ? whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : whole;
2276
+ const sign = value < 0 && Number(fixed) !== 0 ? "-" : "";
2277
+ return `${sign}${grouped}${fraction ? `.${fraction}` : ""}`;
2278
+ }
2279
+ function formatCountUp(text, animation, blockTime) {
2280
+ const count = animation.count ?? countUpSpecFor(text);
2281
+ if (!count) return text;
2282
+ const delay = animation.delay ?? 0;
2283
+ const duration = animation.duration ?? 1.5;
2284
+ const progress = duration <= 0 ? 1 : Math.min(1, Math.max(0, (blockTime - delay) / duration));
2285
+ if (progress >= 1) return animation.count ? formatFinal(count) : text;
2286
+ const from = count.from ?? 0;
2287
+ const value = from + (count.to - from) * easeOutCubic(progress);
2288
+ return `${count.prefix ?? ""}${formatCountValue(value, count.decimals ?? 0, count.grouping ?? false)}${count.suffix ?? ""}`;
2289
+ }
2290
+ function formatFinal(count) {
2291
+ return `${count.prefix ?? ""}${formatCountValue(count.to, count.decimals ?? 0, count.grouping ?? false)}${count.suffix ?? ""}`;
2292
+ }
2293
+
2218
2294
  // src/doc/templates/statHighlight.ts
2219
2295
  var SAFE_TOP = 0.08;
2220
2296
  var SAFE_BOTTOM = 0.92;
@@ -2235,6 +2311,10 @@ function statHighlight(input, context) {
2235
2311
  const descFontSize = themedFontSize(32, context, false);
2236
2312
  const detailFontSize = themedFontSize(26, context, false);
2237
2313
  const layers = [createBackgroundLayer("bg", themedSurfaceGradient(context, 180))];
2314
+ const motion = getMotionProfile(context);
2315
+ const countSpec = motion.countUp ? countUpSpecFor(stat) : null;
2316
+ const statEntrance = getTemplateHint(context, "statHighlight", "entrance", "subtle") === "dramatic" ? { type: "zoomIn", duration: 0.4 } : { type: "zoomIn", duration: 0.6 };
2317
+ const statAnimation = countSpec ? { type: "countUp", duration: motion.countUpDuration, delay: 0.15, count: countSpec } : statEntrance;
2238
2318
  if (accentImage) {
2239
2319
  layers.push(
2240
2320
  ...createAccentLayers(
@@ -2317,7 +2397,7 @@ function statHighlight(input, context) {
2317
2397
  }
2318
2398
  },
2319
2399
  position: box(statTop, statBoxH),
2320
- animation: getTemplateHint(context, "statHighlight", "entrance", "subtle") === "dramatic" ? { type: "zoomIn", duration: 0.4 } : { type: "zoomIn", duration: 0.6 }
2400
+ animation: statAnimation
2321
2401
  });
2322
2402
  if (descFit) {
2323
2403
  layers.push({
@@ -2337,7 +2417,7 @@ function statHighlight(input, context) {
2337
2417
  }
2338
2418
  },
2339
2419
  position: box(descTop, descBoxH),
2340
- animation: { type: "fadeIn", duration: 1, delay: 0.3 }
2420
+ animation: motionEntrance(context, { type: "fadeIn", duration: 1, delay: 0.3 })
2341
2421
  });
2342
2422
  }
2343
2423
  if (detailFit) {
@@ -2361,7 +2441,7 @@ function statHighlight(input, context) {
2361
2441
  }
2362
2442
  },
2363
2443
  position: box(detailTop, detailBoxH),
2364
- animation: { type: "fadeIn", duration: 1, delay: 1 }
2444
+ animation: motionEntrance(context, { type: "fadeIn", duration: 1, delay: 1 })
2365
2445
  });
2366
2446
  }
2367
2447
  return layers;
@@ -2524,7 +2604,7 @@ function quoteBlock(input, context) {
2524
2604
  height: pct(quoteFit.heightPx),
2525
2605
  anchor: "top-left"
2526
2606
  },
2527
- animation: themedEntrance(context, "text", { type: "fadeIn", duration: 2 })
2607
+ animation: motionEntrance(context, { type: "fadeIn", duration: 2 })
2528
2608
  };
2529
2609
  layers.push(quoteLayer);
2530
2610
  if (attribution && attrFit) {
@@ -2657,7 +2737,7 @@ function factCard(input, context) {
2657
2737
  }
2658
2738
  },
2659
2739
  position: box(factTop, factBoxH),
2660
- animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1.5 })
2740
+ animation: motionEntrance(context, { type: "fadeIn", duration: 1.5 })
2661
2741
  });
2662
2742
  if (explainFit) {
2663
2743
  layers.push({
@@ -2677,7 +2757,7 @@ function factCard(input, context) {
2677
2757
  }
2678
2758
  },
2679
2759
  position: box(explainTop, explainBoxH),
2680
- animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1, delay: 0.8 })
2760
+ animation: motionEntrance(context, { type: "fadeIn", duration: 1, delay: 0.8 })
2681
2761
  });
2682
2762
  }
2683
2763
  if (sourceFit) {
@@ -2791,7 +2871,7 @@ function twoColumn(input, context) {
2791
2871
  width: `${bandWidthPct}%`,
2792
2872
  anchor: "top-left"
2793
2873
  },
2794
- animation: themedEntrance(context, "text", { type: "fadeIn", duration: 0.8 })
2874
+ animation: motionEntrance(context, { type: "fadeIn", duration: 0.8 })
2795
2875
  });
2796
2876
  }
2797
2877
  let leftY;
@@ -2830,7 +2910,7 @@ function twoColumn(input, context) {
2830
2910
  anchor: "center",
2831
2911
  width: columnWidth
2832
2912
  },
2833
- animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1 })
2913
+ animation: motionEntrance(context, { type: "fadeIn", duration: 1 })
2834
2914
  });
2835
2915
  if (left.sublabel) {
2836
2916
  layers.push({
@@ -2898,7 +2978,7 @@ function twoColumn(input, context) {
2898
2978
  anchor: "center",
2899
2979
  width: columnWidth
2900
2980
  },
2901
- animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1, delay: 0.5 })
2981
+ animation: motionEntrance(context, { type: "fadeIn", duration: 1, delay: 0.5 })
2902
2982
  });
2903
2983
  if (right.sublabel) {
2904
2984
  layers.push({
@@ -3156,7 +3236,7 @@ function imageWithCaption(input, context) {
3156
3236
  anchor: "center",
3157
3237
  width: layout.maxTextWidth
3158
3238
  },
3159
- animation: { type: "fadeIn", duration: 0.8 }
3239
+ animation: motionEntrance(context, { type: "fadeIn", duration: 0.8 })
3160
3240
  });
3161
3241
  if (subtitle) {
3162
3242
  layers.push({
@@ -3178,7 +3258,7 @@ function imageWithCaption(input, context) {
3178
3258
  anchor: "center",
3179
3259
  width: layout.maxTextWidth
3180
3260
  },
3181
- animation: { type: "fadeIn", duration: 0.8, delay: 0.2 }
3261
+ animation: motionEntrance(context, { type: "fadeIn", duration: 0.8, delay: 0.2 })
3182
3262
  });
3183
3263
  }
3184
3264
  return layers;
@@ -3212,7 +3292,10 @@ function imageWithCaption(input, context) {
3212
3292
  textAlign: "center",
3213
3293
  shadow: shouldUseShadow(context),
3214
3294
  lineHeight: 1.18,
3215
- maxLines: 2
3295
+ maxLines: 2,
3296
+ // A two-line caption centres on the band instead of hanging its
3297
+ // second line below it (the block centres on the anchor).
3298
+ shrinkToFit: true
3216
3299
  }
3217
3300
  },
3218
3301
  position: {
@@ -3221,7 +3304,7 @@ function imageWithCaption(input, context) {
3221
3304
  anchor: "center",
3222
3305
  width: "78%"
3223
3306
  },
3224
- animation: { type: "fadeIn", duration: 1.5, delay: 0.5 }
3307
+ animation: motionEntrance(context, { type: "fadeIn", duration: 1.5, delay: 0.5 })
3225
3308
  });
3226
3309
  }
3227
3310
  return layers;
@@ -3409,7 +3492,7 @@ function buildFeatureLayers(input, context, side) {
3409
3492
  anchor: textAnchor,
3410
3493
  width: pctW(titleWrapWidth)
3411
3494
  },
3412
- animation: themedEntrance(context, "text", { type: "fadeIn", duration: 0.8 })
3495
+ animation: motionEntrance(context, { type: "fadeIn", duration: 0.8 })
3413
3496
  });
3414
3497
  }
3415
3498
  if (body && bodyFit) {
@@ -3433,7 +3516,7 @@ function buildFeatureLayers(input, context, side) {
3433
3516
  anchor: textAnchor,
3434
3517
  width: pctW(bodyWrapWidth)
3435
3518
  },
3436
- animation: themedEntrance(context, "text", { type: "fadeIn", duration: 0.8, delay: 0.2 })
3519
+ animation: motionEntrance(context, { type: "fadeIn", duration: 0.8, delay: 0.2 })
3437
3520
  });
3438
3521
  }
3439
3522
  return layers;
@@ -3626,7 +3709,10 @@ function fullBleedQuote(input, context) {
3626
3709
  height: pct(fit.heightPx),
3627
3710
  anchor: "top-left"
3628
3711
  },
3629
- animation: getTemplateHint(context, "fullBleedQuote", "entrance", "subtle") === "dramatic" ? { type: "zoomIn", duration: 0.8 } : { type: "fadeIn", duration: 1.5 }
3712
+ animation: motionEntrance(
3713
+ context,
3714
+ getTemplateHint(context, "fullBleedQuote", "entrance", "subtle") === "dramatic" ? { type: "zoomIn", duration: 0.8 } : { type: "fadeIn", duration: 1.5 }
3715
+ )
3630
3716
  }
3631
3717
  ];
3632
3718
  }
@@ -3741,7 +3827,7 @@ function listBlock(input, context) {
3741
3827
  y: adjustY(`${titleTopPct}%`, accentLayout),
3742
3828
  width: accentLayout.textWidth
3743
3829
  },
3744
- animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1 })
3830
+ animation: motionEntrance(context, { type: "fadeIn", duration: 1 })
3745
3831
  });
3746
3832
  }
3747
3833
  const availablePx = Math.max(0, (bottomLimitPct - startY) / 100 * context.viewport.height);
@@ -3758,7 +3844,7 @@ function listBlock(input, context) {
3758
3844
  const itemText = items[i];
3759
3845
  const itemHtml = richItems[i]?.text === itemText ? richItems[i]?.html : void 0;
3760
3846
  const lineCount = estimateProseLineCount(itemText, itemFontSize, bodyWidthPx);
3761
- const animation = themedEntrance(context, "text", {
3847
+ const animation = motionEntrance(context, {
3762
3848
  type: "fadeIn",
3763
3849
  duration: 0.8,
3764
3850
  delay: 0.3 + 0.3 * i
@@ -3825,6 +3911,7 @@ function photoGrid(input, context) {
3825
3911
  const caption = rawCaption ? cleanCaption(rawCaption) : rawCaption;
3826
3912
  const { theme, layout } = context;
3827
3913
  const treatment = themedImageTreatment(context, input.imageTreatment);
3914
+ const stagger = getMotionProfile(context).photoGridStagger;
3828
3915
  const layers = [
3829
3916
  // Theme surface background (visible in gaps between images)
3830
3917
  {
@@ -3863,7 +3950,12 @@ function photoGrid(input, context) {
3863
3950
  height: `${pos.h}%`
3864
3951
  },
3865
3952
  // Apply ambient motion to the first (largest) image only
3866
- animation: i === 0 && ambientMotion ? { type: ambientMotion, duration: 15 } : { type: "fadeIn", duration: 1, delay: 0.2 * i }
3953
+ animation: i === 0 && ambientMotion ? { type: ambientMotion, duration: 15 } : stagger > 0 ? {
3954
+ type: "reveal",
3955
+ origin: pos.x < 50 ? "left" : "right",
3956
+ duration: 0.7,
3957
+ delay: Number((0.15 + stagger * i).toFixed(3))
3958
+ } : { type: "fadeIn", duration: 1, delay: 0.2 * i }
3867
3959
  });
3868
3960
  }
3869
3961
  if (caption) {
@@ -3893,7 +3985,8 @@ function photoGrid(input, context) {
3893
3985
  textAlign: "center",
3894
3986
  shadow: true,
3895
3987
  lineHeight: 1.18,
3896
- maxLines: layout.stackColumns ? 2 : 1
3988
+ maxLines: layout.stackColumns ? 2 : 1,
3989
+ shrinkToFit: true
3897
3990
  }
3898
3991
  },
3899
3992
  position: {
@@ -3902,7 +3995,7 @@ function photoGrid(input, context) {
3902
3995
  anchor: "center",
3903
3996
  width: "78%"
3904
3997
  },
3905
- animation: { type: "fadeIn", duration: 1, delay: 0.5 }
3998
+ animation: motionEntrance(context, { type: "fadeIn", duration: 1, delay: 0.5 })
3906
3999
  });
3907
4000
  }
3908
4001
  return layers;
@@ -4051,7 +4144,7 @@ function definitionCard(input, context) {
4051
4144
  }
4052
4145
  },
4053
4146
  position: box(termTop, termH + termFit.fontSize * BOX_SLACK_EM2),
4054
- animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1.5 })
4147
+ animation: motionEntrance(context, { type: "fadeIn", duration: 1.5 })
4055
4148
  });
4056
4149
  layers.push({
4057
4150
  type: "shape",
@@ -4088,7 +4181,7 @@ function definitionCard(input, context) {
4088
4181
  }
4089
4182
  },
4090
4183
  position: box(defTop, defH + defFit.fontSize * BOX_SLACK_EM2),
4091
- animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1, delay: 0.8 })
4184
+ animation: motionEntrance(context, { type: "fadeIn", duration: 1, delay: 0.8 })
4092
4185
  });
4093
4186
  }
4094
4187
  if (originText) {
@@ -4122,8 +4215,8 @@ function comparisonBar(input, context) {
4122
4215
  const colors = resolveColorScheme(context, colorScheme);
4123
4216
  const leftValue = Number.isFinite(input.leftValue) ? input.leftValue : 0;
4124
4217
  const rightValue = Number.isFinite(input.rightValue) ? input.rightValue : 0;
4125
- const labelFontSize = themedFontSize(28, context, false);
4126
- const valueFontSize = themedFontSize(48, context, true);
4218
+ const labelFontSize = themedFontSize(34, context, false);
4219
+ const valueFontSize = themedFontSize(58, context, true);
4127
4220
  const formatValue = (v) => {
4128
4221
  if (!Number.isFinite(v)) return "\u2014";
4129
4222
  if (v >= 1e6) return `${(v / 1e6).toFixed(1)}M`;
@@ -4132,16 +4225,40 @@ function comparisonBar(input, context) {
4132
4225
  };
4133
4226
  const leftDisplay = unit ? `${formatValue(leftValue)} ${unit}` : formatValue(leftValue);
4134
4227
  const rightDisplay = unit ? `${formatValue(rightValue)} ${unit}` : formatValue(rightValue);
4135
- const barStartX = 15;
4228
+ const motion = getMotionProfile(context);
4229
+ const growEasing = motion.textEntrance.easing;
4230
+ const barAnimation = (fallback) => {
4231
+ if (!motion.chartGrowth) return fallback;
4232
+ return {
4233
+ type: "grow",
4234
+ origin: "left",
4235
+ duration: motion.chartGrowthDuration,
4236
+ delay: motionDelay(context, fallback.delay ?? 0),
4237
+ ...growEasing ? { easing: growEasing } : {}
4238
+ };
4239
+ };
4240
+ const valueAnimation = (display, fallback) => {
4241
+ const count = motion.countUp ? countUpSpecFor(display) : null;
4242
+ if (!count) return motionEntrance(context, fallback);
4243
+ return {
4244
+ type: "countUp",
4245
+ duration: motion.countUpDuration,
4246
+ delay: motionDelay(context, fallback.delay ?? 0),
4247
+ count
4248
+ };
4249
+ };
4250
+ const labelAnimation = motion.name === "calm" ? void 0 : motionEntrance(context, { type: "fadeIn", duration: 0.6 });
4136
4251
  const barHeight = 6;
4137
- const topBarY = 36;
4138
- const bottomBarY = 58;
4252
+ const topBarY = 39;
4253
+ const bottomBarY = 61;
4139
4254
  const leftExtent = Math.max(0, leftValue);
4140
4255
  const rightExtent = Math.max(0, rightValue);
4141
4256
  const maxValue = Math.max(leftExtent, rightExtent, 1);
4142
- const longestLabelPx = Math.max(leftDisplay.length, rightDisplay.length) * valueFontSize * 0.58;
4257
+ const longestLabelPx = Math.max(leftDisplay.length, rightDisplay.length) * valueFontSize * 0.7;
4143
4258
  const labelWidthPct = longestLabelPx / viewport.width * 100;
4144
- const maxBarWidth = Math.max(20, Math.min(65, 96 - barStartX - labelWidthPct - 2));
4259
+ const marginX = 8;
4260
+ const maxBarWidth = Math.max(20, Math.min(65, 100 - marginX * 2 - labelWidthPct - 2));
4261
+ const barStartX = Math.max(marginX, (100 - (maxBarWidth + 2 + labelWidthPct)) / 2);
4145
4262
  const leftBarWidth = leftExtent / maxValue * maxBarWidth;
4146
4263
  const rightBarWidth = rightExtent / maxValue * maxBarWidth;
4147
4264
  return [
@@ -4167,7 +4284,8 @@ function comparisonBar(input, context) {
4167
4284
  color: theme.colors.textMuted
4168
4285
  }
4169
4286
  },
4170
- position: { x: `${barStartX}%`, y: `${topBarY - 8}%` }
4287
+ position: { x: `${barStartX}%`, y: `${topBarY - 8}%` },
4288
+ ...labelAnimation ? { animation: labelAnimation } : {}
4171
4289
  },
4172
4290
  // Top bar value
4173
4291
  {
@@ -4183,7 +4301,7 @@ function comparisonBar(input, context) {
4183
4301
  }
4184
4302
  },
4185
4303
  position: { x: `${barStartX + leftBarWidth + 2}%`, y: `${topBarY - 1}%` },
4186
- animation: { type: "fadeIn", duration: 0.8, delay: 0.3 }
4304
+ animation: valueAnimation(leftDisplay, { type: "fadeIn", duration: 0.8, delay: 0.3 })
4187
4305
  },
4188
4306
  // Top bar
4189
4307
  {
@@ -4200,7 +4318,7 @@ function comparisonBar(input, context) {
4200
4318
  width: `${leftBarWidth}%`,
4201
4319
  height: `${barHeight}%`
4202
4320
  },
4203
- animation: { type: "fadeIn", duration: 1 }
4321
+ animation: barAnimation({ type: "fadeIn", duration: 1 })
4204
4322
  },
4205
4323
  // Bottom bar label
4206
4324
  {
@@ -4214,7 +4332,8 @@ function comparisonBar(input, context) {
4214
4332
  color: theme.colors.textMuted
4215
4333
  }
4216
4334
  },
4217
- position: { x: `${barStartX}%`, y: `${bottomBarY - 8}%` }
4335
+ position: { x: `${barStartX}%`, y: `${bottomBarY - 8}%` },
4336
+ ...labelAnimation ? { animation: labelAnimation } : {}
4218
4337
  },
4219
4338
  // Bottom bar value
4220
4339
  {
@@ -4230,7 +4349,7 @@ function comparisonBar(input, context) {
4230
4349
  }
4231
4350
  },
4232
4351
  position: { x: `${barStartX + rightBarWidth + 2}%`, y: `${bottomBarY - 1}%` },
4233
- animation: { type: "fadeIn", duration: 0.8, delay: 0.6 }
4352
+ animation: valueAnimation(rightDisplay, { type: "fadeIn", duration: 0.8, delay: 0.6 })
4234
4353
  },
4235
4354
  // Bottom bar
4236
4355
  {
@@ -4247,7 +4366,7 @@ function comparisonBar(input, context) {
4247
4366
  width: `${rightBarWidth}%`,
4248
4367
  height: `${barHeight}%`
4249
4368
  },
4250
- animation: { type: "fadeIn", duration: 1, delay: 0.3 }
4369
+ animation: barAnimation({ type: "fadeIn", duration: 1, delay: 0.3 })
4251
4370
  }
4252
4371
  ];
4253
4372
  }
@@ -4328,7 +4447,7 @@ function pullQuoteLockup(options, context) {
4328
4447
  anchor: "top-left",
4329
4448
  width: `${COLUMN_WIDTH_PCT}%`
4330
4449
  },
4331
- animation: themedEntrance(context, "text", { type: "fadeIn", duration: 2 })
4450
+ animation: motionEntrance(context, { type: "fadeIn", duration: 2 })
4332
4451
  };
4333
4452
  const layers = [];
4334
4453
  if (showDeco) {
@@ -4373,7 +4492,7 @@ function pullQuoteLockup(options, context) {
4373
4492
  anchor: "top-left",
4374
4493
  width: `${COLUMN_WIDTH_PCT}%`
4375
4494
  },
4376
- animation: { type: "fadeIn", duration: 1, delay: 1.5 }
4495
+ animation: motionEntrance(context, { type: "fadeIn", duration: 1, delay: 1.5 })
4377
4496
  });
4378
4497
  }
4379
4498
  return layers;
@@ -5478,7 +5597,7 @@ var PADDING = 80;
5478
5597
  var CANVAS_PADDING = PADDING;
5479
5598
  var CANVAS_TITLE_LINE_HEIGHT = 1.15;
5480
5599
  var CANVAS_TITLE_MAX_LINES = 2;
5481
- var CANVAS_TITLE_GAP_PX = 36;
5600
+ var CANVAS_TITLE_GAP_PX = 60;
5482
5601
  var CANVAS_DESCRIPTION_LINE_HEIGHT = 1.35;
5483
5602
  var CANVAS_DESCRIPTION_MAX_LINES = 3;
5484
5603
  var CANVAS_DESCRIPTION_GAP_PX = 28;
@@ -5782,6 +5901,16 @@ function diagramBlock(input, context) {
5782
5901
  }
5783
5902
  const containers = resolved.nodes.filter((n) => n.isContainer).sort((a, b) => a.depth - b.depth || a.y - b.y || a.x - b.x);
5784
5903
  const leaves = resolved.nodes.filter((n) => !n.isContainer);
5904
+ const build = getMotionProfile(context).diagramBuild;
5905
+ const leafOrder = /* @__PURE__ */ new Map();
5906
+ [...leaves].sort((a, b) => a.y - b.y || a.x - b.x).forEach((node, index) => leafOrder.set(node.id, index));
5907
+ const containerDepth = new Map(containers.map((node) => [node.id, node.depth]));
5908
+ const nodeDelay = (id) => {
5909
+ const order = leafOrder.get(id);
5910
+ if (order !== void 0) return Number(Math.min(2, 0.2 + order * 0.12).toFixed(3));
5911
+ return Number((0.05 + (containerDepth.get(id) ?? 0) * 0.08).toFixed(3));
5912
+ };
5913
+ const at = (animation) => build ? { animation } : {};
5785
5914
  for (const node of containers) {
5786
5915
  const t = transform(node);
5787
5916
  const card = {
@@ -5795,7 +5924,8 @@ function diagramBlock(input, context) {
5795
5924
  strokeWidth: Math.max(1, strokeW - 1),
5796
5925
  borderRadius: 10
5797
5926
  },
5798
- position: { x: t.x, y: t.y, width: t.w, height: t.h }
5927
+ position: { x: t.x, y: t.y, width: t.w, height: t.h },
5928
+ ...at({ type: "fadeIn", duration: 0.5, delay: nodeDelay(node.id) })
5799
5929
  };
5800
5930
  layers.push(card);
5801
5931
  }
@@ -5812,6 +5942,9 @@ function diagramBlock(input, context) {
5812
5942
  const b = positions.get(edge.target);
5813
5943
  if (!a || !b) continue;
5814
5944
  const endMarker = edge.directed ? configuredEnd : "none";
5945
+ const edgeDelay = Number(
5946
+ (Math.max(nodeDelay(edge.source), nodeDelay(edge.target)) + 0.3).toFixed(3)
5947
+ );
5815
5948
  const snapped = snapEndpoints(a, b);
5816
5949
  const start = edge.sourceAnchor ? anchorPoint(a, edge.sourceAnchor) : snapped.start;
5817
5950
  const end = edge.targetAnchor ? anchorPoint(b, edge.targetAnchor) : snapped.end;
@@ -5827,7 +5960,8 @@ function diagramBlock(input, context) {
5827
5960
  ...startMarker !== "none" ? { startMarker } : {},
5828
5961
  ...endMarker !== "none" ? { endMarker } : {}
5829
5962
  },
5830
- position: { x: 0, y: 0, width: "100%", height: "100%" }
5963
+ position: { x: 0, y: 0, width: "100%", height: "100%" },
5964
+ ...at({ type: "drawOn", duration: 0.6, delay: edgeDelay })
5831
5965
  };
5832
5966
  layers.push(pathLayer);
5833
5967
  if (edge.label) {
@@ -5846,7 +5980,8 @@ function diagramBlock(input, context) {
5846
5980
  textAlign: "center"
5847
5981
  }
5848
5982
  },
5849
- position: { x: mx, y: my - labelFontSize * 0.9, anchor: "center" }
5983
+ position: { x: mx, y: my - labelFontSize * 0.9, anchor: "center" },
5984
+ ...at({ type: "fadeIn", duration: 0.4, delay: Number((edgeDelay + 0.5).toFixed(3)) })
5850
5985
  };
5851
5986
  layers.push(labelLayer);
5852
5987
  }
@@ -5863,7 +5998,8 @@ function diagramBlock(input, context) {
5863
5998
  strokeWidth: strokeW,
5864
5999
  borderRadius: input.nodeShape === "pill" ? t.h / 2 : 10
5865
6000
  },
5866
- position: { x: t.x, y: t.y, width: t.w, height: t.h }
6001
+ position: { x: t.x, y: t.y, width: t.w, height: t.h },
6002
+ ...at({ type: "grow", origin: "center", duration: 0.45, delay: nodeDelay(node.id) })
5867
6003
  };
5868
6004
  layers.push(card);
5869
6005
  const preferredFontSize = themedFontSize(Math.round(22 * fontAdj), context, false);
@@ -5887,7 +6023,12 @@ function diagramBlock(input, context) {
5887
6023
  y: t.y + t.h / 2 + fit.firstLineOffset,
5888
6024
  anchor: "center",
5889
6025
  width: fit.textWidth
5890
- }
6026
+ },
6027
+ ...at({
6028
+ type: "fadeIn",
6029
+ duration: 0.3,
6030
+ delay: Number((nodeDelay(node.id) + 0.15).toFixed(3))
6031
+ })
5891
6032
  };
5892
6033
  layers.push(label);
5893
6034
  }
@@ -5907,7 +6048,8 @@ function diagramBlock(input, context) {
5907
6048
  textAlign: "center"
5908
6049
  }
5909
6050
  },
5910
- position: { x: t.x + t.w / 2, y: t.y + fontSize, anchor: "center", width: t.w }
6051
+ position: { x: t.x + t.w / 2, y: t.y + fontSize, anchor: "center", width: t.w },
6052
+ ...at({ type: "fadeIn", duration: 0.4, delay: nodeDelay(node.id) })
5911
6053
  };
5912
6054
  layers.push(label);
5913
6055
  }
@@ -6073,7 +6215,7 @@ function coerceLinks(raw, eventIds) {
6073
6215
  }
6074
6216
  return links;
6075
6217
  }
6076
- function diamondLayer(id, x, y, radius, color, delay) {
6218
+ function diamondLayer(id, x, y, radius, color, delay, build) {
6077
6219
  return {
6078
6220
  type: "path",
6079
6221
  id,
@@ -6084,7 +6226,7 @@ function diamondLayer(id, x, y, radius, color, delay) {
6084
6226
  strokeWidth: 2
6085
6227
  },
6086
6228
  position: { x: x - radius, y: y - radius, width: radius * 2, height: radius * 2 },
6087
- animation: { type: "zoomIn", duration: 0.35, delay }
6229
+ animation: build ? { type: "grow", origin: "center", duration: 0.35, delay } : { type: "zoomIn", duration: 0.35, delay }
6088
6230
  };
6089
6231
  }
6090
6232
  function branchPath(source, target, bend) {
@@ -6102,10 +6244,22 @@ function timelineBlock(input, context) {
6102
6244
  const colors = resolveColorScheme(context, input.colorScheme);
6103
6245
  const tracks = coerceTracks(input.tracks);
6104
6246
  const layers = [createBackgroundLayer("bg", themedSurfaceGradient(context, 165))];
6105
- const titleFontSize = themedFontSize(44, context, true);
6106
- const trackFontSize = themedFontSize(22, context, false);
6107
- const baseEventFontSize = themedFontSize(24, context, false);
6108
- const baseDescriptionFontSize = themedFontSize(17, context, false);
6247
+ const build = getMotionProfile(context).diagramBuild;
6248
+ const strokeIn = (duration, delay, fallbackDuration) => build ? { type: "drawOn", duration, delay } : { type: "fadeIn", duration: fallbackDuration, delay };
6249
+ const titleFontSize = themedFontSize(52, context, true);
6250
+ const trackFontSize = themedFontSize(26, context, false);
6251
+ const baseEventFontSize = themedFontSize(28, context, false);
6252
+ const baseDescriptionFontSize = themedFontSize(20, context, false);
6253
+ const trackCount = Math.max(1, tracks.length);
6254
+ const viewportScale = Math.min(viewport.width, viewport.height) / 1080;
6255
+ const calloutExtent = 78 + Math.min(baseEventFontSize, 28 * viewportScale) * 1.4 + Math.min(baseDescriptionFontSize, 20 * viewportScale) * 1.4 * 2 + 28;
6256
+ const naturalBand = calloutExtent * 2 + 24;
6257
+ const titleBlock2 = input.title ? titleFontSize * 1.3 + 40 : 0;
6258
+ const maxTracksHeight = viewport.height * 0.82 - titleBlock2;
6259
+ const trackBand = Math.max(120, Math.min(naturalBand, maxTracksHeight / trackCount));
6260
+ const groupHeight = titleBlock2 + trackBand * trackCount;
6261
+ const groupTop = Math.max(viewport.height * 0.06, (viewport.height - groupHeight) / 2);
6262
+ const contentTop = groupTop + titleBlock2;
6109
6263
  if (input.title) {
6110
6264
  layers.push({
6111
6265
  type: "text",
@@ -6121,8 +6275,8 @@ function timelineBlock(input, context) {
6121
6275
  shadow: shouldUseShadow(context)
6122
6276
  }
6123
6277
  },
6124
- position: { x: "50%", y: Math.max(50, titleFontSize * 1.2), width: "82%", anchor: "center" },
6125
- animation: { type: "fadeIn", duration: 0.7 }
6278
+ position: { x: "50%", y: groupTop + titleFontSize * 0.65, width: "82%", anchor: "center" },
6279
+ animation: motionEntrance(context, { type: "fadeIn", duration: 0.7 })
6126
6280
  });
6127
6281
  }
6128
6282
  if (tracks.length === 0) {
@@ -6148,10 +6302,6 @@ function timelineBlock(input, context) {
6148
6302
  const axisStartX = outerMargin + labelWidth;
6149
6303
  const axisEndX = viewport.width - outerMargin;
6150
6304
  const axisWidth = Math.max(1, axisEndX - axisStartX);
6151
- const contentTop = input.title ? Math.max(titleFontSize * 2.2, viewport.height * 0.2) : viewport.height * 0.09;
6152
- const contentBottom = viewport.height * 0.91;
6153
- const availableHeight = Math.max(1, contentBottom - contentTop);
6154
- const trackBand = availableHeight / tracks.length;
6155
6305
  const markerRadius = Math.max(6, Math.min(11, Math.min(viewport.width, viewport.height) / 105));
6156
6306
  const trackColor = withAlpha(colors.text ?? theme.colors.primary, 0.72);
6157
6307
  const accentColor = colors.accent ?? theme.colors.primary;
@@ -6166,8 +6316,8 @@ function timelineBlock(input, context) {
6166
6316
  Math.min(330, axisWidth / Math.min(maxCalloutEvents, 4) * 0.86)
6167
6317
  );
6168
6318
  const minViewportScale = Math.min(viewport.width, viewport.height) / 1080;
6169
- const eventFontSize = Math.min(baseEventFontSize, 24 * minViewportScale);
6170
- const descriptionFontSize = Math.min(baseDescriptionFontSize, 17 * minViewportScale);
6319
+ const eventFontSize = Math.min(baseEventFontSize, 28 * minViewportScale);
6320
+ const descriptionFontSize = Math.min(baseDescriptionFontSize, 20 * minViewportScale);
6171
6321
  const trackMarkerRadii = tracks.map((track) => {
6172
6322
  const positions = track.events.map((event) => clamp012(event.position)).sort((a, b) => a - b);
6173
6323
  let minGapPx = Number.POSITIVE_INFINITY;
@@ -6193,7 +6343,7 @@ function timelineBlock(input, context) {
6193
6343
  endMarker: "arrow"
6194
6344
  },
6195
6345
  position: { x: axisStartX, y: y - markerRadius, width: axisWidth, height: markerRadius * 2 },
6196
- animation: { type: "fadeIn", duration: 0.65, delay: trackDelay }
6346
+ animation: strokeIn(0.9, trackDelay, 0.65)
6197
6347
  };
6198
6348
  layers.push(line);
6199
6349
  if (track.label) {
@@ -6216,7 +6366,7 @@ function timelineBlock(input, context) {
6216
6366
  width: Math.max(1, labelWidth - 32),
6217
6367
  anchor: "center"
6218
6368
  },
6219
- animation: { type: "fadeIn", duration: 0.5, delay: trackDelay }
6369
+ animation: motionEntrance(context, { type: "fadeIn", duration: 0.5, delay: trackDelay })
6220
6370
  };
6221
6371
  layers.push(label);
6222
6372
  }
@@ -6240,7 +6390,7 @@ function timelineBlock(input, context) {
6240
6390
  width: Math.max(48, outerMargin - markerRadius * 2),
6241
6391
  anchor: "top-left"
6242
6392
  },
6243
- animation: { type: "fadeIn", duration: 0.5, delay: trackDelay }
6393
+ animation: motionEntrance(context, { type: "fadeIn", duration: 0.5, delay: trackDelay })
6244
6394
  });
6245
6395
  }
6246
6396
  track.events.forEach((event, eventIndex) => {
@@ -6272,7 +6422,7 @@ function timelineBlock(input, context) {
6272
6422
  endMarker: "arrow"
6273
6423
  },
6274
6424
  position: { x: 0, y: 0, width: "100%", height: "100%" },
6275
- animation: { type: "fadeIn", duration: 0.6, delay: 0.45 + linkIndex * 0.08 }
6425
+ animation: strokeIn(0.6, 0.45 + linkIndex * 0.08, 0.6)
6276
6426
  });
6277
6427
  if (link.label) {
6278
6428
  const sameTrack = source.trackIndex === target.trackIndex;
@@ -6330,7 +6480,11 @@ function timelineBlock(input, context) {
6330
6480
  height: linkLabelHeight,
6331
6481
  anchor: "center"
6332
6482
  },
6333
- animation: { type: "fadeIn", duration: 0.5, delay: 0.55 + linkIndex * 0.08 }
6483
+ animation: motionEntrance(context, {
6484
+ type: "fadeIn",
6485
+ duration: 0.5,
6486
+ delay: 0.55 + linkIndex * 0.08
6487
+ })
6334
6488
  });
6335
6489
  }
6336
6490
  });
@@ -6360,7 +6514,7 @@ function timelineBlock(input, context) {
6360
6514
  width: Math.max(2, Math.abs(calloutX - x)),
6361
6515
  height: Math.max(2, Math.abs(endY - y))
6362
6516
  },
6363
- animation: { type: "fadeIn", duration: 0.4, delay: stemDelay }
6517
+ animation: strokeIn(0.35, stemDelay, 0.4)
6364
6518
  });
6365
6519
  };
6366
6520
  if (event.callout !== false) {
@@ -6384,7 +6538,8 @@ function timelineBlock(input, context) {
6384
6538
  y,
6385
6539
  pointMarkerRadius,
6386
6540
  accentColor,
6387
- delay
6541
+ delay,
6542
+ build
6388
6543
  )
6389
6544
  );
6390
6545
  } else {
@@ -6403,7 +6558,7 @@ function timelineBlock(input, context) {
6403
6558
  width: pointMarkerRadius * 2,
6404
6559
  height: pointMarkerRadius * 2
6405
6560
  },
6406
- animation: { type: "zoomIn", duration: 0.35, delay }
6561
+ animation: build ? { type: "grow", origin: "center", duration: 0.35, delay } : { type: "zoomIn", duration: 0.35, delay }
6407
6562
  };
6408
6563
  layers.push(dot);
6409
6564
  }
@@ -6437,7 +6592,7 @@ function timelineBlock(input, context) {
6437
6592
  }
6438
6593
  },
6439
6594
  position: { x: calloutX, y: labelY, width: calloutWidth, anchor: "center" },
6440
- animation: { type: "fadeIn", duration: 0.5, delay: delay + 0.08 }
6595
+ animation: motionEntrance(context, { type: "fadeIn", duration: 0.5, delay: delay + 0.08 })
6441
6596
  });
6442
6597
  if (event.description) {
6443
6598
  const descriptionSide = event.descriptionSide ?? side;
@@ -6486,7 +6641,11 @@ function timelineBlock(input, context) {
6486
6641
  }
6487
6642
  },
6488
6643
  position: { x: calloutX, y: descriptionY, width: calloutWidth, anchor: "center" },
6489
- animation: { type: "fadeIn", duration: 0.5, delay: delay + 0.14 }
6644
+ animation: motionEntrance(context, {
6645
+ type: "fadeIn",
6646
+ duration: 0.5,
6647
+ delay: delay + 0.14
6648
+ })
6490
6649
  });
6491
6650
  }
6492
6651
  }
@@ -7699,12 +7858,13 @@ function stackSeries(series) {
7699
7858
  })
7700
7859
  );
7701
7860
  }
7861
+ var CHART_CHAR_WIDTH_EM = 0.62;
7702
7862
  function layoutLegend(labels, fontSize, maxWidth) {
7703
7863
  const swatchSize = fontSize * 0.9;
7704
- const gap = fontSize * 1.6;
7864
+ const gap = fontSize * 2.2;
7705
7865
  const rowHeight = fontSize * 2;
7706
7866
  const widths = labels.map(
7707
- (label) => swatchSize + fontSize * 0.5 + label.length * fontSize * 0.58
7867
+ (label) => swatchSize + fontSize * 0.5 + label.length * fontSize * CHART_CHAR_WIDTH_EM
7708
7868
  );
7709
7869
  const rows = [];
7710
7870
  let current = [];
@@ -7758,10 +7918,10 @@ function renderChart(kind, input, context) {
7758
7918
  const { theme, viewport } = context;
7759
7919
  const templateId = TEMPLATE_ID[kind];
7760
7920
  const title = (input.title ?? "").trim();
7761
- const titleFontSize = themedFontSize(44, context, true);
7762
- const axisFontSize = themedFontSize(20, context, false);
7763
- const labelFontSize = themedFontSize(22, context, false);
7764
- const legendFontSize = themedFontSize(22, context, false);
7921
+ const titleFontSize = themedFontSize(52, context, true);
7922
+ const axisFontSize = themedFontSize(24, context, false);
7923
+ const labelFontSize = themedFontSize(26, context, false);
7924
+ const legendFontSize = themedFontSize(26, context, false);
7765
7925
  const tableFontSize = themedFontSize(20, context, false);
7766
7926
  const bodyFont = getThemeFont(context, "body");
7767
7927
  const isRadial = kind === "pie" || kind === "donut";
@@ -7790,11 +7950,55 @@ function renderChart(kind, input, context) {
7790
7950
  tableFontSize
7791
7951
  });
7792
7952
  const entranceHint = getTemplateHint(context, templateId, "entrance", "staggered");
7953
+ const markDelay = (index) => entranceHint === "subtle" ? 0.1 : Math.min(1.2, 0.2 + index * 0.08);
7793
7954
  const entrance = (index) => ({
7794
7955
  type: "fadeIn",
7795
7956
  duration: 0.6,
7796
- delay: entranceHint === "subtle" ? 0.1 : Math.min(1.2, 0.2 + index * 0.08)
7957
+ delay: markDelay(index)
7797
7958
  });
7959
+ const motion = getMotionProfile(context);
7960
+ const growDuration = motion.chartGrowthDuration;
7961
+ const growEasing = motion.textEntrance.easing;
7962
+ const eased = (animation) => growEasing ? { ...animation, easing: growEasing } : animation;
7963
+ const mark = (index, kind2, along = 0) => {
7964
+ if (!motion.chartGrowth) return entrance(index);
7965
+ const delay = motionDelay(context, markDelay(index));
7966
+ switch (kind2) {
7967
+ case "bar":
7968
+ return eased({ type: "grow", origin: "left", duration: growDuration, delay });
7969
+ case "bar-negative":
7970
+ return eased({ type: "grow", origin: "right", duration: growDuration, delay });
7971
+ case "column":
7972
+ return eased({ type: "grow", origin: "bottom", duration: growDuration, delay });
7973
+ case "column-negative":
7974
+ return eased({ type: "grow", origin: "top", duration: growDuration, delay });
7975
+ case "line":
7976
+ return { type: "drawOn", duration: growDuration * 1.4, delay };
7977
+ case "area":
7978
+ return { type: "reveal", origin: "left", duration: growDuration * 1.4, delay };
7979
+ case "point":
7980
+ return {
7981
+ type: "grow",
7982
+ origin: "center",
7983
+ duration: 0.35,
7984
+ delay: Number((delay + growDuration * 1.4 * along).toFixed(3))
7985
+ };
7986
+ case "wedge":
7987
+ return eased({
7988
+ type: "grow",
7989
+ origin: "center",
7990
+ duration: Math.max(0.4, growDuration * 0.6),
7991
+ delay
7992
+ });
7993
+ case "dot":
7994
+ return eased({ type: "grow", origin: "center", duration: 0.4, delay });
7995
+ }
7996
+ };
7997
+ const labelAnimation = (index, fallback, along = 0) => {
7998
+ if (!motion.chartGrowth) return fallback;
7999
+ const settled = motionDelay(context, markDelay(index)) + growDuration * (along > 0 ? 1.4 * along : 1);
8000
+ return { type: "fadeIn", duration: 0.4, delay: Number(settled.toFixed(3)) };
8001
+ };
7798
8002
  const paint = {
7799
8003
  kind,
7800
8004
  input,
@@ -7807,7 +8011,9 @@ function renderChart(kind, input, context) {
7807
8011
  bodyFont,
7808
8012
  gridColor: withAlpha(theme.colors.text, 0.12),
7809
8013
  axisTextColor: theme.colors.textMuted,
7810
- entrance
8014
+ entrance,
8015
+ mark,
8016
+ labelAnimation
7811
8017
  };
7812
8018
  const layers = [createBackgroundLayer("bg", themedSurfaceGradient(context, 170))];
7813
8019
  if (title && frame.titleBand) {
@@ -7831,7 +8037,7 @@ function renderChart(kind, input, context) {
7831
8037
  width: frame.titleBand.width,
7832
8038
  anchor: "center"
7833
8039
  },
7834
- animation: { type: "fadeIn", duration: 0.8 }
8040
+ animation: motionEntrance(context, { type: "fadeIn", duration: 0.8 })
7835
8041
  });
7836
8042
  }
7837
8043
  switch (kind) {
@@ -8005,7 +8211,7 @@ function paintCategoryAxisX(paint, xForCenter) {
8005
8211
  return layers;
8006
8212
  }
8007
8213
  function paintColumns(paint) {
8008
- const { frame, data, colors, input, entrance } = paint;
8214
+ const { frame, data, colors, input, mark, labelAnimation } = paint;
8009
8215
  const scale = valueTicks("column", data, input);
8010
8216
  const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
8011
8217
  const layers = paintValueAxisY(paint, scale, yFor);
@@ -8030,7 +8236,7 @@ function paintColumns(paint) {
8030
8236
  id: `mark-${s}-${i}`,
8031
8237
  content: { shape: "rect", fill: colors[s], borderRadius: stacked ? 0 : 3 },
8032
8238
  position: { x, y: top, width: barWidth, height },
8033
- animation: entrance(i + s)
8239
+ animation: mark(i + s, extent.y1 <= 0 ? "column-negative" : "column")
8034
8240
  });
8035
8241
  if (input.showValues === true && value !== null && !stacked) {
8036
8242
  layers.push(
@@ -8039,7 +8245,8 @@ function paintColumns(paint) {
8039
8245
  `val-${s}-${i}`,
8040
8246
  value,
8041
8247
  x + barWidth / 2,
8042
- value >= 0 ? top - paint.axisFontSize * 0.9 : top + height + paint.axisFontSize * 0.9
8248
+ value >= 0 ? top - paint.axisFontSize * 0.9 : top + height + paint.axisFontSize * 0.9,
8249
+ labelAnimation(i + s)
8043
8250
  )
8044
8251
  );
8045
8252
  }
@@ -8053,7 +8260,8 @@ function paintBars(paint) {
8053
8260
  data,
8054
8261
  colors,
8055
8262
  input,
8056
- entrance,
8263
+ mark,
8264
+ labelAnimation,
8057
8265
  labelFontSize,
8058
8266
  axisFontSize,
8059
8267
  bodyFont,
@@ -8062,7 +8270,7 @@ function paintBars(paint) {
8062
8270
  } = paint;
8063
8271
  const scale = valueTicks("bar", data, input);
8064
8272
  const labelGap = axisFontSize * 0.6;
8065
- const labelWidthPx = (value) => formatChartValue(value, input.unit).length * axisFontSize * 0.58;
8273
+ const labelWidthPx = (value) => formatChartValue(value, input.unit).length * axisFontSize * CHART_CHAR_WIDTH_EM;
8066
8274
  const showValueLabels = input.showValues === true && !(input.stacked === true);
8067
8275
  const reservedForLabels = showValueLabels ? data.series.flatMap((series) => series.values).filter((v) => v !== null).reduce((max, v) => Math.max(max, labelWidthPx(v)), 0) + labelGap : 0;
8068
8276
  const axisWidth = Math.max(20, frame.plot.width - reservedForLabels);
@@ -8140,19 +8348,28 @@ function paintBars(paint) {
8140
8348
  id: `mark-${s}-${i}`,
8141
8349
  content: { shape: "rect", fill: colors[s], borderRadius: stacked ? 0 : 3 },
8142
8350
  position: { x: left, y, width, height: barHeight },
8143
- animation: entrance(i + s)
8351
+ animation: mark(i + s, extent.y1 <= 0 ? "bar-negative" : "bar")
8144
8352
  });
8145
8353
  if (showValueLabels && value !== null) {
8146
8354
  const half = labelWidthPx(value) / 2;
8147
8355
  const labelX = value >= 0 ? left + width + labelGap + half : left - labelGap - half;
8148
- layers.push(valueLabel(paint, `val-${s}-${i}`, value, labelX, y + barHeight / 2));
8356
+ layers.push(
8357
+ valueLabel(
8358
+ paint,
8359
+ `val-${s}-${i}`,
8360
+ value,
8361
+ labelX,
8362
+ y + barHeight / 2,
8363
+ labelAnimation(i + s)
8364
+ )
8365
+ );
8149
8366
  }
8150
8367
  });
8151
8368
  });
8152
8369
  return layers;
8153
8370
  }
8154
8371
  function paintRadial(paint) {
8155
- const { kind, frame, data, colors, input, entrance, context } = paint;
8372
+ const { kind, frame, data, colors, input, entrance, mark, labelAnimation, context } = paint;
8156
8373
  const slices = data.labels.map((label, index) => ({ label, value: data.series[0].values[index], index })).filter(
8157
8374
  (s) => s.value !== null && Number.isFinite(s.value) && s.value > 0
8158
8375
  );
@@ -8175,7 +8392,7 @@ function paintRadial(paint) {
8175
8392
  strokeWidth: 2
8176
8393
  },
8177
8394
  position: { x: cx - rOuter, y: cy - rOuter, width: rOuter * 2, height: rOuter * 2 },
8178
- animation: entrance(order)
8395
+ animation: mark(order, "wedge")
8179
8396
  });
8180
8397
  if (input.showValues === true && sweep >= 14) {
8181
8398
  const mid = startDeg + sweep / 2;
@@ -8200,7 +8417,7 @@ function paintRadial(paint) {
8200
8417
  y: cy + rLabel * Math.sin(rad),
8201
8418
  anchor: "center"
8202
8419
  },
8203
- animation: entrance(order)
8420
+ animation: labelAnimation(order, entrance(order))
8204
8421
  });
8205
8422
  }
8206
8423
  startDeg += sweep;
@@ -8208,7 +8425,7 @@ function paintRadial(paint) {
8208
8425
  return layers;
8209
8426
  }
8210
8427
  function paintLines(paint) {
8211
- const { kind, frame, data, colors, input, entrance } = paint;
8428
+ const { kind, frame, data, colors, input, mark, labelAnimation } = paint;
8212
8429
  const scale = valueTicks(kind, data, input);
8213
8430
  const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
8214
8431
  const n = data.labels.length;
@@ -8249,7 +8466,7 @@ function paintLines(paint) {
8249
8466
  width: frame.plot.width,
8250
8467
  height: frame.plot.height
8251
8468
  },
8252
- animation: entrance(s)
8469
+ animation: mark(s, "area")
8253
8470
  });
8254
8471
  }
8255
8472
  if (segment.length > 1) {
@@ -8263,7 +8480,7 @@ function paintLines(paint) {
8263
8480
  width: frame.plot.width,
8264
8481
  height: frame.plot.height
8265
8482
  },
8266
- animation: entrance(s)
8483
+ animation: mark(s, "line")
8267
8484
  });
8268
8485
  }
8269
8486
  });
@@ -8276,7 +8493,7 @@ function paintLines(paint) {
8276
8493
  id: `pt-${s}-${i}`,
8277
8494
  content: { shape: "circle", fill: colors[s] },
8278
8495
  position: { x: xForCenter(i) - r, y: yFor(value) - r, width: r * 2, height: r * 2 },
8279
- animation: entrance(s)
8496
+ animation: mark(s, "point", i / Math.max(1, n - 1))
8280
8497
  });
8281
8498
  if (input.showValues === true) {
8282
8499
  layers.push(
@@ -8285,7 +8502,8 @@ function paintLines(paint) {
8285
8502
  `val-${s}-${i}`,
8286
8503
  value,
8287
8504
  xForCenter(i),
8288
- yFor(value) - paint.axisFontSize
8505
+ yFor(value) - paint.axisFontSize,
8506
+ labelAnimation(s, void 0, i / Math.max(1, n - 1))
8289
8507
  )
8290
8508
  );
8291
8509
  }
@@ -8295,7 +8513,7 @@ function paintLines(paint) {
8295
8513
  return layers;
8296
8514
  }
8297
8515
  function paintScatter(paint) {
8298
- const { frame, data, colors, input, entrance } = paint;
8516
+ const { frame, data, colors, input, mark } = paint;
8299
8517
  const scale = valueTicks("scatter", data, input);
8300
8518
  const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
8301
8519
  const layers = paintValueAxisY(paint, scale, yFor);
@@ -8353,7 +8571,7 @@ function paintScatter(paint) {
8353
8571
  id: `mark-${s}-${i}`,
8354
8572
  content: { shape: "circle", fill: colors[s], fillOpacity: 0.85 },
8355
8573
  position: { x: x - r, y: yFor(value) - r, width: r * 2, height: r * 2 },
8356
- animation: entrance(Math.floor(i / 3) + s)
8574
+ animation: mark(Math.floor(i / 3) + s, "dot")
8357
8575
  });
8358
8576
  });
8359
8577
  });
@@ -8390,7 +8608,7 @@ function paintLegend(paint, legend, band, fontSize) {
8390
8608
  });
8391
8609
  return layers;
8392
8610
  }
8393
- function valueLabel(paint, id, value, x, y) {
8611
+ function valueLabel(paint, id, value, x, y, animation) {
8394
8612
  return {
8395
8613
  type: "text",
8396
8614
  id,
@@ -8404,7 +8622,8 @@ function valueLabel(paint, id, value, x, y) {
8404
8622
  textAlign: "center"
8405
8623
  }
8406
8624
  },
8407
- position: { x, y, anchor: "center" }
8625
+ position: { x, y, anchor: "center" },
8626
+ ...animation ? { animation } : {}
8408
8627
  };
8409
8628
  }
8410
8629
  function tableLayer(table, band, fontSize, input, context) {
@@ -8729,6 +8948,25 @@ function avatarLayers(idPrefix, name, imageSrc, x, y, context) {
8729
8948
  }
8730
8949
 
8731
8950
  // src/doc/templates/registry.ts
8951
+ var GEOMETRY_BOUND_TEMPLATES = /* @__PURE__ */ new Set([
8952
+ "barChart",
8953
+ "columnChart",
8954
+ "lineChart",
8955
+ "areaChart",
8956
+ "pieChart",
8957
+ "donutChart",
8958
+ "scatterChart",
8959
+ "comparisonBar",
8960
+ "timeline",
8961
+ "diagram",
8962
+ "drawing",
8963
+ "tree",
8964
+ "map",
8965
+ "dataTable"
8966
+ ]);
8967
+ function isGeometryBoundTemplate(template) {
8968
+ return template !== void 0 && GEOMETRY_BOUND_TEMPLATES.has(template);
8969
+ }
8732
8970
  var templateRegistry = {
8733
8971
  title: titleBlock,
8734
8972
  sectionHeader,
@@ -9412,7 +9650,7 @@ function materializeBlockLayersWithRuntime(block, options, runtime) {
9412
9650
  source: layers.length > 0 ? "rich-content" : "empty"
9413
9651
  };
9414
9652
  }
9415
- const context = runtime.templateContext ? cloneData(runtime.templateContext) : createTemplateContext(theme, blockIndex, totalBlocks, viewport);
9653
+ const context = runtime.templateContext ? cloneData(runtime.templateContext) : createTemplateContext(theme, blockIndex, totalBlocks, viewport, { motion: options.motion });
9416
9654
  context.block = cloneData(block);
9417
9655
  const execution = executeTemplateMaterialization(block, context, runtime.registry);
9418
9656
  if (execution.status === "ok") {
@@ -10297,8 +10535,9 @@ function coverBlock(input, context) {
10297
10535
  anchor: "center",
10298
10536
  width: layout.maxTextWidth
10299
10537
  },
10300
- // No animation delay - shown immediately at rest
10301
- animation: { type: "fadeIn", duration: 0.8 }
10538
+ // No animation delay - shown immediately at rest. Word-by-word reveal is
10539
+ // a profile opt-in (off by default: the cover doubles as the poster).
10540
+ animation: getMotionProfile(context).coverWordReveal ? { type: "wordReveal", duration: 0.5, delay: 0.1, stagger: 0.12 } : { type: "fadeIn", duration: 0.8 }
10302
10541
  });
10303
10542
  if (subtitle) {
10304
10543
  layers.push({
@@ -10321,7 +10560,7 @@ function coverBlock(input, context) {
10321
10560
  anchor: "center",
10322
10561
  width: layout.maxTextWidth
10323
10562
  },
10324
- animation: { type: "fadeIn", duration: 0.8, delay: 0.2 }
10563
+ animation: motionEntrance(context, { type: "fadeIn", duration: 0.8, delay: 0.2 })
10325
10564
  });
10326
10565
  }
10327
10566
  return layers;
@@ -10416,7 +10655,7 @@ function expandCoverBlock(config, context, template = "cover") {
10416
10655
  }
10417
10656
 
10418
10657
  // src/doc/templates/index.ts
10419
- function materializeScheduledBlock(sourceBlock, blockIndex, totalBlocks, theme, viewport, persistentLayers, expandedPersistentLayers, registry, failureMode, onDiagnostic) {
10658
+ function materializeScheduledBlock(sourceBlock, blockIndex, totalBlocks, theme, viewport, persistentLayers, expandedPersistentLayers, registry, failureMode, onDiagnostic, motion) {
10420
10659
  const materialized = materializeBlockLayersWithRuntime(
10421
10660
  sourceBlock,
10422
10661
  {
@@ -10425,7 +10664,8 @@ function materializeScheduledBlock(sourceBlock, blockIndex, totalBlocks, theme,
10425
10664
  persistentLayers: persistentLayers ?? false,
10426
10665
  blockIndex,
10427
10666
  totalBlocks,
10428
- failureMode
10667
+ failureMode,
10668
+ motion
10429
10669
  },
10430
10670
  { registry, expandedPersistentLayers }
10431
10671
  );
@@ -10461,7 +10701,8 @@ function expandDocBlocks(blocks, options = {}) {
10461
10701
  failureMode = "fallback",
10462
10702
  onDiagnostic,
10463
10703
  splitLongBlocks = true,
10464
- mergeShortBlocks = true
10704
+ mergeShortBlocks = true,
10705
+ motion
10465
10706
  } = opts;
10466
10707
  const totalBlocks = blocks.length;
10467
10708
  const registry = customTemplates && customTemplates.length > 0 ? buildRegistry(customTemplates) : templateRegistry;
@@ -10483,7 +10724,8 @@ function expandDocBlocks(blocks, options = {}) {
10483
10724
  expandedPersistentLayers,
10484
10725
  registry,
10485
10726
  failureMode,
10486
- onDiagnostic
10727
+ onDiagnostic,
10728
+ motion
10487
10729
  );
10488
10730
  expandedBlock.startTime = currentTime;
10489
10731
  currentTime += expandedBlock.duration;
@@ -10515,7 +10757,8 @@ function expandDocBlocks(blocks, options = {}) {
10515
10757
  expandedPersistentLayers,
10516
10758
  registry,
10517
10759
  failureMode,
10518
- onDiagnostic
10760
+ onDiagnostic,
10761
+ motion
10519
10762
  );
10520
10763
  expandedBlock.startTime = offsetTime;
10521
10764
  offsetTime += expandedBlock.duration;
@@ -10553,7 +10796,8 @@ function expandDocBlocks(blocks, options = {}) {
10553
10796
  expandedPersistentLayers,
10554
10797
  registry,
10555
10798
  failureMode,
10556
- onDiagnostic
10799
+ onDiagnostic,
10800
+ motion
10557
10801
  );
10558
10802
  const templateBlock = block;
10559
10803
  expandedInfos.push({
@@ -11669,6 +11913,7 @@ function markdownToDoc(markdownDoc, options) {
11669
11913
  const customTemplates = readCustomTemplatesFromFrontmatter(markdownDoc.frontmatter);
11670
11914
  const customThemes = readCustomThemesFromFrontmatter(markdownDoc.frontmatter);
11671
11915
  const themeId = readFrontmatterThemeId(markdownDoc.frontmatter);
11916
+ const motion = readFrontmatterMotion(markdownDoc.frontmatter);
11672
11917
  const doc = {
11673
11918
  articleId,
11674
11919
  duration: currentTime,
@@ -11679,6 +11924,7 @@ function markdownToDoc(markdownDoc, options) {
11679
11924
  ...captions ? { captions } : {},
11680
11925
  ...markdownDoc.frontmatter ? { frontmatter: markdownDoc.frontmatter } : {},
11681
11926
  ...themeId ? { themeId } : {},
11927
+ ...motion ? { motion } : {},
11682
11928
  ...customTemplates ? { customTemplates } : {},
11683
11929
  ...customThemes ? { customThemes } : {},
11684
11930
  ...diagnostics.length > 0 ? { diagnostics } : {},
@@ -11987,6 +12233,9 @@ export {
11987
12233
  scaleAnimationDuration,
11988
12234
  getDefaultAnimation,
11989
12235
  themedEntrance,
12236
+ getMotionProfile,
12237
+ motionEntrance,
12238
+ motionDelay,
11990
12239
  shouldUseShadow,
11991
12240
  getOverlayOpacity,
11992
12241
  themedImageTreatment,
@@ -12028,6 +12277,10 @@ export {
12028
12277
  autoTemplatePreservesContent,
12029
12278
  deriveTemplateInputs,
12030
12279
  contentBlock,
12280
+ parseStatNumber,
12281
+ countUpSpecFor,
12282
+ formatCountValue,
12283
+ formatCountUp,
12031
12284
  statHighlight,
12032
12285
  quoteBlock,
12033
12286
  factCard,
@@ -12065,6 +12318,8 @@ export {
12065
12318
  drawingBlock,
12066
12319
  computeLayoutLayers,
12067
12320
  layoutBlock,
12321
+ GEOMETRY_BOUND_TEMPLATES,
12322
+ isGeometryBoundTemplate,
12068
12323
  templateRegistry,
12069
12324
  buildRegistry,
12070
12325
  themeWantsAmbientMotion,