@hortonstudio/main 1.2.35 → 1.4.1
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 +22 -1
- package/TEMP-before-after-attributes.md +158 -0
- package/animations/hero.js +741 -611
- package/animations/text.js +532 -317
- package/animations/transition.js +36 -21
- package/autoInit/accessibility.js +173 -50
- package/autoInit/counter.js +338 -0
- package/autoInit/form.js +471 -0
- package/autoInit/modal.js +43 -38
- package/autoInit/navbar.js +494 -371
- package/autoInit/smooth-scroll.js +86 -84
- package/index.js +138 -88
- package/package.json +1 -1
- package/utils/before-after.js +279 -146
- package/utils/scroll-progress.js +26 -21
- package/utils/toc.js +73 -66
- package/CLAUDE.md +0 -45
- package/debug-version.html +0 -37
@@ -1,89 +1,91 @@
|
|
1
|
-
const API_NAME =
|
1
|
+
const API_NAME = "hsmain";
|
2
2
|
|
3
3
|
export async function init() {
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
});
|
4
|
+
const api = window[API_NAME];
|
5
|
+
api.afterWebflowReady(() => {
|
6
|
+
if (typeof $ !== "undefined") {
|
7
|
+
$(document).off("click.wf-scroll");
|
51
8
|
}
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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")
|
23
|
+
.trim();
|
24
|
+
return parseInt(offsetValue) || 0;
|
25
|
+
}
|
26
|
+
|
27
|
+
// Smooth scroll to element with offset
|
28
|
+
function scrollToElement(target, offset = 0) {
|
29
|
+
if (!target) return;
|
30
|
+
|
31
|
+
// Skip animation if user prefers reduced motion
|
32
|
+
if (prefersReducedMotion()) {
|
33
|
+
const targetPosition =
|
34
|
+
target.getBoundingClientRect().top + window.scrollY - offset;
|
35
|
+
window.scrollTo(0, targetPosition);
|
36
|
+
target.setAttribute("tabindex", "-1");
|
37
|
+
target.focus({ preventScroll: true });
|
38
|
+
return;
|
61
39
|
}
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
40
|
+
|
41
|
+
gsap.to(window, {
|
42
|
+
duration: 1,
|
43
|
+
scrollTo: {
|
44
|
+
y: target,
|
45
|
+
offsetY: offset,
|
46
|
+
},
|
47
|
+
ease: "power2.out",
|
48
|
+
onComplete: function () {
|
49
|
+
target.setAttribute("tabindex", "-1");
|
50
|
+
target.focus({ preventScroll: true });
|
51
|
+
},
|
52
|
+
});
|
53
|
+
}
|
54
|
+
|
55
|
+
// Handle anchor link clicks and keyboard activation
|
56
|
+
function handleAnchorClicks() {
|
57
|
+
document.addEventListener("click", handleAnchorActivation);
|
58
|
+
document.addEventListener("keydown", function (e) {
|
59
|
+
if (e.key === "Enter" || e.key === " ") {
|
60
|
+
handleAnchorActivation(e);
|
61
|
+
}
|
62
|
+
});
|
63
|
+
}
|
64
|
+
|
65
|
+
function handleAnchorActivation(e) {
|
66
|
+
const link = e.target.closest('a[href^="#"]');
|
67
|
+
if (!link) return;
|
68
|
+
|
69
|
+
const href = link.getAttribute("href");
|
70
|
+
if (!href || href === "#") return;
|
71
|
+
|
72
|
+
const targetId = href.substring(1);
|
73
|
+
const targetElement = document.getElementById(targetId);
|
74
|
+
|
75
|
+
if (targetElement) {
|
76
|
+
e.preventDefault();
|
77
|
+
if (history.replaceState) {
|
78
|
+
history.replaceState(null, null, `#${targetElement.id}`);
|
79
|
+
}
|
80
|
+
const offset = getScrollOffset();
|
81
|
+
scrollToElement(targetElement, offset);
|
81
82
|
}
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
}
|
83
|
+
}
|
84
|
+
|
85
|
+
// Initialize anchor link handling
|
86
|
+
handleAnchorClicks();
|
87
|
+
|
88
|
+
return {
|
89
|
+
result: "autoInit-smooth-scroll initialized",
|
90
|
+
};
|
91
|
+
}
|
package/index.js
CHANGED
@@ -1,73 +1,88 @@
|
|
1
|
-
// Version:1.
|
1
|
+
// Version:1.4.1
|
2
2
|
|
3
|
-
const API_NAME =
|
3
|
+
const API_NAME = "hsmain";
|
4
4
|
|
5
5
|
const initializeHsMain = async () => {
|
6
|
-
if (
|
6
|
+
if (
|
7
|
+
window[API_NAME] &&
|
8
|
+
!Array.isArray(window[API_NAME]) &&
|
9
|
+
window[API_NAME].loaded
|
10
|
+
) {
|
7
11
|
return;
|
8
12
|
}
|
9
13
|
|
10
14
|
const queuedModules = Array.isArray(window[API_NAME]) ? window[API_NAME] : [];
|
11
15
|
|
12
16
|
const animationModules = {
|
13
|
-
|
14
|
-
|
15
|
-
|
17
|
+
"data-hs-anim-text": true,
|
18
|
+
"data-hs-anim-hero": true,
|
19
|
+
"data-hs-anim-transition": true,
|
16
20
|
};
|
17
21
|
|
18
22
|
const utilityModules = {
|
19
|
-
|
20
|
-
|
21
|
-
|
23
|
+
"data-hs-util-toc": true,
|
24
|
+
"data-hs-util-progress": true,
|
25
|
+
"data-hs-util-ba": true,
|
22
26
|
};
|
23
27
|
|
24
28
|
const autoInitModules = {
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
+
"smooth-scroll": true,
|
30
|
+
modal: true,
|
31
|
+
navbar: true,
|
32
|
+
accessibility: true,
|
33
|
+
counter: true,
|
34
|
+
form: true,
|
29
35
|
};
|
30
36
|
|
31
37
|
const allDataAttributes = { ...animationModules, ...utilityModules };
|
32
38
|
|
33
|
-
const waitForWebflow = async () =>
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
const waitForDOMContentLoaded = async () => new Promise(resolve => {
|
39
|
-
if (document.readyState === 'loading') {
|
40
|
-
document.addEventListener('DOMContentLoaded', resolve);
|
41
|
-
} else {
|
42
|
-
resolve();
|
43
|
-
}
|
44
|
-
});
|
39
|
+
const waitForWebflow = async () =>
|
40
|
+
new Promise((resolve) => {
|
41
|
+
if (!window.Webflow) window.Webflow = [];
|
42
|
+
window.Webflow.push(resolve);
|
43
|
+
});
|
45
44
|
|
46
45
|
const moduleMap = {
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
46
|
+
"data-hs-anim-text": () => import("./animations/text.js"),
|
47
|
+
"data-hs-anim-hero": () => import("./animations/hero.js"),
|
48
|
+
"data-hs-anim-transition": () => import("./animations/transition.js"),
|
49
|
+
"data-hs-util-toc": () => import("./utils/toc.js"),
|
50
|
+
"data-hs-util-progress": () => import("./utils/scroll-progress.js"),
|
51
|
+
"data-hs-util-ba": () => import("./utils/before-after.js"),
|
52
|
+
"smooth-scroll": () => import("./autoInit/smooth-scroll.js"),
|
53
|
+
modal: () => import("./autoInit/modal.js"),
|
54
|
+
navbar: () => import("./autoInit/navbar.js"),
|
55
|
+
accessibility: () => import("./autoInit/accessibility.js"),
|
56
|
+
counter: () => import("./autoInit/counter.js"),
|
57
|
+
form: () => import("./autoInit/form.js"),
|
57
58
|
};
|
58
59
|
|
59
|
-
let scripts = [
|
60
|
-
|
60
|
+
let scripts = [
|
61
|
+
...document.querySelectorAll(
|
62
|
+
`script[type="module"][src="${import.meta.url}"]`,
|
63
|
+
),
|
64
|
+
];
|
65
|
+
|
61
66
|
if (scripts.length === 0) {
|
62
|
-
scripts = [
|
63
|
-
.
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
67
|
+
scripts = [
|
68
|
+
...document.querySelectorAll(
|
69
|
+
'script[type="module"][src*="@hortonstudio/main"]',
|
70
|
+
),
|
71
|
+
].filter((script) => {
|
72
|
+
const scriptSrc = script.src;
|
73
|
+
const currentSrc = import.meta.url;
|
74
|
+
const scriptPackage = scriptSrc.match(
|
75
|
+
/@hortonstudio\/main(@[\d.]+)?/,
|
76
|
+
)?.[0];
|
77
|
+
const currentPackage = currentSrc.match(
|
78
|
+
/@hortonstudio\/main(@[\d.]+)?/,
|
79
|
+
)?.[0];
|
80
|
+
return (
|
81
|
+
scriptPackage &&
|
82
|
+
currentPackage &&
|
83
|
+
scriptPackage.split("@")[0] === currentPackage.split("@")[0]
|
84
|
+
);
|
85
|
+
});
|
71
86
|
}
|
72
87
|
|
73
88
|
const loadModule = async (moduleName) => {
|
@@ -121,9 +136,12 @@ const initializeHsMain = async () => {
|
|
121
136
|
process: new Set(),
|
122
137
|
load: loadModule,
|
123
138
|
loaded: false,
|
139
|
+
barbaEnabled: false,
|
140
|
+
embedScriptManager: null,
|
141
|
+
barbaManager: null,
|
124
142
|
push(...items) {
|
125
143
|
for (const [moduleName, callback] of items) {
|
126
|
-
if (typeof callback ===
|
144
|
+
if (typeof callback === "function") {
|
127
145
|
this.modules[moduleName]?.loading?.then(callback);
|
128
146
|
} else {
|
129
147
|
this.load(moduleName);
|
@@ -135,8 +153,61 @@ const initializeHsMain = async () => {
|
|
135
153
|
this.modules[moduleName]?.destroy?.();
|
136
154
|
}
|
137
155
|
},
|
156
|
+
async reinitialize() {
|
157
|
+
// Complete teardown and reinit
|
158
|
+
this.destroy();
|
159
|
+
this.modules = {};
|
160
|
+
this.process = new Set();
|
161
|
+
|
162
|
+
// Re-scan for modules including in w-embeds
|
163
|
+
await this.scanForEmbedModules();
|
164
|
+
processModules();
|
165
|
+
},
|
166
|
+
async scanForEmbedModules() {
|
167
|
+
const embeds = document.querySelectorAll(".w-embed");
|
168
|
+
embeds.forEach((embed) => {
|
169
|
+
const scripts = embed.querySelectorAll("script[data-hs-module]");
|
170
|
+
scripts.forEach((script) => {
|
171
|
+
// Extract module info from data attributes
|
172
|
+
const moduleType = script.getAttribute("data-hs-module-type");
|
173
|
+
const moduleName = script.getAttribute("data-hs-module");
|
174
|
+
|
175
|
+
if (moduleType && moduleName) {
|
176
|
+
const moduleKey = `data-hs-${moduleType}-${moduleName}`;
|
177
|
+
if (moduleMap[moduleKey]) {
|
178
|
+
this.load(moduleKey);
|
179
|
+
}
|
180
|
+
}
|
181
|
+
});
|
182
|
+
});
|
183
|
+
},
|
184
|
+
async setBarbaMode(enabled) {
|
185
|
+
this.barbaEnabled = enabled;
|
186
|
+
if (enabled && !this.barbaManager) {
|
187
|
+
try {
|
188
|
+
const module = await import("./barba/barbaManager.js");
|
189
|
+
this.barbaManager = module.initializeBarba();
|
190
|
+
if (!this.barbaManager) {
|
191
|
+
}
|
192
|
+
} catch (error) {
|
193
|
+
this.barbaEnabled = false;
|
194
|
+
}
|
195
|
+
}
|
196
|
+
},
|
197
|
+
enableBarba() {
|
198
|
+
return this.setBarbaMode(true);
|
199
|
+
},
|
200
|
+
async initEmbedScriptManager() {
|
201
|
+
if (!this.embedScriptManager) {
|
202
|
+
const { EmbedScriptManager } = await import(
|
203
|
+
"./barba/embedScriptManager.js"
|
204
|
+
);
|
205
|
+
this.embedScriptManager = new EmbedScriptManager();
|
206
|
+
}
|
207
|
+
return this.embedScriptManager;
|
208
|
+
},
|
138
209
|
afterReady(callback) {
|
139
|
-
if (typeof callback ===
|
210
|
+
if (typeof callback === "function") {
|
140
211
|
if (this.loaded) {
|
141
212
|
callback();
|
142
213
|
} else {
|
@@ -145,7 +216,7 @@ const initializeHsMain = async () => {
|
|
145
216
|
}
|
146
217
|
},
|
147
218
|
afterWebflowReady(callback) {
|
148
|
-
if (typeof callback ===
|
219
|
+
if (typeof callback === "function") {
|
149
220
|
if (this.loaded) {
|
150
221
|
callback();
|
151
222
|
} else {
|
@@ -157,7 +228,7 @@ const initializeHsMain = async () => {
|
|
157
228
|
if (moduleName) {
|
158
229
|
return {
|
159
230
|
loaded: !!this.modules[moduleName],
|
160
|
-
loading: this.process.has(moduleName)
|
231
|
+
loading: this.process.has(moduleName),
|
161
232
|
};
|
162
233
|
}
|
163
234
|
return {
|
@@ -165,76 +236,55 @@ const initializeHsMain = async () => {
|
|
165
236
|
loading: [...this.process],
|
166
237
|
animations: Object.keys(animationModules),
|
167
238
|
utilities: Object.keys(utilityModules),
|
168
|
-
autoInit: Object.keys(autoInitModules)
|
239
|
+
autoInit: Object.keys(autoInitModules),
|
240
|
+
barbaEnabled: this.barbaEnabled,
|
169
241
|
};
|
170
|
-
}
|
242
|
+
},
|
171
243
|
};
|
172
244
|
|
173
245
|
const processModules = () => {
|
174
246
|
for (const script of scripts) {
|
175
|
-
const isAutoDetect = script.getAttribute('data-hs-auto') === 'true';
|
176
|
-
|
177
247
|
for (const attrName of Object.keys(allDataAttributes)) {
|
178
248
|
if (script.hasAttribute(attrName)) {
|
179
249
|
loadModule(attrName);
|
180
250
|
}
|
181
251
|
}
|
182
|
-
|
183
|
-
if (isAutoDetect) {
|
184
|
-
waitForDOMContentLoaded().then(() => {
|
185
|
-
const foundAttributes = new Set();
|
186
|
-
const allElements = document.querySelectorAll('*');
|
187
|
-
|
188
|
-
for (const element of allElements) {
|
189
|
-
for (const attrName of element.getAttributeNames()) {
|
190
|
-
const match = attrName.match(/^(data-hs-(?:anim|util)-[^-=]+)/)?.[1];
|
191
|
-
if (match && allDataAttributes[match]) {
|
192
|
-
foundAttributes.add(match);
|
193
|
-
}
|
194
|
-
}
|
195
|
-
}
|
196
|
-
|
197
|
-
for (const attrName of foundAttributes) {
|
198
|
-
loadModule(attrName);
|
199
|
-
}
|
200
|
-
});
|
201
|
-
}
|
202
252
|
}
|
203
253
|
|
204
254
|
for (const moduleName of Object.keys(autoInitModules)) {
|
205
255
|
loadModule(moduleName);
|
206
256
|
}
|
207
257
|
|
208
|
-
if (
|
209
|
-
|
210
|
-
|
258
|
+
if (
|
259
|
+
!scripts.some((script) => script.hasAttribute("data-hs-anim-transition"))
|
260
|
+
) {
|
261
|
+
document.querySelectorAll(".transition").forEach((element) => {
|
262
|
+
element.style.display = "none";
|
211
263
|
});
|
212
264
|
}
|
213
265
|
};
|
214
266
|
|
215
|
-
document.querySelectorAll(
|
216
|
-
richtext.querySelectorAll(
|
217
|
-
img.loading =
|
267
|
+
document.querySelectorAll(".w-richtext").forEach((richtext) => {
|
268
|
+
richtext.querySelectorAll("img").forEach((img) => {
|
269
|
+
img.loading = "eager";
|
218
270
|
});
|
219
271
|
});
|
220
272
|
|
221
273
|
const finalize = async () => {
|
222
274
|
processModules();
|
223
275
|
await waitForWebflow();
|
276
|
+
|
224
277
|
window[API_NAME].loaded = true;
|
225
|
-
readyCallbacks.forEach(callback => {
|
278
|
+
readyCallbacks.forEach((callback) => {
|
226
279
|
try {
|
227
280
|
callback();
|
228
|
-
} catch (error) {
|
229
|
-
// Silent fail for callbacks
|
230
|
-
}
|
281
|
+
} catch (error) {}
|
231
282
|
});
|
283
|
+
readyCallbacks.length = 0; // Clear array after execution
|
232
284
|
};
|
233
285
|
|
234
286
|
window[API_NAME].push(...queuedModules);
|
235
|
-
finalize().catch(() => {
|
236
|
-
// Silent fail for initialization
|
237
|
-
});
|
287
|
+
finalize().catch(() => {});
|
238
288
|
};
|
239
289
|
|
240
|
-
initializeHsMain();
|
290
|
+
initializeHsMain();
|