@teleporthq/teleport-shared 0.43.70 → 0.43.71

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 (150) hide show
  1. package/__tests__/motion-runtime/motion-engine.ts +102 -0
  2. package/__tests__/motion-runtime/scroll-video-engine.ts +28 -0
  3. package/__tests__/scroll-scene/story-lanes.ts +126 -0
  4. package/dist/cjs/index.d.ts +5 -1
  5. package/dist/cjs/index.d.ts.map +1 -1
  6. package/dist/cjs/index.js +9 -1
  7. package/dist/cjs/index.js.map +1 -1
  8. package/dist/cjs/motion-runtime/index.d.ts +3 -0
  9. package/dist/cjs/motion-runtime/index.d.ts.map +1 -0
  10. package/dist/cjs/motion-runtime/index.js +8 -0
  11. package/dist/cjs/motion-runtime/index.js.map +1 -0
  12. package/dist/cjs/motion-runtime/motion-engine-source.d.ts +16 -0
  13. package/dist/cjs/motion-runtime/motion-engine-source.d.ts.map +1 -0
  14. package/dist/cjs/motion-runtime/motion-engine-source.js +20 -0
  15. package/dist/cjs/motion-runtime/motion-engine-source.js.map +1 -0
  16. package/dist/cjs/motion-runtime/scroll-video-engine-source.d.ts +9 -0
  17. package/dist/cjs/motion-runtime/scroll-video-engine-source.d.ts.map +1 -0
  18. package/dist/cjs/motion-runtime/scroll-video-engine-source.js +13 -0
  19. package/dist/cjs/motion-runtime/scroll-video-engine-source.js.map +1 -0
  20. package/dist/cjs/page-transition/index.d.ts +4 -0
  21. package/dist/cjs/page-transition/index.d.ts.map +1 -0
  22. package/dist/cjs/page-transition/index.js +20 -0
  23. package/dist/cjs/page-transition/index.js.map +1 -0
  24. package/dist/cjs/page-transition/morph-source.d.ts +20 -0
  25. package/dist/cjs/page-transition/morph-source.d.ts.map +1 -0
  26. package/dist/cjs/page-transition/morph-source.js +25 -0
  27. package/dist/cjs/page-transition/morph-source.js.map +1 -0
  28. package/dist/cjs/page-transition/variants.d.ts +87 -0
  29. package/dist/cjs/page-transition/variants.d.ts.map +1 -0
  30. package/dist/cjs/page-transition/variants.js +330 -0
  31. package/dist/cjs/page-transition/variants.js.map +1 -0
  32. package/dist/cjs/page-transition/view-transition-css.d.ts +21 -0
  33. package/dist/cjs/page-transition/view-transition-css.d.ts.map +1 -0
  34. package/dist/cjs/page-transition/view-transition-css.js +188 -0
  35. package/dist/cjs/page-transition/view-transition-css.js.map +1 -0
  36. package/dist/cjs/scroll-scene/chapter-index.d.ts +15 -0
  37. package/dist/cjs/scroll-scene/chapter-index.d.ts.map +1 -0
  38. package/dist/cjs/scroll-scene/chapter-index.js +30 -0
  39. package/dist/cjs/scroll-scene/chapter-index.js.map +1 -0
  40. package/dist/cjs/scroll-scene/controller-source.d.ts +26 -0
  41. package/dist/cjs/scroll-scene/controller-source.d.ts.map +1 -0
  42. package/dist/cjs/scroll-scene/controller-source.js +34 -0
  43. package/dist/cjs/scroll-scene/controller-source.js.map +1 -0
  44. package/dist/cjs/scroll-scene/engine-source.d.ts +2 -0
  45. package/dist/cjs/scroll-scene/engine-source.d.ts.map +1 -0
  46. package/dist/cjs/scroll-scene/engine-source.js +26 -0
  47. package/dist/cjs/scroll-scene/engine-source.js.map +1 -0
  48. package/dist/cjs/scroll-scene/index.d.ts +7 -0
  49. package/dist/cjs/scroll-scene/index.d.ts.map +1 -0
  50. package/dist/cjs/scroll-scene/index.js +22 -0
  51. package/dist/cjs/scroll-scene/index.js.map +1 -0
  52. package/dist/cjs/scroll-scene/moment.d.ts +21 -0
  53. package/dist/cjs/scroll-scene/moment.d.ts.map +1 -0
  54. package/dist/cjs/scroll-scene/moment.js +50 -0
  55. package/dist/cjs/scroll-scene/moment.js.map +1 -0
  56. package/dist/cjs/scroll-scene/points.d.ts +23 -0
  57. package/dist/cjs/scroll-scene/points.d.ts.map +1 -0
  58. package/dist/cjs/scroll-scene/points.js +51 -0
  59. package/dist/cjs/scroll-scene/points.js.map +1 -0
  60. package/dist/cjs/scroll-scene/unclip.d.ts +15 -0
  61. package/dist/cjs/scroll-scene/unclip.d.ts.map +1 -0
  62. package/dist/cjs/scroll-scene/unclip.js +44 -0
  63. package/dist/cjs/scroll-scene/unclip.js.map +1 -0
  64. package/dist/cjs/state-bindings/index.d.ts +74 -0
  65. package/dist/cjs/state-bindings/index.d.ts.map +1 -0
  66. package/dist/cjs/state-bindings/index.js +193 -0
  67. package/dist/cjs/state-bindings/index.js.map +1 -0
  68. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  69. package/dist/esm/index.d.ts +5 -1
  70. package/dist/esm/index.d.ts.map +1 -1
  71. package/dist/esm/index.js +5 -1
  72. package/dist/esm/index.js.map +1 -1
  73. package/dist/esm/motion-runtime/index.d.ts +3 -0
  74. package/dist/esm/motion-runtime/index.d.ts.map +1 -0
  75. package/dist/esm/motion-runtime/index.js +3 -0
  76. package/dist/esm/motion-runtime/index.js.map +1 -0
  77. package/dist/esm/motion-runtime/motion-engine-source.d.ts +16 -0
  78. package/dist/esm/motion-runtime/motion-engine-source.d.ts.map +1 -0
  79. package/dist/esm/motion-runtime/motion-engine-source.js +16 -0
  80. package/dist/esm/motion-runtime/motion-engine-source.js.map +1 -0
  81. package/dist/esm/motion-runtime/scroll-video-engine-source.d.ts +9 -0
  82. package/dist/esm/motion-runtime/scroll-video-engine-source.d.ts.map +1 -0
  83. package/dist/esm/motion-runtime/scroll-video-engine-source.js +9 -0
  84. package/dist/esm/motion-runtime/scroll-video-engine-source.js.map +1 -0
  85. package/dist/esm/page-transition/index.d.ts +4 -0
  86. package/dist/esm/page-transition/index.d.ts.map +1 -0
  87. package/dist/esm/page-transition/index.js +4 -0
  88. package/dist/esm/page-transition/index.js.map +1 -0
  89. package/dist/esm/page-transition/morph-source.d.ts +20 -0
  90. package/dist/esm/page-transition/morph-source.d.ts.map +1 -0
  91. package/dist/esm/page-transition/morph-source.js +21 -0
  92. package/dist/esm/page-transition/morph-source.js.map +1 -0
  93. package/dist/esm/page-transition/variants.d.ts +87 -0
  94. package/dist/esm/page-transition/variants.d.ts.map +1 -0
  95. package/dist/esm/page-transition/variants.js +318 -0
  96. package/dist/esm/page-transition/variants.js.map +1 -0
  97. package/dist/esm/page-transition/view-transition-css.d.ts +21 -0
  98. package/dist/esm/page-transition/view-transition-css.d.ts.map +1 -0
  99. package/dist/esm/page-transition/view-transition-css.js +161 -0
  100. package/dist/esm/page-transition/view-transition-css.js.map +1 -0
  101. package/dist/esm/scroll-scene/chapter-index.d.ts +15 -0
  102. package/dist/esm/scroll-scene/chapter-index.d.ts.map +1 -0
  103. package/dist/esm/scroll-scene/chapter-index.js +26 -0
  104. package/dist/esm/scroll-scene/chapter-index.js.map +1 -0
  105. package/dist/esm/scroll-scene/controller-source.d.ts +26 -0
  106. package/dist/esm/scroll-scene/controller-source.d.ts.map +1 -0
  107. package/dist/esm/scroll-scene/controller-source.js +26 -0
  108. package/dist/esm/scroll-scene/controller-source.js.map +1 -0
  109. package/dist/esm/scroll-scene/engine-source.d.ts +2 -0
  110. package/dist/esm/scroll-scene/engine-source.d.ts.map +1 -0
  111. package/dist/esm/scroll-scene/engine-source.js +22 -0
  112. package/dist/esm/scroll-scene/engine-source.js.map +1 -0
  113. package/dist/esm/scroll-scene/index.d.ts +7 -0
  114. package/dist/esm/scroll-scene/index.d.ts.map +1 -0
  115. package/dist/esm/scroll-scene/index.js +7 -0
  116. package/dist/esm/scroll-scene/index.js.map +1 -0
  117. package/dist/esm/scroll-scene/moment.d.ts +21 -0
  118. package/dist/esm/scroll-scene/moment.d.ts.map +1 -0
  119. package/dist/esm/scroll-scene/moment.js +46 -0
  120. package/dist/esm/scroll-scene/moment.js.map +1 -0
  121. package/dist/esm/scroll-scene/points.d.ts +23 -0
  122. package/dist/esm/scroll-scene/points.d.ts.map +1 -0
  123. package/dist/esm/scroll-scene/points.js +45 -0
  124. package/dist/esm/scroll-scene/points.js.map +1 -0
  125. package/dist/esm/scroll-scene/unclip.d.ts +15 -0
  126. package/dist/esm/scroll-scene/unclip.d.ts.map +1 -0
  127. package/dist/esm/scroll-scene/unclip.js +40 -0
  128. package/dist/esm/scroll-scene/unclip.js.map +1 -0
  129. package/dist/esm/state-bindings/index.d.ts +74 -0
  130. package/dist/esm/state-bindings/index.d.ts.map +1 -0
  131. package/dist/esm/state-bindings/index.js +176 -0
  132. package/dist/esm/state-bindings/index.js.map +1 -0
  133. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  134. package/package.json +3 -3
  135. package/src/index.ts +8 -0
  136. package/src/motion-runtime/index.ts +2 -0
  137. package/src/motion-runtime/motion-engine-source.ts +289 -0
  138. package/src/motion-runtime/scroll-video-engine-source.ts +591 -0
  139. package/src/page-transition/index.ts +3 -0
  140. package/src/page-transition/morph-source.ts +86 -0
  141. package/src/page-transition/variants.ts +407 -0
  142. package/src/page-transition/view-transition-css.ts +237 -0
  143. package/src/scroll-scene/chapter-index.ts +25 -0
  144. package/src/scroll-scene/controller-source.ts +451 -0
  145. package/src/scroll-scene/engine-source.ts +330 -0
  146. package/src/scroll-scene/index.ts +12 -0
  147. package/src/scroll-scene/moment.ts +64 -0
  148. package/src/scroll-scene/points.ts +46 -0
  149. package/src/scroll-scene/unclip.ts +44 -0
  150. package/src/state-bindings/index.ts +207 -0
@@ -0,0 +1,289 @@
1
+ /**
2
+ * The element-motion engine as SOURCE TEXT: easing curves, the preset table, the
3
+ * from/to state helpers and the viewport-fraction maths. Plain JavaScript with
4
+ * no framework in it, shared word for word by the two exports:
5
+ *
6
+ * - the Next export wraps it in the TqMotion React component (framer-motion
7
+ * plays the entrance and supplies the scroll progress);
8
+ * - the HTML export wraps it in a vanilla controller (the Web Animations API
9
+ * plays the entrance, a scroll listener supplies the progress).
10
+ *
11
+ * One copy means a new preset reaches both exports at once. It mirrors the
12
+ * canvas renderer's motion-keyframes module. (No backticks in the engine's own
13
+ * comments unless escaped: it is a template literal.)
14
+ */
15
+ export const motionEngineSource = (): string => `const EASINGS = {
16
+ ease: [0.25, 0.1, 0.25, 1],
17
+ 'ease-in': [0.42, 0, 1, 1],
18
+ 'ease-out': [0, 0, 0.58, 1],
19
+ 'ease-in-out': [0.42, 0, 0.58, 1],
20
+ linear: [0, 0, 1, 1],
21
+ spring: [0.34, 1.56, 0.64, 1],
22
+ back: [0.68, -0.6, 0.32, 1.6],
23
+ bounce: [0.22, 1.2, 0.36, 1],
24
+ }
25
+
26
+ // Mirrors the canvas renderer's motion-keyframes helpers so a scroll-linked
27
+ // element interpolates EVERY from/to property (x/y/scale/rotate/opacity/filter)
28
+ // identically in the canvas, the preview and the published site. The key ORDER
29
+ // is the order the canvas composes them in: it has to match \`cssFromState\` in
30
+ // the renderer's motion-keyframes exactly, or a staggered card and its canvas
31
+ // twin would compose the same values differently. Both the scroll-linked path
32
+ // (\`applyScrollState\`) and the in-place stagger (\`cssFromState\`) build on these.
33
+ const TRANSFORM_KEYS = ['x', 'y', 'scale', 'rotate']
34
+
35
+ // The transform family is written as the INDIVIDUAL transform properties
36
+ // (translate / scale / rotate) and never as the transform property itself, so
37
+ // whatever the author put in transform (a translateX(-50%) centering, a tilt)
38
+ // survives the animation and the browser composes the two. Mirrors the canvas.
39
+ const withUnit = (value, unit) => (typeof value === 'number' ? value + unit : String(value))
40
+
41
+ const writeTransformProps = (css, state) => {
42
+ if (state.x !== undefined || state.y !== undefined) {
43
+ css.translate =
44
+ withUnit(state.x !== undefined ? state.x : 0, 'px') +
45
+ ' ' +
46
+ withUnit(state.y !== undefined ? state.y : 0, 'px')
47
+ }
48
+ if (state.scale !== undefined) {
49
+ css.scale = String(state.scale)
50
+ }
51
+ if (state.rotate !== undefined) {
52
+ css.rotate = withUnit(state.rotate, 'deg')
53
+ }
54
+ }
55
+
56
+ // Composed filters (blur(10px) grayscale(1) -> blur(0px) grayscale(0))
57
+ // interpolate function-by-function when both sides carry the same sequence —
58
+ // mirrors the canvas contract.
59
+ const FILTER_LIST_RE = /^(?:[a-z-]+\\(\\s*-?\\d*\\.?\\d+(?:px|deg|%)?\\s*\\)\\s*)+$/
60
+ const FILTER_FN_RE = /([a-z-]+)\\(\\s*(-?\\d*\\.?\\d+)(px|deg|%)?\\s*\\)/g
61
+
62
+ const interpolateFilter = (from, to, p) => {
63
+ if (!FILTER_LIST_RE.test(from.trim()) || !FILTER_LIST_RE.test(to.trim())) {
64
+ return null
65
+ }
66
+ const fromFns = Array.from(from.matchAll(FILTER_FN_RE))
67
+ const toFns = Array.from(to.matchAll(FILTER_FN_RE))
68
+ if (fromFns.length !== toFns.length || fromFns.length === 0) {
69
+ return null
70
+ }
71
+ const parts = []
72
+ for (let index = 0; index < fromFns.length; index++) {
73
+ const fn = fromFns[index][1]
74
+ const unit = fromFns[index][3] || ''
75
+ if (fn !== toFns[index][1] || unit !== (toFns[index][3] || '')) {
76
+ return null
77
+ }
78
+ const value =
79
+ parseFloat(fromFns[index][2]) +
80
+ (parseFloat(toFns[index][2]) - parseFloat(fromFns[index][2])) * p
81
+ parts.push(fn + '(' + value + unit + ')')
82
+ }
83
+ return parts.join(' ')
84
+ }
85
+
86
+ // A value with a unit (-50%, 2rem) glides in that unit when both ends use the
87
+ // same one, as it does on the canvas. A played entrance gets this from
88
+ // framer-motion or the Web Animations API; the scroll-linked path gets it here.
89
+ const UNIT_VALUE_RE = /^(-?\\d*\\.?\\d+)(px|%|deg|rem|em|vw|vh)$/
90
+
91
+ const interpolateUnitValue = (from, to, p) => {
92
+ const start = UNIT_VALUE_RE.exec(from.trim())
93
+ const end = UNIT_VALUE_RE.exec(to.trim())
94
+ if (!start || !end || start[2] !== end[2]) {
95
+ return null
96
+ }
97
+ const startAmount = parseFloat(start[1])
98
+ return startAmount + (parseFloat(end[1]) - startAmount) * p + start[2]
99
+ }
100
+
101
+ const interpolateValue = (from, to, p) => {
102
+ if (typeof from === 'number' && typeof to === 'number') {
103
+ return from + (to - from) * p
104
+ }
105
+ if (typeof from === 'string' && typeof to === 'string') {
106
+ const united = interpolateUnitValue(from, to, p)
107
+ if (united !== null) {
108
+ return united
109
+ }
110
+ const filter = interpolateFilter(from, to, p)
111
+ if (filter !== null) {
112
+ return filter
113
+ }
114
+ }
115
+ return p >= 1 ? to : from
116
+ }
117
+
118
+ const applyScrollState = (element, fromVars, toVars, p) => {
119
+ if (!element) {
120
+ return
121
+ }
122
+ const keys = new Set([...Object.keys(fromVars), ...Object.keys(toVars)])
123
+ const transformState = {}
124
+ keys.forEach((key) => {
125
+ const fromValue = fromVars[key] !== undefined ? fromVars[key] : toVars[key]
126
+ const toValue = toVars[key] !== undefined ? toVars[key] : fromVars[key]
127
+ const value = interpolateValue(fromValue, toValue, p)
128
+ if (TRANSFORM_KEYS.includes(key)) {
129
+ transformState[key] = value
130
+ } else {
131
+ element.style[key] = String(value)
132
+ }
133
+ })
134
+ const transformCss = {}
135
+ writeTransformProps(transformCss, transformState)
136
+ Object.keys(transformCss).forEach((prop) => {
137
+ element.style[prop] = transformCss[prop]
138
+ })
139
+ }
140
+
141
+ // useScroll offset pairs per motion-scroll-offset mode — mirrors the canvas
142
+ // runtime's scrollProgressForOffset semantics.
143
+ const SCROLL_OFFSET_RANGES = {
144
+ pass: ['start end', 'end start'],
145
+ contained: ['start start', 'end end'],
146
+ enter: ['start end', 'start start'],
147
+ exit: ['end end', 'end start'],
148
+ }
149
+
150
+ const presetStates = (preset, distance) => {
151
+ switch (preset) {
152
+ case 'fade-in':
153
+ return { from: { opacity: 0 }, to: { opacity: 1 } }
154
+ case 'slide-up':
155
+ return { from: { opacity: 0, y: distance }, to: { opacity: 1, y: 0 } }
156
+ case 'slide-down':
157
+ return { from: { opacity: 0, y: -distance }, to: { opacity: 1, y: 0 } }
158
+ case 'slide-left':
159
+ return { from: { opacity: 0, x: distance }, to: { opacity: 1, x: 0 } }
160
+ case 'slide-right':
161
+ return { from: { opacity: 0, x: -distance }, to: { opacity: 1, x: 0 } }
162
+ case 'scale-in':
163
+ return { from: { opacity: 0, scale: 0.8 }, to: { opacity: 1, scale: 1 } }
164
+ case 'zoom-in':
165
+ return { from: { opacity: 0, scale: 0.5 }, to: { opacity: 1, scale: 1 } }
166
+ case 'pop-in':
167
+ return { from: { opacity: 0, scale: 0.6 }, to: { opacity: 1, scale: 1 } }
168
+ case 'rotate-in':
169
+ return { from: { opacity: 0, rotate: -8 }, to: { opacity: 1, rotate: 0 } }
170
+ case 'tilt-in':
171
+ return { from: { opacity: 0, rotate: -10, y: distance }, to: { opacity: 1, rotate: 0, y: 0 } }
172
+ case 'blur-in':
173
+ return { from: { opacity: 0, filter: 'blur(12px)' }, to: { opacity: 1, filter: 'blur(0px)' } }
174
+ case 'blur-up':
175
+ return {
176
+ from: { opacity: 0, y: distance, filter: 'blur(8px)' },
177
+ to: { opacity: 1, y: 0, filter: 'blur(0px)' },
178
+ }
179
+ case 'drop-in':
180
+ return {
181
+ from: { opacity: 0, y: -distance, scale: 1.05 },
182
+ to: { opacity: 1, y: 0, scale: 1 },
183
+ }
184
+ case 'swing-in':
185
+ return { from: { opacity: 0, rotate: -20, y: distance }, to: { opacity: 1, rotate: 0, y: 0 } }
186
+ case 'spin-in':
187
+ return {
188
+ from: { opacity: 0, rotate: -180, scale: 0.6 },
189
+ to: { opacity: 1, rotate: 0, scale: 1 },
190
+ }
191
+ case 'blur-zoom':
192
+ return {
193
+ from: { opacity: 0, scale: 1.35, filter: 'blur(10px)' },
194
+ to: { opacity: 1, scale: 1, filter: 'blur(0px)' },
195
+ }
196
+ case 'zoom-out':
197
+ return { from: { opacity: 0, scale: 1.4 }, to: { opacity: 1, scale: 1 } }
198
+ case 'develop':
199
+ return {
200
+ from: { opacity: 0, filter: 'blur(6px) grayscale(1)' },
201
+ to: { opacity: 1, filter: 'blur(0px) grayscale(0)' },
202
+ }
203
+ case 'glow-in':
204
+ return {
205
+ from: { opacity: 0, filter: 'brightness(1.8) saturate(0.4)' },
206
+ to: { opacity: 1, filter: 'brightness(1) saturate(1)' },
207
+ }
208
+ // Attention loops: rest at the natural state (NO opacity ramp — a looping
209
+ // opacity would strobe) and read as animation only when repeated (mirror).
210
+ case 'pulse':
211
+ return { from: { scale: 1 }, to: { scale: 1.06 } }
212
+ case 'float':
213
+ return { from: { y: 0 }, to: { y: -12 } }
214
+ case 'spin':
215
+ return { from: { rotate: 0 }, to: { rotate: 360 } }
216
+ case 'wobble':
217
+ return { from: { rotate: -3 }, to: { rotate: 3 } }
218
+ case 'none':
219
+ return { from: {}, to: {} }
220
+ default:
221
+ return { from: { opacity: 0 }, to: { opacity: 1 } }
222
+ }
223
+ }
224
+
225
+ // Turn a resolved motion state ({ opacity, x, y, scale, rotate, filter, … }) into
226
+ // an inline style object: the transform keys become the individual translate /
227
+ // scale / rotate properties and every other key passes through as its own CSS
228
+ // property. Mirrors the canvas.
229
+ const cssFromState = (state) => {
230
+ const css = {}
231
+ writeTransformProps(css, state)
232
+ Object.keys(state).forEach((key) => {
233
+ if (TRANSFORM_KEYS.indexOf(key) !== -1) {
234
+ return
235
+ }
236
+ css[key] = state[key]
237
+ })
238
+ return css
239
+ }
240
+
241
+ // How much of the element is inside the viewport right now, as a 0..1 fraction
242
+ // of its own area — the same quantity IntersectionObserver thresholds on, so the
243
+ // failsafe below can apply exactly the threshold useInView was given.
244
+ const visibleFraction = (rect, vh, vw) => {
245
+ const height = rect.bottom - rect.top
246
+ const width = rect.right - rect.left
247
+ if (height <= 0 || width <= 0) {
248
+ return 0
249
+ }
250
+ const visibleHeight = Math.max(0, Math.min(rect.bottom, vh) - Math.max(rect.top, 0))
251
+ const visibleWidth = Math.max(0, Math.min(rect.right, vw) - Math.max(rect.left, 0))
252
+ return (visibleHeight * visibleWidth) / (height * width)
253
+ }
254
+
255
+ // The largest fraction this element could EVER have on screen. An element taller
256
+ // than the viewport can never be 60% visible, so an inViewAmount above that is
257
+ // unsatisfiable and would hold the content at opacity 0 permanently. Clamping to
258
+ // what is physically reachable turns "impossible" into "as visible as it gets".
259
+ const reachableFraction = (rect, vh, vw) => {
260
+ const height = rect.bottom - rect.top
261
+ const width = rect.right - rect.left
262
+ if (height <= 0 || width <= 0) {
263
+ return 0
264
+ }
265
+ return Math.min(1, vh / height) * Math.min(1, vw / width)
266
+ }
267
+
268
+ // A row that slides by itself is the editor's loop: a clipping frame
269
+ // (data-marquee) holding this Motion track, which holds the row and the hidden
270
+ // copy that closes the loop (data-marquee-copy). For a visitor who asks for
271
+ // less motion the track does not play, so the row stands still and the frame
272
+ // lets the visitor swipe it instead: every item stays reachable. The copy
273
+ // goes, or every item would show twice, and the track stops reserving room
274
+ // for it. Mirrors letMarqueeSwipe in the editor's marquee contract.
275
+ const MARQUEE_FRAME_ATTR = 'data-marquee'
276
+ const MARQUEE_COPY_ATTR = 'data-marquee-copy'
277
+ const letMarqueeSwipe = (track) => {
278
+ const frame = track && track.parentElement
279
+ if (!frame || !frame.hasAttribute(MARQUEE_FRAME_ATTR)) {
280
+ return
281
+ }
282
+ frame.style.overflowX = 'auto'
283
+ track.style.minWidth = '100%'
284
+ for (const child of Array.from(track.children)) {
285
+ if (child.hasAttribute(MARQUEE_COPY_ATTR)) {
286
+ child.style.display = 'none'
287
+ }
288
+ }
289
+ }`