@particle-academy/fancy-slides 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -94,9 +94,18 @@ element.animation = {
94
94
  duration: 500, // ms
95
95
  delay: 0, // ms
96
96
  order: 0, // build order, ascending; ties broken by element index
97
+ byParagraph: false, // text only — reveal one line/bullet per click
97
98
  };
98
99
  ```
99
100
 
101
+ For a **text** element you can set `byParagraph: true` to reveal it one
102
+ paragraph at a time (PowerPoint/Google Slides "By paragraph"). The element's
103
+ `content` is split on `"\n"` (a trailing empty line is dropped) and expands into
104
+ one build per line — the first line uses the element's own `trigger`, every
105
+ subsequent line reveals on a fresh click. Each markdown line renders through the
106
+ normal path, so a `- …` bullet line builds in as its own item. Toggle it from
107
+ the Build tab when a text element has an effect selected.
108
+
100
109
  A slide's builds group into *click steps*: the first build and every
101
110
  `on-click` build opens a new step; `with-prev` plays alongside that step's lead
102
111
  and `after-prev` follows it. In `SlideViewer` / `PresenterView`, → / Space /
package/dist/index.cjs CHANGED
@@ -74,6 +74,15 @@ function cn(...parts) {
74
74
  }
75
75
 
76
76
  // src/utils/builds.ts
77
+ function splitParagraphs(content) {
78
+ const lines = content.split("\n");
79
+ if (lines.length > 1 && lines[lines.length - 1] === "") lines.pop();
80
+ return lines;
81
+ }
82
+ function isByParagraph(element, animation) {
83
+ if (!animation.byParagraph || element.type !== "text") return false;
84
+ return splitParagraphs(element.content).length > 1;
85
+ }
77
86
  var DEFAULT_BUILD_DURATION = 500;
78
87
  function collectBuilds(slide) {
79
88
  if (!slide) return [];
@@ -83,12 +92,25 @@ function collectBuilds(slide) {
83
92
  builds.push({ element, animation: element.animation, index });
84
93
  }
85
94
  });
86
- return builds.sort((a, b) => {
95
+ const ordered = builds.sort((a, b) => {
87
96
  const ao = a.animation.order ?? 0;
88
97
  const bo = b.animation.order ?? 0;
89
98
  if (ao !== bo) return ao - bo;
90
99
  return a.index - b.index;
91
100
  });
101
+ const expanded = [];
102
+ for (const build of ordered) {
103
+ if (isByParagraph(build.element, build.animation)) {
104
+ const paras = splitParagraphs(build.element.content);
105
+ paras.forEach((_, paraIndex) => {
106
+ const animation = paraIndex === 0 ? build.animation : { ...build.animation, trigger: "on-click" };
107
+ expanded.push({ element: build.element, animation, index: build.index, paraIndex });
108
+ });
109
+ } else {
110
+ expanded.push(build);
111
+ }
112
+ }
113
+ return expanded;
92
114
  }
93
115
  function buildSteps(slide) {
94
116
  const builds = collectBuilds(slide);
@@ -112,7 +134,9 @@ function visibleElementIds(slide, buildStep) {
112
134
  const steps = buildSteps(slide);
113
135
  const stepOfElement = /* @__PURE__ */ new Map();
114
136
  steps.forEach((step, i) => {
115
- for (const b of step.builds) stepOfElement.set(b.element.id, i + 1);
137
+ for (const b of step.builds) {
138
+ if (!stepOfElement.has(b.element.id)) stepOfElement.set(b.element.id, i + 1);
139
+ }
116
140
  });
117
141
  for (const element of slide.elements) {
118
142
  const revealStep = stepOfElement.get(element.id);
@@ -124,6 +148,25 @@ function visibleElementIds(slide, buildStep) {
124
148
  }
125
149
  return visible;
126
150
  }
151
+ function paragraphReveals(slide, buildStep) {
152
+ const out = /* @__PURE__ */ new Map();
153
+ if (!slide) return out;
154
+ const steps = buildSteps(slide);
155
+ steps.forEach((step, i) => {
156
+ const stepNum = i + 1;
157
+ for (const b of step.builds) {
158
+ if (b.paraIndex === void 0) continue;
159
+ const fired = buildStep >= stepNum;
160
+ const prev = out.get(b.element.id) ?? { revealed: 0 };
161
+ if (fired) {
162
+ prev.revealed = Math.max(prev.revealed, b.paraIndex + 1);
163
+ if (stepNum === buildStep) prev.firingParaIndex = b.paraIndex;
164
+ }
165
+ out.set(b.element.id, prev);
166
+ }
167
+ });
168
+ return out;
169
+ }
127
170
  function buildsForStep(slide, buildStep) {
128
171
  const steps = buildSteps(slide);
129
172
  const step = steps[buildStep - 1];
@@ -145,13 +188,95 @@ function stepDelays(builds) {
145
188
  }
146
189
  return delays;
147
190
  }
191
+
192
+ // src/components/Slide/builds-style.ts
193
+ var DEFAULT_BUILD_DURATION2 = 500;
194
+ var EASE = "cubic-bezier(0.16, 1, 0.3, 1)";
195
+ function buildEnterStyle(animation, effectiveDelay) {
196
+ const duration = animation.duration ?? DEFAULT_BUILD_DURATION2;
197
+ const dir = animation.direction ?? "left";
198
+ let name;
199
+ switch (animation.effect) {
200
+ case "fade":
201
+ name = "fs-build-fade";
202
+ break;
203
+ case "zoom":
204
+ name = "fs-build-zoom";
205
+ break;
206
+ case "fly-in":
207
+ name = `fs-build-fly-${dir}`;
208
+ break;
209
+ case "wipe":
210
+ name = `fs-build-wipe-${dir}`;
211
+ break;
212
+ default:
213
+ name = "fs-build-fade";
214
+ }
215
+ return {
216
+ animationName: name,
217
+ animationDuration: `${duration}ms`,
218
+ animationDelay: `${effectiveDelay}ms`,
219
+ animationTimingFunction: EASE,
220
+ animationFillMode: "both"
221
+ };
222
+ }
223
+ var BUILD_KEYFRAMES = `
224
+ @media (prefers-reduced-motion: reduce) {
225
+ .fs-build-enter { animation: none !important; }
226
+ }
227
+ @media (prefers-reduced-motion: no-preference) {
228
+ @keyframes fs-build-fade {
229
+ from { opacity: 0; }
230
+ to { opacity: 1; }
231
+ }
232
+ @keyframes fs-build-zoom {
233
+ from { opacity: 0; transform: scale(0.8); }
234
+ to { opacity: 1; transform: scale(1); }
235
+ }
236
+ @keyframes fs-build-fly-left {
237
+ from { opacity: 0; transform: translateX(-24%); }
238
+ to { opacity: 1; transform: translateX(0); }
239
+ }
240
+ @keyframes fs-build-fly-right {
241
+ from { opacity: 0; transform: translateX(24%); }
242
+ to { opacity: 1; transform: translateX(0); }
243
+ }
244
+ @keyframes fs-build-fly-up {
245
+ from { opacity: 0; transform: translateY(24%); }
246
+ to { opacity: 1; transform: translateY(0); }
247
+ }
248
+ @keyframes fs-build-fly-down {
249
+ from { opacity: 0; transform: translateY(-24%); }
250
+ to { opacity: 1; transform: translateY(0); }
251
+ }
252
+ /* wipe: clip-path inset reveals from the named edge toward the opposite one.
253
+ inset(top right bottom left) \u2014 start fully clipped on the far side. */
254
+ @keyframes fs-build-wipe-left {
255
+ from { clip-path: inset(0 100% 0 0); }
256
+ to { clip-path: inset(0 0 0 0); }
257
+ }
258
+ @keyframes fs-build-wipe-right {
259
+ from { clip-path: inset(0 0 0 100%); }
260
+ to { clip-path: inset(0 0 0 0); }
261
+ }
262
+ @keyframes fs-build-wipe-up {
263
+ from { clip-path: inset(100% 0 0 0); }
264
+ to { clip-path: inset(0 0 0 0); }
265
+ }
266
+ @keyframes fs-build-wipe-down {
267
+ from { clip-path: inset(0 0 100% 0); }
268
+ to { clip-path: inset(0 0 0 0); }
269
+ }
270
+ }
271
+ `;
148
272
  function TextElementRenderer({
149
273
  element,
150
274
  theme,
151
275
  slideWidthPx,
152
276
  editing = false,
153
277
  selected = false,
154
- onContentChange
278
+ onContentChange,
279
+ paraReveal
155
280
  }) {
156
281
  const t = resolveTheme(theme);
157
282
  const style = element.style ?? {};
@@ -198,30 +323,53 @@ function TextElementRenderer({
198
323
  }
199
324
  );
200
325
  }
326
+ const proseScope = `[data-fs-text-scope="${scopeId}"]`;
327
+ const doubleScope = `${proseScope}${proseScope}`;
328
+ const proseStyle = /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
329
+ ${proseScope} > div { width: 100%; height: 100%; font-size: inherit; }
330
+ ${doubleScope} :is(p, ul, ol, li, blockquote, h1, h2, h3, h4, h5, h6, pre, code, strong, em, a) {
331
+ font-size: inherit;
332
+ }
333
+ ${doubleScope} h1 { font-size: 1.6em; font-weight: 700; }
334
+ ${doubleScope} h2 { font-size: 1.35em; font-weight: 700; }
335
+ ${doubleScope} h3 { font-size: 1.15em; font-weight: 600; }
336
+ ${proseScope} :where(p, ul, ol, h1, h2, h3, h4, h5, h6, pre, blockquote) {
337
+ margin: 0;
338
+ padding: 0;
339
+ }
340
+ ${proseScope} :where(p, li) + :where(p, li, ul, ol) { margin-top: 0.4em; }
341
+ ${proseScope} :where(ul, ol) { padding-left: 1.4em; }
342
+ ${proseScope} :where(strong) { font-weight: ${Math.max(700, weight(style.weight) ?? 400 + 200)}; }
343
+ ${proseScope} :where(a) { color: inherit; text-decoration: underline; }
344
+ ${proseScope} :where(code) { font-family: ${t.fonts?.mono ?? "monospace"}; }
345
+ ` });
346
+ const renderChunk = (content) => format === "plain" ? content : /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContentRenderer, { value: content, format: format === "html" ? "html" : "markdown" });
347
+ if (paraReveal) {
348
+ const paras = splitParagraphs(element.content);
349
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-fs-text-scope": scopeId, style: css, children: [
350
+ proseStyle,
351
+ paras.map((para, i) => {
352
+ if (i >= paraReveal.revealed) return null;
353
+ const firing = i === paraReveal.firingParaIndex && !!element.animation;
354
+ const enter = firing ? buildEnterStyle(element.animation, element.animation.delay ?? 0) : null;
355
+ return /* @__PURE__ */ jsxRuntime.jsx(
356
+ "div",
357
+ {
358
+ className: firing ? "fs-build-enter" : void 0,
359
+ style: { whiteSpace: format === "plain" ? "pre-wrap" : "normal", ...enter },
360
+ "data-fancy-slides-paragraph": i,
361
+ children: renderChunk(para)
362
+ },
363
+ i
364
+ );
365
+ })
366
+ ] });
367
+ }
201
368
  if (format === "plain") {
202
369
  return /* @__PURE__ */ jsxRuntime.jsx("div", { style: css, children: element.content });
203
370
  }
204
- const proseScope = `[data-fs-text-scope="${scopeId}"]`;
205
- const doubleScope = `${proseScope}${proseScope}`;
206
371
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-fs-text-scope": scopeId, style: css, children: [
207
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
208
- ${proseScope} > div { width: 100%; height: 100%; font-size: inherit; }
209
- ${doubleScope} :is(p, ul, ol, li, blockquote, h1, h2, h3, h4, h5, h6, pre, code, strong, em, a) {
210
- font-size: inherit;
211
- }
212
- ${doubleScope} h1 { font-size: 1.6em; font-weight: 700; }
213
- ${doubleScope} h2 { font-size: 1.35em; font-weight: 700; }
214
- ${doubleScope} h3 { font-size: 1.15em; font-weight: 600; }
215
- ${proseScope} :where(p, ul, ol, h1, h2, h3, h4, h5, h6, pre, blockquote) {
216
- margin: 0;
217
- padding: 0;
218
- }
219
- ${proseScope} :where(p, li) + :where(p, li, ul, ol) { margin-top: 0.4em; }
220
- ${proseScope} :where(ul, ol) { padding-left: 1.4em; }
221
- ${proseScope} :where(strong) { font-weight: ${Math.max(700, weight(style.weight) ?? 400 + 200)}; }
222
- ${proseScope} :where(a) { color: inherit; text-decoration: underline; }
223
- ${proseScope} :where(code) { font-family: ${t.fonts?.mono ?? "monospace"}; }
224
- ` }),
372
+ proseStyle,
225
373
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContentRenderer, { value: element.content, format: format === "html" ? "html" : "markdown" })
226
374
  ] });
227
375
  }
@@ -353,87 +501,6 @@ function relativeLuminance(r, g, b) {
353
501
  };
354
502
  return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
355
503
  }
356
-
357
- // src/components/Slide/builds-style.ts
358
- var DEFAULT_BUILD_DURATION2 = 500;
359
- var EASE = "cubic-bezier(0.16, 1, 0.3, 1)";
360
- function buildEnterStyle(animation, effectiveDelay) {
361
- const duration = animation.duration ?? DEFAULT_BUILD_DURATION2;
362
- const dir = animation.direction ?? "left";
363
- let name;
364
- switch (animation.effect) {
365
- case "fade":
366
- name = "fs-build-fade";
367
- break;
368
- case "zoom":
369
- name = "fs-build-zoom";
370
- break;
371
- case "fly-in":
372
- name = `fs-build-fly-${dir}`;
373
- break;
374
- case "wipe":
375
- name = `fs-build-wipe-${dir}`;
376
- break;
377
- default:
378
- name = "fs-build-fade";
379
- }
380
- return {
381
- animationName: name,
382
- animationDuration: `${duration}ms`,
383
- animationDelay: `${effectiveDelay}ms`,
384
- animationTimingFunction: EASE,
385
- animationFillMode: "both"
386
- };
387
- }
388
- var BUILD_KEYFRAMES = `
389
- @media (prefers-reduced-motion: reduce) {
390
- .fs-build-enter { animation: none !important; }
391
- }
392
- @media (prefers-reduced-motion: no-preference) {
393
- @keyframes fs-build-fade {
394
- from { opacity: 0; }
395
- to { opacity: 1; }
396
- }
397
- @keyframes fs-build-zoom {
398
- from { opacity: 0; transform: scale(0.8); }
399
- to { opacity: 1; transform: scale(1); }
400
- }
401
- @keyframes fs-build-fly-left {
402
- from { opacity: 0; transform: translateX(-24%); }
403
- to { opacity: 1; transform: translateX(0); }
404
- }
405
- @keyframes fs-build-fly-right {
406
- from { opacity: 0; transform: translateX(24%); }
407
- to { opacity: 1; transform: translateX(0); }
408
- }
409
- @keyframes fs-build-fly-up {
410
- from { opacity: 0; transform: translateY(24%); }
411
- to { opacity: 1; transform: translateY(0); }
412
- }
413
- @keyframes fs-build-fly-down {
414
- from { opacity: 0; transform: translateY(-24%); }
415
- to { opacity: 1; transform: translateY(0); }
416
- }
417
- /* wipe: clip-path inset reveals from the named edge toward the opposite one.
418
- inset(top right bottom left) \u2014 start fully clipped on the far side. */
419
- @keyframes fs-build-wipe-left {
420
- from { clip-path: inset(0 100% 0 0); }
421
- to { clip-path: inset(0 0 0 0); }
422
- }
423
- @keyframes fs-build-wipe-right {
424
- from { clip-path: inset(0 0 0 100%); }
425
- to { clip-path: inset(0 0 0 0); }
426
- }
427
- @keyframes fs-build-wipe-up {
428
- from { clip-path: inset(100% 0 0 0); }
429
- to { clip-path: inset(0 0 0 0); }
430
- }
431
- @keyframes fs-build-wipe-down {
432
- from { clip-path: inset(0 0 100% 0); }
433
- to { clip-path: inset(0 0 0 0); }
434
- }
435
- }
436
- `;
437
504
  function Slide({
438
505
  slide,
439
506
  theme,
@@ -490,13 +557,16 @@ function Slide({
490
557
  if (steps.length === 0) return null;
491
558
  const revealStep = /* @__PURE__ */ new Map();
492
559
  steps.forEach((step, i) => {
493
- for (const b of step.builds) revealStep.set(b.element.id, i + 1);
560
+ for (const b of step.builds) {
561
+ if (!revealStep.has(b.element.id)) revealStep.set(b.element.id, i + 1);
562
+ }
494
563
  });
495
564
  const driven = buildStep !== void 0;
496
565
  const currentStep = driven ? buildStep : steps.length;
497
566
  const firing = driven ? steps[currentStep - 1] : void 0;
498
567
  const delays = firing ? stepDelays(firing.builds) : /* @__PURE__ */ new Map();
499
- return { revealStep, currentStep, delays };
568
+ const paraReveals = driven ? paragraphReveals(slide, currentStep) : /* @__PURE__ */ new Map();
569
+ return { revealStep, currentStep, delays, paraReveals, driven };
500
570
  }, [editing, slide, buildStep]);
501
571
  return /* @__PURE__ */ jsxRuntime.jsx(SlideContext.Provider, { value: slideContext, children: /* @__PURE__ */ jsxRuntime.jsxs(
502
572
  "div",
@@ -522,12 +592,13 @@ function Slide({
522
592
  let buildHidden = false;
523
593
  let buildAnimation;
524
594
  let buildDelay = 0;
595
+ const paraReveal = buildInfo?.paraReveals.get(element.id);
525
596
  if (buildInfo) {
526
597
  const step = buildInfo.revealStep.get(element.id);
527
598
  if (step !== void 0) {
528
599
  if (buildInfo.currentStep < step) {
529
600
  buildHidden = true;
530
- } else if (buildInfo.currentStep === step && element.animation) {
601
+ } else if (paraReveal) ; else if (buildInfo.currentStep === step && element.animation) {
531
602
  buildAnimation = element.animation;
532
603
  buildDelay = buildInfo.delays.get(element.id) ?? 0;
533
604
  }
@@ -549,7 +620,8 @@ function Slide({
549
620
  onResize: onElementResize,
550
621
  renderElement,
551
622
  buildAnimation,
552
- buildDelay
623
+ buildDelay,
624
+ paraReveal
553
625
  },
554
626
  element.id
555
627
  );
@@ -573,7 +645,8 @@ function SlideElementHost({
573
645
  onResize,
574
646
  renderElement,
575
647
  buildAnimation,
576
- buildDelay = 0
648
+ buildDelay = 0,
649
+ paraReveal
577
650
  }) {
578
651
  const dragRef = react.useRef(null);
579
652
  if (element.hidden) return null;
@@ -645,7 +718,7 @@ function SlideElementHost({
645
718
  touchAction: canMove ? "none" : void 0,
646
719
  ...buildAnimation ? buildEnterStyle(buildAnimation, buildDelay) : null
647
720
  };
648
- const inner = renderInner({ element, theme, slideWidthPx, editing, selected, onContentChange }) ?? renderElement?.(element, slideWidthPx);
721
+ const inner = renderInner({ element, theme, slideWidthPx, editing, selected, onContentChange, paraReveal }) ?? renderElement?.(element, slideWidthPx) ?? elementPlaceholder(element);
649
722
  return /* @__PURE__ */ jsxRuntime.jsxs(
650
723
  "div",
651
724
  {
@@ -712,7 +785,7 @@ function ResizeHandles({ onStart, onMove, onEnd }) {
712
785
  anchor
713
786
  )) });
714
787
  }
715
- function renderInner({ element, theme, slideWidthPx, editing, selected, onContentChange }) {
788
+ function renderInner({ element, theme, slideWidthPx, editing, selected, onContentChange, paraReveal }) {
716
789
  switch (element.type) {
717
790
  case "text":
718
791
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -723,7 +796,8 @@ function renderInner({ element, theme, slideWidthPx, editing, selected, onConten
723
796
  slideWidthPx,
724
797
  editing,
725
798
  selected,
726
- onContentChange: onContentChange ? (c) => onContentChange(element.id, c) : void 0
799
+ onContentChange: onContentChange ? (c) => onContentChange(element.id, c) : void 0,
800
+ paraReveal
727
801
  }
728
802
  );
729
803
  case "image":
@@ -739,6 +813,37 @@ function renderInner({ element, theme, slideWidthPx, editing, selected, onConten
739
813
  return null;
740
814
  }
741
815
  }
816
+ function elementPlaceholder(element) {
817
+ if (element.type !== "chart" && element.type !== "code" && element.type !== "table" && element.type !== "embed") {
818
+ return null;
819
+ }
820
+ const label = element.type.charAt(0).toUpperCase() + element.type.slice(1);
821
+ return /* @__PURE__ */ jsxRuntime.jsxs(
822
+ "div",
823
+ {
824
+ style: {
825
+ width: "100%",
826
+ height: "100%",
827
+ display: "flex",
828
+ flexDirection: "column",
829
+ alignItems: "center",
830
+ justifyContent: "center",
831
+ gap: "0.35em",
832
+ textAlign: "center",
833
+ padding: "0.5em",
834
+ boxSizing: "border-box",
835
+ border: "1px dashed currentColor",
836
+ borderRadius: 8,
837
+ opacity: 0.55,
838
+ overflow: "hidden"
839
+ },
840
+ children: [
841
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 600 }, children: label }),
842
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.7em", opacity: 0.8 }, children: "Pass renderElement to render" })
843
+ ]
844
+ }
845
+ );
846
+ }
742
847
  function orderedElements(elements) {
743
848
  return [...elements].sort((a, b) => {
744
849
  const az = a.z ?? -1;
@@ -1897,7 +2002,7 @@ function ElementInspector({ element, onPatch, onDelete, onLockToggle, slide, onS
1897
2002
  ] }),
1898
2003
  /* @__PURE__ */ jsxRuntime.jsxs(reactFancy.Tabs.Panels, { children: [
1899
2004
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Tabs.Panel, { value: "style", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Card, { padding: "md", className: "!bg-white dark:!bg-zinc-950", children: /* @__PURE__ */ jsxRuntime.jsx(StyleSection, { element, onPatch }) }) }),
1900
- /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Tabs.Panel, { value: "build", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Card, { padding: "md", className: "!bg-white dark:!bg-zinc-950", children: /* @__PURE__ */ jsxRuntime.jsx(AnimateSection, { animation: element.animation, onSetAnimation }) }) }),
2005
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Tabs.Panel, { value: "build", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Card, { padding: "md", className: "!bg-white dark:!bg-zinc-950", children: /* @__PURE__ */ jsxRuntime.jsx(AnimateSection, { animation: element.animation, onSetAnimation, isText: element.type === "text" }) }) }),
1901
2006
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Tabs.Panel, { value: "layout", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Card, { padding: "md", className: "!bg-white dark:!bg-zinc-950", children: /* @__PURE__ */ jsxRuntime.jsx(LayoutSection, { element, onPatch }) }) }),
1902
2007
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Tabs.Panel, { value: "advanced", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Card, { padding: "md", className: "!bg-white dark:!bg-zinc-950", children: /* @__PURE__ */ jsxRuntime.jsx(AdvancedSection, { element, onPatch }) }) })
1903
2008
  ] })
@@ -2047,7 +2152,8 @@ function AdvancedSection({ element, onPatch }) {
2047
2152
  var NO_ANIMATION = "none";
2048
2153
  function AnimateSection({
2049
2154
  animation,
2050
- onSetAnimation
2155
+ onSetAnimation,
2156
+ isText
2051
2157
  }) {
2052
2158
  if (!onSetAnimation) {
2053
2159
  return /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Text, { size: "sm", className: "!text-zinc-500", children: "Build animations aren't wired up in this editor." });
@@ -2134,6 +2240,14 @@ function AnimateSection({
2134
2240
  onChange: (e) => set({ order: parseInt(e.target.value, 10) || 0 })
2135
2241
  }
2136
2242
  ),
2243
+ isText && /* @__PURE__ */ jsxRuntime.jsx(
2244
+ reactFancy.Switch,
2245
+ {
2246
+ label: "Animate by paragraph (one line per click)",
2247
+ checked: !!animation?.byParagraph,
2248
+ onCheckedChange: (v) => set({ byParagraph: v })
2249
+ }
2250
+ ),
2137
2251
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Text, { size: "xs", className: "!text-zinc-500", children: `Builds reveal in ascending order. "On click" starts a new step; "with previous" plays alongside the step's lead; "after previous" follows it. Honors prefers-reduced-motion.` })
2138
2252
  ] })
2139
2253
  ] });
@@ -2847,10 +2961,13 @@ exports.deckId = deckId;
2847
2961
  exports.defaultTheme = defaultTheme;
2848
2962
  exports.defineTheme = defineTheme;
2849
2963
  exports.elementId = elementId;
2964
+ exports.isByParagraph = isByParagraph;
2850
2965
  exports.nextId = nextId;
2966
+ exports.paragraphReveals = paragraphReveals;
2851
2967
  exports.reduceDeck = reduce;
2852
2968
  exports.resolveTheme = resolveTheme;
2853
2969
  exports.slideId = slideId;
2970
+ exports.splitParagraphs = splitParagraphs;
2854
2971
  exports.stepDelays = stepDelays;
2855
2972
  exports.totalBuildSteps = totalBuildSteps;
2856
2973
  exports.useDeckState = useDeckState;