@hortonstudio/main 1.9.11 → 1.9.20

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.
Files changed (120) hide show
  1. package/.prettierrc +8 -0
  2. package/README.md +146 -0
  3. package/eslint.config.js +32 -0
  4. package/index.ts +275 -0
  5. package/package.json +19 -2
  6. package/public/bootstrap.js +16 -0
  7. package/src/animations/animations.ts +93 -0
  8. package/src/animations/functions/counter/counter.ts +137 -0
  9. package/src/config.json +570 -0
  10. package/src/config.ts +105 -0
  11. package/src/modules/default/README.md +167 -0
  12. package/src/modules/default/default.ts +71 -0
  13. package/{autoInit → src/modules/default/functions}/accessibility/README.md +44 -12
  14. package/src/modules/default/functions/accessibility/accessibility.ts +54 -0
  15. package/src/modules/default/functions/accordion/README.md +451 -0
  16. package/src/modules/default/functions/accordion/accordion.ts +189 -0
  17. package/src/modules/default/functions/comparison/comparison.ts +424 -0
  18. package/src/modules/default/functions/marquee/marquee.ts +206 -0
  19. package/src/modules/default/functions/navbar/README.md +393 -0
  20. package/src/modules/default/functions/navbar/functions/arrow-navigation/arrow-navigation.ts +183 -0
  21. package/src/modules/default/functions/navbar/functions/dropdown/dropdown.ts +313 -0
  22. package/src/modules/default/functions/navbar/functions/menu/menu.ts +315 -0
  23. package/src/modules/default/functions/navbar/navbar.ts +51 -0
  24. package/{autoInit → src/modules/default/functions}/smooth-scroll/README.md +45 -14
  25. package/{autoInit/smooth-scroll/smooth-scroll.js → src/modules/default/functions/smooth-scroll/smooth-scroll.ts} +33 -38
  26. package/{autoInit → src/modules/default/functions}/transition/README.md +59 -32
  27. package/src/modules/default/functions/transition/transition.ts +290 -0
  28. package/src/modules/normalize/README.md +172 -0
  29. package/src/modules/normalize/functions/clickable/README.md +84 -0
  30. package/src/modules/normalize/functions/clickable/clickable.ts +43 -0
  31. package/src/modules/normalize/functions/clickable/functions/normalize/README.md +213 -0
  32. package/src/modules/normalize/functions/clickable/functions/normalize/normalize.ts +68 -0
  33. package/src/modules/normalize/functions/dupe/README.md +405 -0
  34. package/src/modules/normalize/functions/dupe/dupe.ts +197 -0
  35. package/src/modules/normalize/functions/sync/sync.ts +378 -0
  36. package/src/modules/normalize/normalize.ts +58 -0
  37. package/src/modules/structure/README.md +190 -0
  38. package/src/modules/structure/functions/form/README.md +94 -0
  39. package/src/modules/structure/functions/form/form.ts +54 -0
  40. package/src/modules/structure/functions/form/functions/honeypot/README.md +77 -0
  41. package/src/modules/structure/functions/form/functions/honeypot/honeypot.ts +37 -0
  42. package/src/modules/structure/functions/form/functions/range/README.md +410 -0
  43. package/src/modules/structure/functions/form/functions/range/range.ts +92 -0
  44. package/src/modules/structure/functions/form/functions/select/README.md +393 -0
  45. package/src/modules/structure/functions/form/functions/select/functions/custom-select/custom-select.ts +637 -0
  46. package/src/modules/structure/functions/form/functions/select/functions/states/states.ts +118 -0
  47. package/src/modules/structure/functions/form/functions/select/select.ts +48 -0
  48. package/src/modules/structure/functions/form/functions/test/test.ts +132 -0
  49. package/{autoInit/accessibility → src/modules/structure}/functions/pagination/README.md +147 -72
  50. package/{autoInit/accessibility/functions/pagination/pagination.js → src/modules/structure/functions/pagination/pagination.ts} +98 -50
  51. package/{autoInit → src/modules/structure/functions}/site-settings/README.md +57 -27
  52. package/{autoInit/site-settings/site-settings.js → src/modules/structure/functions/site-settings/site-settings.ts} +36 -32
  53. package/{autoInit/accessibility → src/modules/structure}/functions/toc/README.md +18 -15
  54. package/{autoInit/accessibility/functions/toc/toc.js → src/modules/structure/functions/toc/functions/heading-links/heading-links.ts} +43 -63
  55. package/src/modules/structure/functions/toc/functions/progress-bar/progress-bar.ts +101 -0
  56. package/src/modules/structure/functions/toc/toc.ts +35 -0
  57. package/{autoInit/accessibility → src/modules/structure}/functions/year-replacement/README.md +7 -6
  58. package/src/modules/structure/functions/year-replacement/year-replacement.ts +59 -0
  59. package/src/modules/structure/structure.ts +59 -0
  60. package/src/utils/attributeSelector.ts +78 -0
  61. package/src/utils/cssVariables.ts +24 -0
  62. package/src/utils/gsap.ts +198 -0
  63. package/src/utils/heightAnimator.ts +130 -0
  64. package/src/utils/modalManager.ts +150 -0
  65. package/src/utils.ts +54 -0
  66. package/tsconfig.json +24 -0
  67. package/vite.config.js +45 -0
  68. package/.claude/settings.local.json +0 -70
  69. package/archive/hero.js +0 -794
  70. package/archive/modal.js +0 -80
  71. package/archive/text.js +0 -628
  72. package/autoInit/accessibility/accessibility.js +0 -53
  73. package/autoInit/accessibility/functions/blog-remover/README.md +0 -61
  74. package/autoInit/accessibility/functions/blog-remover/blog-remover.js +0 -31
  75. package/autoInit/accessibility/functions/click-forwarding/README.md +0 -60
  76. package/autoInit/accessibility/functions/click-forwarding/click-forwarding.js +0 -82
  77. package/autoInit/accessibility/functions/dropdown/README.md +0 -212
  78. package/autoInit/accessibility/functions/dropdown/dropdown.js +0 -167
  79. package/autoInit/accessibility/functions/list-accessibility/README.md +0 -56
  80. package/autoInit/accessibility/functions/list-accessibility/list-accessibility.js +0 -23
  81. package/autoInit/accessibility/functions/text-synchronization/README.md +0 -62
  82. package/autoInit/accessibility/functions/text-synchronization/text-synchronization.js +0 -101
  83. package/autoInit/accessibility/functions/year-replacement/year-replacement.js +0 -43
  84. package/autoInit/button/README.md +0 -122
  85. package/autoInit/button/button.js +0 -51
  86. package/autoInit/counter/README.md +0 -274
  87. package/autoInit/counter/counter.js +0 -185
  88. package/autoInit/form/README.md +0 -338
  89. package/autoInit/form/form.js +0 -374
  90. package/autoInit/navbar/README.md +0 -366
  91. package/autoInit/navbar/navbar.js +0 -786
  92. package/autoInit/transition/transition.js +0 -116
  93. package/index.js +0 -305
  94. package/utils/before-after/README.md +0 -520
  95. package/utils/before-after/before-after.js +0 -653
  96. package/utils/css-animations/buttons/main/bgbasic/btn-main-bgbasic.html +0 -10
  97. package/utils/css-animations/buttons/main/bgfill/btn-main-bgfill.html +0 -29
  98. package/utils/css-animations/buttons/navbar/bgbasic/navbar-main-bgbasic.html +0 -17
  99. package/utils/css-animations/buttons/navbar/bgbasic/navbar-menu-bgbasic.html +0 -16
  100. package/utils/css-animations/buttons/navbar/bgfill/navbar-main-bgfill.html +0 -46
  101. package/utils/css-animations/buttons/navbar/bgfill/navbar-menu-bgfill.html +0 -39
  102. package/utils/css-animations/buttons/navbar/color/navbar-announce-color.html +0 -5
  103. package/utils/css-animations/buttons/navbar/color/navbar-main-color.html +0 -7
  104. package/utils/css-animations/buttons/navbar/color/navbar-menu-color.html +0 -7
  105. package/utils/css-animations/buttons/navbar/double-slide/navbar-announce-double-slide.html +0 -40
  106. package/utils/css-animations/buttons/navbar/double-slide/navbar-main-double-slide.html +0 -77
  107. package/utils/css-animations/buttons/navbar/scale/navbar-announce-scale.html +0 -6
  108. package/utils/css-animations/buttons/navbar/scale/navbar-main-scale.html +0 -9
  109. package/utils/css-animations/buttons/navbar/scale/navbar-menu-scale.html +0 -8
  110. package/utils/css-animations/buttons/navbar/underline/navbar-announce-underline.html +0 -32
  111. package/utils/css-animations/buttons/navbar/underline/navbar-main-underline.html +0 -56
  112. package/utils/css-animations/buttons/text/color/text-footer-color.html +0 -5
  113. package/utils/css-animations/buttons/text/color/text-main-color.html +0 -5
  114. package/utils/css-animations/buttons/text/double-slide/text-main-double-slide.html +0 -56
  115. package/utils/css-animations/buttons/text/scale/text-footer-scale.html +0 -6
  116. package/utils/css-animations/buttons/text/scale/text-main-scale.html +0 -6
  117. package/utils/css-animations/buttons/text/underline/text-footer-underline.html +0 -45
  118. package/utils/css-animations/buttons/text/underline/text-main-underline.html +0 -58
  119. package/utils/css-animations/cards/card-clickable.html +0 -11
  120. package/utils/css-animations/defaults.html +0 -69
package/.prettierrc ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "semi": true,
3
+ "trailingComma": "es5",
4
+ "singleQuote": true,
5
+ "printWidth": 100,
6
+ "tabWidth": 2,
7
+ "useTabs": false
8
+ }
package/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # @hortonstudio/main
2
+
3
+ **Version:** 2.0.0
4
+
5
+ Auto-initializing JavaScript library for Webflow sites with animations, accessibility, and utility functions.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```html
12
+ <script src="https://cdn.jsdelivr.net/npm/@hortonstudio/main@2/dist/bootstrap.js"></script>
13
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@hortonstudio/main@2/dist/main.js"></script>
14
+ ```
15
+
16
+ **Local Development:**
17
+
18
+ ```html
19
+ <!-- Local dev with HMR (run: npm run dev) -->
20
+ <script src="http://localhost:5173/public/bootstrap.js"></script>
21
+ <script type="module" src="http://localhost:5173/index.ts"></script>
22
+ ```
23
+
24
+ ---
25
+
26
+ ## Configuration
27
+
28
+ ### SPA Mode (Barba.js, Swup, etc.)
29
+
30
+ For single-page applications, add `data-hs-spa="true"` to disable automatic link interception:
31
+
32
+ ```html
33
+ <script
34
+ type="module"
35
+ src="https://cdn.jsdelivr.net/npm/@hortonstudio/main@2/dist/main.js"
36
+ data-hs-spa="true"
37
+ ></script>
38
+ ```
39
+
40
+ Then dispatch events from your router:
41
+
42
+ ```javascript
43
+ // Example: Barba.js integration
44
+ barba.hooks.before(() => {
45
+ window.dispatchEvent(new CustomEvent('hsmain:transition-exit'));
46
+ });
47
+
48
+ barba.hooks.after(() => {
49
+ window.dispatchEvent(new CustomEvent('hsmain:transition-enter'));
50
+ });
51
+ ```
52
+
53
+ ---
54
+
55
+ ## API Usage
56
+
57
+ ### Manual Module Loading
58
+
59
+ ```javascript
60
+ // Load a single module
61
+ window.hsmain.load('counter');
62
+
63
+ // With callback
64
+ window.hsmain.push([
65
+ 'counter',
66
+ () => {
67
+ console.log('Counter module ready');
68
+ },
69
+ ]);
70
+ ```
71
+
72
+ ### Lifecycle Hooks
73
+
74
+ ```javascript
75
+ // Execute code after all modules load
76
+ window.hsmain.afterReady(() => {
77
+ console.log('Library fully initialized');
78
+ });
79
+
80
+ // Check module status
81
+ const status = window.hsmain.status('counter');
82
+ console.log(status); // { loaded: true, loading: false }
83
+ ```
84
+
85
+ ### Cleanup
86
+
87
+ ```javascript
88
+ // Destroy all modules (useful for SPA navigation)
89
+ window.hsmain.destroy();
90
+
91
+ // Reinitialize everything
92
+ await window.hsmain.reinitialize();
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Architecture
98
+
99
+ - **TypeScript:** Full type safety with TypeScript source
100
+ - **Vite Bundled:** Optimized build with code splitting (~24kb gzipped total)
101
+ - **Config-driven:** Centralized configuration in `config.json`
102
+ - **SPA Compatible:** Works with Barba.js, Swup, and other routers
103
+ - **Memory Safe:** Proper cleanup tracking for observers and event handlers
104
+ - **Phase-based Loading:** Optimized initialization sequence
105
+
106
+ ---
107
+
108
+ ## Module Documentation
109
+
110
+ Each module has its own README with detailed usage instructions:
111
+
112
+ - **Animations:** `src/modules/default/` - Counter, comparison sliders, marquee
113
+ - **Navigation:** `src/modules/default/navbar/` - Dropdowns and mobile menus
114
+ - **Transitions:** `src/modules/transition/` - Page transitions with namespace support
115
+ - **Forms:** `src/modules/structure/functions/form/` - Custom selects and form enhancements
116
+ - **Accessibility:** `src/modules/default/functions/accordion/` - ARIA attributes
117
+ - **Utilities:** `src/modules/structure/` - Year replacement, TOC, pagination, site settings
118
+
119
+ ---
120
+
121
+ ## Development
122
+
123
+ ### Build
124
+
125
+ ```bash
126
+ npm run build
127
+ ```
128
+
129
+ ### Type Checking
130
+
131
+ ```bash
132
+ npm run type-check
133
+ ```
134
+
135
+ ### Linting
136
+
137
+ ```bash
138
+ npm run lint
139
+ npm run format
140
+ ```
141
+
142
+ ---
143
+
144
+ ## License
145
+
146
+ ISC
@@ -0,0 +1,32 @@
1
+ import tsParser from '@typescript-eslint/parser';
2
+ import tsPlugin from '@typescript-eslint/eslint-plugin';
3
+ import prettierConfig from 'eslint-config-prettier';
4
+
5
+ export default [
6
+ {
7
+ files: ['**/*.ts'],
8
+ languageOptions: {
9
+ parser: tsParser,
10
+ parserOptions: {
11
+ ecmaVersion: 'latest',
12
+ sourceType: 'module',
13
+ },
14
+ },
15
+ plugins: {
16
+ '@typescript-eslint': tsPlugin,
17
+ },
18
+ rules: {
19
+ ...tsPlugin.configs.recommended.rules,
20
+ '@typescript-eslint/no-explicit-any': 'error',
21
+ '@typescript-eslint/explicit-module-boundary-types': 'off',
22
+ '@typescript-eslint/no-unused-vars': [
23
+ 'error',
24
+ {
25
+ argsIgnorePattern: '^_',
26
+ varsIgnorePattern: '^_',
27
+ },
28
+ ],
29
+ },
30
+ },
31
+ prettierConfig,
32
+ ];
package/index.ts ADDED
@@ -0,0 +1,275 @@
1
+ import config from '@config';
2
+
3
+ const API_NAME = config._global.apiName;
4
+
5
+ const initializeHsMain = async () => {
6
+ if (window[API_NAME] && !Array.isArray(window[API_NAME]) && window[API_NAME].loaded) {
7
+ return;
8
+ }
9
+
10
+ // Add initialized class to HTML element immediately
11
+ document.documentElement.classList.add(config._global.classes.initialized);
12
+
13
+ const queuedModules = Array.isArray(window[API_NAME]) ? window[API_NAME] : [];
14
+
15
+ const moduleMap = {
16
+ normalize: () => import('./src/modules/normalize/normalize.ts'),
17
+ structure: () => import('./src/modules/structure/structure.ts'),
18
+ default: () => import('./src/modules/default/default.ts'),
19
+ animations: () => import('./src/animations/animations.ts'),
20
+ };
21
+
22
+ let scripts = [...document.querySelectorAll(`script[type="module"][src="${import.meta.url}"]`)];
23
+
24
+ if (scripts.length === 0) {
25
+ scripts = [
26
+ ...document.querySelectorAll('script[type="module"][src*="@hortonstudio/main"]'),
27
+ ].filter((script) => {
28
+ const scriptSrc = script.src;
29
+ const currentSrc = import.meta.url;
30
+ const scriptPackage = scriptSrc.match(/@hortonstudio\/main(@[\d.]+)?/)?.[0];
31
+ const currentPackage = currentSrc.match(/@hortonstudio\/main(@[\d.]+)?/)?.[0];
32
+ return (
33
+ scriptPackage &&
34
+ currentPackage &&
35
+ scriptPackage.split('@')[0] === currentPackage.split('@')[0]
36
+ );
37
+ });
38
+ }
39
+
40
+ // Read settings from script tag attributes
41
+ const settings = {
42
+ isSPA: scripts.some((script) => script.getAttribute('data-hs-spa') === 'true'),
43
+ };
44
+
45
+ // Define 3-phase initialization system
46
+ const PHASES = {
47
+ normalize: {
48
+ modules: ['normalize'],
49
+ parallel: false, // Sequential execution critical for DOM fixes
50
+ },
51
+ structure: {
52
+ modules: ['structure'],
53
+ parallel: false, // Must complete before default
54
+ },
55
+ default: {
56
+ modules: ['default', 'animations'],
57
+ parallel: true, // Run default and animations synchronously
58
+ waitForIx3: true, // Wait for Webflow IX3 before starting
59
+ },
60
+ };
61
+
62
+ const loadModule = async (moduleName) => {
63
+ const instance = window[API_NAME];
64
+ if (instance.process.has(moduleName)) {
65
+ return instance.modules[moduleName]?.loading;
66
+ }
67
+
68
+ instance.process.add(moduleName);
69
+
70
+ const moduleData = instance.modules[moduleName] || {};
71
+ instance.modules[moduleName] = moduleData;
72
+
73
+ moduleData.loading = new Promise((resolve, reject) => {
74
+ moduleData.resolve = resolve;
75
+ moduleData.reject = reject;
76
+ });
77
+
78
+ let destroy;
79
+ try {
80
+ const { init } = await moduleMap[moduleName]();
81
+ const result = await init();
82
+ const resultData = result || {};
83
+ destroy = resultData.destroy;
84
+ const initResult = resultData.result;
85
+
86
+ moduleData.destroy = () => {
87
+ destroy?.();
88
+ instance.process.delete(moduleName);
89
+ };
90
+ moduleData.restart = () => {
91
+ moduleData.destroy?.();
92
+ instance.load(moduleName);
93
+ };
94
+
95
+ moduleData.resolve?.(initResult);
96
+ delete moduleData.resolve;
97
+ delete moduleData.reject;
98
+
99
+ return initResult;
100
+ } catch (error) {
101
+ destroy?.();
102
+ moduleData.reject?.(error);
103
+ instance.process.delete(moduleName);
104
+ throw error;
105
+ }
106
+ };
107
+
108
+ // Helper to load and execute a phase
109
+ const executePhase = async (phaseName) => {
110
+ const phase = PHASES[phaseName];
111
+
112
+ // Download all modules in parallel
113
+ await Promise.allSettled(phase.modules.map((name) => moduleMap[name]()));
114
+
115
+ // Execute based on parallel setting
116
+ if (phase.parallel) {
117
+ const initPromises = phase.modules.map((name) => loadModule(name));
118
+ await Promise.allSettled(initPromises);
119
+ } else {
120
+ // Sequential execution
121
+ for (const name of phase.modules) {
122
+ await loadModule(name);
123
+ }
124
+ }
125
+ };
126
+
127
+ // Trigger DOM ready event
128
+ const triggerDomReady = () => {
129
+ const event = new CustomEvent('hsmain:dom-ready');
130
+ window.dispatchEvent(event);
131
+ };
132
+
133
+ // Wait for Webflow IX3 to be ready
134
+ const waitForIx3 = async () => {
135
+ return new Promise((resolve) => {
136
+ const checkIx3 = () => {
137
+ const htmlElement = document.documentElement;
138
+ const hasIx3 = htmlElement.classList.contains('w-mod-ix3');
139
+
140
+ if (hasIx3) {
141
+ resolve(true);
142
+ } else {
143
+ setTimeout(checkIx3, 50);
144
+ }
145
+ };
146
+ checkIx3();
147
+ });
148
+ };
149
+
150
+ // Initialize all modules in 3 phases
151
+ const initializeInPhases = async () => {
152
+ // Phase 1: Normalize (critical DOM fixes)
153
+ await executePhase('normalize');
154
+
155
+ // Phase 2: Structure (DOM modifications)
156
+ await executePhase('structure');
157
+
158
+ // Wait for IX3 if needed for Phase 3
159
+ if (PHASES.default.waitForIx3) {
160
+ await waitForIx3();
161
+ }
162
+
163
+ // Phase 3: Default + Transition (parallel with 50ms transition delay)
164
+ await executePhase('default');
165
+
166
+ // Trigger DOM ready after everything initialized
167
+ triggerDomReady();
168
+ };
169
+
170
+ const readyCallbacks = [];
171
+
172
+ window[API_NAME] = {
173
+ scripts,
174
+ settings,
175
+ modules: {},
176
+ process: new Set(),
177
+ load: loadModule,
178
+ loaded: false,
179
+ push(...items) {
180
+ for (const [moduleName, callback] of items) {
181
+ if (typeof callback === 'function') {
182
+ this.modules[moduleName]?.loading?.then(callback);
183
+ } else {
184
+ this.load(moduleName);
185
+ }
186
+ }
187
+ },
188
+ destroy() {
189
+ for (const moduleName in this.modules) {
190
+ this.modules[moduleName]?.destroy?.();
191
+ }
192
+ },
193
+ async reinitialize() {
194
+ // Complete teardown
195
+ this.loaded = false;
196
+ this.destroy();
197
+ this.modules = {};
198
+ this.process = new Set();
199
+
200
+ // Re-scan for modules including in w-embeds
201
+ await this.scanForEmbedModules();
202
+
203
+ // Use same phased initialization flow
204
+ await initializeInPhases();
205
+
206
+ // Wait for IX3 to be ready before marking as loaded
207
+ await waitForIx3();
208
+ this.loaded = true;
209
+ },
210
+ async scanForEmbedModules() {
211
+ const embeds = document.querySelectorAll('.w-embed');
212
+ embeds.forEach((embed) => {
213
+ const scripts = embed.querySelectorAll('script[data-hs-module]');
214
+ scripts.forEach((script) => {
215
+ // Extract module info from data attributes
216
+ const moduleType = script.getAttribute('data-hs-module-type');
217
+ const moduleName = script.getAttribute('data-hs-module');
218
+
219
+ if (moduleType && moduleName) {
220
+ const moduleKey = `data-hs-${moduleType}-${moduleName}`;
221
+ if (moduleMap[moduleKey]) {
222
+ this.load(moduleKey);
223
+ }
224
+ }
225
+ });
226
+ });
227
+ },
228
+ afterReady(callback) {
229
+ if (typeof callback === 'function') {
230
+ if (this.loaded) {
231
+ callback();
232
+ } else {
233
+ readyCallbacks.push(callback);
234
+ }
235
+ }
236
+ },
237
+ status(moduleName) {
238
+ if (moduleName) {
239
+ return {
240
+ loaded: !!this.modules[moduleName],
241
+ loading: this.process.has(moduleName),
242
+ };
243
+ }
244
+ return {
245
+ loaded: Object.keys(this.modules),
246
+ loading: [...this.process],
247
+ phases: PHASES,
248
+ };
249
+ },
250
+ };
251
+
252
+ const finalize = async () => {
253
+ window[API_NAME].push(...queuedModules);
254
+
255
+ // Run phased initialization
256
+ await initializeInPhases();
257
+
258
+ // Wait for IX3 to be ready before firing callbacks
259
+ await waitForIx3();
260
+
261
+ window[API_NAME].loaded = true;
262
+ readyCallbacks.forEach((callback) => {
263
+ try {
264
+ callback();
265
+ } catch {
266
+ // Silent error handling
267
+ }
268
+ });
269
+ readyCallbacks.length = 0; // Clear array after execution
270
+ };
271
+
272
+ finalize().catch(() => {});
273
+ };
274
+
275
+ initializeHsMain();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hortonstudio/main",
3
- "version": "1.9.11",
3
+ "version": "1.9.20",
4
4
  "description": "Animation and utility library for client websites",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -13,6 +13,23 @@
13
13
  "author": "Horton Studio",
14
14
  "license": "ISC",
15
15
  "scripts": {
16
- "test": "echo \"Error: no test specified\" && exit 1"
16
+ "dev": "vite --host",
17
+ "build": "vite build",
18
+ "preview": "vite preview",
19
+ "type-check": "tsc --noEmit",
20
+ "lint": "eslint src/**/*.ts index.ts",
21
+ "format": "prettier --write \"**/*.{ts,js,json,md}\"",
22
+ "build:strict": "tsc --noEmit && vite build"
23
+ },
24
+ "devDependencies": {
25
+ "@typescript-eslint/eslint-plugin": "^8.46.4",
26
+ "@typescript-eslint/parser": "^8.46.4",
27
+ "eslint": "^9.39.1",
28
+ "eslint-config-prettier": "^10.1.8",
29
+ "prettier": "^3.6.2",
30
+ "terser": "^5.44.1",
31
+ "typescript": "^5.9.3",
32
+ "vite": "^7.2.2",
33
+ "vite-plugin-compression": "^0.5.1"
17
34
  }
18
35
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Bootstrap stub for @hortonstudio/main
3
+ * Initializes the API before the main bundle loads
4
+ * This prevents race conditions and allows users to queue module loads
5
+ */
6
+ (function () {
7
+ 'use strict';
8
+
9
+ // Get API name from config (defaults to 'hsmain')
10
+ const API_NAME = 'hsmain';
11
+
12
+ // Initialize API if not already present
13
+ if (!window[API_NAME]) {
14
+ window[API_NAME] = [];
15
+ }
16
+ })();
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Animations Orchestrator
3
+ *
4
+ * Conditionally loads animation modules based on DOM presence.
5
+ * Uses dynamic imports for tree-shaking - only bundles animations that exist on the page.
6
+ *
7
+ * Architecture:
8
+ * - Runs synchronously with default phase (Phase 3)
9
+ * - Scans DOM for animation attributes
10
+ * - Dynamically imports only the animations found
11
+ * - Exposes rescan() for SPA page transitions
12
+ *
13
+ * @version 2.0.0
14
+ */
15
+
16
+ import config from '@config';
17
+
18
+ export async function init() {
19
+ const activeModules = new Map();
20
+ const CONFIG_ROOT = 'animations';
21
+ const moduleConfig = config[CONFIG_ROOT];
22
+
23
+ /**
24
+ * Scan DOM and initialize animations that exist on the page
25
+ */
26
+ async function scanAndInitialize() {
27
+ const results = [];
28
+
29
+ // Counter Animation
30
+ if (document.querySelector('[data-hs-anim="counter"]')) {
31
+ if (!activeModules.has('counter')) {
32
+ try {
33
+ const { init } = await import('./functions/counter/counter.ts');
34
+ const instance = await init(moduleConfig.counter);
35
+ activeModules.set('counter', instance);
36
+ results.push({ name: 'counter', status: 'loaded' });
37
+ } catch (error) {
38
+ console.error('[animations] Failed to load counter:', error);
39
+ results.push({ name: 'counter', status: 'failed' });
40
+ }
41
+ } else {
42
+ results.push({ name: 'counter', status: 'already-loaded' });
43
+ }
44
+ }
45
+
46
+ // Add more animation checks here as you create them:
47
+ // if (document.querySelector('[data-hs-hamburger="squeeze"]')) { ... }
48
+ // if (document.querySelector('[data-hs-scroll-reveal]')) { ... }
49
+
50
+ return results;
51
+ }
52
+
53
+ // Initial scan on page load
54
+ const initialResults = await scanAndInitialize();
55
+
56
+ // Log what was loaded
57
+ const loaded = initialResults.filter((r) => r.status === 'loaded');
58
+ if (loaded.length > 0) {
59
+ console.log(
60
+ `[animations] Loaded ${loaded.length} animation(s):`,
61
+ loaded.map((r) => r.name).join(', ')
62
+ );
63
+ }
64
+
65
+ return {
66
+ result: `animations initialized (${loaded.length} active)`,
67
+ rescan: async () => {
68
+ // Rescan for new animations after SPA navigation
69
+ const results = await scanAndInitialize();
70
+ const newlyLoaded = results.filter((r) => r.status === 'loaded');
71
+ if (newlyLoaded.length > 0) {
72
+ console.log(
73
+ `[animations] Rescanned and loaded ${newlyLoaded.length} new animation(s):`,
74
+ newlyLoaded.map((r) => r.name).join(', ')
75
+ );
76
+ }
77
+ return results;
78
+ },
79
+ destroy: () => {
80
+ // Destroy all active animation modules
81
+ activeModules.forEach((instance, name) => {
82
+ try {
83
+ if (instance?.destroy) {
84
+ instance.destroy();
85
+ }
86
+ } catch (error) {
87
+ console.error(`[animations] Error destroying ${name}:`, error);
88
+ }
89
+ });
90
+ activeModules.clear();
91
+ },
92
+ };
93
+ }