@hortonstudio/main 1.0.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/animations/READ +0 -0
- package/animations/modules/hero.js +154 -0
- package/animations/modules/text.js +165 -0
- package/animations/modules/transition.js +40 -0
- package/index.js +122 -0
- package/package.json +13 -0
- package/utils/READ +0 -0
- package/utils/modules/toc.js +0 -0
package/animations/READ
ADDED
File without changes
|
@@ -0,0 +1,154 @@
|
|
1
|
+
// Hero Animations Module
|
2
|
+
let heroTimeline = null;
|
3
|
+
let headingSplits = [];
|
4
|
+
|
5
|
+
export async function init() {
|
6
|
+
|
7
|
+
// Check for GSAP
|
8
|
+
if (typeof window.gsap === "undefined") {
|
9
|
+
console.error('GSAP not found - hero animations disabled');
|
10
|
+
return;
|
11
|
+
}
|
12
|
+
|
13
|
+
// Register plugins
|
14
|
+
gsap.registerPlugin(ScrollTrigger, SplitText);
|
15
|
+
|
16
|
+
// Variable to control when animation starts (in seconds)
|
17
|
+
const heroAnimationDelay = 0.2;
|
18
|
+
|
19
|
+
// SET INITIAL STATES IMMEDIATELY
|
20
|
+
gsap.set('[hero-anim="announce"]', { opacity: 0, yPercent: -100 });
|
21
|
+
gsap.set('[hero-anim="nav-container"]', { opacity: 0, yPercent: -100 });
|
22
|
+
gsap.set('[hero-anim="subheading"]', { y: 50, opacity: 0 });
|
23
|
+
gsap.set('[hero-anim="button"]', { y: 50, opacity: 0 });
|
24
|
+
gsap.set('[hero-anim="image"]', { opacity: 0 });
|
25
|
+
gsap.set('[hero-anim="appear1"]', { y: 50, opacity: 0 });
|
26
|
+
gsap.set('[hero-anim="appear2"]', { y: 50, opacity: 0 });
|
27
|
+
gsap.set('[hero-anim="appear3"]', { y: 50, opacity: 0 });
|
28
|
+
gsap.set('[hero-anim="appear4"]', { y: 50, opacity: 0 });
|
29
|
+
|
30
|
+
// SET UP SPLIT TEXT WITH MASKS
|
31
|
+
const headingElements = document.querySelectorAll('[hero-anim="heading"]');
|
32
|
+
headingSplits = []; // Reset array
|
33
|
+
|
34
|
+
headingElements.forEach(heading => {
|
35
|
+
const split = new SplitText(heading, {
|
36
|
+
type: "words",
|
37
|
+
mask: "words",
|
38
|
+
wordsClass: "word"
|
39
|
+
});
|
40
|
+
headingSplits.push(split);
|
41
|
+
|
42
|
+
// Set initial state for split text
|
43
|
+
gsap.set(split.words, { yPercent: 110 });
|
44
|
+
gsap.set(heading, { autoAlpha: 1 });
|
45
|
+
});
|
46
|
+
|
47
|
+
// Wait for fonts to load, then start animation
|
48
|
+
document.fonts.ready.then(() => {
|
49
|
+
|
50
|
+
setTimeout(() => {
|
51
|
+
// Create main timeline
|
52
|
+
heroTimeline = gsap.timeline();
|
53
|
+
|
54
|
+
// 0s - Announce
|
55
|
+
heroTimeline.to('[hero-anim="announce"]',
|
56
|
+
{ opacity: 1, yPercent: 0, duration: 1, ease: 'power3.out' },
|
57
|
+
0
|
58
|
+
);
|
59
|
+
|
60
|
+
// 0.05s - Nav Container
|
61
|
+
heroTimeline.to('[hero-anim="nav-container"]',
|
62
|
+
{ opacity: 1, yPercent: 0, duration: 1, ease: 'power3.out' },
|
63
|
+
0.05
|
64
|
+
);
|
65
|
+
|
66
|
+
// 0.15s - Heading (Split Text with masks)
|
67
|
+
headingSplits.forEach(split => {
|
68
|
+
heroTimeline.to(split.words,
|
69
|
+
{ yPercent: 0, duration: 1.25, stagger: 0.1, ease: 'power4.out' },
|
70
|
+
0.15
|
71
|
+
);
|
72
|
+
});
|
73
|
+
|
74
|
+
// 0.25s - Subheading
|
75
|
+
heroTimeline.to('[hero-anim="subheading"]',
|
76
|
+
{ y: 0, opacity: 1, duration: 1.5, ease: 'power3.out' },
|
77
|
+
0.25
|
78
|
+
);
|
79
|
+
|
80
|
+
// 0.35s - Button
|
81
|
+
heroTimeline.to('[hero-anim="button"]',
|
82
|
+
{ y: 0, opacity: 1, duration: 1.5, ease: 'power3.out' },
|
83
|
+
0.35
|
84
|
+
);
|
85
|
+
|
86
|
+
// 0.35s - Image
|
87
|
+
heroTimeline.to('[hero-anim="image"]',
|
88
|
+
{ opacity: 1, duration: 1.25, ease: 'power3.out' },
|
89
|
+
0.35
|
90
|
+
);
|
91
|
+
|
92
|
+
// 0.45s - Appear1
|
93
|
+
heroTimeline.to('[hero-anim="appear1"]',
|
94
|
+
{ y: 0, opacity: 1, duration: 1.5, ease: 'power3.out' },
|
95
|
+
0.45
|
96
|
+
);
|
97
|
+
|
98
|
+
// 0.5s - Appear2
|
99
|
+
heroTimeline.to('[hero-anim="appear2"]',
|
100
|
+
{ y: 0, opacity: 1, duration: 1.5, ease: 'power3.out' },
|
101
|
+
0.5
|
102
|
+
);
|
103
|
+
|
104
|
+
// 0.55s - Appear3
|
105
|
+
heroTimeline.to('[hero-anim="appear3"]',
|
106
|
+
{ y: 0, opacity: 1, duration: 1.5, ease: 'power3.out' },
|
107
|
+
0.55
|
108
|
+
);
|
109
|
+
|
110
|
+
// 0.55s - Appear4
|
111
|
+
heroTimeline.to('[hero-anim="appear4"]',
|
112
|
+
{ y: 0, opacity: 1, duration: 1.5, ease: 'power3.out' },
|
113
|
+
0.55
|
114
|
+
);
|
115
|
+
|
116
|
+
}, heroAnimationDelay * 1000);
|
117
|
+
|
118
|
+
});
|
119
|
+
|
120
|
+
return { result: 'hero-main initialized' };
|
121
|
+
}
|
122
|
+
|
123
|
+
export function destroy() {
|
124
|
+
console.log('🧹 Hero animations destroyed');
|
125
|
+
|
126
|
+
// Kill the main timeline
|
127
|
+
if (heroTimeline) {
|
128
|
+
heroTimeline.kill();
|
129
|
+
heroTimeline = null;
|
130
|
+
}
|
131
|
+
|
132
|
+
// Revert all SplitText instances
|
133
|
+
headingSplits.forEach(split => {
|
134
|
+
if (split && split.revert) {
|
135
|
+
split.revert();
|
136
|
+
}
|
137
|
+
});
|
138
|
+
headingSplits = [];
|
139
|
+
|
140
|
+
// Kill any ScrollTriggers (in case there are any)
|
141
|
+
ScrollTrigger.getAll().forEach(trigger => trigger.kill());
|
142
|
+
|
143
|
+
// Reset elements to normal state
|
144
|
+
gsap.set('[hero-anim="announce"]', { clearProps: "all" });
|
145
|
+
gsap.set('[hero-anim="nav-container"]', { clearProps: "all" });
|
146
|
+
gsap.set('[hero-anim="subheading"]', { clearProps: "all" });
|
147
|
+
gsap.set('[hero-anim="button"]', { clearProps: "all" });
|
148
|
+
gsap.set('[hero-anim="image"]', { clearProps: "all" });
|
149
|
+
gsap.set('[hero-anim="appear1"]', { clearProps: "all" });
|
150
|
+
gsap.set('[hero-anim="appear2"]', { clearProps: "all" });
|
151
|
+
gsap.set('[hero-anim="appear3"]', { clearProps: "all" });
|
152
|
+
gsap.set('[hero-anim="appear4"]', { clearProps: "all" });
|
153
|
+
gsap.set('[hero-anim="heading"]', { clearProps: "all" });
|
154
|
+
}
|
@@ -0,0 +1,165 @@
|
|
1
|
+
let activeAnimations = [];
|
2
|
+
let resizeTimeout;
|
3
|
+
let lastWidth = window.innerWidth;
|
4
|
+
|
5
|
+
function waitForFonts() {
|
6
|
+
return document.fonts.ready;
|
7
|
+
}
|
8
|
+
|
9
|
+
function checkGSAP() {
|
10
|
+
if (typeof window.gsap === "undefined") {
|
11
|
+
document.documentElement.classList.add("gsap-not-found");
|
12
|
+
return false;
|
13
|
+
}
|
14
|
+
gsap.registerPlugin(ScrollTrigger, SplitText);
|
15
|
+
return true;
|
16
|
+
}
|
17
|
+
|
18
|
+
const WordSplitAnimations = {
|
19
|
+
async initial() {
|
20
|
+
await waitForFonts();
|
21
|
+
|
22
|
+
document.querySelectorAll(".a-word-split").forEach((text) => {
|
23
|
+
if (text.splitTextInstance) {
|
24
|
+
text.splitTextInstance.revert();
|
25
|
+
}
|
26
|
+
});
|
27
|
+
|
28
|
+
document.querySelectorAll(".a-word-split").forEach((text) => {
|
29
|
+
const split = SplitText.create(text, {
|
30
|
+
type: "words",
|
31
|
+
mask: "words",
|
32
|
+
wordsClass: "word",
|
33
|
+
});
|
34
|
+
text.splitTextInstance = split;
|
35
|
+
gsap.set(split.words, {
|
36
|
+
yPercent: 110
|
37
|
+
});
|
38
|
+
gsap.set(text, { autoAlpha: 1 });
|
39
|
+
});
|
40
|
+
},
|
41
|
+
|
42
|
+
async animate() {
|
43
|
+
await waitForFonts();
|
44
|
+
|
45
|
+
document.querySelectorAll(".a-word-split").forEach((text) => {
|
46
|
+
const words = text.querySelectorAll('.word');
|
47
|
+
const tl = gsap.timeline({
|
48
|
+
scrollTrigger: {
|
49
|
+
trigger: text,
|
50
|
+
start: "top 95%",
|
51
|
+
invalidateOnRefresh: true,
|
52
|
+
},
|
53
|
+
});
|
54
|
+
|
55
|
+
tl.to(words, {
|
56
|
+
yPercent: 0,
|
57
|
+
duration: 1.25,
|
58
|
+
stagger: .1,
|
59
|
+
ease: "power4.out",
|
60
|
+
});
|
61
|
+
|
62
|
+
activeAnimations.push({ timeline: tl, element: text });
|
63
|
+
});
|
64
|
+
|
65
|
+
ScrollTrigger.refresh();
|
66
|
+
}
|
67
|
+
};
|
68
|
+
|
69
|
+
const AppearAnimations = {
|
70
|
+
async initial() {
|
71
|
+
await waitForFonts();
|
72
|
+
|
73
|
+
const elements = document.querySelectorAll('.a-appear');
|
74
|
+
elements.forEach(element => {
|
75
|
+
gsap.set(element, {
|
76
|
+
y: 50,
|
77
|
+
opacity: 0
|
78
|
+
});
|
79
|
+
});
|
80
|
+
},
|
81
|
+
|
82
|
+
async animate() {
|
83
|
+
await waitForFonts();
|
84
|
+
|
85
|
+
document.querySelectorAll('.a-appear').forEach(element => {
|
86
|
+
const tl = gsap.timeline({
|
87
|
+
scrollTrigger: {
|
88
|
+
trigger: element,
|
89
|
+
start: 'top 95%',
|
90
|
+
invalidateOnRefresh: true,
|
91
|
+
}
|
92
|
+
});
|
93
|
+
|
94
|
+
tl.to(element, {
|
95
|
+
y: 0,
|
96
|
+
opacity: 1,
|
97
|
+
duration: 1.5,
|
98
|
+
ease: 'power3.out'
|
99
|
+
});
|
100
|
+
|
101
|
+
activeAnimations.push({ timeline: tl, element: element });
|
102
|
+
});
|
103
|
+
|
104
|
+
ScrollTrigger.refresh();
|
105
|
+
}
|
106
|
+
};
|
107
|
+
|
108
|
+
async function setInitialStates() {
|
109
|
+
if (!checkGSAP()) return;
|
110
|
+
|
111
|
+
await Promise.all([
|
112
|
+
WordSplitAnimations.initial(),
|
113
|
+
AppearAnimations.initial()
|
114
|
+
]);
|
115
|
+
}
|
116
|
+
|
117
|
+
async function initAnimations() {
|
118
|
+
if (!checkGSAP()) return;
|
119
|
+
|
120
|
+
await Promise.all([
|
121
|
+
WordSplitAnimations.animate(),
|
122
|
+
AppearAnimations.animate()
|
123
|
+
]);
|
124
|
+
}
|
125
|
+
|
126
|
+
function handleResize() {
|
127
|
+
clearTimeout(resizeTimeout);
|
128
|
+
resizeTimeout = setTimeout(() => {
|
129
|
+
const currentWidth = window.innerWidth;
|
130
|
+
|
131
|
+
if (Math.abs(currentWidth - lastWidth) > 10) {
|
132
|
+
lastWidth = currentWidth;
|
133
|
+
|
134
|
+
setTimeout(() => {
|
135
|
+
setInitialStates();
|
136
|
+
setTimeout(() => {
|
137
|
+
initAnimations();
|
138
|
+
}, 100);
|
139
|
+
}, 50);
|
140
|
+
}
|
141
|
+
}, 150);
|
142
|
+
}
|
143
|
+
|
144
|
+
export async function init() {
|
145
|
+
if (!checkGSAP()) return;
|
146
|
+
|
147
|
+
await setInitialStates();
|
148
|
+
|
149
|
+
setTimeout(() => {
|
150
|
+
initAnimations();
|
151
|
+
}, 750);
|
152
|
+
|
153
|
+
window.addEventListener('resize', handleResize);
|
154
|
+
window.hsMain.textResizeHandler = handleResize;
|
155
|
+
|
156
|
+
return {
|
157
|
+
result: 'text-main initialized',
|
158
|
+
reinit: () => {
|
159
|
+
setInitialStates();
|
160
|
+
setTimeout(() => {
|
161
|
+
initAnimations();
|
162
|
+
}, 100);
|
163
|
+
}
|
164
|
+
};
|
165
|
+
}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
// Page Transition Module
|
2
|
+
export async function init() {
|
3
|
+
|
4
|
+
// Your original code with minimal changes
|
5
|
+
let transitionTrigger = $(".transition-trigger");
|
6
|
+
let introDurationMS = 800;
|
7
|
+
let exitDurationMS = 400;
|
8
|
+
let excludedClass = "no-transition";
|
9
|
+
|
10
|
+
// On Page Load
|
11
|
+
if (transitionTrigger.length > 0) {
|
12
|
+
Webflow.push(function () {
|
13
|
+
transitionTrigger.click();
|
14
|
+
});
|
15
|
+
$("body").addClass("no-scroll-transition");
|
16
|
+
setTimeout(() => {$("body").removeClass("no-scroll-transition");}, introDurationMS);
|
17
|
+
}
|
18
|
+
|
19
|
+
// On Link Click
|
20
|
+
$("a").on("click", function (e) {
|
21
|
+
if ($(this).prop("hostname") == window.location.host && $(this).attr("href").indexOf("#") === -1 &&
|
22
|
+
!$(this).hasClass(excludedClass) && $(this).attr("target") !== "_blank" && transitionTrigger.length > 0) {
|
23
|
+
e.preventDefault();
|
24
|
+
$("body").addClass("no-scroll-transition");
|
25
|
+
let transitionURL = $(this).attr("href");
|
26
|
+
transitionTrigger.click();
|
27
|
+
setTimeout(function () {window.location = transitionURL;}, exitDurationMS);
|
28
|
+
}
|
29
|
+
});
|
30
|
+
|
31
|
+
// On Back Button Tap
|
32
|
+
window.onpageshow = function(event) {if (event.persisted) {window.location.reload()}};
|
33
|
+
|
34
|
+
// Hide Transition on Window Width Resize
|
35
|
+
setTimeout(() => {$(window).on("resize", function () {
|
36
|
+
setTimeout(() => {$(".transition").css("display", "none");}, 50);});
|
37
|
+
}, introDurationMS);
|
38
|
+
|
39
|
+
return { result: 'transition-main initialized' };
|
40
|
+
}
|
package/index.js
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
window.hsMain = window.hsMain || {};
|
2
|
+
|
3
|
+
const supportedModules = new Set([
|
4
|
+
"hs-a-text", "hs-a-hero", "hs-u-toc", "hs-a-transition"
|
5
|
+
]);
|
6
|
+
|
7
|
+
const moduleMap = {
|
8
|
+
"hs-a-text": "./animations/modules/text.js",
|
9
|
+
"hs-a-hero": "./animations/modules/hero.js",
|
10
|
+
"hs-u-toc": "./utils/modules/toc.js",
|
11
|
+
"hs-a-transition": "./animations/modules/transition.js"
|
12
|
+
};
|
13
|
+
|
14
|
+
const loadModule = (moduleName) => {
|
15
|
+
const modulePath = moduleMap[moduleName];
|
16
|
+
if (!modulePath) {
|
17
|
+
throw new Error(`HortonStudio module "${moduleName}" is not supported.`);
|
18
|
+
}
|
19
|
+
return import(modulePath);
|
20
|
+
};
|
21
|
+
|
22
|
+
const findCurrentScriptTag = () => {
|
23
|
+
const scriptTag = document.querySelector('script[hs-main]');
|
24
|
+
return scriptTag || null;
|
25
|
+
};
|
26
|
+
|
27
|
+
const processModules = (scriptTag) => {
|
28
|
+
if (!scriptTag) {
|
29
|
+
return;
|
30
|
+
}
|
31
|
+
|
32
|
+
for (const moduleName of supportedModules) {
|
33
|
+
if (scriptTag.hasAttribute(moduleName)) {
|
34
|
+
loadHsModule(moduleName);
|
35
|
+
}
|
36
|
+
}
|
37
|
+
};
|
38
|
+
|
39
|
+
const loadHsModule = async (moduleName) => {
|
40
|
+
const { hsMain } = window;
|
41
|
+
|
42
|
+
if (hsMain.process.has(moduleName)) {
|
43
|
+
return hsMain.modules[moduleName]?.loading;
|
44
|
+
}
|
45
|
+
|
46
|
+
hsMain.process.add(moduleName);
|
47
|
+
|
48
|
+
const moduleObj = hsMain.modules[moduleName] || {};
|
49
|
+
hsMain.modules[moduleName] = moduleObj;
|
50
|
+
|
51
|
+
moduleObj.loading = new Promise((resolve, reject) => {
|
52
|
+
moduleObj.resolve = resolve;
|
53
|
+
moduleObj.reject = reject;
|
54
|
+
});
|
55
|
+
|
56
|
+
try {
|
57
|
+
const { init, version } = await loadModule(moduleName);
|
58
|
+
const initResult = await init();
|
59
|
+
const { result } = initResult || {};
|
60
|
+
|
61
|
+
moduleObj.version = version;
|
62
|
+
moduleObj.restart = () => {
|
63
|
+
hsMain.process.delete(moduleName);
|
64
|
+
return loadHsModule(moduleName);
|
65
|
+
};
|
66
|
+
|
67
|
+
moduleObj.resolve?.(result);
|
68
|
+
delete moduleObj.resolve;
|
69
|
+
delete moduleObj.reject;
|
70
|
+
|
71
|
+
return result;
|
72
|
+
|
73
|
+
} catch (error) {
|
74
|
+
moduleObj.reject?.(error);
|
75
|
+
hsMain.process.delete(moduleName);
|
76
|
+
throw error;
|
77
|
+
}
|
78
|
+
};
|
79
|
+
|
80
|
+
const initializeHsMain = () => {
|
81
|
+
const { hsMain } = window;
|
82
|
+
|
83
|
+
const existingRequests = Array.isArray(hsMain) ? hsMain : [];
|
84
|
+
const scriptTag = findCurrentScriptTag();
|
85
|
+
|
86
|
+
window.hsMain = {
|
87
|
+
scriptTag,
|
88
|
+
modules: {},
|
89
|
+
process: new Set(),
|
90
|
+
|
91
|
+
load: loadHsModule,
|
92
|
+
|
93
|
+
status(moduleName) {
|
94
|
+
if (!moduleName) {
|
95
|
+
return {
|
96
|
+
loaded: Object.keys(this.modules),
|
97
|
+
loading: [...this.process]
|
98
|
+
};
|
99
|
+
}
|
100
|
+
return {
|
101
|
+
loaded: !!this.modules[moduleName],
|
102
|
+
loading: this.process.has(moduleName)
|
103
|
+
};
|
104
|
+
}
|
105
|
+
};
|
106
|
+
|
107
|
+
processModules(scriptTag);
|
108
|
+
|
109
|
+
if (existingRequests.length > 0) {
|
110
|
+
existingRequests.forEach(request => {
|
111
|
+
if (typeof request === 'string') {
|
112
|
+
window.hsMain.load(request);
|
113
|
+
}
|
114
|
+
});
|
115
|
+
}
|
116
|
+
};
|
117
|
+
|
118
|
+
if (document.readyState === 'loading') {
|
119
|
+
document.addEventListener('DOMContentLoaded', initializeHsMain);
|
120
|
+
} else {
|
121
|
+
initializeHsMain();
|
122
|
+
}
|
package/package.json
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
{
|
2
|
+
"name": "@hortonstudio/main",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"main": "index.js",
|
5
|
+
"type": "module",
|
6
|
+
"scripts": {
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
8
|
+
},
|
9
|
+
"keywords": [],
|
10
|
+
"author": "Horton Studio",
|
11
|
+
"license": "ISC",
|
12
|
+
"description": "Animation and utility library for client websites"
|
13
|
+
}
|
package/utils/READ
ADDED
File without changes
|
File without changes
|