@bendyline/squisq 2.11.9 → 2.11.11

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-DmElSKmg.d.ts → Doc-BWtfp7dT.d.ts} +207 -3
  2. package/dist/{ImageEditDoc-Dm_BETxC.d.ts → ImageEditDoc-CYRQK8so.d.ts} +1 -1
  3. package/dist/{chunk-YNO6JV36.js → chunk-2SRF5III.js} +4 -4
  4. package/dist/{chunk-QOVINAB2.js → chunk-42IU2SJY.js} +383 -114
  5. package/dist/{chunk-DTM4FG7S.js → chunk-7WN7DDMI.js} +1 -1
  6. package/dist/{chunk-2PGC4JDT.js → chunk-BR7ZMDWW.js} +1 -1
  7. package/dist/{chunk-CCRBHFNO.js → chunk-EWO2VLOS.js} +2 -2
  8. package/dist/{chunk-B5STNW6C.js → chunk-HVJ6VFDX.js} +2 -2
  9. package/dist/{chunk-PCCQLD3G.js → chunk-PFH3WV6J.js} +143 -3
  10. package/dist/{chunk-VRTXPGP7.js → chunk-S4427XKY.js} +98 -4
  11. package/dist/{chunk-QDA7NXDO.js → chunk-X2TDC4E5.js} +2 -2
  12. package/dist/doc/index.d.ts +103 -6
  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 +47 -9
  19. package/dist/jsonForm/index.d.ts +1 -1
  20. package/dist/jsonForm/index.js +4 -4
  21. package/dist/{materializePageSection-BfttC9Zj.d.ts → materializePageSection-Db7pABI0.d.ts} +1 -1
  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 +15 -3
  27. package/dist/{themeLibrary-ChnTImby.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-PCCQLD3G.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
  }
@@ -6010,6 +6152,13 @@ function treeBlock(input, context) {
6010
6152
  return layers;
6011
6153
  }
6012
6154
 
6155
+ // src/doc/utils/textMetrics.ts
6156
+ var AVERAGE_CHAR_WIDTH_EM = 0.62;
6157
+ var BOLD_CHAR_WIDTH_EM = 0.66;
6158
+ function estimateTextWidth(text, fontSize, charWidthEm = AVERAGE_CHAR_WIDTH_EM) {
6159
+ return text.length * fontSize * charWidthEm;
6160
+ }
6161
+
6013
6162
  // src/doc/templates/timelineBlock.ts
6014
6163
  var clamp012 = (value) => Math.max(0, Math.min(1, value));
6015
6164
  var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
@@ -6073,7 +6222,7 @@ function coerceLinks(raw, eventIds) {
6073
6222
  }
6074
6223
  return links;
6075
6224
  }
6076
- function diamondLayer(id, x, y, radius, color, delay) {
6225
+ function diamondLayer(id, x, y, radius, color, delay, build) {
6077
6226
  return {
6078
6227
  type: "path",
6079
6228
  id,
@@ -6084,7 +6233,7 @@ function diamondLayer(id, x, y, radius, color, delay) {
6084
6233
  strokeWidth: 2
6085
6234
  },
6086
6235
  position: { x: x - radius, y: y - radius, width: radius * 2, height: radius * 2 },
6087
- animation: { type: "zoomIn", duration: 0.35, delay }
6236
+ animation: build ? { type: "grow", origin: "center", duration: 0.35, delay } : { type: "zoomIn", duration: 0.35, delay }
6088
6237
  };
6089
6238
  }
6090
6239
  function branchPath(source, target, bend) {
@@ -6102,10 +6251,22 @@ function timelineBlock(input, context) {
6102
6251
  const colors = resolveColorScheme(context, input.colorScheme);
6103
6252
  const tracks = coerceTracks(input.tracks);
6104
6253
  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);
6254
+ const build = getMotionProfile(context).diagramBuild;
6255
+ const strokeIn = (duration, delay, fallbackDuration) => build ? { type: "drawOn", duration, delay } : { type: "fadeIn", duration: fallbackDuration, delay };
6256
+ const titleFontSize = themedFontSize(52, context, true);
6257
+ const trackFontSize = themedFontSize(26, context, false);
6258
+ const baseEventFontSize = themedFontSize(28, context, false);
6259
+ const baseDescriptionFontSize = themedFontSize(20, context, false);
6260
+ const trackCount = Math.max(1, tracks.length);
6261
+ const viewportScale = Math.min(viewport.width, viewport.height) / 1080;
6262
+ const calloutExtent = 78 + Math.min(baseEventFontSize, 28 * viewportScale) * 1.4 + Math.min(baseDescriptionFontSize, 20 * viewportScale) * 1.4 * 2 + 28;
6263
+ const naturalBand = calloutExtent * 2 + 24;
6264
+ const titleBlock2 = input.title ? titleFontSize * 1.3 + 40 : 0;
6265
+ const maxTracksHeight = viewport.height * 0.82 - titleBlock2;
6266
+ const trackBand = Math.max(120, Math.min(naturalBand, maxTracksHeight / trackCount));
6267
+ const groupHeight = titleBlock2 + trackBand * trackCount;
6268
+ const groupTop = Math.max(viewport.height * 0.06, (viewport.height - groupHeight) / 2);
6269
+ const contentTop = groupTop + titleBlock2;
6109
6270
  if (input.title) {
6110
6271
  layers.push({
6111
6272
  type: "text",
@@ -6121,8 +6282,8 @@ function timelineBlock(input, context) {
6121
6282
  shadow: shouldUseShadow(context)
6122
6283
  }
6123
6284
  },
6124
- position: { x: "50%", y: Math.max(50, titleFontSize * 1.2), width: "82%", anchor: "center" },
6125
- animation: { type: "fadeIn", duration: 0.7 }
6285
+ position: { x: "50%", y: groupTop + titleFontSize * 0.65, width: "82%", anchor: "center" },
6286
+ animation: motionEntrance(context, { type: "fadeIn", duration: 0.7 })
6126
6287
  });
6127
6288
  }
6128
6289
  if (tracks.length === 0) {
@@ -6144,14 +6305,17 @@ function timelineBlock(input, context) {
6144
6305
  }
6145
6306
  const hasTrackLabels = tracks.some((track) => !!track.label);
6146
6307
  const outerMargin = Math.max(48, viewport.width * 0.065);
6147
- const labelWidth = hasTrackLabels ? Math.min(280, viewport.width * 0.18) : 0;
6308
+ const labelGap = 32;
6309
+ const longestLabel = Math.max(
6310
+ 0,
6311
+ ...tracks.map(
6312
+ (track) => track.label ? estimateTextWidth(track.label, trackFontSize, BOLD_CHAR_WIDTH_EM) : 0
6313
+ )
6314
+ );
6315
+ const labelWidth = hasTrackLabels ? Math.min(Math.min(280, viewport.width * 0.18), Math.ceil(longestLabel) + labelGap) : 0;
6148
6316
  const axisStartX = outerMargin + labelWidth;
6149
6317
  const axisEndX = viewport.width - outerMargin;
6150
6318
  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
6319
  const markerRadius = Math.max(6, Math.min(11, Math.min(viewport.width, viewport.height) / 105));
6156
6320
  const trackColor = withAlpha(colors.text ?? theme.colors.primary, 0.72);
6157
6321
  const accentColor = colors.accent ?? theme.colors.primary;
@@ -6166,8 +6330,8 @@ function timelineBlock(input, context) {
6166
6330
  Math.min(330, axisWidth / Math.min(maxCalloutEvents, 4) * 0.86)
6167
6331
  );
6168
6332
  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);
6333
+ const eventFontSize = Math.min(baseEventFontSize, 28 * minViewportScale);
6334
+ const descriptionFontSize = Math.min(baseDescriptionFontSize, 20 * minViewportScale);
6171
6335
  const trackMarkerRadii = tracks.map((track) => {
6172
6336
  const positions = track.events.map((event) => clamp012(event.position)).sort((a, b) => a - b);
6173
6337
  let minGapPx = Number.POSITIVE_INFINITY;
@@ -6193,7 +6357,7 @@ function timelineBlock(input, context) {
6193
6357
  endMarker: "arrow"
6194
6358
  },
6195
6359
  position: { x: axisStartX, y: y - markerRadius, width: axisWidth, height: markerRadius * 2 },
6196
- animation: { type: "fadeIn", duration: 0.65, delay: trackDelay }
6360
+ animation: strokeIn(0.9, trackDelay, 0.65)
6197
6361
  };
6198
6362
  layers.push(line);
6199
6363
  if (track.label) {
@@ -6211,12 +6375,12 @@ function timelineBlock(input, context) {
6211
6375
  }
6212
6376
  },
6213
6377
  position: {
6214
- x: outerMargin + labelWidth / 2 - 16,
6378
+ x: outerMargin + (labelWidth - labelGap) / 2,
6215
6379
  y,
6216
- width: Math.max(1, labelWidth - 32),
6380
+ width: Math.max(1, labelWidth - labelGap),
6217
6381
  anchor: "center"
6218
6382
  },
6219
- animation: { type: "fadeIn", duration: 0.5, delay: trackDelay }
6383
+ animation: motionEntrance(context, { type: "fadeIn", duration: 0.5, delay: trackDelay })
6220
6384
  };
6221
6385
  layers.push(label);
6222
6386
  }
@@ -6240,7 +6404,7 @@ function timelineBlock(input, context) {
6240
6404
  width: Math.max(48, outerMargin - markerRadius * 2),
6241
6405
  anchor: "top-left"
6242
6406
  },
6243
- animation: { type: "fadeIn", duration: 0.5, delay: trackDelay }
6407
+ animation: motionEntrance(context, { type: "fadeIn", duration: 0.5, delay: trackDelay })
6244
6408
  });
6245
6409
  }
6246
6410
  track.events.forEach((event, eventIndex) => {
@@ -6272,7 +6436,7 @@ function timelineBlock(input, context) {
6272
6436
  endMarker: "arrow"
6273
6437
  },
6274
6438
  position: { x: 0, y: 0, width: "100%", height: "100%" },
6275
- animation: { type: "fadeIn", duration: 0.6, delay: 0.45 + linkIndex * 0.08 }
6439
+ animation: strokeIn(0.6, 0.45 + linkIndex * 0.08, 0.6)
6276
6440
  });
6277
6441
  if (link.label) {
6278
6442
  const sameTrack = source.trackIndex === target.trackIndex;
@@ -6330,7 +6494,11 @@ function timelineBlock(input, context) {
6330
6494
  height: linkLabelHeight,
6331
6495
  anchor: "center"
6332
6496
  },
6333
- animation: { type: "fadeIn", duration: 0.5, delay: 0.55 + linkIndex * 0.08 }
6497
+ animation: motionEntrance(context, {
6498
+ type: "fadeIn",
6499
+ duration: 0.5,
6500
+ delay: 0.55 + linkIndex * 0.08
6501
+ })
6334
6502
  });
6335
6503
  }
6336
6504
  });
@@ -6360,7 +6528,7 @@ function timelineBlock(input, context) {
6360
6528
  width: Math.max(2, Math.abs(calloutX - x)),
6361
6529
  height: Math.max(2, Math.abs(endY - y))
6362
6530
  },
6363
- animation: { type: "fadeIn", duration: 0.4, delay: stemDelay }
6531
+ animation: strokeIn(0.35, stemDelay, 0.4)
6364
6532
  });
6365
6533
  };
6366
6534
  if (event.callout !== false) {
@@ -6384,7 +6552,8 @@ function timelineBlock(input, context) {
6384
6552
  y,
6385
6553
  pointMarkerRadius,
6386
6554
  accentColor,
6387
- delay
6555
+ delay,
6556
+ build
6388
6557
  )
6389
6558
  );
6390
6559
  } else {
@@ -6403,7 +6572,7 @@ function timelineBlock(input, context) {
6403
6572
  width: pointMarkerRadius * 2,
6404
6573
  height: pointMarkerRadius * 2
6405
6574
  },
6406
- animation: { type: "zoomIn", duration: 0.35, delay }
6575
+ animation: build ? { type: "grow", origin: "center", duration: 0.35, delay } : { type: "zoomIn", duration: 0.35, delay }
6407
6576
  };
6408
6577
  layers.push(dot);
6409
6578
  }
@@ -6437,7 +6606,7 @@ function timelineBlock(input, context) {
6437
6606
  }
6438
6607
  },
6439
6608
  position: { x: calloutX, y: labelY, width: calloutWidth, anchor: "center" },
6440
- animation: { type: "fadeIn", duration: 0.5, delay: delay + 0.08 }
6609
+ animation: motionEntrance(context, { type: "fadeIn", duration: 0.5, delay: delay + 0.08 })
6441
6610
  });
6442
6611
  if (event.description) {
6443
6612
  const descriptionSide = event.descriptionSide ?? side;
@@ -6486,7 +6655,11 @@ function timelineBlock(input, context) {
6486
6655
  }
6487
6656
  },
6488
6657
  position: { x: calloutX, y: descriptionY, width: calloutWidth, anchor: "center" },
6489
- animation: { type: "fadeIn", duration: 0.5, delay: delay + 0.14 }
6658
+ animation: motionEntrance(context, {
6659
+ type: "fadeIn",
6660
+ duration: 0.5,
6661
+ delay: delay + 0.14
6662
+ })
6490
6663
  });
6491
6664
  }
6492
6665
  }
@@ -7699,12 +7872,13 @@ function stackSeries(series) {
7699
7872
  })
7700
7873
  );
7701
7874
  }
7875
+ var CHART_CHAR_WIDTH_EM = 0.62;
7702
7876
  function layoutLegend(labels, fontSize, maxWidth) {
7703
7877
  const swatchSize = fontSize * 0.9;
7704
- const gap = fontSize * 1.6;
7878
+ const gap = fontSize * 2.2;
7705
7879
  const rowHeight = fontSize * 2;
7706
7880
  const widths = labels.map(
7707
- (label) => swatchSize + fontSize * 0.5 + label.length * fontSize * 0.58
7881
+ (label) => swatchSize + fontSize * 0.5 + label.length * fontSize * CHART_CHAR_WIDTH_EM
7708
7882
  );
7709
7883
  const rows = [];
7710
7884
  let current = [];
@@ -7758,10 +7932,10 @@ function renderChart(kind, input, context) {
7758
7932
  const { theme, viewport } = context;
7759
7933
  const templateId = TEMPLATE_ID[kind];
7760
7934
  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);
7935
+ const titleFontSize = themedFontSize(52, context, true);
7936
+ const axisFontSize = themedFontSize(24, context, false);
7937
+ const labelFontSize = themedFontSize(26, context, false);
7938
+ const legendFontSize = themedFontSize(26, context, false);
7765
7939
  const tableFontSize = themedFontSize(20, context, false);
7766
7940
  const bodyFont = getThemeFont(context, "body");
7767
7941
  const isRadial = kind === "pie" || kind === "donut";
@@ -7790,11 +7964,55 @@ function renderChart(kind, input, context) {
7790
7964
  tableFontSize
7791
7965
  });
7792
7966
  const entranceHint = getTemplateHint(context, templateId, "entrance", "staggered");
7967
+ const markDelay = (index) => entranceHint === "subtle" ? 0.1 : Math.min(1.2, 0.2 + index * 0.08);
7793
7968
  const entrance = (index) => ({
7794
7969
  type: "fadeIn",
7795
7970
  duration: 0.6,
7796
- delay: entranceHint === "subtle" ? 0.1 : Math.min(1.2, 0.2 + index * 0.08)
7971
+ delay: markDelay(index)
7797
7972
  });
7973
+ const motion = getMotionProfile(context);
7974
+ const growDuration = motion.chartGrowthDuration;
7975
+ const growEasing = motion.textEntrance.easing;
7976
+ const eased = (animation) => growEasing ? { ...animation, easing: growEasing } : animation;
7977
+ const mark = (index, kind2, along = 0) => {
7978
+ if (!motion.chartGrowth) return entrance(index);
7979
+ const delay = motionDelay(context, markDelay(index));
7980
+ switch (kind2) {
7981
+ case "bar":
7982
+ return eased({ type: "grow", origin: "left", duration: growDuration, delay });
7983
+ case "bar-negative":
7984
+ return eased({ type: "grow", origin: "right", duration: growDuration, delay });
7985
+ case "column":
7986
+ return eased({ type: "grow", origin: "bottom", duration: growDuration, delay });
7987
+ case "column-negative":
7988
+ return eased({ type: "grow", origin: "top", duration: growDuration, delay });
7989
+ case "line":
7990
+ return { type: "drawOn", duration: growDuration * 1.4, delay };
7991
+ case "area":
7992
+ return { type: "reveal", origin: "left", duration: growDuration * 1.4, delay };
7993
+ case "point":
7994
+ return {
7995
+ type: "grow",
7996
+ origin: "center",
7997
+ duration: 0.35,
7998
+ delay: Number((delay + growDuration * 1.4 * along).toFixed(3))
7999
+ };
8000
+ case "wedge":
8001
+ return eased({
8002
+ type: "grow",
8003
+ origin: "center",
8004
+ duration: Math.max(0.4, growDuration * 0.6),
8005
+ delay
8006
+ });
8007
+ case "dot":
8008
+ return eased({ type: "grow", origin: "center", duration: 0.4, delay });
8009
+ }
8010
+ };
8011
+ const labelAnimation = (index, fallback, along = 0) => {
8012
+ if (!motion.chartGrowth) return fallback;
8013
+ const settled = motionDelay(context, markDelay(index)) + growDuration * (along > 0 ? 1.4 * along : 1);
8014
+ return { type: "fadeIn", duration: 0.4, delay: Number(settled.toFixed(3)) };
8015
+ };
7798
8016
  const paint = {
7799
8017
  kind,
7800
8018
  input,
@@ -7807,7 +8025,9 @@ function renderChart(kind, input, context) {
7807
8025
  bodyFont,
7808
8026
  gridColor: withAlpha(theme.colors.text, 0.12),
7809
8027
  axisTextColor: theme.colors.textMuted,
7810
- entrance
8028
+ entrance,
8029
+ mark,
8030
+ labelAnimation
7811
8031
  };
7812
8032
  const layers = [createBackgroundLayer("bg", themedSurfaceGradient(context, 170))];
7813
8033
  if (title && frame.titleBand) {
@@ -7831,7 +8051,7 @@ function renderChart(kind, input, context) {
7831
8051
  width: frame.titleBand.width,
7832
8052
  anchor: "center"
7833
8053
  },
7834
- animation: { type: "fadeIn", duration: 0.8 }
8054
+ animation: motionEntrance(context, { type: "fadeIn", duration: 0.8 })
7835
8055
  });
7836
8056
  }
7837
8057
  switch (kind) {
@@ -8005,7 +8225,7 @@ function paintCategoryAxisX(paint, xForCenter) {
8005
8225
  return layers;
8006
8226
  }
8007
8227
  function paintColumns(paint) {
8008
- const { frame, data, colors, input, entrance } = paint;
8228
+ const { frame, data, colors, input, mark, labelAnimation } = paint;
8009
8229
  const scale = valueTicks("column", data, input);
8010
8230
  const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
8011
8231
  const layers = paintValueAxisY(paint, scale, yFor);
@@ -8030,7 +8250,7 @@ function paintColumns(paint) {
8030
8250
  id: `mark-${s}-${i}`,
8031
8251
  content: { shape: "rect", fill: colors[s], borderRadius: stacked ? 0 : 3 },
8032
8252
  position: { x, y: top, width: barWidth, height },
8033
- animation: entrance(i + s)
8253
+ animation: mark(i + s, extent.y1 <= 0 ? "column-negative" : "column")
8034
8254
  });
8035
8255
  if (input.showValues === true && value !== null && !stacked) {
8036
8256
  layers.push(
@@ -8039,7 +8259,8 @@ function paintColumns(paint) {
8039
8259
  `val-${s}-${i}`,
8040
8260
  value,
8041
8261
  x + barWidth / 2,
8042
- value >= 0 ? top - paint.axisFontSize * 0.9 : top + height + paint.axisFontSize * 0.9
8262
+ value >= 0 ? top - paint.axisFontSize * 0.9 : top + height + paint.axisFontSize * 0.9,
8263
+ labelAnimation(i + s)
8043
8264
  )
8044
8265
  );
8045
8266
  }
@@ -8053,7 +8274,8 @@ function paintBars(paint) {
8053
8274
  data,
8054
8275
  colors,
8055
8276
  input,
8056
- entrance,
8277
+ mark,
8278
+ labelAnimation,
8057
8279
  labelFontSize,
8058
8280
  axisFontSize,
8059
8281
  bodyFont,
@@ -8062,7 +8284,7 @@ function paintBars(paint) {
8062
8284
  } = paint;
8063
8285
  const scale = valueTicks("bar", data, input);
8064
8286
  const labelGap = axisFontSize * 0.6;
8065
- const labelWidthPx = (value) => formatChartValue(value, input.unit).length * axisFontSize * 0.58;
8287
+ const labelWidthPx = (value) => formatChartValue(value, input.unit).length * axisFontSize * CHART_CHAR_WIDTH_EM;
8066
8288
  const showValueLabels = input.showValues === true && !(input.stacked === true);
8067
8289
  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
8290
  const axisWidth = Math.max(20, frame.plot.width - reservedForLabels);
@@ -8140,19 +8362,28 @@ function paintBars(paint) {
8140
8362
  id: `mark-${s}-${i}`,
8141
8363
  content: { shape: "rect", fill: colors[s], borderRadius: stacked ? 0 : 3 },
8142
8364
  position: { x: left, y, width, height: barHeight },
8143
- animation: entrance(i + s)
8365
+ animation: mark(i + s, extent.y1 <= 0 ? "bar-negative" : "bar")
8144
8366
  });
8145
8367
  if (showValueLabels && value !== null) {
8146
8368
  const half = labelWidthPx(value) / 2;
8147
8369
  const labelX = value >= 0 ? left + width + labelGap + half : left - labelGap - half;
8148
- layers.push(valueLabel(paint, `val-${s}-${i}`, value, labelX, y + barHeight / 2));
8370
+ layers.push(
8371
+ valueLabel(
8372
+ paint,
8373
+ `val-${s}-${i}`,
8374
+ value,
8375
+ labelX,
8376
+ y + barHeight / 2,
8377
+ labelAnimation(i + s)
8378
+ )
8379
+ );
8149
8380
  }
8150
8381
  });
8151
8382
  });
8152
8383
  return layers;
8153
8384
  }
8154
8385
  function paintRadial(paint) {
8155
- const { kind, frame, data, colors, input, entrance, context } = paint;
8386
+ const { kind, frame, data, colors, input, entrance, mark, labelAnimation, context } = paint;
8156
8387
  const slices = data.labels.map((label, index) => ({ label, value: data.series[0].values[index], index })).filter(
8157
8388
  (s) => s.value !== null && Number.isFinite(s.value) && s.value > 0
8158
8389
  );
@@ -8175,7 +8406,7 @@ function paintRadial(paint) {
8175
8406
  strokeWidth: 2
8176
8407
  },
8177
8408
  position: { x: cx - rOuter, y: cy - rOuter, width: rOuter * 2, height: rOuter * 2 },
8178
- animation: entrance(order)
8409
+ animation: mark(order, "wedge")
8179
8410
  });
8180
8411
  if (input.showValues === true && sweep >= 14) {
8181
8412
  const mid = startDeg + sweep / 2;
@@ -8200,7 +8431,7 @@ function paintRadial(paint) {
8200
8431
  y: cy + rLabel * Math.sin(rad),
8201
8432
  anchor: "center"
8202
8433
  },
8203
- animation: entrance(order)
8434
+ animation: labelAnimation(order, entrance(order))
8204
8435
  });
8205
8436
  }
8206
8437
  startDeg += sweep;
@@ -8208,7 +8439,7 @@ function paintRadial(paint) {
8208
8439
  return layers;
8209
8440
  }
8210
8441
  function paintLines(paint) {
8211
- const { kind, frame, data, colors, input, entrance } = paint;
8442
+ const { kind, frame, data, colors, input, mark, labelAnimation } = paint;
8212
8443
  const scale = valueTicks(kind, data, input);
8213
8444
  const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
8214
8445
  const n = data.labels.length;
@@ -8249,7 +8480,7 @@ function paintLines(paint) {
8249
8480
  width: frame.plot.width,
8250
8481
  height: frame.plot.height
8251
8482
  },
8252
- animation: entrance(s)
8483
+ animation: mark(s, "area")
8253
8484
  });
8254
8485
  }
8255
8486
  if (segment.length > 1) {
@@ -8263,7 +8494,7 @@ function paintLines(paint) {
8263
8494
  width: frame.plot.width,
8264
8495
  height: frame.plot.height
8265
8496
  },
8266
- animation: entrance(s)
8497
+ animation: mark(s, "line")
8267
8498
  });
8268
8499
  }
8269
8500
  });
@@ -8276,7 +8507,7 @@ function paintLines(paint) {
8276
8507
  id: `pt-${s}-${i}`,
8277
8508
  content: { shape: "circle", fill: colors[s] },
8278
8509
  position: { x: xForCenter(i) - r, y: yFor(value) - r, width: r * 2, height: r * 2 },
8279
- animation: entrance(s)
8510
+ animation: mark(s, "point", i / Math.max(1, n - 1))
8280
8511
  });
8281
8512
  if (input.showValues === true) {
8282
8513
  layers.push(
@@ -8285,7 +8516,8 @@ function paintLines(paint) {
8285
8516
  `val-${s}-${i}`,
8286
8517
  value,
8287
8518
  xForCenter(i),
8288
- yFor(value) - paint.axisFontSize
8519
+ yFor(value) - paint.axisFontSize,
8520
+ labelAnimation(s, void 0, i / Math.max(1, n - 1))
8289
8521
  )
8290
8522
  );
8291
8523
  }
@@ -8295,7 +8527,7 @@ function paintLines(paint) {
8295
8527
  return layers;
8296
8528
  }
8297
8529
  function paintScatter(paint) {
8298
- const { frame, data, colors, input, entrance } = paint;
8530
+ const { frame, data, colors, input, mark } = paint;
8299
8531
  const scale = valueTicks("scatter", data, input);
8300
8532
  const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
8301
8533
  const layers = paintValueAxisY(paint, scale, yFor);
@@ -8353,7 +8585,7 @@ function paintScatter(paint) {
8353
8585
  id: `mark-${s}-${i}`,
8354
8586
  content: { shape: "circle", fill: colors[s], fillOpacity: 0.85 },
8355
8587
  position: { x: x - r, y: yFor(value) - r, width: r * 2, height: r * 2 },
8356
- animation: entrance(Math.floor(i / 3) + s)
8588
+ animation: mark(Math.floor(i / 3) + s, "dot")
8357
8589
  });
8358
8590
  });
8359
8591
  });
@@ -8390,7 +8622,7 @@ function paintLegend(paint, legend, band, fontSize) {
8390
8622
  });
8391
8623
  return layers;
8392
8624
  }
8393
- function valueLabel(paint, id, value, x, y) {
8625
+ function valueLabel(paint, id, value, x, y, animation) {
8394
8626
  return {
8395
8627
  type: "text",
8396
8628
  id,
@@ -8404,7 +8636,8 @@ function valueLabel(paint, id, value, x, y) {
8404
8636
  textAlign: "center"
8405
8637
  }
8406
8638
  },
8407
- position: { x, y, anchor: "center" }
8639
+ position: { x, y, anchor: "center" },
8640
+ ...animation ? { animation } : {}
8408
8641
  };
8409
8642
  }
8410
8643
  function tableLayer(table, band, fontSize, input, context) {
@@ -8729,6 +8962,25 @@ function avatarLayers(idPrefix, name, imageSrc, x, y, context) {
8729
8962
  }
8730
8963
 
8731
8964
  // src/doc/templates/registry.ts
8965
+ var GEOMETRY_BOUND_TEMPLATES = /* @__PURE__ */ new Set([
8966
+ "barChart",
8967
+ "columnChart",
8968
+ "lineChart",
8969
+ "areaChart",
8970
+ "pieChart",
8971
+ "donutChart",
8972
+ "scatterChart",
8973
+ "comparisonBar",
8974
+ "timeline",
8975
+ "diagram",
8976
+ "drawing",
8977
+ "tree",
8978
+ "map",
8979
+ "dataTable"
8980
+ ]);
8981
+ function isGeometryBoundTemplate(template) {
8982
+ return template !== void 0 && GEOMETRY_BOUND_TEMPLATES.has(template);
8983
+ }
8732
8984
  var templateRegistry = {
8733
8985
  title: titleBlock,
8734
8986
  sectionHeader,
@@ -9412,7 +9664,7 @@ function materializeBlockLayersWithRuntime(block, options, runtime) {
9412
9664
  source: layers.length > 0 ? "rich-content" : "empty"
9413
9665
  };
9414
9666
  }
9415
- const context = runtime.templateContext ? cloneData(runtime.templateContext) : createTemplateContext(theme, blockIndex, totalBlocks, viewport);
9667
+ const context = runtime.templateContext ? cloneData(runtime.templateContext) : createTemplateContext(theme, blockIndex, totalBlocks, viewport, { motion: options.motion });
9416
9668
  context.block = cloneData(block);
9417
9669
  const execution = executeTemplateMaterialization(block, context, runtime.registry);
9418
9670
  if (execution.status === "ok") {
@@ -10297,8 +10549,9 @@ function coverBlock(input, context) {
10297
10549
  anchor: "center",
10298
10550
  width: layout.maxTextWidth
10299
10551
  },
10300
- // No animation delay - shown immediately at rest
10301
- animation: { type: "fadeIn", duration: 0.8 }
10552
+ // No animation delay - shown immediately at rest. Word-by-word reveal is
10553
+ // a profile opt-in (off by default: the cover doubles as the poster).
10554
+ animation: getMotionProfile(context).coverWordReveal ? { type: "wordReveal", duration: 0.5, delay: 0.1, stagger: 0.12 } : { type: "fadeIn", duration: 0.8 }
10302
10555
  });
10303
10556
  if (subtitle) {
10304
10557
  layers.push({
@@ -10321,7 +10574,7 @@ function coverBlock(input, context) {
10321
10574
  anchor: "center",
10322
10575
  width: layout.maxTextWidth
10323
10576
  },
10324
- animation: { type: "fadeIn", duration: 0.8, delay: 0.2 }
10577
+ animation: motionEntrance(context, { type: "fadeIn", duration: 0.8, delay: 0.2 })
10325
10578
  });
10326
10579
  }
10327
10580
  return layers;
@@ -10416,7 +10669,7 @@ function expandCoverBlock(config, context, template = "cover") {
10416
10669
  }
10417
10670
 
10418
10671
  // src/doc/templates/index.ts
10419
- function materializeScheduledBlock(sourceBlock, blockIndex, totalBlocks, theme, viewport, persistentLayers, expandedPersistentLayers, registry, failureMode, onDiagnostic) {
10672
+ function materializeScheduledBlock(sourceBlock, blockIndex, totalBlocks, theme, viewport, persistentLayers, expandedPersistentLayers, registry, failureMode, onDiagnostic, motion) {
10420
10673
  const materialized = materializeBlockLayersWithRuntime(
10421
10674
  sourceBlock,
10422
10675
  {
@@ -10425,7 +10678,8 @@ function materializeScheduledBlock(sourceBlock, blockIndex, totalBlocks, theme,
10425
10678
  persistentLayers: persistentLayers ?? false,
10426
10679
  blockIndex,
10427
10680
  totalBlocks,
10428
- failureMode
10681
+ failureMode,
10682
+ motion
10429
10683
  },
10430
10684
  { registry, expandedPersistentLayers }
10431
10685
  );
@@ -10461,7 +10715,8 @@ function expandDocBlocks(blocks, options = {}) {
10461
10715
  failureMode = "fallback",
10462
10716
  onDiagnostic,
10463
10717
  splitLongBlocks = true,
10464
- mergeShortBlocks = true
10718
+ mergeShortBlocks = true,
10719
+ motion
10465
10720
  } = opts;
10466
10721
  const totalBlocks = blocks.length;
10467
10722
  const registry = customTemplates && customTemplates.length > 0 ? buildRegistry(customTemplates) : templateRegistry;
@@ -10483,7 +10738,8 @@ function expandDocBlocks(blocks, options = {}) {
10483
10738
  expandedPersistentLayers,
10484
10739
  registry,
10485
10740
  failureMode,
10486
- onDiagnostic
10741
+ onDiagnostic,
10742
+ motion
10487
10743
  );
10488
10744
  expandedBlock.startTime = currentTime;
10489
10745
  currentTime += expandedBlock.duration;
@@ -10515,7 +10771,8 @@ function expandDocBlocks(blocks, options = {}) {
10515
10771
  expandedPersistentLayers,
10516
10772
  registry,
10517
10773
  failureMode,
10518
- onDiagnostic
10774
+ onDiagnostic,
10775
+ motion
10519
10776
  );
10520
10777
  expandedBlock.startTime = offsetTime;
10521
10778
  offsetTime += expandedBlock.duration;
@@ -10553,7 +10810,8 @@ function expandDocBlocks(blocks, options = {}) {
10553
10810
  expandedPersistentLayers,
10554
10811
  registry,
10555
10812
  failureMode,
10556
- onDiagnostic
10813
+ onDiagnostic,
10814
+ motion
10557
10815
  );
10558
10816
  const templateBlock = block;
10559
10817
  expandedInfos.push({
@@ -11669,6 +11927,7 @@ function markdownToDoc(markdownDoc, options) {
11669
11927
  const customTemplates = readCustomTemplatesFromFrontmatter(markdownDoc.frontmatter);
11670
11928
  const customThemes = readCustomThemesFromFrontmatter(markdownDoc.frontmatter);
11671
11929
  const themeId = readFrontmatterThemeId(markdownDoc.frontmatter);
11930
+ const motion = readFrontmatterMotion(markdownDoc.frontmatter);
11672
11931
  const doc = {
11673
11932
  articleId,
11674
11933
  duration: currentTime,
@@ -11679,6 +11938,7 @@ function markdownToDoc(markdownDoc, options) {
11679
11938
  ...captions ? { captions } : {},
11680
11939
  ...markdownDoc.frontmatter ? { frontmatter: markdownDoc.frontmatter } : {},
11681
11940
  ...themeId ? { themeId } : {},
11941
+ ...motion ? { motion } : {},
11682
11942
  ...customTemplates ? { customTemplates } : {},
11683
11943
  ...customThemes ? { customThemes } : {},
11684
11944
  ...diagnostics.length > 0 ? { diagnostics } : {},
@@ -11987,6 +12247,9 @@ export {
11987
12247
  scaleAnimationDuration,
11988
12248
  getDefaultAnimation,
11989
12249
  themedEntrance,
12250
+ getMotionProfile,
12251
+ motionEntrance,
12252
+ motionDelay,
11990
12253
  shouldUseShadow,
11991
12254
  getOverlayOpacity,
11992
12255
  themedImageTreatment,
@@ -12028,6 +12291,10 @@ export {
12028
12291
  autoTemplatePreservesContent,
12029
12292
  deriveTemplateInputs,
12030
12293
  contentBlock,
12294
+ parseStatNumber,
12295
+ countUpSpecFor,
12296
+ formatCountValue,
12297
+ formatCountUp,
12031
12298
  statHighlight,
12032
12299
  quoteBlock,
12033
12300
  factCard,
@@ -12065,6 +12332,8 @@ export {
12065
12332
  drawingBlock,
12066
12333
  computeLayoutLayers,
12067
12334
  layoutBlock,
12335
+ GEOMETRY_BOUND_TEMPLATES,
12336
+ isGeometryBoundTemplate,
12068
12337
  templateRegistry,
12069
12338
  buildRegistry,
12070
12339
  themeWantsAmbientMotion,