@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.
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