@brandocms/jupiter 4.0.0-beta.1 → 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 (50) hide show
  1. package/README.md +191 -2
  2. package/package.json +20 -18
  3. package/src/index.js +10 -10
  4. package/src/modules/Application/index.js +203 -157
  5. package/src/modules/Cookies/index.js +34 -55
  6. package/src/modules/CoverOverlay/index.js +20 -13
  7. package/src/modules/Dataloader/index.js +71 -24
  8. package/src/modules/Dataloader/url-sync.js +238 -0
  9. package/src/modules/Dom/index.js +18 -0
  10. package/src/modules/DoubleHeader/index.js +571 -0
  11. package/src/modules/Dropdown/index.js +101 -75
  12. package/src/modules/EqualHeightElements/index.js +5 -7
  13. package/src/modules/EqualHeightImages/index.js +7 -2
  14. package/src/modules/FixedHeader/index.js +60 -30
  15. package/src/modules/FooterReveal/index.js +3 -3
  16. package/src/modules/HeroSlider/index.js +207 -91
  17. package/src/modules/HeroVideo/index.js +15 -27
  18. package/src/modules/Lazyload/index.js +101 -80
  19. package/src/modules/Lightbox/index.js +17 -55
  20. package/src/modules/Links/index.js +54 -49
  21. package/src/modules/Looper/index.js +1737 -0
  22. package/src/modules/Marquee/index.js +106 -37
  23. package/src/modules/MobileMenu/index.js +70 -124
  24. package/src/modules/Moonwalk/index.js +349 -150
  25. package/src/modules/Popover/index.js +186 -28
  26. package/src/modules/Popup/index.js +27 -34
  27. package/src/modules/StackedBoxes/index.js +3 -3
  28. package/src/modules/StickyHeader/index.js +364 -155
  29. package/src/modules/Toggler/index.js +184 -27
  30. package/src/utils/motion-helpers.js +330 -0
  31. package/types/index.d.ts +1 -30
  32. package/types/modules/Application/index.d.ts +6 -6
  33. package/types/modules/Breakpoints/index.d.ts +2 -0
  34. package/types/modules/Dataloader/index.d.ts +5 -2
  35. package/types/modules/Dataloader/url-sync.d.ts +36 -0
  36. package/types/modules/Dom/index.d.ts +7 -0
  37. package/types/modules/DoubleHeader/index.d.ts +63 -0
  38. package/types/modules/Dropdown/index.d.ts +7 -30
  39. package/types/modules/EqualHeightImages/index.d.ts +1 -1
  40. package/types/modules/FixedHeader/index.d.ts +1 -1
  41. package/types/modules/Lazyload/index.d.ts +9 -9
  42. package/types/modules/Lightbox/index.d.ts +0 -5
  43. package/types/modules/Looper/index.d.ts +127 -0
  44. package/types/modules/Moonwalk/index.d.ts +6 -15
  45. package/types/modules/Parallax/index.d.ts +10 -32
  46. package/types/modules/Popover/index.d.ts +12 -0
  47. package/types/modules/Popup/index.d.ts +6 -19
  48. package/types/modules/ScrollSpy/index.d.ts +1 -1
  49. package/types/modules/StickyHeader/index.d.ts +171 -14
  50. package/types/modules/Toggler/index.d.ts +24 -2
@@ -1,6 +1,7 @@
1
- import gsap from 'gsap/gsap-core'
1
+ import { animate } from 'motion'
2
2
  import _defaultsDeep from 'lodash.defaultsdeep'
3
3
  import Dom from '../Dom'
4
+ import { set, clearProps } from '../../utils/motion-helpers'
4
5
 
5
6
  const DEFAULT_OPTIONS = {
6
7
  speed: 100,
@@ -11,7 +12,7 @@ const DEFAULT_OPTIONS = {
11
12
  spacer: '<span>&nbsp;&mdash;&nbsp;</span>',
12
13
 
13
14
  onReveal: marqueeEl => {
14
- gsap.to(marqueeEl, { opacity: 1, ease: 'none' })
15
+ animate(marqueeEl, { opacity: 1 }, { ease: 'linear' })
15
16
  }
16
17
  }
17
18
 
@@ -31,7 +32,7 @@ export default class Marquee {
31
32
  }
32
33
 
33
34
  initialize() {
34
- gsap.set(this.elements.$marquee, { opacity: 0 })
35
+ set(this.elements.$marquee, { opacity: 0 })
35
36
  window.addEventListener('APPLICATION:RESIZE', this.updateMarquee.bind(this))
36
37
  window.addEventListener('APPLICATION:REVEALED', this.revealMarquee.bind(this))
37
38
  this.updateMarquee()
@@ -57,15 +58,19 @@ export default class Marquee {
57
58
 
58
59
  this.killTweens()
59
60
  this.clearHolders()
60
- this.setHeight()
61
61
  this.fillText()
62
+ this.setHeight()
62
63
 
63
64
  const holderWidth = this.elements.$holder.offsetWidth
64
65
  const $allHolders = Dom.all(this.elements.$el, '[data-marquee-holder]')
65
66
  const marqueeWidth = holderWidth * $allHolders.length
66
- this.duration = (holderWidth + marqueeWidth) / this.opts.speed
67
+ // Cap duration at 40s to prevent precision issues at slow speeds
68
+ this.duration = Math.min(
69
+ (holderWidth + marqueeWidth) / this.opts.speed,
70
+ 40
71
+ )
67
72
 
68
- gsap.set(this.elements.$marquee, { width: marqueeWidth })
73
+ set(this.elements.$marquee, { width: marqueeWidth })
69
74
  this.initializeTween()
70
75
 
71
76
  if (Dom.inViewport(this.elements.$el)) {
@@ -75,29 +80,43 @@ export default class Marquee {
75
80
 
76
81
  clearHolders() {
77
82
  const $allHolders = Dom.all(this.elements.$el, '[data-marquee-holder]')
78
- Array.from($allHolders).forEach(h => gsap.set(h, { clearProps: 'all' }))
83
+ Array.from($allHolders).forEach(h => clearProps(h, 'all'))
79
84
  }
80
85
 
81
86
  killTweens() {
82
87
  if (this.timeline) {
83
- this.timeline.kill()
88
+ this.timeline.stop()
84
89
  this.timeline = null
85
90
  }
91
+ if (this.speedAnimation) {
92
+ this.speedAnimation.stop()
93
+ this.speedAnimation = null
94
+ }
86
95
  }
87
96
 
88
97
  initializeTween() {
89
98
  const $allHolders = Dom.all(this.elements.$el, '[data-marquee-holder]')
90
99
 
91
100
  Array.from($allHolders).forEach((h, idx) => {
92
- gsap.set(h, { position: 'absolute', left: h.offsetWidth * idx })
101
+ set(h, {
102
+ position: 'absolute',
103
+ left: h.offsetWidth * idx,
104
+ transform: 'translateZ(0)',
105
+ willChange: 'transform'
106
+ })
93
107
  })
94
108
 
95
- this.timeline = gsap.timeline({ paused: true })
96
- this.timeline
97
- .to($allHolders, { xPercent: -100, ease: 'none', duration: this.duration }, 'standard')
98
- .repeat(-1)
109
+ this.timeline = animate(
110
+ $allHolders,
111
+ { transform: ['translateX(0) translateZ(0)', 'translateX(-100%) translateZ(0)'] },
112
+ { duration: this.duration, ease: 'linear', repeat: Infinity }
113
+ )
114
+ this.timeline.pause()
99
115
 
100
- this.timeline.totalProgress(this.opts.startProgress)
116
+ // Set initial progress if specified
117
+ if (this.opts.startProgress > 0) {
118
+ this.timeline.currentTime = this.opts.startProgress * this.duration
119
+ }
101
120
 
102
121
  window.timeline = this.timeline
103
122
  window.marquee = this
@@ -105,45 +124,85 @@ export default class Marquee {
105
124
 
106
125
  play(rampUp = false) {
107
126
  this.playing = true
108
- gsap.killTweensOf(this.timeline)
127
+ if (this.speedAnimation) {
128
+ this.speedAnimation.stop()
129
+ }
109
130
 
110
131
  if (rampUp) {
111
132
  this.timeline.play()
112
- gsap.to(this.timeline, {
113
- timeScale: 1,
114
- ease: 'sine.in',
115
- duration: 0.8
116
- })
133
+ const state = { speed: this.timeline.speed || 0 }
134
+ this.speedAnimation = animate(
135
+ state,
136
+ { speed: 1 },
137
+ {
138
+ duration: 0.8,
139
+ ease: 'easeIn',
140
+ onUpdate: () => {
141
+ this.timeline.speed = state.speed
142
+ }
143
+ }
144
+ )
117
145
  } else {
118
- this.timeline.timeScale(1)
146
+ this.timeline.speed = 1
119
147
  this.timeline.play()
120
148
  }
121
149
  }
122
150
 
123
151
  pause() {
124
152
  this.playing = false
125
- gsap.to(this.timeline, {
126
- timeScale: 0.01,
127
- onComplete: () => {
153
+ const state = { speed: this.timeline.speed || 1 }
154
+ this.speedAnimation = animate(
155
+ state,
156
+ { speed: 0.01 },
157
+ {
158
+ duration: 0.8,
159
+ onUpdate: () => {
160
+ this.timeline.speed = state.speed
161
+ }
162
+ }
163
+ )
164
+ this.speedAnimation.finished.then(() => {
165
+ // Only pause if we're still in paused state (haven't called play() in the meantime)
166
+ if (!this.playing) {
128
167
  this.timeline.pause()
129
- },
130
- duration: 0.8
168
+ }
131
169
  })
132
170
  }
133
171
 
134
172
  slowDown() {
135
- gsap.to(this.timeline, {
136
- timeScale: 0.5,
137
- duration: 0.8
138
- })
173
+ if (this.speedAnimation) {
174
+ this.speedAnimation.stop()
175
+ }
176
+ const state = { speed: this.timeline.speed || 1 }
177
+ this.speedAnimation = animate(
178
+ state,
179
+ { speed: 0.5 },
180
+ {
181
+ duration: 0.3,
182
+ ease: [0.4, 0, 0.2, 1], // ease-out
183
+ onUpdate: () => {
184
+ this.timeline.speed = state.speed
185
+ }
186
+ }
187
+ )
139
188
  }
140
189
 
141
190
  speedUp() {
142
- gsap.to(this.timeline, {
143
- timeScale: 1,
144
- duration: 0.8,
145
- ease: 'sine.in'
146
- })
191
+ if (this.speedAnimation) {
192
+ this.speedAnimation.stop()
193
+ }
194
+ const state = { speed: this.timeline.speed || 0.5 }
195
+ this.speedAnimation = animate(
196
+ state,
197
+ { speed: 1 },
198
+ {
199
+ duration: 0.3,
200
+ ease: [0.4, 0, 0.2, 1], // ease-out
201
+ onUpdate: () => {
202
+ this.timeline.speed = state.speed
203
+ }
204
+ }
205
+ )
147
206
  }
148
207
 
149
208
  setupObserver() {
@@ -169,12 +228,19 @@ export default class Marquee {
169
228
  }
170
229
 
171
230
  fillText() {
231
+ // Clear any previously set heights to get accurate measurement
232
+ clearProps(this.elements.$el, 'height')
233
+ clearProps(this.elements.$marquee, 'height')
234
+
172
235
  this.elements.$marquee.innerHTML = ''
173
236
  this.elements.$marquee.appendChild(this.elements.$holder)
174
237
 
175
238
  this.elements.$holder.innerHTML = ''
176
239
  this.elements.$holder.appendChild(this.elements.$item)
177
240
 
241
+ // Measure height of item only (marquee padding will be added by CSS)
242
+ this.measuredHeight = this.elements.$item.offsetHeight
243
+
178
244
  const textWidth = this.elements.$item.offsetWidth
179
245
  if (textWidth) {
180
246
  if (this.opts.spacer) {
@@ -199,7 +265,10 @@ export default class Marquee {
199
265
  }
200
266
 
201
267
  setHeight() {
202
- const height = this.elements.$item.offsetHeight + this.opts.extraHeight
203
- gsap.set(this.elements.$el, { height })
268
+ // Use the height measured in fillText() (before cloning) plus any extra height
269
+ const height = this.measuredHeight + this.opts.extraHeight
270
+ // Set height on both container and marquee to preserve it when holders become absolute
271
+ set(this.elements.$el, { height })
272
+ set(this.elements.$marquee, { height })
204
273
  }
205
274
  }
@@ -1,6 +1,7 @@
1
- import { gsap } from 'gsap/all'
1
+ import { animate, stagger } from 'motion'
2
2
  import _defaultsDeep from 'lodash.defaultsdeep'
3
3
  import * as Events from '../../events'
4
+ import { set, clearProps } from '../../utils/motion-helpers'
4
5
 
5
6
  /**
6
7
  * @typedef {Object} MobileMenuOptions
@@ -23,136 +24,81 @@ const DEFAULT_OPTIONS = {
23
24
  hamburgerColor: '#000',
24
25
 
25
26
  onResize: null,
26
- openTween: (m) => {
27
- const timeline = gsap.timeline()
28
-
27
+ openTween: async (m) => {
29
28
  m.hamburger.classList.toggle('is-active')
30
29
  document.body.classList.toggle('open-menu')
31
30
 
32
- timeline
33
- .fromTo(
34
- m.bg,
35
- {
36
- duration: 0.35,
37
- x: '0%',
38
- opacity: 0,
39
- height: window.innerHeight,
40
- },
41
- {
42
- duration: 0.35,
43
- opacity: 1,
44
- ease: 'sine.in',
45
- }
46
- )
47
- .to(
48
- m.logo,
49
- {
50
- duration: 0.35,
51
- opacity: 0,
52
- ease: 'power3.out',
53
- },
54
- '-=0.35'
55
- )
56
- .to(
57
- m.header,
58
- {
59
- duration: 0.55,
60
- backgroundColor: 'transparent',
61
- ease: 'power3.out',
62
- },
63
- '-=0.35'
64
- )
65
- .call(() => {
66
- m.nav.style.gridTemplateRows = 'auto 1fr'
67
- })
68
- .set(m.nav, { height: window.innerHeight })
69
- .set(m.content, { display: 'block' })
70
- .set(m.logoPath, { fill: m.opts.logoColor })
71
- .set(m.logo, { xPercent: 3 })
72
- .staggerFromTo(
73
- m.lis,
74
- {
75
- duration: 1,
76
- opacity: 0,
77
- x: 20,
78
- },
79
- {
80
- duration: 1,
81
- x: 0,
82
- opacity: 1,
83
- ease: 'power3.out',
84
- },
85
- 0.05
86
- )
87
- .to(
88
- m.logo,
89
- {
90
- duration: 0.55,
91
- opacity: 1,
92
- xPercent: 0,
93
- ease: 'power3.inOut',
94
- },
95
- '-=1.2'
96
- )
97
- .call(m._emitMobileMenuOpenEvent)
31
+ // Set initial state for bg
32
+ set(m.bg, { x: '0%', opacity: 0, height: window.innerHeight })
33
+
34
+ // Parallel animations at start (0-0.35s)
35
+ const timeline = [
36
+ [m.bg, { opacity: 1 }, { duration: 0.35, ease: 'easeIn', at: 0 }],
37
+ [m.logo, { opacity: 0 }, { duration: 0.35, ease: 'easeOut', at: 0 }],
38
+ [m.header, { backgroundColor: 'transparent' }, { duration: 0.55, ease: 'easeOut', at: 0 }]
39
+ ]
40
+
41
+ await animate(timeline).finished
42
+
43
+ // Immediate settings
44
+ m.nav.style.gridTemplateRows = 'auto 1fr'
45
+ set(m.nav, { height: window.innerHeight })
46
+ Array.from(m.content).forEach(el => set(el, { display: 'block' }))
47
+ Array.from(m.logoPath).forEach(path => path.setAttribute('fill', m.opts.logoColor))
48
+ set(m.logo, { x: '3%' })
49
+
50
+ // Staggered li animations and logo animation in parallel
51
+ const lisAnimation = animate(
52
+ m.lis,
53
+ { opacity: [0, 1], x: [20, 0] },
54
+ { duration: 1, ease: 'easeOut', delay: stagger(0.05) }
55
+ )
56
+
57
+ const logoAnimation = animate(
58
+ m.logo,
59
+ { opacity: 1, x: ['3%', '0%'] },
60
+ { duration: 0.55, ease: 'easeInOut', at: 0.15 }
61
+ )
62
+
63
+ await Promise.all([lisAnimation.finished, logoAnimation.finished])
64
+
65
+ m._emitMobileMenuOpenEvent()
98
66
  },
99
67
 
100
- closeTween: (m) => {
68
+ closeTween: async (m) => {
101
69
  document.body.classList.toggle('open-menu')
102
- const timeline = gsap.timeline()
70
+ m.hamburger.classList.toggle('is-active')
103
71
 
104
- timeline
105
- .call(() => {
106
- m.hamburger.classList.toggle('is-active')
107
- })
108
- .fromTo(
109
- m.logo,
110
- {
111
- duration: 0.2,
112
- opacity: 1,
113
- xPercent: 0,
114
- },
115
- {
116
- duration: 0.2,
117
- opacity: 0,
118
- xPercent: 5,
119
- ease: 'power3.out',
120
- }
121
- )
122
- .set(m.logoPath, { clearProps: 'fill' })
123
- .staggerTo(
124
- m.lis,
125
- {
126
- duration: 0.5,
127
- opacity: 0,
128
- x: 20,
129
- ease: 'power3.out',
130
- },
131
- 0.04
132
- )
133
- .set(m.nav, { clearProps: 'height' })
134
- .to(
135
- m.bg,
136
- {
137
- duration: 0.25,
138
- x: '100%',
139
- ease: 'sine.in',
140
- },
141
- '-=0.3'
142
- )
143
- .call(() => {
144
- m._emitMobileMenuClosedEvent()
145
- })
146
- .set(m.content, { display: 'none' })
147
- .call(() => {
148
- m.nav.style.gridTemplateRows = 'auto'
149
- })
150
- .set(m.lis, { clearProps: 'opacity' })
151
- .to(m.logo, {
152
- duration: 0.35,
153
- opacity: 1,
154
- ease: 'power3.in',
155
- })
72
+ // Fade out logo
73
+ await animate(m.logo, { opacity: 0, x: '5%' }, { duration: 0.2, ease: 'easeOut' }).finished
74
+
75
+ // Clear logo fill
76
+ Array.from(m.logoPath).forEach(path => path.removeAttribute('fill'))
77
+
78
+ // Stagger out lis and slide bg in parallel
79
+ const lisAnimation = animate(
80
+ m.lis,
81
+ { opacity: 0, x: 20 },
82
+ { duration: 0.5, ease: 'easeOut', delay: stagger(0.04) }
83
+ )
84
+
85
+ // bg animation starts 0.3s before lis finish
86
+ // lis duration is 0.5s + last stagger delay, so starts around 0.2s
87
+ setTimeout(() => {
88
+ animate(m.bg, { x: '100%' }, { duration: 0.25, ease: 'easeIn' })
89
+ }, 200)
90
+
91
+ await lisAnimation.finished
92
+
93
+ // Cleanup
94
+ clearProps(m.nav, 'height')
95
+ m._emitMobileMenuClosedEvent()
96
+ Array.from(m.content).forEach(el => set(el, { display: 'none' }))
97
+ m.nav.style.gridTemplateRows = 'auto'
98
+ Array.from(m.lis).forEach(li => clearProps(li, 'opacity'))
99
+
100
+ // Fade logo back in
101
+ await animate(m.logo, { opacity: 1 }, { duration: 0.35, ease: 'easeIn' }).finished
156
102
  },
157
103
  }
158
104