rytm-webflow 2.3.0 → 2.3.2

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.
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(git fetch:*)"
4
+ "Bash(git fetch:*)",
5
+ "mcp__metrum__get_task",
6
+ "Bash(git checkout:*)",
7
+ "mcp__metrum__set_subtask_status",
8
+ "Bash(npm run:*)"
5
9
  ]
6
- }
10
+ },
11
+ "enableAllProjectMcpServers": true,
12
+ "enabledMcpjsonServers": [
13
+ "metrum"
14
+ ]
7
15
  }
package/.mcp.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "metrum": {
4
+ "command": "node",
5
+ "args": ["/Users/rk/Sites/metrum.rytm.org/mcp/server.js"]
6
+ }
7
+ }
8
+ }
package/CLAUDE.md CHANGED
@@ -3,7 +3,7 @@
3
3
  AJAX view-swapping engine and scroll-triggered animation framework for tuki CMS projects.
4
4
  Built on GSAP + ScrollMagic. Transforms server-rendered pages into pseudo-SPAs.
5
5
 
6
- **Version**: 2.2.4 | **Entry**: `scripts/index.js` | **Deps**: gsap, imagesloaded, scrollmagic
6
+ **Version**: 2.3.2 | **Entry**: `scripts/index.js` | **Deps**: gsap, imagesloaded, scrollmagic
7
7
 
8
8
  ## Exports
9
9
 
@@ -361,3 +361,4 @@ RytmWebflow.scrollController.destroy() // Clean up
361
361
  7. **Stagger not working** — requires `data-webset="selector:..."` attribute
362
362
  8. **Easing not applied** — use GSAP v3 syntax: `e:power3.out` (not `e:Power3.easeOut`)
363
363
  9. **Cache causes stale content** — add `no-as-cache` class to forms or dynamic links
364
+ 10. **Webscroll elements and viewport growth** — since 2.3.2 `webscroll` elements that enter the viewport via a resize / devtools toggle / background-tab activation (no scroll) are revealed automatically; earlier versions only fired on a `FORWARD` scroll crossing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rytm-webflow",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
4
4
  "description": "rytm webflow pack - ASwap, ShowUp",
5
5
  "main": "scripts/index.js",
6
6
  "types": "scripts/index.d.ts",
@@ -1,6 +1,10 @@
1
+ import ScrollMagic from 'scrollmagic';
1
2
  import gsap from 'gsap';
2
3
  import View from './View';
4
+ import scrollController from './../showup/ScrollController';
3
5
  import { getWebflowAnimationProps, parseProps } from './../lib/dataTweenParser';
6
+ import { getScrollMagicSceneProps } from './../lib/dataScrollMagicParser';
7
+ import { elementIsVisibleInViewport } from '../lib/helpers';
4
8
  import { CLASS_NAME_WEBSCROLL_FIRED } from './WebflowView';
5
9
 
6
10
  // data-webscroll-... (scroll magic)
@@ -14,6 +18,8 @@ class WebflowListView extends View {
14
18
 
15
19
  constructor(id) {
16
20
  super(id);
21
+ this.scenes = [];
22
+ this._revealTimer = null;
17
23
  }
18
24
  /**
19
25
  * animate in (show)
@@ -25,8 +31,15 @@ class WebflowListView extends View {
25
31
  console.warn("Unknown selector for WebflowListView", this);
26
32
  return;
27
33
  }
28
- const list = [...container.querySelectorAll(this.webset.selector)].filter(this.elementBelongsToView);
29
- list.forEach(this.listElementShow.bind(this));
34
+ const items = [...container.querySelectorAll(this.webset.selector)].filter(this.elementBelongsToView);
35
+ // 1. Set initial state for ALL scroll elements (regardless of viewport)
36
+ this.hideAllScrollElements(items);
37
+ // 2. Stagger-animate in-viewport items (existing behavior)
38
+ items.forEach(this.listElementShow.bind(this));
39
+ // 3. Build ScrollMagic scenes for off-screen items
40
+ this.buildScrollScenesForOffscreenItems(items);
41
+ // 4. Listen for resize to refresh ScrollMagic
42
+ this.addEventListeners();
30
43
  }
31
44
  /**
32
45
  * animate out (hide)
@@ -40,6 +53,16 @@ class WebflowListView extends View {
40
53
  }
41
54
  const list = [...container.querySelectorAll(this.webset.selector)].filter(this.elementBelongsToView);
42
55
  list.forEach(this.listElementHide.bind(this));
56
+ // Also hide off-screen scroll elements that have ScrollMagic scenes
57
+ this.hideOffscreenScrollElements(container);
58
+ }
59
+ /**
60
+ * hidden (cleanup after hide animation)
61
+ **/
62
+ hidden(container) {
63
+ super.hidden(container);
64
+ this.removeEventListeners();
65
+ this.destroyScenes();
43
66
  }
44
67
  /**
45
68
  * ############
@@ -106,6 +129,163 @@ class WebflowListView extends View {
106
129
  });
107
130
  }
108
131
  }
132
+ /**
133
+ * ##################
134
+ * ### DOM events ###
135
+ * ##################
136
+ */
137
+ handleEvent(e) {
138
+ switch (e.type) {
139
+ case 'resize':
140
+ case 'pageshow':
141
+ case 'DOMContentLoaded':
142
+ this.onWindowUpdate();
143
+ break;
144
+ }
145
+ }
146
+ addEventListeners() {
147
+ window.addEventListener('resize', this);
148
+ window.addEventListener('pageshow', this);
149
+ document.addEventListener('DOMContentLoaded', this);
150
+ }
151
+ removeEventListeners() {
152
+ window.removeEventListener('resize', this);
153
+ window.removeEventListener('pageshow', this);
154
+ document.removeEventListener('DOMContentLoaded', this);
155
+ clearTimeout(this._revealTimer);
156
+ }
157
+ onWindowUpdate() {
158
+ if (this.scenes.length > 0) {
159
+ scrollController.refresh();
160
+ // viewport size changed - recompute viewport-dependent scene offsets
161
+ // (they are baked from window.innerHeight at scene-build time)
162
+ this.refreshSceneOffsets();
163
+ // reveal elements that entered the viewport without a scroll (debounced -
164
+ // resize fires in bursts while dragging / toggling devtools)
165
+ clearTimeout(this._revealTimer);
166
+ this._revealTimer = setTimeout(() => this.revealVisibleScrollElements(), 150);
167
+ }
168
+ }
169
+ /**
170
+ * Recompute viewport-dependent scene offsets after a viewport change
171
+ */
172
+ refreshSceneOffsets() {
173
+ this.scenes.forEach(({ scene, el, setup }) => {
174
+ scene.offset(getScrollMagicSceneProps(el, setup).offset);
175
+ });
176
+ }
177
+ /**
178
+ * Reveal scene elements that entered the viewport without a scroll
179
+ * (devtools toggle, window resize, background-tab activation) -
180
+ * resize-driven scene updates carry scrollDirection "PAUSED", so the
181
+ * FORWARD guard in the scene start handler never fires for them
182
+ */
183
+ revealVisibleScrollElements() {
184
+ this.scenes.forEach(({ el, propsShow }) => {
185
+ if (el.classList.contains(CLASS_NAME_WEBSCROLL_FIRED)) {
186
+ return;
187
+ }
188
+ if (!elementIsVisibleInViewport(el, true)) {
189
+ return;
190
+ }
191
+ gsap.to(el, {
192
+ duration: propsShow.time,
193
+ ...propsShow.tween,
194
+ onComplete: () => {
195
+ el.classList.add(CLASS_NAME_WEBSCROLL_FIRED);
196
+ }
197
+ });
198
+ });
199
+ }
200
+ /**
201
+ * #####################
202
+ * ### SCROLL SCENES ###
203
+ * #####################
204
+ */
205
+ // set initial state for all scroll elements in list items
206
+ hideAllScrollElements(items) {
207
+ items.forEach((el) => {
208
+ const scrollEls = [...el.querySelectorAll('*[data-' + DATA_ATTR_WEBSCROLL_SHOW + ']')]
209
+ .filter(this.elementBelongsToView);
210
+ scrollEls.forEach((scrollEl) => {
211
+ const propsInitial = this.getTweenProps(scrollEl, DATA_ATTR_WEBSCROLL_INIT);
212
+ if (propsInitial) {
213
+ gsap.killTweensOf(scrollEl);
214
+ gsap.set(scrollEl, { ...propsInitial.tween });
215
+ }
216
+ });
217
+ });
218
+ }
219
+ // build ScrollMagic scenes for off-screen list items
220
+ buildScrollScenesForOffscreenItems(items) {
221
+ items.forEach((el) => {
222
+ if (this.isElementInViewport(el)) return;
223
+ const scrollEls = [...el.querySelectorAll('*[data-' + DATA_ATTR_WEBSCROLL_SHOW + ']')]
224
+ .filter(this.elementBelongsToView);
225
+ scrollEls.forEach((scrollEl) => {
226
+ this.buildScrollmagicScene(scrollEl);
227
+ });
228
+ });
229
+ }
230
+ // build a single ScrollMagic scene for a scroll element
231
+ buildScrollmagicScene(el) {
232
+ const propsInitial = this.getTweenProps(el, DATA_ATTR_WEBSCROLL_INIT);
233
+ const propsShow = this.getTweenProps(el, DATA_ATTR_WEBSCROLL_SHOW);
234
+ const setup = parseProps(el.dataset[DATA_ATTR_SETUP] || '');
235
+ if (propsInitial && propsShow) {
236
+ const smsp = getScrollMagicSceneProps(el, setup);
237
+ const scene = new ScrollMagic.Scene(smsp);
238
+ scene.on("start", (e) => {
239
+ if (el.classList.contains(CLASS_NAME_WEBSCROLL_FIRED)) return;
240
+ if (e.scrollDirection === "FORWARD") {
241
+ gsap.killTweensOf(el);
242
+ gsap.set(el, { ...propsInitial.tween });
243
+ gsap.to(el, {
244
+ duration: propsShow.time,
245
+ ...propsShow.tween,
246
+ onComplete: () => {
247
+ el.classList.add(CLASS_NAME_WEBSCROLL_FIRED);
248
+ }
249
+ });
250
+ }
251
+ });
252
+ scene.on("add", () => {
253
+ setTimeout(() => {
254
+ if (elementIsVisibleInViewport(el, true)) {
255
+ gsap.to(el, {
256
+ duration: propsShow.time,
257
+ ...propsShow.tween,
258
+ onComplete: () => {
259
+ el.classList.add(CLASS_NAME_WEBSCROLL_FIRED);
260
+ }
261
+ });
262
+ }
263
+ }, 10);
264
+ });
265
+ scene.addTo(scrollController.get());
266
+ this.scenes.push({ scene, el, propsShow, setup });
267
+ }
268
+ }
269
+ // hide off-screen scroll elements during view hide
270
+ hideOffscreenScrollElements(container) {
271
+ const scrollEls = [...container.querySelectorAll('*[data-' + DATA_ATTR_WEBSCROLL_HIDE + ']')]
272
+ .filter(this.elementBelongsToView);
273
+ scrollEls.forEach((el) => {
274
+ if (this.isElementInViewport(el.closest(this.webset.selector))) return;
275
+ const propsHide = this.getTweenProps(el, DATA_ATTR_WEBSCROLL_HIDE);
276
+ if (propsHide) {
277
+ gsap.killTweensOf(el);
278
+ gsap.to(el, { duration: propsHide.time, ...propsHide.tween });
279
+ }
280
+ });
281
+ }
282
+ // destroy all ScrollMagic scenes
283
+ destroyScenes() {
284
+ this.scenes.forEach(({ scene }) => {
285
+ scene.destroy();
286
+ });
287
+ this.scenes = [];
288
+ }
109
289
  /**
110
290
  * ###############
111
291
  * ### HELPERS ###
@@ -28,6 +28,7 @@ class WebflowView extends View {
28
28
  constructor(id) {
29
29
  super(id);
30
30
  this.scenes = [];
31
+ this._revealTimer = null;
31
32
  }
32
33
  /**
33
34
  * prepare (before show)
@@ -85,6 +86,9 @@ class WebflowView extends View {
85
86
  case 'resize':
86
87
  this.onWindowUpdate(e);
87
88
  break;
89
+ case 'pageshow':
90
+ this.onWindowUpdate(e);
91
+ break;
88
92
  case 'DOMContentLoaded':
89
93
  this.onWindowUpdate(e);
90
94
  break;
@@ -95,6 +99,7 @@ class WebflowView extends View {
95
99
  */
96
100
  addEventListeners() {
97
101
  window.addEventListener('resize', this);
102
+ window.addEventListener('pageshow', this);
98
103
  document.addEventListener('DOMContentLoaded', this);
99
104
  }
100
105
  /**
@@ -102,13 +107,53 @@ class WebflowView extends View {
102
107
  */
103
108
  removeEventListeners() {
104
109
  window.removeEventListener('resize', this);
110
+ window.removeEventListener('pageshow', this);
105
111
  document.removeEventListener('DOMContentLoaded', this);
112
+ clearTimeout(this._revealTimer);
106
113
  }
107
114
  onWindowUpdate(e) {
108
115
  if (this.scenes.length > 0) {
109
116
  scrollController.refresh();
117
+ // viewport size changed - recompute viewport-dependent scene offsets
118
+ // (they are baked from window.innerHeight at scene-build time)
119
+ this.refreshSceneOffsets();
120
+ // reveal elements that entered the viewport without a scroll (debounced -
121
+ // resize fires in bursts while dragging / toggling devtools)
122
+ clearTimeout(this._revealTimer);
123
+ this._revealTimer = setTimeout(() => this.revealVisibleScrollElements(), 150);
110
124
  }
111
125
  }
126
+ /**
127
+ * Recompute viewport-dependent scene offsets after a viewport change
128
+ */
129
+ refreshSceneOffsets() {
130
+ this.scenes.forEach(({ scene, el, setup }) => {
131
+ scene.offset(getScrollMagicSceneProps(el, setup).offset);
132
+ });
133
+ }
134
+ /**
135
+ * Reveal scene elements that entered the viewport without a scroll
136
+ * (devtools toggle, window resize, background-tab activation) -
137
+ * resize-driven scene updates carry scrollDirection "PAUSED", so the
138
+ * FORWARD guard in the scene start handler never fires for them
139
+ */
140
+ revealVisibleScrollElements() {
141
+ this.scenes.forEach(({ el, propsShow }) => {
142
+ if (el.classList.contains(CLASS_NAME_WEBSCROLL_FIRED)) {
143
+ return;
144
+ }
145
+ if (!elementIsVisibleInViewport(el, true)) {
146
+ return;
147
+ }
148
+ gsap.to(el, {
149
+ duration: propsShow.time,
150
+ ...propsShow.tween,
151
+ onComplete: () => {
152
+ el.classList.add(CLASS_NAME_WEBSCROLL_FIRED);
153
+ }
154
+ });
155
+ });
156
+ }
112
157
  /**
113
158
  * ############
114
159
  * ### SHOW ###
@@ -234,7 +279,7 @@ class WebflowView extends View {
234
279
  }, 10);
235
280
  });
236
281
  scene.addTo(scrollController.get());
237
- this.scenes.push(scene);
282
+ this.scenes.push({ scene, el, propsShow, setup });
238
283
  }
239
284
  }
240
285
  /**
@@ -310,9 +355,8 @@ class WebflowView extends View {
310
355
  }
311
356
  // ## Destroy scrollmagic scenes
312
357
  destroyScenes() {
313
- this.scenes.forEach((scene) => {
358
+ this.scenes.forEach(({ scene }) => {
314
359
  scene.destroy();
315
- scene = null;
316
360
  })
317
361
  this.scenes = [];
318
362
  }