@vectoriox/iox-ui 5.71.2 → 5.72.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.
@@ -20,13 +20,26 @@ var SceneProp;
20
20
  SceneProp["Rotate"] = "rotate";
21
21
  /** 0–1. */
22
22
  SceneProp["Opacity"] = "opacity";
23
- /** Inset from every side, in % of the layer (0 = whole, 50 = gone). */
23
+ /** How much of the layer shows, in % (100 = whole, 0 = gone). Its track's `from` says where it opens from. */
24
24
  SceneProp["Clip"] = "clip";
25
+ /** How many of the layer's words are lit, in % (100 = all): the word-by-word light-up. Split in the browser only. */
26
+ SceneProp["Words"] = "words";
25
27
  /** The layer's background colour. */
26
28
  SceneProp["Ground"] = "ground";
27
29
  /** The layer's text colour. */
28
30
  SceneProp["Ink"] = "ink";
29
31
  })(SceneProp || (SceneProp = {}));
32
+ /** Where a reveal opens from. Start and End mirror in RTL. */
33
+ var SceneRevealFrom;
34
+ (function (SceneRevealFrom) {
35
+ SceneRevealFrom["Centre"] = "centre";
36
+ SceneRevealFrom["Bottom"] = "bottom";
37
+ SceneRevealFrom["Top"] = "top";
38
+ SceneRevealFrom["Start"] = "start";
39
+ SceneRevealFrom["End"] = "end";
40
+ })(SceneRevealFrom || (SceneRevealFrom = {}));
41
+ /** What a reveal opens from when nobody has said: the page's own direction of travel, upward as it scrolls. */
42
+ const SCENE_REVEAL_DEFAULT = SceneRevealFrom.Bottom;
30
43
  var SceneAttribute;
31
44
  (function (SceneAttribute) {
32
45
  SceneAttribute["Root"] = "data-iox-scene";
@@ -44,6 +57,9 @@ const smoothstep = (t) => t * t * (3 - 2 * t);
44
57
  const COLOUR = /^(#[0-9a-f]{3,8}|(rgb|hsl)a?\([\d\s.,%/]+\)|var\(--iox-[\w-]+\))$/i;
45
58
  const COLOUR_PROPS = new Set([SceneProp.Ground, SceneProp.Ink]);
46
59
  const ALL_PROPS = new Set(Object.values(SceneProp));
60
+ const REVEAL_FROMS = new Set(Object.values(SceneRevealFrom));
61
+ const PERCENT$1 = 100;
62
+ const clampPercent = (v) => Math.min(PERCENT$1, Math.max(0, v));
47
63
  function validKey(key, colour) {
48
64
  if (!Array.isArray(key) || key.length !== 2)
49
65
  return false;
@@ -68,11 +84,35 @@ function normalizeTracks(tracks) {
68
84
  .filter(key => validKey(key, COLOUR_PROPS.has(prop)))
69
85
  .slice()
70
86
  .sort((a, b) => a[0] - b[0]);
71
- if (keys.length)
72
- out.push({ prop, keys });
87
+ if (!keys.length)
88
+ continue;
89
+ out.push(prop === SceneProp.Clip ? clipTrack(keys, track.from) : { prop, keys });
73
90
  }
74
91
  return out;
75
92
  }
93
+ /**
94
+ * A Clip track with a direction. One with none was saved when a reveal could only close in from every side, its value
95
+ * the inset per side (50 = gone): it reads as the same movement, from the centre, in % showing.
96
+ */
97
+ function clipTrack(keys, from) {
98
+ if (typeof from === 'string' && REVEAL_FROMS.has(from))
99
+ return { prop: SceneProp.Clip, from: from, keys };
100
+ return { prop: SceneProp.Clip, from: SceneRevealFrom.Centre, keys: keys.map(([p, inset]) => [p, clampPercent(PERCENT$1 - Number(inset) * 2)]) };
101
+ }
102
+ /**
103
+ * The clip-path of a layer `showing` % revealed, opening from `from`. The one shape behind a Scene's Clip track and the
104
+ * clipReveal action. `clip-path` has no logical sides, so Start and End take the direction from `inlineSign`.
105
+ */
106
+ function revealClipPath(showing, from = SCENE_REVEAL_DEFAULT, inlineSign = 1) {
107
+ const hidden = round(PERCENT$1 - clampPercent(showing));
108
+ switch (from) {
109
+ case SceneRevealFrom.Centre: return `inset(${round(hidden / 2)}%)`;
110
+ case SceneRevealFrom.Top: return `inset(0 0 ${hidden}% 0)`;
111
+ case SceneRevealFrom.Start: return inlineSign === 1 ? `inset(0 ${hidden}% 0 0)` : `inset(0 0 0 ${hidden}%)`;
112
+ case SceneRevealFrom.End: return inlineSign === 1 ? `inset(0 0 0 ${hidden}%)` : `inset(0 ${hidden}% 0 0)`;
113
+ default: return `inset(${hidden}% 0 0 0)`;
114
+ }
115
+ }
76
116
  /** The two keys around `p` and how far between them it eases. */
77
117
  function span(keys, p) {
78
118
  const at = clamp01(p);
@@ -120,7 +160,7 @@ const TRANSFORM_REST = { [SceneProp.X]: 0, [SceneProp.Y]: 0, [SceneProp.Scale]:
120
160
  function sceneFrame(tracks, p, options = {}) {
121
161
  const frame = {};
122
162
  const moves = {};
123
- for (const { prop, keys } of tracks) {
163
+ for (const { prop, keys, from } of tracks) {
124
164
  if (prop in TRANSFORM_REST)
125
165
  moves[prop] = valueAt(keys, p);
126
166
  else if (prop === SceneProp.Opacity) {
@@ -129,7 +169,7 @@ function sceneFrame(tracks, p, options = {}) {
129
169
  frame.pointerEvents = opacity > 0 ? '' : 'none';
130
170
  }
131
171
  else if (prop === SceneProp.Clip)
132
- frame.clipPath = `inset(${Math.min(50, Math.max(0, valueAt(keys, p)))}%)`;
172
+ frame.clipPath = revealClipPath(valueAt(keys, p), from ?? SceneRevealFrom.Centre, options.inlineSign ?? 1);
133
173
  else if (prop === SceneProp.Ground)
134
174
  frame.backgroundColor = colourAt(keys, p);
135
175
  else if (prop === SceneProp.Ink)
@@ -142,6 +182,17 @@ function sceneFrame(tracks, p, options = {}) {
142
182
  }
143
183
  return frame;
144
184
  }
185
+ /** How far an unlit word still shows: readable as a shape, plainly not yet said. */
186
+ const SCENE_WORD_DIM = 0.16;
187
+ /** How many of a layer's words are lit at `p`, in %; null when it has no such track. */
188
+ function sceneWordsLit(tracks, p) {
189
+ const track = tracks.find(t => t.prop === SceneProp.Words);
190
+ return track ? clampPercent(valueAt(track.keys, p)) : null;
191
+ }
192
+ /** Word `index` of `count`, with `lit` % lit: words light one after the other, each easing in as the last finishes. */
193
+ function sceneWordOpacity(index, count, lit) {
194
+ return round(SCENE_WORD_DIM + (1 - SCENE_WORD_DIM) * clamp01((lit / PERCENT$1) * count - index));
195
+ }
145
196
  const CSS_NAME = {
146
197
  transform: 'transform',
147
198
  opacity: 'opacity',
@@ -175,7 +226,11 @@ function sceneNodeAttributes(node) {
175
226
  /** The frame a layer ships in from the server: where it stands before anyone has scrolled. Without it the page would
176
227
  * paint every layer at once, and then jump when the script arrives. */
177
228
  function sceneFirstFrame(node) {
178
- return sceneFrame(normalizeTracks(node?.sceneTracks), 0);
229
+ const tracks = normalizeTracks(node?.sceneTracks);
230
+ const frame = sceneFrame(tracks, 0);
231
+ // The words are split in the browser, so until then the whole layer stands for them, or it paints lit and then dims.
232
+ const lit = sceneWordsLit(tracks, 0);
233
+ return lit !== null && lit < PERCENT$1 && frame.opacity === undefined ? { ...frame, opacity: String(SCENE_WORD_DIM) } : frame;
179
234
  }
180
235
  /**
181
236
  * Make an element carry its node's Scene attributes: the live sync on the canvas and the component path's stamp.
@@ -232,7 +287,8 @@ const SCENE_TRACK_DEFS = [
232
287
  { prop: SceneProp.Scale, colour: false, rest: 1, min: 0, max: 2000, step: 1, factor: 100, unit: PERCENT },
233
288
  { prop: SceneProp.Rotate, colour: false, rest: 0, min: -720, max: 720, step: 1, factor: 1, unit: DEGREES },
234
289
  { prop: SceneProp.Opacity, colour: false, rest: 1, min: 0, max: 100, step: 1, factor: 100, unit: PERCENT },
235
- { prop: SceneProp.Clip, colour: false, rest: 0, min: 0, max: 50, step: 1, factor: 1, unit: PERCENT },
290
+ { prop: SceneProp.Clip, colour: false, rest: 100, min: 0, max: 100, step: 1, factor: 1, unit: PERCENT },
291
+ { prop: SceneProp.Words, colour: false, rest: 100, min: 0, max: 100, step: 1, factor: 1, unit: PERCENT },
236
292
  { prop: SceneProp.Ground, colour: true, rest: 0, min: 0, max: 0, step: 0, factor: 1, unit: '' },
237
293
  { prop: SceneProp.Ink, colour: true, rest: 0, min: 0, max: 0, step: 0, factor: 1, unit: '' },
238
294
  ];
@@ -277,9 +333,23 @@ function replaceTrack(tracks, prop, keys) {
277
333
  return rest;
278
334
  const at = tracks.findIndex(t => t.prop === prop);
279
335
  const next = [...rest];
280
- next.splice(at < 0 ? rest.length : at, 0, { prop, keys });
336
+ const from = prop === SceneProp.Clip ? { from: tracks[at]?.from ?? SCENE_REVEAL_DEFAULT } : {};
337
+ next.splice(at < 0 ? rest.length : at, 0, { prop, ...from, keys });
281
338
  return next;
282
339
  }
340
+ /** Where a layer's reveal opens from: its track's direction, else where a new one would open from. */
341
+ function sceneRevealFromOf(tracks) {
342
+ return trackOf(tracks, SceneProp.Clip)?.from ?? SCENE_REVEAL_DEFAULT;
343
+ }
344
+ /**
345
+ * Open a layer's reveal from `from`. With no reveal yet it starts one with a key at `p` holding the layer whole, so the
346
+ * direction is kept and nothing on screen changes until the author sets a second key.
347
+ */
348
+ function withSceneRevealFrom(tracks, from, p) {
349
+ if (!trackOf(tracks, SceneProp.Clip))
350
+ return [...tracks, { prop: SceneProp.Clip, from, keys: [[sceneKeyPosition(p), sceneTrackDef(SceneProp.Clip).rest]] }];
351
+ return tracks.map(track => (track.prop === SceneProp.Clip ? { ...track, from } : track));
352
+ }
283
353
  /**
284
354
  * Set a key at `p`, in the author's units. A number is clamped to its bounds; a colour that is no colour changes nothing,
285
355
  * because `normalizeTracks` would drop it anyway and the author's other keys must survive.
@@ -467,6 +537,166 @@ function buildSceneRecipe(create, words = {}) {
467
537
  return root;
468
538
  }
469
539
 
540
+ /**
541
+ * Text into pieces, in the browser only (premium sites SC3, Motion v2 §3c). A saved node keeps its plain text and the
542
+ * server sends it whole, so every word is in the raw response, editing never meets a piece, and there is no markup for
543
+ * hydration to disagree with. Each original text node is kept and put back as it was, never rebuilt from a string: a
544
+ * framework may hold that node, and a layer may hold live components.
545
+ */
546
+ var TextRevealBy;
547
+ (function (TextRevealBy) {
548
+ TextRevealBy["Line"] = "line";
549
+ TextRevealBy["Word"] = "word";
550
+ })(TextRevealBy || (TextRevealBy = {}));
551
+ /** Past these a text is plainly no heading: it fades whole, and lights up whole. */
552
+ const REVEAL_PIECE_CAP = 120;
553
+ const LIGHT_UP_PIECE_CAP = 300;
554
+ const SKIPPED_TAGS = new Set(['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'SELECT', 'OPTION']);
555
+ const SHOW_TEXT = 4;
556
+ /** Hebrew through Arabic Extended, and their presentation forms. */
557
+ const RTL_LETTER = /[֐-ࣿיִ-﷿ﹰ-]/;
558
+ /**
559
+ * A string as words and the space between them. With `keepLtrRuns` a run of words holding no RTL letter stays ONE word:
560
+ * an inline-block is neutral to the bidi algorithm, so "Studio Alma" inside a Hebrew heading, split, would read reversed.
561
+ */
562
+ function textPieces(text, keepLtrRuns = false) {
563
+ const pieces = (text.match(/\s+|\S+/g) ?? []).map(part => ({ text: part, word: /\S/.test(part) }));
564
+ if (!keepLtrRuns)
565
+ return pieces;
566
+ const merged = [];
567
+ for (const piece of pieces) {
568
+ const space = merged[merged.length - 1];
569
+ const word = merged[merged.length - 2];
570
+ if (piece.word && !RTL_LETTER.test(piece.text) && space && !space.word && word?.word && !RTL_LETTER.test(word.text)) {
571
+ word.text += space.text + piece.text;
572
+ merged.pop();
573
+ }
574
+ else {
575
+ merged.push({ ...piece });
576
+ }
577
+ }
578
+ return merged;
579
+ }
580
+ function textNodesOf(root) {
581
+ const walker = root.ownerDocument.createTreeWalker(root, SHOW_TEXT);
582
+ const found = [];
583
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
584
+ const parent = node.parentElement;
585
+ if (parent && !SKIPPED_TAGS.has(parent.tagName) && /\S/.test(node.nodeValue ?? ''))
586
+ found.push(node);
587
+ }
588
+ return found;
589
+ }
590
+ function pieceOf(doc, text, mask) {
591
+ const moving = doc.createElement('span');
592
+ moving.textContent = text;
593
+ if (!mask)
594
+ return { outer: moving, moving };
595
+ moving.style.display = 'inline-block';
596
+ const outer = doc.createElement('span');
597
+ outer.style.display = 'inline-block';
598
+ // A clip-path and not overflow, which would move the box's baseline. The negative sides leave descenders and italics.
599
+ outer.style.clipPath = 'inset(-0.1em -0.1em -0.18em -0.1em)';
600
+ outer.appendChild(moving);
601
+ return { outer, moving };
602
+ }
603
+ /** Split `root`'s text, or null when it holds none or more than `cap` pieces (nothing is changed then). */
604
+ function splitText(root, options) {
605
+ const doc = root.ownerDocument;
606
+ const mask = !!options.mask;
607
+ const planned = textNodesOf(root).map(original => ({ original, parts: textPieces(original.nodeValue ?? '', mask && !!options.rtl) }));
608
+ const count = planned.reduce((sum, { parts }) => sum + parts.filter(part => part.word).length, 0);
609
+ if (!count || count > options.cap)
610
+ return null;
611
+ const pieces = [];
612
+ const replacements = planned.map(({ original, parts }) => {
613
+ const nodes = parts.map((part) => {
614
+ if (!part.word)
615
+ return doc.createTextNode(part.text);
616
+ const { outer, moving } = pieceOf(doc, part.text, mask);
617
+ pieces.push(moving);
618
+ return outer;
619
+ });
620
+ const parent = original.parentNode;
621
+ for (const node of nodes)
622
+ parent.insertBefore(node, original);
623
+ parent.removeChild(original);
624
+ return { original, nodes };
625
+ });
626
+ return {
627
+ pieces,
628
+ restore: () => {
629
+ for (const { original, nodes } of replacements) {
630
+ const first = nodes[0];
631
+ if (!first?.isConnected || !first.parentNode)
632
+ continue;
633
+ first.parentNode.insertBefore(original, first);
634
+ for (const node of nodes)
635
+ node.parentNode?.removeChild(node);
636
+ }
637
+ },
638
+ };
639
+ }
640
+ /** The line each piece stands on: 0, 0, 1, 1, 1, 2… Read once, as a reveal starts. */
641
+ function lineIndexes(tops, heights) {
642
+ let line = 0;
643
+ let top = tops[0] ?? 0;
644
+ return tops.map((next, i) => {
645
+ if (next - top > (heights[i] ?? 0) / 2) {
646
+ line++;
647
+ top = next;
648
+ }
649
+ return line;
650
+ });
651
+ }
652
+ const LINE_STAGGER_MS = 90;
653
+ const WORD_STAGGER_MS = 40;
654
+ /** However long the heading, the last piece starts within this. */
655
+ const STAGGER_BUDGET_MS = 1000;
656
+ function revealStagger(groups, by) {
657
+ const step = by === TextRevealBy.Line ? LINE_STAGGER_MS : WORD_STAGGER_MS;
658
+ return groups > 1 ? Math.min(step, STAGGER_BUDGET_MS / (groups - 1)) : 0;
659
+ }
660
+ /** Past the mask's negative inset, so nothing shows before a piece's turn. */
661
+ const PIECE_FRAMES = [{ transform: 'translateY(120%)' }, { transform: 'translateY(0)' }];
662
+ const DEFAULT_PIECE_MS = 700;
663
+ const playing = new WeakMap();
664
+ /**
665
+ * Play a text reveal: each piece rises out of its mask, line by line or word by word. Returns ONE animation for the
666
+ * whole, so a runtime treats it as any other: it finishes when the last piece lands, and on finish or cancel the text
667
+ * is put back whole, which leaves nothing for a screen reader to stumble on and nothing to re-split on a resize. Null
668
+ * when there is nothing to split or it is being edited: the caller plays its plain fade.
669
+ */
670
+ function playTextReveal(el, by, timing) {
671
+ playing.get(el)?.cancel();
672
+ if (el.isContentEditable)
673
+ return null;
674
+ const rtl = el.ownerDocument.defaultView?.getComputedStyle(el).direction === 'rtl';
675
+ const split = splitText(el, { cap: REVEAL_PIECE_CAP, mask: true, rtl });
676
+ if (!split)
677
+ return null;
678
+ const boxes = by === TextRevealBy.Line ? split.pieces.map(piece => piece.parentElement.getBoundingClientRect()) : [];
679
+ const order = by === TextRevealBy.Line ? lineIndexes(boxes.map(box => box.top), boxes.map(box => box.height)) : split.pieces.map((_, i) => i);
680
+ const groups = Math.max(...order) + 1;
681
+ const step = revealStagger(groups, by);
682
+ const duration = Number(timing.duration) || DEFAULT_PIECE_MS;
683
+ const delay = timing.delay ?? 0;
684
+ const pieces = split.pieces.map((piece, i) => piece.animate(PIECE_FRAMES, { duration, delay: delay + order[i] * step, easing: timing.easing, fill: 'both' }));
685
+ // The element itself just stands visible: its entrance's first frame had hidden it whole.
686
+ const whole = el.animate([{ opacity: 1 }, { opacity: 1 }], { duration: delay + duration + step * (groups - 1), fill: timing.fill ?? 'both' });
687
+ playing.set(el, whole);
688
+ const finished = () => {
689
+ if (playing.get(el) === whole)
690
+ playing.delete(el);
691
+ for (const piece of pieces)
692
+ piece.cancel();
693
+ split.restore();
694
+ };
695
+ whole.addEventListener('finish', finished, { once: true });
696
+ whole.addEventListener('cancel', finished, { once: true });
697
+ return whole;
698
+ }
699
+
470
700
  /**
471
701
  * The ONE scroll loop (premium-sites plan §5b, SC0). Every Scene on a page reads one progress value from it, on the
472
702
  * canvas and on the live site alike. It reads where the root is and nothing else, so it has one code path: under the
@@ -568,8 +798,10 @@ class ScrollDriver {
568
798
  this.resize?.unobserve(root);
569
799
  if (scene.stage)
570
800
  scene.stage.style.removeProperty('translate');
571
- for (const layer of scene.layers)
801
+ for (const layer of scene.layers) {
572
802
  layer.el.style.removeProperty('will-change');
803
+ this.restoreWords(layer);
804
+ }
573
805
  this.scenes.delete(root);
574
806
  }
575
807
  /** Read a root's layers again now, keeping where it stands: an editor changed their tracks. */
@@ -648,11 +880,16 @@ class ScrollDriver {
648
880
  layer.el.style.removeProperty('will-change');
649
881
  for (const key of Object.keys(layer.last))
650
882
  layer.el.style[key] = '';
883
+ this.restoreWords(layer);
884
+ }
885
+ restoreWords(layer) {
886
+ layer.words?.restore();
887
+ layer.words = null;
651
888
  }
652
889
  readLayer(el, scene) {
653
890
  const previous = scene.layers.find(layer => layer.el === el);
654
891
  el.style.setProperty('will-change', TRANSFORM_WILL_CHANGE);
655
- return { el, tracks: parseTracks(el.getAttribute(SceneAttribute.Tracks)), phoneTracks: parseTracks(el.getAttribute(SceneAttribute.PhoneTracks)), last: previous?.last ?? {}, stale: true };
892
+ return { el, tracks: parseTracks(el.getAttribute(SceneAttribute.Tracks)), phoneTracks: parseTracks(el.getAttribute(SceneAttribute.PhoneTracks)), last: previous?.last ?? {}, stale: true, words: previous?.words ?? null, wordsRefused: false };
656
893
  }
657
894
  remeasureAll() {
658
895
  for (const scene of this.scenes.values())
@@ -677,6 +914,7 @@ class ScrollDriver {
677
914
  write(scene) {
678
915
  for (const layer of scene.layers) {
679
916
  const tracks = scene.phone && layer.phoneTracks.length ? layer.phoneTracks : layer.tracks;
917
+ this.writeWords(layer, sceneWordsLit(tracks, scene.progress));
680
918
  const frame = sceneFrame(tracks, scene.progress, { inlineSign: scene.inlineSign });
681
919
  for (const key of Object.keys(frame)) {
682
920
  if (layer.stale || frame[key] !== layer.last[key])
@@ -691,6 +929,29 @@ class ScrollDriver {
691
929
  layer.stale = false;
692
930
  }
693
931
  }
932
+ /** One opacity per word, and a write only for the few the scroll actually changed. */
933
+ writeWords(layer, lit) {
934
+ if (lit === null) {
935
+ this.restoreWords(layer);
936
+ return;
937
+ }
938
+ // Gone from the page: the text was replaced under it (an edit on the canvas), so its words are new.
939
+ if (layer.words && !layer.words.pieces[0]?.isConnected)
940
+ layer.words = null;
941
+ if (!layer.words && !layer.wordsRefused) {
942
+ layer.words = splitText(layer.el, { cap: LIGHT_UP_PIECE_CAP });
943
+ layer.wordsRefused = !layer.words;
944
+ // The whole layer stood for its words until now (`sceneFirstFrame`); an Opacity track's frame writes after this.
945
+ layer.el.style.removeProperty('opacity');
946
+ layer.stale = true;
947
+ }
948
+ const pieces = layer.words?.pieces ?? [];
949
+ pieces.forEach((piece, i) => {
950
+ const opacity = String(sceneWordOpacity(i, pieces.length, lit));
951
+ if (piece.style.opacity !== opacity)
952
+ piece.style.opacity = opacity;
953
+ });
954
+ }
694
955
  }
695
956
  /** A page's Scenes, driven by one driver; null when it holds none, or when the visitor asked for less motion (the
696
957
  * page's own CSS then lets each Scene flow). The live site's door. */
@@ -717,5 +978,5 @@ function attachScenes(container, options = {}) {
717
978
  * Generated bundle index. Do not edit.
718
979
  */
719
980
 
720
- export { SCENE_BUDGET, SCENE_DAMPING, SCENE_FLOW_RULES, SCENE_LENGTH, SCENE_MAX_SCREENS, SCENE_MIN_SCREENS, SCENE_PHONE_LENGTH, SCENE_PHONE_MAX_WIDTH, SCENE_REDUCED_MOTION_CSS, SCENE_STAGE_HEIGHT, SCENE_TRACK_DEFS, SCENE_WORDS, SceneAttribute, SceneProp, ScrollDriver, applySceneFirstFrame, attachScenes, buildSceneRecipe, colourAt, dampToward, movedSceneKey, normalizeTracks, sceneBudget, sceneColourKeyAt, sceneDisplayValue, sceneFirstFrame, sceneFrame, sceneFrameDeclarations, sceneHasKeyAt, sceneHeightForScreens, sceneKeyPosition, sceneLayersOf, sceneNodeAttributes, sceneRootOnPath, sceneScreensOf, sceneScrollState, sceneTrackDef, sceneTrackSpan, scenesIn, syncSceneAttributes, valueAt, withSceneKey, withoutSceneKey };
981
+ export { LIGHT_UP_PIECE_CAP, REVEAL_PIECE_CAP, SCENE_BUDGET, SCENE_DAMPING, SCENE_FLOW_RULES, SCENE_LENGTH, SCENE_MAX_SCREENS, SCENE_MIN_SCREENS, SCENE_PHONE_LENGTH, SCENE_PHONE_MAX_WIDTH, SCENE_REDUCED_MOTION_CSS, SCENE_REVEAL_DEFAULT, SCENE_STAGE_HEIGHT, SCENE_TRACK_DEFS, SCENE_WORDS, SCENE_WORD_DIM, SceneAttribute, SceneProp, SceneRevealFrom, ScrollDriver, TextRevealBy, applySceneFirstFrame, attachScenes, buildSceneRecipe, colourAt, dampToward, lineIndexes, movedSceneKey, normalizeTracks, playTextReveal, revealClipPath, revealStagger, sceneBudget, sceneColourKeyAt, sceneDisplayValue, sceneFirstFrame, sceneFrame, sceneFrameDeclarations, sceneHasKeyAt, sceneHeightForScreens, sceneKeyPosition, sceneLayersOf, sceneNodeAttributes, sceneRevealFromOf, sceneRootOnPath, sceneScreensOf, sceneScrollState, sceneTrackDef, sceneTrackSpan, sceneWordOpacity, sceneWordsLit, scenesIn, splitText, syncSceneAttributes, textPieces, valueAt, withSceneKey, withSceneRevealFrom, withoutSceneKey };
721
982
  //# sourceMappingURL=vectoriox-iox-ui-scene-core.mjs.map