@brandocms/jupiter 3.55.0 → 4.0.0-beta.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.
Files changed (76) hide show
  1. package/README.md +509 -54
  2. package/package.json +30 -18
  3. package/src/index.js +15 -10
  4. package/src/modules/Application/index.js +236 -158
  5. package/src/modules/Breakpoints/index.js +116 -36
  6. package/src/modules/Cookies/index.js +95 -64
  7. package/src/modules/CoverOverlay/index.js +21 -14
  8. package/src/modules/Dataloader/index.js +71 -24
  9. package/src/modules/Dataloader/url-sync.js +238 -0
  10. package/src/modules/Dom/index.js +24 -0
  11. package/src/modules/DoubleHeader/index.js +571 -0
  12. package/src/modules/Dropdown/index.js +108 -73
  13. package/src/modules/EqualHeightElements/index.js +8 -8
  14. package/src/modules/EqualHeightImages/index.js +15 -7
  15. package/src/modules/FixedHeader/index.js +116 -30
  16. package/src/modules/FooterReveal/index.js +5 -5
  17. package/src/modules/HeroSlider/index.js +231 -106
  18. package/src/modules/HeroVideo/index.js +72 -44
  19. package/src/modules/Lazyload/index.js +128 -80
  20. package/src/modules/Lightbox/index.js +101 -80
  21. package/src/modules/Links/index.js +77 -51
  22. package/src/modules/Looper/index.js +1737 -0
  23. package/src/modules/Marquee/index.js +106 -37
  24. package/src/modules/MobileMenu/index.js +105 -130
  25. package/src/modules/Moonwalk/index.js +479 -153
  26. package/src/modules/Parallax/index.js +280 -57
  27. package/src/modules/Popover/index.js +187 -17
  28. package/src/modules/Popup/index.js +172 -53
  29. package/src/modules/ScrollSpy/index.js +21 -0
  30. package/src/modules/StackedBoxes/index.js +8 -6
  31. package/src/modules/StickyHeader/index.js +394 -164
  32. package/src/modules/Toggler/index.js +207 -11
  33. package/src/modules/Typography/index.js +33 -20
  34. package/src/utils/motion-helpers.js +330 -0
  35. package/types/README.md +159 -0
  36. package/types/events/index.d.ts +20 -0
  37. package/types/index.d.ts +6 -0
  38. package/types/modules/Application/index.d.ts +168 -0
  39. package/types/modules/Breakpoints/index.d.ts +40 -0
  40. package/types/modules/Cookies/index.d.ts +81 -0
  41. package/types/modules/CoverOverlay/index.d.ts +6 -0
  42. package/types/modules/Dataloader/index.d.ts +38 -0
  43. package/types/modules/Dataloader/url-sync.d.ts +36 -0
  44. package/types/modules/Dom/index.d.ts +47 -0
  45. package/types/modules/DoubleHeader/index.d.ts +63 -0
  46. package/types/modules/Dropdown/index.d.ts +15 -0
  47. package/types/modules/EqualHeightElements/index.d.ts +8 -0
  48. package/types/modules/EqualHeightImages/index.d.ts +11 -0
  49. package/types/modules/FeatureTests/index.d.ts +27 -0
  50. package/types/modules/FixedHeader/index.d.ts +219 -0
  51. package/types/modules/Fontloader/index.d.ts +5 -0
  52. package/types/modules/FooterReveal/index.d.ts +5 -0
  53. package/types/modules/HeroSlider/index.d.ts +28 -0
  54. package/types/modules/HeroVideo/index.d.ts +83 -0
  55. package/types/modules/Lazyload/index.d.ts +80 -0
  56. package/types/modules/Lightbox/index.d.ts +123 -0
  57. package/types/modules/Links/index.d.ts +55 -0
  58. package/types/modules/Looper/index.d.ts +127 -0
  59. package/types/modules/Marquee/index.d.ts +23 -0
  60. package/types/modules/MobileMenu/index.d.ts +63 -0
  61. package/types/modules/Moonwalk/index.d.ts +322 -0
  62. package/types/modules/Parallax/index.d.ts +71 -0
  63. package/types/modules/Popover/index.d.ts +29 -0
  64. package/types/modules/Popup/index.d.ts +76 -0
  65. package/types/modules/ScrollSpy/index.d.ts +29 -0
  66. package/types/modules/StackedBoxes/index.d.ts +9 -0
  67. package/types/modules/StickyHeader/index.d.ts +220 -0
  68. package/types/modules/Toggler/index.d.ts +48 -0
  69. package/types/modules/Typography/index.d.ts +77 -0
  70. package/types/utils/dispatchElementEvent.d.ts +1 -0
  71. package/types/utils/imageIsLoaded.d.ts +1 -0
  72. package/types/utils/imagesAreLoaded.d.ts +1 -0
  73. package/types/utils/loadScript.d.ts +2 -0
  74. package/types/utils/prefersReducedMotion.d.ts +4 -0
  75. package/types/utils/rafCallback.d.ts +2 -0
  76. package/types/utils/zoom.d.ts +4 -0
@@ -9,14 +9,12 @@
9
9
  *
10
10
  */
11
11
 
12
- import { gsap } from 'gsap'
13
- import { CSSPlugin } from 'gsap/CSSPlugin'
12
+ import { animate } from 'motion'
14
13
  import _defaultsDeep from 'lodash.defaultsdeep'
15
14
  import prefersReducedMotion from '../../utils/prefersReducedMotion'
16
15
  import * as Events from '../../events'
17
16
  import imageIsLoaded from '../../utils/imageIsLoaded'
18
-
19
- gsap.registerPlugin(CSSPlugin)
17
+ import { set, delayedCall } from '../../utils/motion-helpers'
20
18
 
21
19
  const DEFAULT_OPTIONS = {
22
20
  el: '[data-hero-slider]',
@@ -30,7 +28,7 @@ const DEFAULT_OPTIONS = {
30
28
  zIndex: {
31
29
  visible: 5,
32
30
  next: 4,
33
- regular: 3
31
+ regular: 3,
34
32
  },
35
33
  transition: {
36
34
  /* how long the actual transition from slide to slide takes */
@@ -38,31 +36,32 @@ const DEFAULT_OPTIONS = {
38
36
  /* the transition type. 'parallax' or 'fade' */
39
37
  type: 'parallax',
40
38
  /* how much to scale when 'idle' */
41
- scale: 1.05
39
+ scale: 1.05,
42
40
  },
43
41
 
44
- onTransition: hs => {
42
+ onTransition: (hs) => {
45
43
  hs.slide('parallax')
46
44
  },
47
45
 
48
46
  onInitialize: (/* hs */) => {},
49
47
 
50
48
  onFadeIn: (hs, callback) => {
51
- if (hs.slides.length > 1) {
52
- gsap.to(hs.el, {
53
- duration: 0.25,
54
- opacity: 1,
55
- onComplete: () => {
56
- callback()
57
- }
58
- })
59
- } else {
60
- gsap.to(hs.el, {
61
- duration: 0.25,
62
- opacity: 1
63
- })
49
+ // Get the first slide's image to start zooming during reveal
50
+ const firstSlideImg = hs.slides[hs._currentSlideIdx].querySelector('.hero-slide-img')
51
+
52
+ // Fade in the container
53
+ animate(hs.el, { opacity: 1 }, { duration: 0.25 })
54
+
55
+ // Start zoom immediately as container fades in
56
+ if (firstSlideImg && hs.slides.length > 1) {
57
+ // Animate with linear easing - MUST specify type: "tween"!
58
+ animate(
59
+ firstSlideImg,
60
+ { scale: [1, hs.opts.transition.scale] },
61
+ { type: "tween", duration: hs.opts.interval, ease: "linear" }
62
+ ).finished.then(callback)
64
63
  }
65
- }
64
+ },
66
65
  }
67
66
 
68
67
  export default class HeroSlider {
@@ -85,14 +84,15 @@ export default class HeroSlider {
85
84
 
86
85
  initialize() {
87
86
  this._addResizeHandler()
87
+ this._addVisibilityHandler()
88
88
  // style the container
89
- gsap.set(this.el, {
89
+ set(this.el, {
90
90
  position: 'absolute',
91
91
  top: 0,
92
92
  left: 0,
93
93
  width: '100%',
94
94
  height: '100%',
95
- overflow: 'hidden'
95
+ overflow: 'hidden',
96
96
  })
97
97
 
98
98
  this.slides = this.el.querySelectorAll('[data-hero-slide]')
@@ -102,31 +102,39 @@ export default class HeroSlider {
102
102
  this._currentSlideIdx = this.opts.initialSlideNumber
103
103
 
104
104
  // style the slides
105
- Array.from(this.slides).forEach(s => {
106
- gsap.set(s, {
105
+ Array.from(this.slides).forEach((s, index) => {
106
+ set(s, {
107
107
  zIndex: this.opts.zIndex.regular,
108
108
  position: 'absolute',
109
109
  top: 0,
110
110
  left: 0,
111
111
  width: '100%',
112
- height: '100%'
112
+ height: '100%',
113
+ opacity: index === 0 ? 1 : 0, // Only first slide visible initially
113
114
  })
114
115
 
115
116
  const img = s.querySelector('.hero-slide-img')
116
117
 
117
118
  if (img) {
118
- gsap.set(img, {
119
+ set(img, {
119
120
  width: document.body.clientWidth,
120
121
  height: '100%',
121
122
  top: 0,
122
123
  left: 0,
123
- position: 'absolute'
124
+ position: 'absolute',
125
+ transition: 'none',
126
+ transformOrigin: 'center center',
127
+ willChange: 'transform',
128
+ scale: 1, // Initialize scale
124
129
  })
125
130
  } else {
126
- console.error('==> JUPITER/HEROSLIDER: MISSING .hero-slide-img INSIDE [data-hero-slide]')
131
+ console.error(
132
+ '==> JUPITER/HEROSLIDER: MISSING .hero-slide-img INSIDE [data-hero-slide]'
133
+ )
127
134
  }
128
135
  })
129
136
 
137
+ // Set proper z-indexes for first two slides
130
138
  this.slides[0].style.zIndex = this.opts.zIndex.visible
131
139
  if (this.slides[1]) {
132
140
  this.slides[1].style.zIndex = this.opts.zIndex.next
@@ -180,91 +188,147 @@ export default class HeroSlider {
180
188
  * Switches between slides
181
189
  */
182
190
  slide(type) {
183
- const timeline = gsap.timeline()
184
-
185
191
  switch (type) {
186
192
  case 'fade':
187
- timeline
188
- .set(this._currentSlide, {
193
+ {
194
+ // Setup: set current slide invisible at correct z-index
195
+ set(this._currentSlide, {
189
196
  opacity: 0,
190
- scale: 1,
191
- zIndex: this.opts.zIndex.visible
192
- })
193
- .set(this._nextSlide, {
194
- opacity: 0
195
- })
196
- .to(this._previousSlide, {
197
- duration: this.opts.interval,
198
- scale: this.opts.transition.scale
199
- })
200
- .to(this._currentSlide, {
201
- duration: this.opts.transition.duration,
202
- opacity: 1,
203
- delay: this.opts.interval - this.opts.transition.duration,
204
- force3D: true,
205
- ease: 'sine.inOut'
197
+ zIndex: this.opts.zIndex.visible,
206
198
  })
207
- .set(this._previousSlide, {
208
- opacity: 0
199
+ set(this._nextSlide, { opacity: 0 })
200
+
201
+ // Get the images to animate
202
+ const previousSlideImg = this._previousSlide.querySelector('.hero-slide-img')
203
+ const currentSlideImg = this._currentSlide.querySelector('.hero-slide-img')
204
+
205
+ // Explicitly set starting scale for current slide
206
+ set(currentSlideImg, { scale: 1 })
207
+
208
+ // Build animation sequence
209
+ // Previous slide continues zooming at same rate during fade out
210
+ // Zoom rate: (scale - 1) / interval, so in transition.duration we zoom by that rate * duration
211
+ const zoomRate = (this.opts.transition.scale - 1) / this.opts.interval
212
+ const continueZoomAmount = zoomRate * this.opts.transition.duration
213
+ const continueZoomTarget = this.opts.transition.scale + continueZoomAmount
214
+
215
+ const sequence = [
216
+ // Previous slide continues zooming during fade at same rate
217
+ [
218
+ previousSlideImg,
219
+ { scale: [this.opts.transition.scale, continueZoomTarget] },
220
+ { type: "tween", duration: this.opts.transition.duration, ease: "linear", at: 0 },
221
+ ],
222
+ // Current slide fade-in (starts immediately)
223
+ [
224
+ this._currentSlide,
225
+ { opacity: 1 },
226
+ {
227
+ type: "tween",
228
+ duration: this.opts.transition.duration,
229
+ ease: [0.45, 0, 0.55, 1], // sine.inOut bezier
230
+ at: 0,
231
+ },
232
+ ],
233
+ // Current slide image zoom (starts immediately as it fades in)
234
+ [
235
+ currentSlideImg,
236
+ { scale: [1, this.opts.transition.scale] },
237
+ { type: "tween", duration: this.opts.interval, ease: "linear", at: 0 },
238
+ ],
239
+ ]
240
+
241
+ this._currentAnimation = animate(sequence)
242
+
243
+ this._currentAnimation.finished.then(() => {
244
+ // Cleanup after animation completes
245
+ set(this._previousSlide, { opacity: 0 })
246
+ set(this._currentSlide, { opacity: 1 })
247
+ set(previousSlideImg, { scale: 1 })
248
+ this._nextSlide.style.zIndex = this.opts.zIndex.visible
249
+ this._currentSlide.style.zIndex = this.opts.zIndex.regular
250
+ this._previousSlide.style.zIndex = this.opts.zIndex.regular
251
+ this.next()
209
252
  })
210
- .call(
211
- () => {
212
- this._nextSlide.style.zIndex = this.opts.zIndex.visible
213
- this._currentSlide.style.zIndex = this.opts.zIndex.regular
214
- this._previousSlide.style.zIndex = this.opts.zIndex.regular
215
- this.next()
216
- },
217
- null,
218
- this
219
- )
220
-
253
+ }
221
254
  break
222
255
 
223
256
  case 'parallax':
224
- timeline
225
- .set(this._currentSlide, {
257
+ {
258
+ // Setup: current slide behind previous slide
259
+ set(this._currentSlide, {
226
260
  zIndex: this.opts.zIndex.next,
227
- scale: 1.0,
228
- width: '100%'
229
- })
230
- .fromTo(
231
- this._previousSlide,
232
- {
233
- duration: this.opts.interval,
234
- overflow: 'hidden'
235
- },
236
- {
237
- duration: this.opts.interval,
238
- scale: this.opts.transition.scale
239
- }
240
- )
241
- .to(this._previousSlide, {
242
- duration: this.opts.transition.duration,
243
- width: 0,
244
- ease: 'power3.in',
245
- autoRound: true,
246
- overwrite: 'preexisting'
247
- })
248
- .set(this._nextSlide, {
249
- zIndex: this.opts.zIndex.next
261
+ width: '100%',
262
+ opacity: 1, // Make sure it's visible underneath
250
263
  })
251
- .set(this._currentSlide, {
252
- zIndex: this.opts.zIndex.visible,
253
- width: '100%'
254
- })
255
- .set(this._previousSlide, {
256
- zIndex: this.opts.zIndex.regular,
257
- scale: 1.0,
258
- width: '100%'
259
- })
260
- .call(() => {
264
+ set(this._previousSlide, { overflow: 'hidden' })
265
+
266
+ // Get the current slide's image to animate
267
+ const previousSlideImg = this._previousSlide.querySelector('.hero-slide-img')
268
+ const currentSlideImg = this._currentSlide.querySelector('.hero-slide-img')
269
+
270
+ // Explicitly set starting scale for current slide
271
+ set(currentSlideImg, { scale: 1 })
272
+
273
+ // Build animation sequence
274
+ // Previous slide continues zooming at same rate during collapse
275
+ // Zoom rate: (scale - 1) / interval, so in transition.duration we zoom by that rate * duration
276
+ const zoomRate = (this.opts.transition.scale - 1) / this.opts.interval
277
+ const continueZoomAmount = zoomRate * this.opts.transition.duration
278
+ const continueZoomTarget = this.opts.transition.scale + continueZoomAmount
279
+
280
+ const sequence = [
281
+ // Previous slide continues zooming during collapse at same rate
282
+ [
283
+ previousSlideImg,
284
+ { scale: [this.opts.transition.scale, continueZoomTarget] },
285
+ { type: "tween", duration: this.opts.transition.duration, ease: "linear", at: 0 },
286
+ ],
287
+ // Previous slide width collapse (starts immediately)
288
+ [
289
+ this._previousSlide,
290
+ { width: 0 },
291
+ {
292
+ type: "tween",
293
+ duration: this.opts.transition.duration,
294
+ ease: [0.895, 0.03, 0.685, 0.22], // power3.in bezier
295
+ at: 0,
296
+ },
297
+ ],
298
+ // Current slide image zoom (starts immediately as it's revealed)
299
+ [
300
+ currentSlideImg,
301
+ { scale: [1, this.opts.transition.scale] },
302
+ { type: "tween", duration: this.opts.interval, ease: "linear", at: 0 },
303
+ ],
304
+ ]
305
+
306
+ this._currentAnimation = animate(sequence)
307
+
308
+ this._currentAnimation.finished.then(() => {
309
+ // Cleanup and shuffle z-indexes
310
+ set(this._nextSlide, { zIndex: this.opts.zIndex.next, opacity: 1 })
311
+ set(this._currentSlide, {
312
+ zIndex: this.opts.zIndex.visible,
313
+ width: '100%',
314
+ opacity: 1,
315
+ })
316
+ set(this._previousSlide, {
317
+ zIndex: this.opts.zIndex.regular,
318
+ width: '100%',
319
+ opacity: 0, // Hide previous slide
320
+ })
321
+ // Reset previous slide image scale for next time
322
+ set(previousSlideImg, { scale: 1.0 })
261
323
  this.next()
262
324
  })
263
-
325
+ }
264
326
  break
265
327
 
266
328
  default:
267
- console.error('==> JUPITER/HEROSLIDER: Unrecognized `opts.transition.type` option.')
329
+ console.error(
330
+ '==> JUPITER/HEROSLIDER: Unrecognized `opts.transition.type` option.'
331
+ )
268
332
  }
269
333
  }
270
334
 
@@ -272,13 +336,19 @@ export default class HeroSlider {
272
336
  * Add a window resize handler that resizes slide widths
273
337
  */
274
338
  _addResizeHandler() {
275
- this.observer = new IntersectionObserver(entries => {
339
+ this.observer = new IntersectionObserver((entries) => {
276
340
  const [{ isIntersecting }] = entries
277
341
  if (isIntersecting) {
278
342
  this._resizeSlides()
279
- window.addEventListener(Events.APPLICATION_RESIZE, this._resizeSlides.bind(this))
343
+ window.addEventListener(
344
+ Events.APPLICATION_RESIZE,
345
+ this._resizeSlides.bind(this)
346
+ )
280
347
  } else {
281
- window.removeEventListener(Events.APPLICATION_RESIZE, this._resizeSlides.bind(this))
348
+ window.removeEventListener(
349
+ Events.APPLICATION_RESIZE,
350
+ this._resizeSlides.bind(this)
351
+ )
282
352
  }
283
353
  })
284
354
 
@@ -286,10 +356,65 @@ export default class HeroSlider {
286
356
  }
287
357
 
288
358
  _resizeSlides() {
289
- gsap.to(this.images, {
290
- duration: 0.15,
291
- width: document.body.clientWidth,
292
- overwrite: 'all'
359
+ // Stop any running resize animations
360
+ if (this.resizeAnimation) {
361
+ this.resizeAnimation.stop()
362
+ }
363
+
364
+ this.resizeAnimation = animate(
365
+ this.images,
366
+ { width: document.body.clientWidth },
367
+ { duration: 0.15 }
368
+ )
369
+ }
370
+
371
+ /**
372
+ * Add a visibility change handler to restart animations when tab becomes visible
373
+ */
374
+ _addVisibilityHandler() {
375
+ document.addEventListener('visibilitychange', () => {
376
+ if (document.hidden) {
377
+ // Cancel running animation when tab becomes hidden
378
+ if (this._currentAnimation) {
379
+ this._currentAnimation.cancel()
380
+ this._currentAnimation = null
381
+ }
382
+ } else {
383
+ // Reset and restart when tab becomes visible
384
+ this._resetAndRestart()
385
+ }
293
386
  })
294
387
  }
388
+
389
+ /**
390
+ * Reset slide states and restart the animation cycle
391
+ */
392
+ _resetAndRestart() {
393
+ // Reset all slides to initial state
394
+ Array.from(this.slides).forEach((s, index) => {
395
+ const nextSlideIdx = (this._currentSlideIdx + 1) % this.slides.length
396
+ set(s, {
397
+ width: '100%',
398
+ opacity: index === this._currentSlideIdx ? 1 : 0,
399
+ zIndex: index === this._currentSlideIdx ? this.opts.zIndex.visible :
400
+ index === nextSlideIdx ? this.opts.zIndex.next :
401
+ this.opts.zIndex.regular
402
+ })
403
+ const img = s.querySelector('.hero-slide-img')
404
+ if (img) {
405
+ set(img, { scale: 1 })
406
+ }
407
+ })
408
+
409
+ // Restart the zoom animation on current slide, then continue cycle
410
+ const currentSlideImg = this.slides[this._currentSlideIdx].querySelector('.hero-slide-img')
411
+ if (currentSlideImg && this.slides.length > 1) {
412
+ this._currentAnimation = animate(
413
+ currentSlideImg,
414
+ { scale: [1, this.opts.transition.scale] },
415
+ { type: "tween", duration: this.opts.interval, ease: "linear" }
416
+ )
417
+ this._currentAnimation.finished.then(() => this.next())
418
+ }
419
+ }
295
420
  }
@@ -1,44 +1,54 @@
1
1
  /**
2
- *
3
2
  * HERO VIDEO
4
3
  *
5
- * ## Example
4
+ * Module for handling hero video elements with cover images, play/pause controls,
5
+ * and responsive behavior.
6
6
  *
7
- * const hs = HeroVideo(opts)
7
+ * ## Example
8
8
  *
9
+ * const heroVideo = new HeroVideo(app, opts)
9
10
  */
10
11
 
11
- import { gsap } from 'gsap'
12
- import { CSSPlugin } from 'gsap/CSSPlugin'
12
+ import { animate } from 'motion'
13
13
  import _defaultsDeep from 'lodash.defaultsdeep'
14
14
  import * as Events from '../../events'
15
15
  import prefersReducedMotion from '../../utils/prefersReducedMotion'
16
16
  import imageIsLoaded from '../../utils/imageIsLoaded'
17
17
  import Dom from '../Dom'
18
+ import { set, animateAutoAlpha } from '../../utils/motion-helpers'
18
19
 
19
- gsap.registerPlugin(CSSPlugin)
20
+ /**
21
+ * @typedef {Object} HeroVideoElementGenerators
22
+ * @property {Function} [pause] - Function that returns the pause button HTML
23
+ * @property {Function} [play] - Function that returns the play button HTML
24
+ */
20
25
 
26
+ /**
27
+ * @typedef {Object} HeroVideoOptions
28
+ * @property {string|HTMLElement} [el='[data-hero-video]'] - Target element selector or element
29
+ * @property {Function} [onFadeIn] - Called when video fades in
30
+ * @property {Function} [onFadeInCover] - Called when cover fades in
31
+ * @property {Function} [onFadeOutCover] - Called when cover fades out
32
+ * @property {Function} [onPlayReady] - Called when video is ready to play
33
+ * @property {Function} [onClickPlay] - Called when play button is clicked
34
+ * @property {Function} [onClickPause] - Called when pause button is clicked
35
+ * @property {string} [pauseParent='.hero-content'] - Selector for parent element to append pause button to
36
+ * @property {HeroVideoElementGenerators} [elements] - Element generators for UI components
37
+ */
38
+
39
+ /** @type {HeroVideoOptions} */
21
40
  const DEFAULT_OPTIONS = {
22
41
  el: '[data-hero-video]',
23
- onFadeIn: hero => {
24
- gsap.to(hero.videoDiv, {
25
- duration: 1,
26
- autoAlpha: 1
27
- })
42
+ onFadeIn: (hero) => {
43
+ animateAutoAlpha(hero.videoDiv, 1, { duration: 1 })
28
44
  },
29
45
 
30
- onFadeInCover: hero => {
31
- gsap.to(hero.cover, {
32
- duration: 0.35,
33
- autoAlpha: 1
34
- })
46
+ onFadeInCover: (hero) => {
47
+ animateAutoAlpha(hero.cover, 1, { duration: 0.35 })
35
48
  },
36
49
 
37
- onFadeOutCover: hero => {
38
- gsap.set(hero.cover, {
39
- duration: 0.35,
40
- autoAlpha: 0
41
- })
50
+ onFadeOutCover: (hero) => {
51
+ animateAutoAlpha(hero.cover, 0, { duration: 0.35 })
42
52
  },
43
53
 
44
54
  onPlayReady: () => {},
@@ -57,11 +67,19 @@ const DEFAULT_OPTIONS = {
57
67
 
58
68
  play: () => `
59
69
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 350 350"><circle cx="175" cy="175" r="172.5" stroke="#000" stroke-width="10"/><path stroke="#000" stroke-width="10" d="M240 174l-112-72v148l112-76z"/></svg>
60
- `
61
- }
70
+ `,
71
+ },
62
72
  }
63
73
 
74
+ /**
75
+ * HeroVideo component for handling responsive background videos with controls
76
+ */
64
77
  export default class HeroVideo {
78
+ /**
79
+ * Create a new HeroVideo instance
80
+ * @param {Object} app - Application instance
81
+ * @param {HeroVideoOptions} [opts={}] - HeroVideo options
82
+ */
65
83
  constructor(app, opts = {}) {
66
84
  this.app = app
67
85
  this.booting = true
@@ -86,18 +104,18 @@ export default class HeroVideo {
86
104
  initialize() {
87
105
  this._addResizeHandler()
88
106
  // style the container
89
- gsap.set(this.el, {
107
+ set(this.el, {
90
108
  position: 'absolute',
91
109
  top: 0,
92
110
  left: 0,
93
111
  width: '100%',
94
112
  height: '100%',
95
- overflow: 'hidden'
113
+ overflow: 'hidden',
96
114
  })
97
115
 
98
116
  this.cover = Dom.find(this.el, '[data-cover]')
99
117
  if (this.cover) {
100
- gsap.set(this.cover, { autoAlpha: 0 })
118
+ animateAutoAlpha(this.cover, 0, { duration: 0 })
101
119
  }
102
120
 
103
121
  const pauseParent = document.querySelector(this.opts.pauseParent)
@@ -118,27 +136,30 @@ export default class HeroVideo {
118
136
  this.addEvents()
119
137
  this.setSrc()
120
138
 
121
- gsap.set(this.videoDiv, {
139
+ set(this.videoDiv, {
122
140
  position: 'absolute',
123
141
  top: 0,
124
142
  left: 0,
125
143
  width: '100%',
126
144
  height: '100%',
127
- opacity: 0
145
+ opacity: 0,
128
146
  })
129
147
 
130
148
  if (!this.video) {
131
- console.error('==> JUPITER/HEROVIDEO: MISSING <video> INSIDE [data-hero-video-content]')
149
+ console.error(
150
+ '==> JUPITER/HEROVIDEO: MISSING <video> INSIDE [data-hero-video-content]'
151
+ )
132
152
  return
133
153
  }
134
154
  this.video.muted = true
135
155
 
136
- gsap.set(this.video, {
137
- width: document.body.clientWidth,
156
+ set(this.video, {
157
+ width: '100%',
138
158
  height: '100%',
159
+ objectFit: 'cover',
160
+ position: 'absolute',
139
161
  top: 0,
140
162
  left: 0,
141
- position: 'absolute'
142
163
  })
143
164
 
144
165
  if (this.cover) {
@@ -149,7 +170,11 @@ export default class HeroVideo {
149
170
 
150
171
  window.addEventListener(Events.APPLICATION_READY, () => {
151
172
  /* Wait for the video to load, then fade in container element */
152
- if (!this.video.playing && !prefersReducedMotion() && this.video.readyState >= 3) {
173
+ if (
174
+ !this.video.playing &&
175
+ !prefersReducedMotion() &&
176
+ this.video.readyState >= 3
177
+ ) {
153
178
  this.play()
154
179
  this.fadeIn()
155
180
  this.booting = false
@@ -188,12 +213,12 @@ export default class HeroVideo {
188
213
  this.fadeIn()
189
214
  this.booting = false
190
215
  } else {
191
- gsap.set(this.videoDiv, { opacity: 1 })
216
+ set(this.videoDiv, { opacity: 1 })
192
217
  }
193
218
  }
194
219
  })
195
220
  if (this.elements.pause) {
196
- this.elements.pause.addEventListener('click', e => {
221
+ this.elements.pause.addEventListener('click', (e) => {
197
222
  e.preventDefault()
198
223
  e.stopPropagation()
199
224
  if (this.playing) {
@@ -233,7 +258,7 @@ export default class HeroVideo {
233
258
  }
234
259
 
235
260
  addObserver() {
236
- const observer = new IntersectionObserver(entries => {
261
+ const observer = new IntersectionObserver((entries) => {
237
262
  const [{ isIntersecting }] = entries
238
263
  if (isIntersecting) {
239
264
  if (this.forcePaused) {
@@ -255,13 +280,19 @@ export default class HeroVideo {
255
280
  * Add a window resize handler that resizes video width
256
281
  */
257
282
  _addResizeHandler() {
258
- this.observer = new IntersectionObserver(entries => {
283
+ this.observer = new IntersectionObserver((entries) => {
259
284
  const [{ isIntersecting }] = entries
260
285
  if (isIntersecting) {
261
286
  this._resize()
262
- window.addEventListener(Events.APPLICATION_RESIZE, this._resize.bind(this))
287
+ window.addEventListener(
288
+ Events.APPLICATION_RESIZE,
289
+ this._resize.bind(this)
290
+ )
263
291
  } else {
264
- window.removeEventListener(Events.APPLICATION_RESIZE, this._resize.bind(this))
292
+ window.removeEventListener(
293
+ Events.APPLICATION_RESIZE,
294
+ this._resize.bind(this)
295
+ )
265
296
  }
266
297
  })
267
298
 
@@ -269,10 +300,7 @@ export default class HeroVideo {
269
300
  }
270
301
 
271
302
  _resize() {
272
- gsap.to(this.video, {
273
- duration: 0.15,
274
- width: document.body.clientWidth,
275
- overwrite: 'all'
276
- })
303
+ // Video uses object-fit: cover, so no manual sizing needed
304
+ // This method is kept for compatibility but does nothing
277
305
  }
278
306
  }