@hortonstudio/main 1.2.15 → 1.2.19

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.
@@ -0,0 +1,379 @@
1
+ const API_NAME = 'hsmain';
2
+
3
+ // Check for reduced motion preference
4
+ const prefersReducedMotion = () => {
5
+ return window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
6
+ };
7
+
8
+ const config = {
9
+ global: {
10
+ animationDelay: 0
11
+ },
12
+ wordSplit: {
13
+ duration: 1.5,
14
+ stagger: 0.075,
15
+ yPercent: 110,
16
+ ease: "power4.out",
17
+ start: "top 97%"
18
+ },
19
+ lineSplit: {
20
+ duration: 1.5,
21
+ stagger: 0.1,
22
+ yPercent: 110,
23
+ ease: "power4.out",
24
+ start: "top 97%"
25
+ },
26
+ charSplit: {
27
+ duration: 1.2,
28
+ stagger: 0.03,
29
+ yPercent: 110,
30
+ ease: "power4.out",
31
+ start: "top 97%"
32
+ },
33
+ appear: {
34
+ y: 50,
35
+ duration: 1.5,
36
+ ease: "power3.out",
37
+ start: "top 97%"
38
+ }
39
+ };
40
+
41
+ function updateConfig(newConfig) {
42
+ function deepMerge(target, source) {
43
+ for (const key in source) {
44
+ if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
45
+ target[key] = target[key] || {};
46
+ deepMerge(target[key], source[key]);
47
+ } else {
48
+ target[key] = source[key];
49
+ }
50
+ }
51
+ return target;
52
+ }
53
+
54
+ deepMerge(config, newConfig);
55
+ }
56
+
57
+ function killTextAnimations() {
58
+ activeAnimations.forEach(({ timeline, element }) => {
59
+ if (timeline) {
60
+ timeline.kill();
61
+ }
62
+ if (element?.splitTextInstance) {
63
+ element.splitTextInstance.revert();
64
+ }
65
+ });
66
+ activeAnimations.length = 0;
67
+ }
68
+
69
+ function startTextAnimations() {
70
+ if (prefersReducedMotion()) {
71
+ // For reduced motion, just show elements without animation
72
+ showElementsWithoutAnimation();
73
+ return;
74
+ }
75
+
76
+ setInitialStates().then(() => {
77
+ initAnimations();
78
+ });
79
+ }
80
+
81
+ let activeAnimations = [];
82
+
83
+ function waitForFonts() {
84
+ return document.fonts.ready;
85
+ }
86
+
87
+ function showElementsWithoutAnimation() {
88
+ // Simply show all text elements without any animation or split text
89
+ const allTextElements = [
90
+ ...document.querySelectorAll(".a-char-split > *:first-child"),
91
+ ...document.querySelectorAll(".a-word-split > *:first-child"),
92
+ ...document.querySelectorAll(".a-line-split > *:first-child"),
93
+ ...document.querySelectorAll('.a-appear')
94
+ ];
95
+
96
+ allTextElements.forEach(element => {
97
+ gsap.set(element, {
98
+ autoAlpha: 1,
99
+ y: 0,
100
+ yPercent: 0,
101
+ opacity: 1
102
+ });
103
+ });
104
+ }
105
+
106
+ const CharSplitAnimations = {
107
+ async initial() {
108
+ await waitForFonts();
109
+
110
+ if (prefersReducedMotion()) {
111
+ return;
112
+ }
113
+
114
+ const elements = document.querySelectorAll(".a-char-split > *:first-child");
115
+
116
+ elements.forEach((textElement) => {
117
+ const split = SplitText.create(textElement, {
118
+ type: "chars",
119
+ mask: "chars",
120
+ charsClass: "char",
121
+ });
122
+ textElement.splitTextInstance = split;
123
+
124
+ gsap.set(split.chars, {
125
+ yPercent: config.charSplit.yPercent
126
+ });
127
+ gsap.set(textElement, { autoAlpha: 1 });
128
+ });
129
+ },
130
+
131
+ async animate() {
132
+ await waitForFonts();
133
+
134
+ if (prefersReducedMotion()) {
135
+ return;
136
+ }
137
+
138
+ document.querySelectorAll(".a-char-split > *:first-child").forEach((textElement) => {
139
+ const chars = textElement.querySelectorAll('.char');
140
+ const tl = gsap.timeline({
141
+ scrollTrigger: {
142
+ trigger: textElement,
143
+ start: config.charSplit.start,
144
+ invalidateOnRefresh: true,
145
+ },
146
+ onComplete: () => {
147
+
148
+ }
149
+ });
150
+
151
+ tl.to(chars, {
152
+ yPercent: 0,
153
+ duration: config.charSplit.duration,
154
+ stagger: config.charSplit.stagger,
155
+ ease: config.charSplit.ease,
156
+ });
157
+
158
+ activeAnimations.push({ timeline: tl, element: textElement });
159
+ });
160
+ }
161
+ };
162
+
163
+ const WordSplitAnimations = {
164
+ async initial() {
165
+ await waitForFonts();
166
+
167
+ if (prefersReducedMotion()) {
168
+ return;
169
+ }
170
+
171
+ const elements = document.querySelectorAll(".a-word-split > *:first-child");
172
+
173
+ elements.forEach((textElement) => {
174
+ const split = SplitText.create(textElement, {
175
+ type: "words",
176
+ mask: "words",
177
+ wordsClass: "word",
178
+ });
179
+ textElement.splitTextInstance = split;
180
+
181
+ gsap.set(split.words, {
182
+ yPercent: config.wordSplit.yPercent
183
+ });
184
+ gsap.set(textElement, { autoAlpha: 1 });
185
+ });
186
+ },
187
+
188
+ async animate() {
189
+ await waitForFonts();
190
+
191
+ if (prefersReducedMotion()) {
192
+ return;
193
+ }
194
+
195
+ document.querySelectorAll(".a-word-split > *:first-child").forEach((textElement) => {
196
+ const words = textElement.querySelectorAll('.word');
197
+ const tl = gsap.timeline({
198
+ scrollTrigger: {
199
+ trigger: textElement,
200
+ start: config.wordSplit.start,
201
+ invalidateOnRefresh: true,
202
+ },
203
+ onComplete: () => {
204
+
205
+ }
206
+ });
207
+
208
+ tl.to(words, {
209
+ yPercent: 0,
210
+ duration: config.wordSplit.duration,
211
+ stagger: config.wordSplit.stagger,
212
+ ease: config.wordSplit.ease,
213
+ });
214
+
215
+ activeAnimations.push({ timeline: tl, element: textElement });
216
+ });
217
+ }
218
+ };
219
+
220
+ const LineSplitAnimations = {
221
+ async initial() {
222
+ await waitForFonts();
223
+
224
+ if (prefersReducedMotion()) {
225
+ return;
226
+ }
227
+
228
+ const elements = document.querySelectorAll(".a-line-split > *:first-child");
229
+
230
+ elements.forEach((textElement) => {
231
+ const split = SplitText.create(textElement, {
232
+ type: "lines",
233
+ mask: "lines",
234
+ linesClass: "line",
235
+ });
236
+ textElement.splitTextInstance = split;
237
+
238
+ gsap.set(split.lines, {
239
+ yPercent: config.lineSplit.yPercent
240
+ });
241
+ gsap.set(textElement, { autoAlpha: 1 });
242
+ });
243
+ },
244
+
245
+ async animate() {
246
+ await waitForFonts();
247
+
248
+ if (prefersReducedMotion()) {
249
+ return;
250
+ }
251
+
252
+ document.querySelectorAll(".a-line-split > *:first-child").forEach((textElement) => {
253
+ const lines = textElement.querySelectorAll('.line');
254
+ const tl = gsap.timeline({
255
+ scrollTrigger: {
256
+ trigger: textElement,
257
+ start: config.lineSplit.start,
258
+ invalidateOnRefresh: true,
259
+ },
260
+ onComplete: () => {
261
+
262
+ }
263
+ });
264
+
265
+ tl.to(lines, {
266
+ yPercent: 0,
267
+ duration: config.lineSplit.duration,
268
+ stagger: config.lineSplit.stagger,
269
+ ease: config.lineSplit.ease,
270
+ });
271
+
272
+ activeAnimations.push({ timeline: tl, element: textElement });
273
+ });
274
+ }
275
+ };
276
+
277
+ const AppearAnimations = {
278
+ async initial() {
279
+ await waitForFonts();
280
+
281
+ if (prefersReducedMotion()) {
282
+ return;
283
+ }
284
+
285
+ const elements = document.querySelectorAll('.a-appear');
286
+ elements.forEach(element => {
287
+ gsap.set(element, {
288
+ y: config.appear.y,
289
+ opacity: 0
290
+ });
291
+ });
292
+ },
293
+
294
+ async animate() {
295
+ await waitForFonts();
296
+
297
+ if (prefersReducedMotion()) {
298
+ return;
299
+ }
300
+
301
+ document.querySelectorAll('.a-appear').forEach(element => {
302
+ const tl = gsap.timeline({
303
+ scrollTrigger: {
304
+ trigger: element,
305
+ start: config.appear.start,
306
+ invalidateOnRefresh: true,
307
+ }
308
+ });
309
+
310
+ tl.to(element, {
311
+ y: 0,
312
+ opacity: 1,
313
+ duration: config.appear.duration,
314
+ ease: config.appear.ease
315
+ });
316
+
317
+ activeAnimations.push({ timeline: tl, element: element });
318
+ });
319
+
320
+ }
321
+ };
322
+
323
+ async function setInitialStates() {
324
+ await Promise.all([
325
+ CharSplitAnimations.initial(),
326
+ WordSplitAnimations.initial(),
327
+ LineSplitAnimations.initial(),
328
+ AppearAnimations.initial()
329
+ ]);
330
+ }
331
+
332
+ async function initAnimations() {
333
+ if (config.global.animationDelay > 0) {
334
+ await new Promise(resolve => setTimeout(resolve, config.global.animationDelay * 1000));
335
+ }
336
+
337
+ await Promise.all([
338
+ CharSplitAnimations.animate(),
339
+ WordSplitAnimations.animate(),
340
+ LineSplitAnimations.animate(),
341
+ AppearAnimations.animate()
342
+ ]);
343
+ }
344
+
345
+ export async function init() {
346
+ // Check if there's a persistent config stored globally
347
+ const api = window[API_NAME] || {};
348
+ if (api.textAnimations?.config && api.textAnimations.config !== config) {
349
+ // Merge persistent config into current config
350
+ updateConfig(api.textAnimations.config);
351
+ }
352
+
353
+ if (prefersReducedMotion()) {
354
+ // For reduced motion, just show elements without animation
355
+ showElementsWithoutAnimation();
356
+ } else {
357
+ await setInitialStates();
358
+ initAnimations();
359
+ }
360
+
361
+ window.addEventListener('resize', ScrollTrigger.refresh());
362
+
363
+ api.textAnimations = {
364
+ config: config,
365
+ updateConfig: (newConfig) => {
366
+ updateConfig(newConfig);
367
+ // Store config reference for persistence across restarts
368
+ api.textAnimations.config = config;
369
+ },
370
+ start: startTextAnimations,
371
+ kill: killTextAnimations,
372
+ restart: () => {
373
+ killTextAnimations();
374
+ startTextAnimations();
375
+ }
376
+ };
377
+
378
+ return { result: 'anim-text initialized' };
379
+ }
@@ -0,0 +1,57 @@
1
+ // Page Transition Module
2
+ const API_NAME = 'hsmain';
3
+ export async function init() {
4
+
5
+ // Register the transition logic to run after library is ready
6
+ window[API_NAME].afterReady(() => {
7
+ // Only run if jQuery is available
8
+ if (typeof $ !== 'undefined') {
9
+ initTransitions();
10
+ }
11
+ });
12
+
13
+ return { result: 'anim-transition initialized' };
14
+ }
15
+
16
+ function initTransitions() {
17
+ let transitionTrigger = $(".transition-trigger");
18
+ let introDurationMS = 800;
19
+ let exitDurationMS = 400;
20
+ let excludedClass = "no-transition";
21
+
22
+ // On Page Load
23
+ if (transitionTrigger.length > 0) {
24
+ if (window.Webflow && window.Webflow.push) {
25
+ Webflow.push(function () {
26
+ transitionTrigger.click();
27
+ });
28
+ } else {
29
+ // Non-Webflow initialization
30
+ setTimeout(() => {
31
+ transitionTrigger.click();
32
+ }, 100);
33
+ }
34
+ $("body").addClass("no-scroll-transition");
35
+ setTimeout(() => {$("body").removeClass("no-scroll-transition");}, introDurationMS);
36
+ }
37
+
38
+ // On Link Click
39
+ $("a").on("click", function (e) {
40
+ if ($(this).prop("hostname") == window.location.host && $(this).attr("href").indexOf("#") === -1 &&
41
+ !$(this).hasClass(excludedClass) && $(this).attr("target") !== "_blank" && transitionTrigger.length > 0) {
42
+ e.preventDefault();
43
+ $("body").addClass("no-scroll-transition");
44
+ let transitionURL = $(this).attr("href");
45
+ transitionTrigger.click();
46
+ setTimeout(function () {window.location = transitionURL;}, exitDurationMS);
47
+ }
48
+ });
49
+
50
+ // On Back Button Tap
51
+ window.onpageshow = function(event) {if (event.persisted) {window.location.reload()}};
52
+
53
+ // Hide Transition on Window Width Resize
54
+ setTimeout(() => {$(window).on("resize", function () {
55
+ setTimeout(() => {$(".transition").css("display", "none");}, 50);});
56
+ }, introDurationMS);
57
+ }
@@ -0,0 +1,89 @@
1
+ const API_NAME = 'hsmain';
2
+
3
+ export async function init() {
4
+ const api = window[API_NAME];
5
+ api.afterWebflowReady(() => {
6
+ if (typeof $ !== 'undefined') {
7
+ $(document).off('click.wf-scroll');
8
+ }
9
+ });
10
+
11
+ // Disable CSS smooth scrolling
12
+ document.documentElement.style.scrollBehavior = 'auto';
13
+ document.body.style.scrollBehavior = 'auto';
14
+
15
+ // Check if user prefers reduced motion
16
+ function prefersReducedMotion() {
17
+ return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
18
+ }
19
+
20
+ function getScrollOffset() {
21
+ const offsetValue = getComputedStyle(document.documentElement)
22
+ .getPropertyValue('--misc--scroll-offset').trim();
23
+ return parseInt(offsetValue) || 0;
24
+ }
25
+
26
+ // Smooth scroll to element with offset
27
+ function scrollToElement(target, offset = 0) {
28
+ if (!target) return;
29
+
30
+ // Skip animation if user prefers reduced motion
31
+ if (prefersReducedMotion()) {
32
+ const targetPosition = target.getBoundingClientRect().top + window.scrollY - offset;
33
+ window.scrollTo(0, targetPosition);
34
+ target.setAttribute('tabindex', '-1');
35
+ target.focus({ preventScroll: true });
36
+ return;
37
+ }
38
+
39
+ gsap.to(window, {
40
+ duration: 1,
41
+ scrollTo: {
42
+ y: target,
43
+ offsetY: offset
44
+ },
45
+ ease: "power2.out",
46
+ onComplete: function() {
47
+ target.setAttribute('tabindex', '-1');
48
+ target.focus({ preventScroll: true });
49
+ }
50
+ });
51
+ }
52
+
53
+ // Handle anchor link clicks and keyboard activation
54
+ function handleAnchorClicks() {
55
+ document.addEventListener('click', handleAnchorActivation);
56
+ document.addEventListener('keydown', function(e) {
57
+ if (e.key === 'Enter' || e.key === ' ') {
58
+ handleAnchorActivation(e);
59
+ }
60
+ });
61
+ }
62
+
63
+ function handleAnchorActivation(e) {
64
+ const link = e.target.closest('a[href^="#"]');
65
+ if (!link) return;
66
+
67
+ const href = link.getAttribute('href');
68
+ if (!href || href === '#') return;
69
+
70
+ const targetId = href.substring(1);
71
+ const targetElement = document.getElementById(targetId);
72
+
73
+ if (targetElement) {
74
+ e.preventDefault();
75
+ if (history.replaceState) {
76
+ history.replaceState(null, null, `#${targetElement.id}`);
77
+ }
78
+ const offset = getScrollOffset();
79
+ scrollToElement(targetElement, offset);
80
+ }
81
+ }
82
+
83
+ // Initialize anchor link handling
84
+ handleAnchorClicks();
85
+
86
+ return {
87
+ result: 'autoInit-smooth-scroll initialized'
88
+ };
89
+ }
@@ -0,0 +1,37 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>CDN Version Debug Test</title>
5
+ </head>
6
+ <body>
7
+ <h1>CDN Version Debug Test</h1>
8
+ <div id="results"></div>
9
+
10
+ <!-- Test with major version @1 -->
11
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@hortonstudio/main@1/index.js" data-hs-main data-hs-anim-hero></script>
12
+
13
+ <script>
14
+ setTimeout(() => {
15
+ const results = document.getElementById('results');
16
+
17
+ // Check what scripts are found
18
+ const scripts = [...document.querySelectorAll('script[type="module"]')];
19
+ results.innerHTML += `<h2>Found Scripts:</h2>`;
20
+ scripts.forEach((script, i) => {
21
+ results.innerHTML += `<p>Script ${i}: ${script.src}</p>`;
22
+ });
23
+
24
+ // Check if hsmain loaded and what scripts it found
25
+ if (window.hsmain) {
26
+ results.innerHTML += `<h2>HSMAIN Scripts Found:</h2>`;
27
+ results.innerHTML += `<p>Found ${window.hsmain.scripts.length} matching scripts</p>`;
28
+ window.hsmain.scripts.forEach((script, i) => {
29
+ results.innerHTML += `<p>HSMAIN Script ${i}: ${script.src}</p>`;
30
+ });
31
+ } else {
32
+ results.innerHTML += `<p>HSMAIN not loaded</p>`;
33
+ }
34
+ }, 2000);
35
+ </script>
36
+ </body>
37
+ </html>