@lalalic/markcut 2.9.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -448,15 +448,21 @@ function wrapWithEffects(
448
448
  const absEnd = innerStream.end ?? result.duration;
449
449
  const duration = absEnd - absStart;
450
450
 
451
- // Reset inner stream's timing to be relative (start=0) so the effect
452
- // wrapper owns the absolute timing. The EffectWrapper renders children
453
- // with their relative timing inside its own Sequence.
454
- const resetStream = {
455
- ...innerStream,
456
- start: 0,
457
- end: duration,
458
- durationInSeconds: duration,
459
- } as any;
451
+ // For background nodes without explicit end, keep original timing
452
+ // (start/end undefined) so parent back-propagation fills the correct
453
+ // scene duration. The effect wrapper handles animation timing via
454
+ // durationInSeconds and the parent fills end for proper visibility span.
455
+ // Other nodes: reset to relative timing so the effect wrapper owns
456
+ // the absolute positioning in the parent timeline.
457
+ const isBgNoEnd = innerStream.isBackground && innerStream.end == null;
458
+ const resetStream = isBgNoEnd
459
+ ? { ...innerStream }
460
+ : {
461
+ ...innerStream,
462
+ start: 0,
463
+ end: duration,
464
+ durationInSeconds: duration,
465
+ } as any;
460
466
 
461
467
  // Build nested effect wrappers from innermost → outermost.
462
468
  // The outermost effect uses the original absolute timing;
@@ -476,14 +482,18 @@ function wrapWithEffects(
476
482
  id: uid(),
477
483
  type: "effect",
478
484
  animation: spec.animation,
485
+ animationDurationSeconds: spec.duration,
479
486
  durationInSeconds: spec.duration,
480
487
  animationTimingFunction: spec.animationTimingFunction,
481
488
  animationIterationCount: spec.animationIterationCount ?? 1,
482
489
  customKeyframes: spec.customKeyframes,
483
490
  children: [currentStream],
484
- start: effStart,
485
- end: effEnd,
486
- visible: true,
491
+ // For background inner nodes: propagate start/end as-is so parent
492
+ // back-propagation fills the correct scene duration. The effect's
493
+ // durationInSeconds (animation spec) controls animation timing.
494
+ start: isOutermost && isBgNoEnd ? innerStream.start : effStart,
495
+ end: isOutermost && isBgNoEnd ? undefined : effEnd,
496
+ visible: innerStream.visible ?? true,
487
497
  ...pickOn(node),
488
498
  } as Effect;
489
499
  }
@@ -492,7 +502,10 @@ function wrapWithEffects(
492
502
  }
493
503
 
494
504
  function compileLeaf(node: Exclude<DescriptiveNode, DescriptiveContainer | DescriptiveScene | DescriptiveInclude>, ctx: CompileContext, parentKind: "series" | "parallel" | "transitionSeries"): CompileResult {
495
- const id = node.id ?? uid();
505
+ // Only set id when explicitly provided — auto-generated uids would register
506
+ // in EventContext and may create invalid JS identifiers (starting with digit).
507
+ const id = node.id;
508
+ const hasExplicitId = node.id != null;
496
509
 
497
510
  // Background nodes without explicit duration/endAt: let parent fill timing later
498
511
  const hasOwnDuration = typeof node.duration === "number" || typeof node.endAt === "number";
@@ -506,7 +519,7 @@ function compileLeaf(node: Exclude<DescriptiveNode, DescriptiveContainer | Descr
506
519
  const end = duration != null ? start! + duration : undefined;
507
520
 
508
521
  const base = {
509
- id,
522
+ ...(id ? { id } : {}),
510
523
  style: node.style,
511
524
  visible: node.visible ?? true,
512
525
  isBackground: node.isBackground,
@@ -557,11 +570,10 @@ function compileLeaf(node: Exclude<DescriptiveNode, DescriptiveContainer | Descr
557
570
  "type", "jsx", "id", "instruction", "style", "visible",
558
571
  "isBackground", "duration", "start", "on",
559
572
  ]);
560
- const bindings: Record<string, string> = {};
573
+ const bindings: Record<string, unknown> = {};
561
574
  for (const key of Object.keys(node)) {
562
575
  if (!KNOWN_COMPONENT_KEYS.has(key)) {
563
- const val = (node as any)[key];
564
- if (typeof val === "string") bindings[key] = val;
576
+ bindings[key] = (node as any)[key];
565
577
  }
566
578
  }
567
579
 
@@ -690,14 +702,27 @@ function compileScene(
690
702
  : aggregateDuration(compiledChildren, sceneKind, resolved.time);
691
703
  const localDuration = Math.max(node.duration ?? 0, sceneContentDuration);
692
704
 
693
- // Back-propagate parent duration to background children without own timing
694
- for (const c of compiledChildren) {
695
- if (c.stream.isBackground && c.stream.end == null) {
696
- c.stream.end = localDuration;
697
- c.stream.durationInSeconds = localDuration;
698
- if (c.stream.start == null) c.stream.start = 0;
705
+ // Back-propagate parent duration to nodes without own timing.
706
+ // Also recurses into effect wrappers so nested background children
707
+ // (wrapped by effects) get the correct scene duration.
708
+ function backpropagate(stream: Record<string, any>, dur: number): void {
709
+ if (stream.end == null) {
710
+ stream.end = dur;
711
+ if (stream.durationInSeconds == null) {
712
+ stream.durationInSeconds = dur;
713
+ }
714
+ if (stream.start == null) stream.start = 0;
715
+ }
716
+ // Recursively walk into effect wrapper children
717
+ if (stream.type === "effect" && Array.isArray(stream.children)) {
718
+ for (const child of stream.children) {
719
+ backpropagate(child, dur);
720
+ }
699
721
  }
700
722
  }
723
+ for (const c of compiledChildren) {
724
+ backpropagate(c.stream, localDuration);
725
+ }
701
726
 
702
727
  const start = parentKind === "parallel" ? Math.max(0, node.start ?? 0) : 0;
703
728
  const end = start + localDuration;
@@ -837,14 +862,24 @@ function compileContainer(node: DescriptiveContainer, ctx: CompileContext, paren
837
862
  const children = compileChildren(node.children, ctx, node.type);
838
863
  const duration = aggregateDuration(children, node.type, resolved.time);
839
864
 
840
- // Back-propagate parent duration to background children without own timing
841
- for (const c of children) {
842
- if (c.stream.isBackground && c.stream.end == null) {
843
- c.stream.end = duration;
844
- c.stream.durationInSeconds = duration;
845
- if (c.stream.start == null) c.stream.start = 0;
865
+ // Back-propagate parent duration to nodes without own timing.
866
+ function backpropagate(stream: Record<string, any>, dur: number): void {
867
+ if (stream.end == null) {
868
+ stream.end = dur;
869
+ if (stream.durationInSeconds == null) {
870
+ stream.durationInSeconds = dur;
871
+ }
872
+ if (stream.start == null) stream.start = 0;
873
+ }
874
+ if (stream.type === "effect" && Array.isArray(stream.children)) {
875
+ for (const child of stream.children) {
876
+ backpropagate(child, dur);
877
+ }
846
878
  }
847
879
  }
880
+ for (const c of children) {
881
+ backpropagate(c.stream, duration);
882
+ }
848
883
 
849
884
  const stream: Folder = {
850
885
  id,
@@ -38,6 +38,7 @@ const TYPE_TOKENS: Record<string, string> = {
38
38
  video: "video",
39
39
  audio: "audio",
40
40
  component: "component",
41
+ event: "event",
41
42
  rhythm: "rhythm",
42
43
  include: "include",
43
44
  map: "map",
@@ -251,6 +252,23 @@ function parseNodeLine(content: string, lineNum?: number): DescriptiveNode {
251
252
  preserveVariantAttrs(node, attrs);
252
253
  return node;
253
254
  }
255
+ case "event": {
256
+ // Event-only stub: compiles to a component with empty JSX (renders nothing),
257
+ // just fires events on other registered components via `on`.
258
+ const node: DescriptiveComponent = {
259
+ type: "component",
260
+ id: attrs.id as any,
261
+ jsx: "",
262
+ duration: attrs.duration as any,
263
+ start: attrs.start as any,
264
+ instruction: attrs.instruction as any,
265
+ style: attrs.style as any,
266
+ effects: attrs.effects as any,
267
+ on: attrs.on as any,
268
+ };
269
+ preserveVariantAttrs(node, attrs);
270
+ return node;
271
+ }
254
272
  case "rhythm": {
255
273
  const src = firstPositional ?? (attrs.src as string | undefined);
256
274
  if (!src) throw new DslError("rhythm requires src", ctx);
@@ -122,7 +122,7 @@ var require_react_production = __commonJS({
122
122
  function cloneAndReplaceKey(oldElement, newKey) {
123
123
  return ReactElement(oldElement.type, newKey, oldElement.props);
124
124
  }
125
- function isValidElement(object4) {
125
+ function isValidElement2(object4) {
126
126
  return "object" === typeof object4 && null !== object4 && object4.$$typeof === REACT_ELEMENT_TYPE;
127
127
  }
128
128
  function escape3(key) {
@@ -189,7 +189,7 @@ var require_react_production = __commonJS({
189
189
  if (invokeCallback)
190
190
  return callback = callback(children2), invokeCallback = "" === nameSoFar ? "." + getElementKey(children2, 0) : nameSoFar, isArrayImpl(callback) ? (escapedPrefix = "", null != invokeCallback && (escapedPrefix = invokeCallback.replace(userProvidedKeyEscapeRegex, "$&/") + "/"), mapIntoArray(callback, array5, escapedPrefix, "", function(c5) {
191
191
  return c5;
192
- })) : null != callback && (isValidElement(callback) && (callback = cloneAndReplaceKey(
192
+ })) : null != callback && (isValidElement2(callback) && (callback = cloneAndReplaceKey(
193
193
  callback,
194
194
  escapedPrefix + (null == callback.key || children2 && children2.key === callback.key ? "" : ("" + callback.key).replace(
195
195
  userProvidedKeyEscapeRegex,
@@ -298,7 +298,7 @@ var require_react_production = __commonJS({
298
298
  }) || [];
299
299
  },
300
300
  only: function(children2) {
301
- if (!isValidElement(children2))
301
+ if (!isValidElement2(children2))
302
302
  throw Error(
303
303
  "React.Children.only expected to receive a single React element child."
304
304
  );
@@ -385,7 +385,7 @@ var require_react_production = __commonJS({
385
385
  exports2.forwardRef = function(render8) {
386
386
  return { $$typeof: REACT_FORWARD_REF_TYPE, render: render8 };
387
387
  };
388
- exports2.isValidElement = isValidElement;
388
+ exports2.isValidElement = isValidElement2;
389
389
  exports2.lazy = function(ctor) {
390
390
  return {
391
391
  $$typeof: REACT_LAZY_TYPE,
@@ -225416,10 +225416,100 @@ var mermaid_default = mermaid;
225416
225416
  // src/components/Mermaid.tsx
225417
225417
  var import_jsx_runtime60 = __toESM(require_jsx_runtime(), 1);
225418
225418
  var initialized = false;
225419
- function Mermaid({ children: children2, source = children2, theme = "dark", className: className2 }) {
225419
+ function extractText(x7) {
225420
+ if (typeof x7 === "string") return x7;
225421
+ if (Array.isArray(x7)) return x7.map(extractText).join("");
225422
+ if (x7 && typeof x7 === "object" && "props" in x7) {
225423
+ return extractText(x7.props?.children);
225424
+ }
225425
+ return "";
225426
+ }
225427
+ function findNodeGroup(svg4, name2) {
225428
+ const byId = svg4.querySelector(`[id*="-${CSS.escape(name2)}-"]`);
225429
+ if (byId) {
225430
+ let el = byId;
225431
+ while (el && el.tagName !== "g") el = el.parentElement;
225432
+ return el || byId;
225433
+ }
225434
+ for (const t4 of svg4.querySelectorAll("text")) {
225435
+ const text10 = t4.textContent?.trim() ?? "";
225436
+ if (text10.startsWith(name2)) {
225437
+ let el = t4;
225438
+ while (el && el.tagName !== "g") el = el.parentElement;
225439
+ return el || t4;
225440
+ }
225441
+ }
225442
+ for (const t4 of svg4.querySelectorAll("title")) {
225443
+ if ((t4.textContent?.trim() ?? "").startsWith(name2) && t4.parentElement) {
225444
+ return t4.parentElement;
225445
+ }
225446
+ }
225447
+ return null;
225448
+ }
225449
+ function applyEdgeAnimation(svg4, spec) {
225450
+ if (!spec) return;
225451
+ const allEdges = Array.from(svg4.querySelectorAll(
225452
+ 'path[id*="-L_"]'
225453
+ ));
225454
+ if (spec === true) {
225455
+ for (const path5 of allEdges) {
225456
+ path5.classList.add("edge-animated");
225457
+ }
225458
+ return;
225459
+ }
225460
+ for (const pattern of spec) {
225461
+ const match3 = pattern.match(/^(\w+)\s*->\s*(\w+)$/);
225462
+ if (!match3) continue;
225463
+ const source = match3[1];
225464
+ const target = match3[2];
225465
+ const suffix = `L_${source}_${target}_`;
225466
+ for (const path5 of allEdges) {
225467
+ if (path5.id.includes(suffix)) {
225468
+ path5.classList.add("edge-animated");
225469
+ }
225470
+ }
225471
+ }
225472
+ }
225473
+ function Mermaid({
225474
+ children: children2,
225475
+ source: sourceProp,
225476
+ theme = "dark",
225477
+ className: className2,
225478
+ style: style4,
225479
+ highlight,
225480
+ animateEdges
225481
+ }) {
225420
225482
  const ref2 = React8.useRef(null);
225421
- const [handle2] = React8.useState(() => delayRender("Mermaid rendering"));
225483
+ const renderedRef = React8.useRef(false);
225484
+ const styleRef = React8.useRef(null);
225422
225485
  React8.useEffect(() => {
225486
+ if (!styleRef.current) {
225487
+ const el = document.createElement("style");
225488
+ el.textContent = `
225489
+ @keyframes mermaid-edge-flow {
225490
+ to { stroke-dashoffset: -24; }
225491
+ }
225492
+ .edge-animated {
225493
+ stroke-dasharray: 8 6 !important;
225494
+ animation: mermaid-edge-flow 0.5s linear infinite !important;
225495
+ }
225496
+ `;
225497
+ document.head.appendChild(el);
225498
+ styleRef.current = el;
225499
+ }
225500
+ return () => {
225501
+ if (styleRef.current) {
225502
+ styleRef.current.remove();
225503
+ styleRef.current = null;
225504
+ }
225505
+ };
225506
+ }, []);
225507
+ const source = React8.useMemo(
225508
+ () => extractText(sourceProp ?? children2),
225509
+ [sourceProp, children2]
225510
+ );
225511
+ React8.useEffect(() => {
225512
+ let cancelled = false;
225423
225513
  if (!source || !ref2.current) return;
225424
225514
  if (!initialized) {
225425
225515
  mermaid_default.initialize({
@@ -225431,52 +225521,186 @@ function Mermaid({ children: children2, source = children2, theme = "dark", clas
225431
225521
  }
225432
225522
  const id39 = "mmd-" + Math.random().toString(36).slice(2, 10);
225433
225523
  mermaid_default.render(id39, source).then((result) => {
225434
- if (ref2.current) ref2.current.innerHTML = result.svg;
225435
- continueRender(handle2);
225524
+ if (cancelled || !ref2.current) return;
225525
+ ref2.current.innerHTML = result.svg;
225526
+ const svg4 = ref2.current.querySelector("svg");
225527
+ if (svg4) {
225528
+ svg4.removeAttribute("width");
225529
+ svg4.removeAttribute("height");
225530
+ svg4.style.width = "100%";
225531
+ svg4.style.height = "100%";
225532
+ if (highlight) {
225533
+ const names = Array.isArray(highlight) ? highlight : [highlight];
225534
+ for (const name2 of names) {
225535
+ const node3 = findNodeGroup(svg4, name2);
225536
+ if (node3) node3.classList.add("highlight");
225537
+ }
225538
+ }
225539
+ applyEdgeAnimation(svg4, animateEdges);
225540
+ }
225541
+ renderedRef.current = true;
225436
225542
  }).catch((err) => {
225543
+ if (cancelled) return;
225437
225544
  console.error("Mermaid error:", err);
225438
225545
  if (ref2.current) {
225439
225546
  ref2.current.innerHTML = `<div style="color:#f87171;padding:1em;border:2px dashed #f87171;border-radius:8px;font-family:monospace;font-size:14px;">
225440
225547
  <strong>\u26A0 Mermaid Error</strong><br/>${String(err).replace(/</g, "&lt;").replace(/>/g, "&gt;")}
225441
225548
  </div>`;
225442
225549
  }
225443
- continueRender(handle2);
225444
225550
  });
225445
- }, [source, theme, handle2]);
225446
- return /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
225447
- "div",
225448
- {
225449
- ref: ref2,
225450
- className: className2
225551
+ return () => {
225552
+ cancelled = true;
225553
+ };
225554
+ }, [source, theme]);
225555
+ React8.useEffect(() => {
225556
+ const svg4 = ref2.current?.querySelector("svg");
225557
+ if (!svg4) return;
225558
+ svg4.querySelectorAll(".highlight").forEach((el) => {
225559
+ el.classList.remove("highlight");
225560
+ });
225561
+ const names = Array.isArray(highlight) ? highlight : highlight ? [highlight] : [];
225562
+ for (const name2 of names) {
225563
+ const node3 = findNodeGroup(svg4, name2);
225564
+ if (node3) {
225565
+ node3.classList.add("highlight");
225566
+ }
225451
225567
  }
225452
- );
225568
+ }, [highlight]);
225569
+ React8.useEffect(() => {
225570
+ const svg4 = ref2.current?.querySelector("svg");
225571
+ if (!svg4) return;
225572
+ svg4.querySelectorAll(".edge-animated").forEach((el) => {
225573
+ el.classList.remove("edge-animated");
225574
+ });
225575
+ applyEdgeAnimation(svg4, animateEdges);
225576
+ }, [animateEdges]);
225577
+ const containerStyle3 = {
225578
+ display: "flex",
225579
+ justifyContent: "center",
225580
+ alignItems: "center",
225581
+ width: "100%",
225582
+ height: "100%",
225583
+ ...style4
225584
+ };
225585
+ return /* @__PURE__ */ (0, import_jsx_runtime60.jsx)("div", { ref: ref2, className: className2, style: containerStyle3 });
225453
225586
  }
225454
225587
 
225455
225588
  // src/components/Markdown.tsx
225456
225589
  var import_jsx_runtime61 = __toESM(require_jsx_runtime(), 1);
225457
- function Markdown2({ children: children2, source = children2, className: className2, plugins, components: components3 }) {
225458
- return /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("div", { className: className2, children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
225590
+ var ListCtx = React9.createContext([]);
225591
+ function OrderedList({
225592
+ children: children2,
225593
+ ...props
225594
+ }) {
225595
+ const hl = React9.useContext(ListCtx);
225596
+ const items = React9.Children.toArray(children2).filter(
225597
+ (c5) => React9.isValidElement(c5) && c5.type === "li"
225598
+ );
225599
+ return /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("ol", { ...props, children: items.map((child, i5) => {
225600
+ if (hl.includes(i5)) {
225601
+ return React9.cloneElement(child, { className: "highlight-list-item", key: child.key });
225602
+ }
225603
+ return child;
225604
+ }) });
225605
+ }
225606
+ function UnorderedList({
225607
+ children: children2,
225608
+ ...props
225609
+ }) {
225610
+ const hl = React9.useContext(ListCtx);
225611
+ const items = React9.Children.toArray(children2).filter(
225612
+ (c5) => React9.isValidElement(c5) && c5.type === "li"
225613
+ );
225614
+ return /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("ul", { ...props, children: items.map((child, i5) => {
225615
+ if (hl.includes(i5)) {
225616
+ return React9.cloneElement(child, { className: "highlight-list-item", key: child.key });
225617
+ }
225618
+ return child;
225619
+ }) });
225620
+ }
225621
+ function Markdown2({
225622
+ children: children2,
225623
+ source = children2,
225624
+ className: className2,
225625
+ plugins,
225626
+ components: propComponents,
225627
+ highlight
225628
+ }) {
225629
+ const hl = React9.useMemo(() => highlight ?? [], [highlight]);
225630
+ React9.useEffect(() => {
225631
+ const id39 = "markcut-markdown-defaults";
225632
+ if (document.getElementById(id39)) return;
225633
+ const el = document.createElement("style");
225634
+ el.id = id39;
225635
+ el.textContent = `
225636
+ .highlight-list-item {
225637
+ background: rgba(255, 215, 0, 0.15);
225638
+ border-left: 3px solid #ffd700;
225639
+ padding-left: 8px;
225640
+ border-radius: 0 4px 4px 0;
225641
+ }
225642
+ .slide {
225643
+ display: flex;
225644
+ flex-direction: column;
225645
+ align-items: center;
225646
+ justify-content: center;
225647
+ padding: 24px;
225648
+ text-align: center;
225649
+ }
225650
+ .slide h1 { font-size: 2em; margin: 0.4em 0; font-weight: 700; }
225651
+ .slide h2 { font-size: 1.6em; margin: 0.35em 0; font-weight: 600; }
225652
+ .slide h3 { font-size: 1.3em; margin: 0.3em 0; font-weight: 600; }
225653
+ .slide p { margin: 0.6em 0; line-height: 1.6; }
225654
+ .slide ul, .slide ol { margin: 0.5em 0; padding-left: 1.5em; text-align: left; }
225655
+ .slide li { margin: 0.3em 0; }
225656
+ .slide blockquote {
225657
+ margin: 0.6em 0;
225658
+ padding: 0.4em 1em;
225659
+ border-left: 3px solid rgba(255,255,255,.3);
225660
+ font-style: italic;
225661
+ opacity: .85;
225662
+ }
225663
+ .slide code {
225664
+ background: rgba(255,255,255,.08);
225665
+ padding: 0.15em 0.4em;
225666
+ border-radius: 4px;
225667
+ font-size: 0.9em;
225668
+ }
225669
+ .slide pre { margin: 0.6em 0; text-align: left; width: 100%; }
225670
+ .slide a { color: #4a9eff; text-decoration: none; }
225671
+ .slide a:hover { text-decoration: underline; }
225672
+ `;
225673
+ document.head.appendChild(el);
225674
+ return () => {
225675
+ document.getElementById(id39)?.remove();
225676
+ };
225677
+ }, []);
225678
+ const mergedComponents = React9.useMemo(
225679
+ () => ({
225680
+ ...propComponents,
225681
+ ol: OrderedList,
225682
+ ul: UnorderedList,
225683
+ pre: ({ children: preChildren }) => {
225684
+ const code4 = React9.Children.toArray(preChildren)[0];
225685
+ if (code4?.props?.className === "language-mermaid") {
225686
+ return /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(Mermaid, { source: String(code4.props.children) });
225687
+ }
225688
+ if (propComponents?.pre) {
225689
+ return propComponents.pre({ children: preChildren });
225690
+ }
225691
+ return /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("pre", { children: preChildren });
225692
+ }
225693
+ }),
225694
+ [propComponents]
225695
+ );
225696
+ return /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(ListCtx.Provider, { value: hl, children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("div", { className: className2, children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
225459
225697
  Markdown,
225460
225698
  {
225461
225699
  remarkPlugins: React9.useMemo(() => [remarkGfm, ...plugins || []], [plugins]),
225462
- components: React9.useMemo(
225463
- () => ({
225464
- ...components3,
225465
- pre: ({ children: children3 }) => {
225466
- const code4 = React9.Children.toArray(children3)[0];
225467
- if (code4?.props?.className === "language-mermaid") {
225468
- return /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(Mermaid, { source: String(code4.props.children) });
225469
- } else if (components3.pre) {
225470
- return components3.pre({ children: children3 });
225471
- }
225472
- return /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("pre", { children: children3 });
225473
- }
225474
- }),
225475
- [components3]
225476
- ),
225700
+ components: mergedComponents,
225477
225701
  children: source
225478
225702
  }
225479
- ) });
225703
+ ) }) });
225480
225704
  }
225481
225705
 
225482
225706
  // src/utils/component-import-map.ts
@@ -225588,6 +225812,8 @@ function EventProvider({ children: children2 }) {
225588
225812
  const keys4 = Object.keys(scope);
225589
225813
  const vals = Object.values(scope);
225590
225814
  try {
225815
+ const codeChars = Array.from(code4).map((c5) => c5.charCodeAt(0));
225816
+ console.log(`EventContext.debug: keys=${JSON.stringify(keys4)}, codeLen=${code4.length}, codeChars=${JSON.stringify(codeChars)}, codeStr=${JSON.stringify(code4)}`);
225591
225817
  const fn3 = new Function(...keys4, code4);
225592
225818
  fn3(...vals);
225593
225819
  console.info(`Event evaluation succeeded: "${code4}"`);
@@ -227900,7 +228126,13 @@ var clockWipe = (props) => {
227900
228126
 
227901
228127
  // src/utils/index.ts
227902
228128
  function uid() {
227903
- return Math.random().toString(36).slice(2, 10);
228129
+ const raw = Math.random().toString(36).slice(2, 10);
228130
+ const first3 = raw[0];
228131
+ if (/^[0-9]/.test(first3)) {
228132
+ const letter = String.fromCharCode(97 + Math.floor(Math.random() * 26));
228133
+ return letter + raw;
228134
+ }
228135
+ return raw;
227904
228136
  }
227905
228137
  var KEBAB = /[^a-zA-Z0-9_-]+/g;
227906
228138
  function toClassName(s3) {
@@ -227966,7 +228198,7 @@ function getDurationInSeconds(stream3, update2 = true) {
227966
228198
  for (const child of stream3.children) {
227967
228199
  getDurationInSeconds(child, update2);
227968
228200
  }
227969
- const visible = stream3.children.filter((c5) => !c5.isBackground);
228201
+ const visible = stream3.type === "effect" ? stream3.children : stream3.children.filter((c5) => !c5.isBackground);
227970
228202
  if (stream3.isSeries) {
227971
228203
  const overlap = stream3.transition ? stream3.transitionTime ?? 0.5 : 0;
227972
228204
  for (let i5 = 0; i5 < visible.length; i5++) {
@@ -239667,7 +239899,30 @@ function ComponentLeaf({ stream: stream3 }) {
239667
239899
  () => ({ ...components3, ...stream3.data, ...eventState }),
239668
239900
  [stream3.data, components3, eventState]
239669
239901
  );
239670
- if (!stream3.jsx) return null;
239902
+ if (!stream3.jsx) {
239903
+ const start5 = stream3.start ?? 0;
239904
+ const end3 = stream3.end ?? start5 + (stream3.duration ?? 1);
239905
+ const durFrames2 = Math.max(1, Math.floor(fps * (end3 - start5)));
239906
+ return /* @__PURE__ */ (0, import_jsx_runtime87.jsx)(
239907
+ Sequence,
239908
+ {
239909
+ durationInFrames: durFrames2,
239910
+ from: Math.floor(fps * start5),
239911
+ layout: "none",
239912
+ children: /* @__PURE__ */ (0, import_jsx_runtime87.jsx)(
239913
+ EventAwareComponent,
239914
+ {
239915
+ jsx: "",
239916
+ components: components3,
239917
+ data: bindings,
239918
+ action: { start: start5, end: end3 },
239919
+ durFrames: durFrames2,
239920
+ on: stream3.on
239921
+ }
239922
+ )
239923
+ }
239924
+ );
239925
+ }
239671
239926
  const start4 = stream3.start ?? 0;
239672
239927
  const end2 = stream3.end ?? start4 + (stream3.duration ?? 1);
239673
239928
  const durFrames = Math.max(1, Math.floor(fps * (end2 - start4)));
@@ -239700,6 +239955,7 @@ function EventAwareComponent({
239700
239955
  on: on3
239701
239956
  }) {
239702
239957
  useFrameEvents(on3, durFrames);
239958
+ if (!jsx84) return null;
239703
239959
  return /* @__PURE__ */ (0, import_jsx_runtime87.jsx)(
239704
239960
  TweenedJsxParser,
239705
239961
  {
@@ -242999,15 +243255,17 @@ function EffectWrapper({
242999
243255
  const end2 = Math.ceil(endSec * fps);
243000
243256
  const durationInFrames = end2 - start4;
243001
243257
  if (durationInFrames <= 0) return [];
243258
+ const animDurationSec = stream3.animationDurationSeconds ?? stream3.durationInSeconds ?? durationInFrames / fps;
243259
+ const animDurationFrames = Math.ceil(animDurationSec * fps);
243002
243260
  const animation2 = stream3.animation;
243003
243261
  const timingFn = stream3.animationTimingFunction;
243004
243262
  const iterCount = stream3.animationIterationCount ?? 1;
243005
243263
  const style4 = cssJS(stream3.style) ?? {};
243006
243264
  let currentFrame = frame2;
243007
- if (iterCount > 0 && durationInFrames > 0) {
243008
- const iteration = Math.floor((frame2 - start4) / durationInFrames);
243265
+ if (iterCount > 0 && animDurationFrames > 0) {
243266
+ const iteration = Math.floor((frame2 - start4) / animDurationFrames);
243009
243267
  if (iteration < iterCount) {
243010
- currentFrame = start4 + (frame2 - start4) % durationInFrames;
243268
+ currentFrame = start4 + (frame2 - start4) % animDurationFrames;
243011
243269
  }
243012
243270
  }
243013
243271
  if (currentFrame >= start4 && currentFrame < end2) {
@@ -243017,7 +243275,7 @@ function EffectWrapper({
243017
243275
  if (config4) {
243018
243276
  const animStyle = interpolateKeyframes(config4, actionFrame, {
243019
243277
  fps,
243020
- durationInSeconds: durationInFrames / fps,
243278
+ durationInSeconds: animDurationSec,
243021
243279
  timingFunction: timingFn
243022
243280
  });
243023
243281
  if (animStyle) Object.assign(style4, animStyle);
@@ -243025,7 +243283,7 @@ function EffectWrapper({
243025
243283
  }
243026
243284
  }
243027
243285
  return Object.keys(style4).length > 0 ? [style4] : [];
243028
- }, [frame2, fps, startSec, endSec, stream3.animation, stream3.animationTimingFunction, stream3.animationIterationCount, stream3.customKeyframes, stream3.style]);
243286
+ }, [frame2, fps, startSec, endSec, stream3.animation, stream3.animationTimingFunction, stream3.animationIterationCount, stream3.customKeyframes, stream3.style, stream3.animationDurationSeconds, stream3.durationInSeconds]);
243029
243287
  if (styles7.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime92.jsx)(import_jsx_runtime92.Fragment, { children: children2 });
243030
243288
  return /* @__PURE__ */ (0, import_jsx_runtime92.jsx)(
243031
243289
  "div",
@@ -243070,7 +243328,7 @@ function FolderLeaf({ stream: stream3 }) {
243070
243328
  const isRoot = stream3.id === "root";
243071
243329
  const visibleChildren = stream3.children.filter((c5) => c5.visible !== false);
243072
243330
  const bgChildren = visibleChildren.filter((c5) => c5.isBackground);
243073
- const seriesChildren = isSeries ? visibleChildren.filter((c5) => !c5.isBackground) : visibleChildren;
243331
+ const seriesChildren = visibleChildren.filter((c5) => !c5.isBackground);
243074
243332
  const allAudio = seriesChildren.length > 0 && seriesChildren.every((c5) => c5.type === "audio");
243075
243333
  const effectiveTransition = allAudio ? void 0 : transition2;
243076
243334
  const TypedSeries = React47.useMemo(() => {
@@ -257457,11 +257715,12 @@ var image3 = base.extend({
257457
257715
  var component2 = base.extend({
257458
257716
  type: external_exports.literal("component").default("component"),
257459
257717
  jsx: external_exports.string().describe("usage JSX expression compiled at runtime; tag names resolved from imports"),
257460
- data: external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean()])).optional().describe("extra variables (e.g. from ~~~md source code fences) available in JSX scope")
257718
+ data: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("extra variables (e.g. from ~~~md source code fences) available in JSX scope")
257461
257719
  });
257462
257720
  var effect = base.extend({
257463
257721
  type: external_exports.literal("effect").default("effect"),
257464
257722
  animation: external_exports.string().optional().describe("builtin keyframe name or 'custom'"),
257723
+ animationDurationSeconds: external_exports.number().optional().describe("animation duration (separate from wrapper durationInSeconds which getDurationInSeconds may overwrite)"),
257465
257724
  animationTimingFunction: external_exports.enum(["linear", "ease", "ease-in", "ease-out", "ease-in-out"]).optional(),
257466
257725
  animationIterationCount: external_exports.number().default(1),
257467
257726
  customKeyframes: external_exports.record(external_exports.string(), external_exports.record(external_exports.string(), external_exports.string())).optional().describe('inline keyframes: { "0": { opacity: "0" }, "100": { opacity: "1" } }'),