@hortonstudio/main 1.0.1 → 1.1.0
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.
- package/.claude/settings.local.json +18 -0
- package/CLAUDE.md +45 -0
- package/animations/hero.js +530 -0
- package/animations/text.js +305 -0
- package/animations/{modules/transition.js → transition.js} +2 -1
- package/autoInit/smooth-scroll.js +89 -0
- package/index.js +116 -48
- package/package.json +1 -1
- package/styles.css +10 -3
- package/utils/navbar.js +203 -0
- package/utils/scroll-progress.js +29 -0
- package/utils/toc.js +54 -0
- package/animations/READ +0 -0
- package/animations/modules/hero.js +0 -154
- package/animations/modules/text.js +0 -165
- package/utils/READ +0 -0
- package/utils/modules/toc.js +0 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
{
|
2
|
+
"permissions": {
|
3
|
+
"allow": [
|
4
|
+
"mcp__webflow__pages_get_content",
|
5
|
+
"WebFetch(domain:compassfacilities.webflow.io)",
|
6
|
+
"mcp__webflow__collections_list",
|
7
|
+
"mcp__webflow__collections_get",
|
8
|
+
"mcp__webflow__sites_list",
|
9
|
+
"mcp__webflow__site_applied_scripts_list",
|
10
|
+
"mcp__webflow__site_registered_scripts_list",
|
11
|
+
"mcp__webflow__add_inline_site_script",
|
12
|
+
"Bash(mv:*)",
|
13
|
+
"WebFetch(domain:www.compassfacilities.com)",
|
14
|
+
"mcp__webflow__pages_list"
|
15
|
+
],
|
16
|
+
"deny": []
|
17
|
+
}
|
18
|
+
}
|
package/CLAUDE.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# CLAUDE.md
|
2
|
+
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
4
|
+
|
5
|
+
## Project Overview
|
6
|
+
|
7
|
+
This is `@hortonstudio/main` - an animation and utility library for client websites, primarily designed for Webflow integration. The library uses a modular ES6 system with dynamic loading based on HTML data attributes.
|
8
|
+
|
9
|
+
## Architecture
|
10
|
+
|
11
|
+
**Main API**: `window.hsmain` (configurable via `API_NAME` constant in index.js)
|
12
|
+
|
13
|
+
**Module Categories**:
|
14
|
+
- Animation modules: `data-hs-anim-*` attributes trigger loading
|
15
|
+
- Utility modules: `data-hs-util-*` attributes trigger loading
|
16
|
+
- Auto-init modules: Always loaded automatically
|
17
|
+
|
18
|
+
**Dependencies**: Requires GSAP with ScrollTrigger and SplitText plugins
|
19
|
+
|
20
|
+
**Integration**: Designed for Webflow with `Webflow.ready()` callback system
|
21
|
+
|
22
|
+
## Module Loading System
|
23
|
+
|
24
|
+
Modules are loaded via script tag attributes:
|
25
|
+
```html
|
26
|
+
<script src="index.js" data-hs-main data-hs-anim-hero data-hs-util-toc></script>
|
27
|
+
```
|
28
|
+
|
29
|
+
Each module exports an `init()` function returning `{ result: 'module-name initialized' }`.
|
30
|
+
|
31
|
+
## Key Animation Patterns
|
32
|
+
|
33
|
+
**Hero animations** (`hero.js`): Orchestrated timeline with navigation reveals, split text headings, and staggered element appearances. Uses data attributes like `data-hs-hero="heading"` and `data-hs-split="word|line|char"`.
|
34
|
+
|
35
|
+
**Text animations** (`text.js`): Scroll-triggered animations using CSS classes `.a-word-split`, `.a-line-split`, `.a-char-split`, `.a-appear` on parent elements, targeting first child for animation.
|
36
|
+
|
37
|
+
**Configuration**: All modules expose config objects via `window.hsmain.moduleAnimations.config` with `updateConfig()` methods for runtime modification.
|
38
|
+
|
39
|
+
## Important Implementation Details
|
40
|
+
|
41
|
+
- All animations wait for `document.fonts.ready` before initialization
|
42
|
+
- Split text instances are automatically cleaned up after animations complete
|
43
|
+
- CSS utility classes like `.u-overflow-clip` are dynamically added/removed for animation masking
|
44
|
+
- The library handles Webflow DOM changes by calling `Webflow.ready()` after module loading
|
45
|
+
- Navigation accessibility is temporarily disabled during hero animations then restored
|
@@ -0,0 +1,530 @@
|
|
1
|
+
const API_NAME = 'hsmain';
|
2
|
+
|
3
|
+
// Animation timing (in seconds)
|
4
|
+
const timing = {
|
5
|
+
announce: 0,
|
6
|
+
nav: 0.1,
|
7
|
+
navLogo: 0.3,
|
8
|
+
navList: 0.35,
|
9
|
+
navMenu: 0.35,
|
10
|
+
navButton: 0.5,
|
11
|
+
tag: 0.1,
|
12
|
+
heading: 0.15,
|
13
|
+
subheading: 0.25,
|
14
|
+
button: 0.35,
|
15
|
+
image: 0.5,
|
16
|
+
appear: 0.6
|
17
|
+
};
|
18
|
+
|
19
|
+
// Hero Animations Module
|
20
|
+
let heroTimeline = null;
|
21
|
+
let headingSplits = [];
|
22
|
+
let subheadingSplits = [];
|
23
|
+
let heroTimeout = null; // Track the setTimeout
|
24
|
+
|
25
|
+
const config = {
|
26
|
+
global: {
|
27
|
+
animationDelay: 0.2
|
28
|
+
},
|
29
|
+
headingSplit: {
|
30
|
+
duration: 1.5,
|
31
|
+
stagger: 0.1,
|
32
|
+
yPercent: 110,
|
33
|
+
ease: "power4.out"
|
34
|
+
},
|
35
|
+
subheadingSplit: {
|
36
|
+
duration: 1.5,
|
37
|
+
stagger: 0.1,
|
38
|
+
yPercent: 110,
|
39
|
+
ease: "power4.out"
|
40
|
+
},
|
41
|
+
appear: {
|
42
|
+
y: 50,
|
43
|
+
duration: 1.5,
|
44
|
+
ease: "power3.out"
|
45
|
+
},
|
46
|
+
navStagger: {
|
47
|
+
duration: 1.5,
|
48
|
+
stagger: 0.1,
|
49
|
+
ease: "power3.out"
|
50
|
+
},
|
51
|
+
nav: {
|
52
|
+
duration: 1,
|
53
|
+
ease: "power3.out"
|
54
|
+
}
|
55
|
+
};
|
56
|
+
|
57
|
+
function updateConfig(newConfig) {
|
58
|
+
function deepMerge(target, source) {
|
59
|
+
for (const key in source) {
|
60
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
61
|
+
target[key] = target[key] || {};
|
62
|
+
deepMerge(target[key], source[key]);
|
63
|
+
} else {
|
64
|
+
target[key] = source[key];
|
65
|
+
}
|
66
|
+
}
|
67
|
+
return target;
|
68
|
+
}
|
69
|
+
|
70
|
+
deepMerge(config, newConfig);
|
71
|
+
}
|
72
|
+
|
73
|
+
function killHeroAnimations() {
|
74
|
+
if (heroTimeout) {
|
75
|
+
clearTimeout(heroTimeout);
|
76
|
+
heroTimeout = null;
|
77
|
+
}
|
78
|
+
|
79
|
+
if (heroTimeline) {
|
80
|
+
heroTimeline.kill();
|
81
|
+
heroTimeline = null;
|
82
|
+
}
|
83
|
+
|
84
|
+
headingSplits.forEach(split => {
|
85
|
+
if (split && split.revert) {
|
86
|
+
split.revert();
|
87
|
+
}
|
88
|
+
});
|
89
|
+
headingSplits = [];
|
90
|
+
|
91
|
+
subheadingSplits.forEach(split => {
|
92
|
+
if (split && split.revert) {
|
93
|
+
split.revert();
|
94
|
+
}
|
95
|
+
});
|
96
|
+
subheadingSplits = [];
|
97
|
+
|
98
|
+
// Restore page-wide tabbing if animation is killed
|
99
|
+
const allFocusableElements = document.querySelectorAll('[data-original-tabindex]');
|
100
|
+
allFocusableElements.forEach(el => {
|
101
|
+
el.style.pointerEvents = '';
|
102
|
+
const originalTabindex = el.getAttribute('data-original-tabindex');
|
103
|
+
if (originalTabindex === '0') {
|
104
|
+
el.removeAttribute('tabindex');
|
105
|
+
} else {
|
106
|
+
el.setAttribute('tabindex', originalTabindex);
|
107
|
+
}
|
108
|
+
el.removeAttribute('data-original-tabindex');
|
109
|
+
});
|
110
|
+
// Restore nav pointer events if animation is killed
|
111
|
+
const navElement = document.querySelector('[data-hs-hero="nav"]');
|
112
|
+
if (navElement) {
|
113
|
+
navElement.style.pointerEvents = '';
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
function startHeroAnimations() {
|
118
|
+
killHeroAnimations();
|
119
|
+
init();
|
120
|
+
}
|
121
|
+
|
122
|
+
export async function init() {
|
123
|
+
if (typeof window.gsap === "undefined") {
|
124
|
+
console.error('GSAP not found - hero animations disabled');
|
125
|
+
return;
|
126
|
+
}
|
127
|
+
|
128
|
+
gsap.registerPlugin(ScrollTrigger, SplitText);
|
129
|
+
|
130
|
+
// Element selection
|
131
|
+
const announceElements = document.querySelectorAll('[data-hs-hero="announce"]');
|
132
|
+
const navElement = document.querySelector('[data-hs-hero="nav"]');
|
133
|
+
const navMenuElements = document.querySelectorAll('[data-hs-hero="nav-menu"]');
|
134
|
+
const navLogoElements = document.querySelectorAll('[data-hs-hero="nav-logo"]');
|
135
|
+
const imageElements = document.querySelectorAll('[data-hs-hero="image"]');
|
136
|
+
const appearElements = document.querySelectorAll('[data-hs-hero="appear"]');
|
137
|
+
|
138
|
+
// Check if nav has advanced config
|
139
|
+
const hasAdvancedNav = navElement && navElement.hasAttribute('data-hs-heroconfig') && navElement.getAttribute('data-hs-heroconfig') === 'advanced';
|
140
|
+
|
141
|
+
// First child elements - only select if advanced nav is enabled
|
142
|
+
const navButton = [];
|
143
|
+
if (hasAdvancedNav) {
|
144
|
+
const navButtonParents = document.querySelectorAll('[data-hs-hero="nav-button"]');
|
145
|
+
navButtonParents.forEach(el => {
|
146
|
+
if (el.firstElementChild) navButton.push(el.firstElementChild);
|
147
|
+
});
|
148
|
+
}
|
149
|
+
|
150
|
+
const subheading = [];
|
151
|
+
const subheadingAppearElements = [];
|
152
|
+
const subheadingElements = document.querySelectorAll('[data-hs-hero="subheading"]');
|
153
|
+
const subheadingSplitElements = [];
|
154
|
+
|
155
|
+
subheadingElements.forEach(el => {
|
156
|
+
if (el.firstElementChild) {
|
157
|
+
// Get the heroconfig attribute to determine animation type (default to appear)
|
158
|
+
const heroConfig = el.getAttribute('data-hs-heroconfig') || 'appear';
|
159
|
+
|
160
|
+
if (heroConfig === 'appear') {
|
161
|
+
subheadingAppearElements.push(el.firstElementChild);
|
162
|
+
} else {
|
163
|
+
subheading.push(el.firstElementChild);
|
164
|
+
subheadingSplitElements.push(el);
|
165
|
+
}
|
166
|
+
}
|
167
|
+
});
|
168
|
+
|
169
|
+
const heading = [];
|
170
|
+
const headingAppearElements = [];
|
171
|
+
const headingElements = document.querySelectorAll('[data-hs-hero="heading"]');
|
172
|
+
const headingSplitElements = [];
|
173
|
+
|
174
|
+
headingElements.forEach(el => {
|
175
|
+
if (el.firstElementChild) {
|
176
|
+
// Get the heroconfig attribute to determine animation type
|
177
|
+
const heroConfig = el.getAttribute('data-hs-heroconfig') || 'word'; // default to word if not specified
|
178
|
+
|
179
|
+
if (heroConfig === 'appear') {
|
180
|
+
headingAppearElements.push(el.firstElementChild);
|
181
|
+
} else {
|
182
|
+
heading.push(el.firstElementChild);
|
183
|
+
headingSplitElements.push(el);
|
184
|
+
}
|
185
|
+
}
|
186
|
+
});
|
187
|
+
|
188
|
+
const tag = [];
|
189
|
+
const tagParents = document.querySelectorAll('[data-hs-hero="tag"]');
|
190
|
+
tagParents.forEach(el => {
|
191
|
+
if (el.firstElementChild) tag.push(el.firstElementChild);
|
192
|
+
});
|
193
|
+
|
194
|
+
// All children elements
|
195
|
+
const buttonAllChildren = [];
|
196
|
+
const buttonParents = document.querySelectorAll('[data-hs-hero="button"]');
|
197
|
+
buttonParents.forEach(el => {
|
198
|
+
const children = Array.from(el.children);
|
199
|
+
buttonAllChildren.push(...children);
|
200
|
+
});
|
201
|
+
|
202
|
+
const navListAllChildren = [];
|
203
|
+
if (hasAdvancedNav) {
|
204
|
+
const navListParents = document.querySelectorAll('[data-hs-hero="nav-list"]');
|
205
|
+
navListParents.forEach(el => {
|
206
|
+
const children = Array.from(el.children);
|
207
|
+
|
208
|
+
// Add overflow clip class to each child and collect their first child for animation
|
209
|
+
children.forEach(child => {
|
210
|
+
child.classList.add('u-overflow-clip');
|
211
|
+
if (child.firstElementChild) {
|
212
|
+
navListAllChildren.push(child.firstElementChild);
|
213
|
+
}
|
214
|
+
});
|
215
|
+
});
|
216
|
+
}
|
217
|
+
|
218
|
+
// Initial states
|
219
|
+
if (announceElements.length > 0) gsap.set(announceElements, { opacity: 0, y: -50 });
|
220
|
+
if (navElement) {
|
221
|
+
gsap.set(navElement, { opacity: 0, y: -50 });
|
222
|
+
// Disable nav pointer events until animation completes
|
223
|
+
navElement.style.pointerEvents = 'none';
|
224
|
+
}
|
225
|
+
if (hasAdvancedNav && navListAllChildren.length > 0) gsap.set(navListAllChildren, { opacity: 0, yPercent: 110 });
|
226
|
+
if (hasAdvancedNav && navMenuElements.length > 0) gsap.set(navMenuElements, { opacity: 0 });
|
227
|
+
if (hasAdvancedNav && navButton.length > 0) gsap.set(navButton, { opacity: 0 });
|
228
|
+
if (hasAdvancedNav && navLogoElements.length > 0) gsap.set(navLogoElements, { opacity: 0 });
|
229
|
+
if (subheadingAppearElements.length > 0) gsap.set(subheadingAppearElements, { y: config.appear.y, opacity: 0 });
|
230
|
+
if (tag.length > 0) gsap.set(tag, { y: config.appear.y, opacity: 0 });
|
231
|
+
if (buttonAllChildren.length > 0) gsap.set(buttonAllChildren, { y: config.appear.y, opacity: 0 });
|
232
|
+
if (imageElements.length > 0) gsap.set(imageElements, { opacity: 0 });
|
233
|
+
if (appearElements.length > 0) gsap.set(appearElements, { y: config.appear.y, opacity: 0 });
|
234
|
+
if (headingAppearElements.length > 0) gsap.set(headingAppearElements, { y: config.appear.y, opacity: 0 });
|
235
|
+
|
236
|
+
// Disable page-wide tabbing and interactions until animation completes
|
237
|
+
const allFocusableElements = document.querySelectorAll('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
238
|
+
allFocusableElements.forEach(el => {
|
239
|
+
el.style.pointerEvents = 'none';
|
240
|
+
el.setAttribute('data-original-tabindex', el.getAttribute('tabindex') || '0');
|
241
|
+
el.setAttribute('tabindex', '-1');
|
242
|
+
});
|
243
|
+
|
244
|
+
// Animation timeline
|
245
|
+
document.fonts.ready.then(() => {
|
246
|
+
|
247
|
+
// Split text setup (after fonts are loaded)
|
248
|
+
headingSplits = [];
|
249
|
+
|
250
|
+
if (heading.length > 0) {
|
251
|
+
headingSplitElements.forEach((parent, index) => {
|
252
|
+
const textElement = heading[index];
|
253
|
+
const splitType = parent.getAttribute('data-hs-heroconfig') || 'word';
|
254
|
+
|
255
|
+
let splitConfig = {};
|
256
|
+
let elementsClass = '';
|
257
|
+
|
258
|
+
if (splitType === 'char') {
|
259
|
+
splitConfig = {
|
260
|
+
type: "words,chars",
|
261
|
+
mask: "chars",
|
262
|
+
charsClass: "char"
|
263
|
+
};
|
264
|
+
elementsClass = 'chars';
|
265
|
+
} else if (splitType === 'line') {
|
266
|
+
splitConfig = {
|
267
|
+
type: "lines",
|
268
|
+
mask: "lines",
|
269
|
+
linesClass: "line"
|
270
|
+
};
|
271
|
+
elementsClass = 'lines';
|
272
|
+
} else {
|
273
|
+
splitConfig = {
|
274
|
+
type: "words",
|
275
|
+
mask: "words",
|
276
|
+
wordsClass: "word"
|
277
|
+
};
|
278
|
+
elementsClass = 'words';
|
279
|
+
}
|
280
|
+
|
281
|
+
const split = new SplitText(textElement, splitConfig);
|
282
|
+
split.elementsClass = elementsClass;
|
283
|
+
headingSplits.push(split);
|
284
|
+
|
285
|
+
gsap.set(split[elementsClass], { yPercent: config.headingSplit.yPercent });
|
286
|
+
gsap.set(textElement, { autoAlpha: 1 });
|
287
|
+
});
|
288
|
+
}
|
289
|
+
|
290
|
+
// Split text setup for subheadings
|
291
|
+
if (subheading.length > 0) {
|
292
|
+
subheadingSplitElements.forEach((parent, index) => {
|
293
|
+
const textElement = subheading[index];
|
294
|
+
const splitType = parent.getAttribute('data-hs-heroconfig') || 'word';
|
295
|
+
|
296
|
+
let splitConfig = {};
|
297
|
+
let elementsClass = '';
|
298
|
+
|
299
|
+
if (splitType === 'char') {
|
300
|
+
splitConfig = {
|
301
|
+
type: "words,chars",
|
302
|
+
mask: "chars",
|
303
|
+
charsClass: "char"
|
304
|
+
};
|
305
|
+
elementsClass = 'chars';
|
306
|
+
} else if (splitType === 'line') {
|
307
|
+
splitConfig = {
|
308
|
+
type: "lines",
|
309
|
+
mask: "lines",
|
310
|
+
linesClass: "line"
|
311
|
+
};
|
312
|
+
elementsClass = 'lines';
|
313
|
+
} else {
|
314
|
+
splitConfig = {
|
315
|
+
type: "words",
|
316
|
+
mask: "words",
|
317
|
+
wordsClass: "word"
|
318
|
+
};
|
319
|
+
elementsClass = 'words';
|
320
|
+
}
|
321
|
+
|
322
|
+
const split = new SplitText(textElement, splitConfig);
|
323
|
+
split.elementsClass = elementsClass;
|
324
|
+
subheadingSplits.push(split);
|
325
|
+
|
326
|
+
gsap.set(split[elementsClass], { yPercent: config.subheadingSplit.yPercent });
|
327
|
+
gsap.set(textElement, { autoAlpha: 1 });
|
328
|
+
});
|
329
|
+
}
|
330
|
+
|
331
|
+
heroTimeout = setTimeout(() => {
|
332
|
+
heroTimeline = gsap.timeline();
|
333
|
+
|
334
|
+
if (announceElements.length > 0) {
|
335
|
+
heroTimeline.to(announceElements,
|
336
|
+
{ opacity: 1, y: 0, duration: config.nav.duration, ease: config.nav.ease },
|
337
|
+
timing.announce
|
338
|
+
);
|
339
|
+
}
|
340
|
+
|
341
|
+
if (navElement) {
|
342
|
+
heroTimeline.to(navElement,
|
343
|
+
{ opacity: 1, y: 0, duration: config.nav.duration, ease: config.nav.ease },
|
344
|
+
timing.nav
|
345
|
+
);
|
346
|
+
}
|
347
|
+
|
348
|
+
if (hasAdvancedNav && navLogoElements.length > 0) {
|
349
|
+
heroTimeline.to(navLogoElements,
|
350
|
+
{ opacity: 1, duration: .5, ease: config.nav.ease },
|
351
|
+
timing.navLogo
|
352
|
+
);
|
353
|
+
}
|
354
|
+
|
355
|
+
if (hasAdvancedNav && navListAllChildren.length > 0) {
|
356
|
+
heroTimeline.to(navListAllChildren,
|
357
|
+
{
|
358
|
+
opacity: 1,
|
359
|
+
yPercent: 0,
|
360
|
+
duration: config.nav.duration,
|
361
|
+
stagger: 0.05,
|
362
|
+
ease: config.nav.ease,
|
363
|
+
onComplete: () => {
|
364
|
+
// Remove u-overflow-clip class from list children
|
365
|
+
const navListParents = document.querySelectorAll('[data-hs-hero="nav-list"]');
|
366
|
+
navListParents.forEach(parent => {
|
367
|
+
const children = parent.children;
|
368
|
+
Array.from(children).forEach(child => {
|
369
|
+
child.classList.remove('u-overflow-clip');
|
370
|
+
});
|
371
|
+
});
|
372
|
+
}
|
373
|
+
},
|
374
|
+
timing.navList
|
375
|
+
);
|
376
|
+
}
|
377
|
+
|
378
|
+
if (hasAdvancedNav && navMenuElements.length > 0) {
|
379
|
+
heroTimeline.to(navMenuElements,
|
380
|
+
{ opacity: 1, duration: config.nav.duration, ease: config.nav.ease },
|
381
|
+
timing.navMenu
|
382
|
+
);
|
383
|
+
}
|
384
|
+
|
385
|
+
if (hasAdvancedNav && navButton.length > 0) {
|
386
|
+
heroTimeline.to(navButton,
|
387
|
+
{ opacity: 1, duration: config.nav.duration, ease: config.nav.ease },
|
388
|
+
timing.navButton
|
389
|
+
);
|
390
|
+
}
|
391
|
+
|
392
|
+
if (headingSplits.length > 0) {
|
393
|
+
headingSplits.forEach(split => {
|
394
|
+
heroTimeline.to(split[split.elementsClass],
|
395
|
+
{
|
396
|
+
yPercent: 0,
|
397
|
+
duration: config.headingSplit.duration,
|
398
|
+
stagger: config.headingSplit.stagger,
|
399
|
+
ease: config.headingSplit.ease,
|
400
|
+
onComplete: () => {
|
401
|
+
if (split && split.revert) {
|
402
|
+
split.revert();
|
403
|
+
}
|
404
|
+
}
|
405
|
+
},
|
406
|
+
timing.heading
|
407
|
+
);
|
408
|
+
});
|
409
|
+
}
|
410
|
+
|
411
|
+
if (subheadingSplits.length > 0) {
|
412
|
+
subheadingSplits.forEach(split => {
|
413
|
+
heroTimeline.to(split[split.elementsClass],
|
414
|
+
{
|
415
|
+
yPercent: 0,
|
416
|
+
duration: config.subheadingSplit.duration,
|
417
|
+
stagger: config.subheadingSplit.stagger,
|
418
|
+
ease: config.subheadingSplit.ease,
|
419
|
+
onComplete: () => {
|
420
|
+
if (split && split.revert) {
|
421
|
+
split.revert();
|
422
|
+
}
|
423
|
+
}
|
424
|
+
},
|
425
|
+
timing.subheading
|
426
|
+
);
|
427
|
+
});
|
428
|
+
}
|
429
|
+
|
430
|
+
if (subheadingAppearElements.length > 0) {
|
431
|
+
heroTimeline.to(subheadingAppearElements,
|
432
|
+
{ y: 0, opacity: 1, duration: config.appear.duration, ease: config.appear.ease },
|
433
|
+
timing.subheading
|
434
|
+
);
|
435
|
+
}
|
436
|
+
|
437
|
+
if (tag.length > 0) {
|
438
|
+
heroTimeline.to(tag,
|
439
|
+
{ y: 0, opacity: 1, duration: config.appear.duration, ease: config.appear.ease },
|
440
|
+
timing.tag
|
441
|
+
);
|
442
|
+
}
|
443
|
+
|
444
|
+
if (buttonAllChildren.length > 0) {
|
445
|
+
heroTimeline.to(buttonAllChildren,
|
446
|
+
{ y: 0, opacity: 1, duration: config.navStagger.duration, stagger: config.navStagger.stagger, ease: config.navStagger.ease },
|
447
|
+
timing.button
|
448
|
+
);
|
449
|
+
}
|
450
|
+
|
451
|
+
if (imageElements.length > 0) {
|
452
|
+
heroTimeline.to(imageElements,
|
453
|
+
{ opacity: 1, duration: config.appear.duration, ease: config.appear.ease },
|
454
|
+
timing.image
|
455
|
+
);
|
456
|
+
}
|
457
|
+
|
458
|
+
// Combine appear elements and heading appear elements
|
459
|
+
const allAppearElements = [...appearElements, ...headingAppearElements];
|
460
|
+
|
461
|
+
if (allAppearElements.length > 0) {
|
462
|
+
heroTimeline.to(allAppearElements,
|
463
|
+
{
|
464
|
+
y: 0,
|
465
|
+
opacity: 1,
|
466
|
+
duration: config.appear.duration,
|
467
|
+
ease: config.appear.ease,
|
468
|
+
onComplete: () => {
|
469
|
+
// Restore page-wide tabbing and interactions after hero animation completes
|
470
|
+
const allFocusableElements = document.querySelectorAll('[data-original-tabindex]');
|
471
|
+
allFocusableElements.forEach(el => {
|
472
|
+
el.style.pointerEvents = '';
|
473
|
+
const originalTabindex = el.getAttribute('data-original-tabindex');
|
474
|
+
if (originalTabindex === '0') {
|
475
|
+
el.removeAttribute('tabindex');
|
476
|
+
} else {
|
477
|
+
el.setAttribute('tabindex', originalTabindex);
|
478
|
+
}
|
479
|
+
el.removeAttribute('data-original-tabindex');
|
480
|
+
});
|
481
|
+
// Restore nav pointer events
|
482
|
+
if (navElement) {
|
483
|
+
navElement.style.pointerEvents = '';
|
484
|
+
}
|
485
|
+
}
|
486
|
+
},
|
487
|
+
timing.appear
|
488
|
+
);
|
489
|
+
} else {
|
490
|
+
// If no appear elements, restore tabbing when timeline completes
|
491
|
+
heroTimeline.call(() => {
|
492
|
+
const allFocusableElements = document.querySelectorAll('[data-original-tabindex]');
|
493
|
+
allFocusableElements.forEach(el => {
|
494
|
+
el.style.pointerEvents = '';
|
495
|
+
const originalTabindex = el.getAttribute('data-original-tabindex');
|
496
|
+
if (originalTabindex === '0') {
|
497
|
+
el.removeAttribute('tabindex');
|
498
|
+
} else {
|
499
|
+
el.setAttribute('tabindex', originalTabindex);
|
500
|
+
}
|
501
|
+
el.removeAttribute('data-original-tabindex');
|
502
|
+
});
|
503
|
+
// Restore nav pointer events
|
504
|
+
if (navElement) {
|
505
|
+
navElement.style.pointerEvents = '';
|
506
|
+
}
|
507
|
+
});
|
508
|
+
}
|
509
|
+
|
510
|
+
heroTimeout = null;
|
511
|
+
|
512
|
+
}, config.global.animationDelay * 1000);
|
513
|
+
|
514
|
+
});
|
515
|
+
|
516
|
+
// API exposure
|
517
|
+
window[API_NAME] = window[API_NAME] || {};
|
518
|
+
window[API_NAME].heroAnimations = {
|
519
|
+
config: config,
|
520
|
+
updateConfig: updateConfig,
|
521
|
+
start: startHeroAnimations,
|
522
|
+
kill: killHeroAnimations,
|
523
|
+
restart: () => {
|
524
|
+
killHeroAnimations();
|
525
|
+
startHeroAnimations();
|
526
|
+
}
|
527
|
+
};
|
528
|
+
|
529
|
+
return { result: 'anim-hero initialized' };
|
530
|
+
}
|