@hortonstudio/main 1.5.1 → 1.6.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.
@@ -1,6 +1,5 @@
1
1
  export function init() {
2
- // General accessibility features can be added here
3
- // Stats accessibility has been moved to counter.js
2
+
4
3
 
5
4
  function setupGeneralAccessibility() {
6
5
  setupListAccessibility();
@@ -9,6 +8,8 @@ export function init() {
9
8
  setupConvertToSpan();
10
9
  setupYearReplacement();
11
10
  setupPreventDefault();
11
+ setupRichTextAccessibility();
12
+ setupSummaryAccessibility();
12
13
  }
13
14
 
14
15
  function setupListAccessibility() {
@@ -17,12 +18,10 @@ export function init() {
17
18
 
18
19
  listElements.forEach(element => {
19
20
  element.setAttribute('role', 'list');
20
- element.removeAttribute('data-hs-a11y');
21
21
  });
22
22
 
23
23
  listItemElements.forEach(element => {
24
24
  element.setAttribute('role', 'listitem');
25
- element.removeAttribute('data-hs-a11y');
26
25
  });
27
26
  }
28
27
 
@@ -82,7 +81,6 @@ export function init() {
82
81
  container.parentNode.replaceChild(newDiv, container);
83
82
  } else {
84
83
  // Just remove the attribute if container isn't a semantic list
85
- container.removeAttribute('data-hs-a11y');
86
84
  }
87
85
  });
88
86
  }
@@ -108,11 +106,6 @@ export function init() {
108
106
  contentWrapper.setAttribute('role', 'region');
109
107
  contentWrapper.setAttribute('aria-labelledby', buttonId);
110
108
 
111
- if (contentWrapper.style.height !== '0px') {
112
- button.setAttribute('aria-expanded', 'true');
113
- contentWrapper.setAttribute('aria-hidden', 'false');
114
- }
115
-
116
109
  function toggleFAQ() {
117
110
  const isOpen = button.getAttribute('aria-expanded') === 'true';
118
111
 
@@ -122,7 +115,6 @@ export function init() {
122
115
 
123
116
  button.addEventListener('click', toggleFAQ);
124
117
 
125
- container.removeAttribute('data-hs-a11y');
126
118
  });
127
119
  }
128
120
 
@@ -184,7 +176,6 @@ export function init() {
184
176
  container.parentNode.replaceChild(newSpan, container);
185
177
  } else {
186
178
  // Just remove the attribute if container shouldn't be converted
187
- container.removeAttribute('data-hs-a11y');
188
179
  }
189
180
  });
190
181
  }
@@ -254,7 +245,187 @@ export function init() {
254
245
  }
255
246
  }
256
247
 
257
- element.removeAttribute('data-hs-a11y');
248
+ });
249
+ }
250
+
251
+ function setupSummaryAccessibility() {
252
+ const summaryContainers = document.querySelectorAll('[data-hs-a11y="summary-wrap"]');
253
+
254
+
255
+ summaryContainers.forEach((container, index) => {
256
+
257
+ const button = container.querySelector('[data-hs-a11y="summary-btn"]');
258
+ const contentWrapper = container.querySelector('[data-hs-a11y="summary-content"]');
259
+
260
+ if (!button || !contentWrapper) {
261
+ return;
262
+ }
263
+
264
+
265
+ const buttonId = `summary-button-${index}`;
266
+ const contentId = `summary-content-${index}`;
267
+
268
+ // Get original button text from first text node only
269
+ const walker = document.createTreeWalker(
270
+ button,
271
+ NodeFilter.SHOW_TEXT,
272
+ null,
273
+ false
274
+ );
275
+
276
+ let firstTextNode = walker.nextNode();
277
+ while (firstTextNode && !firstTextNode.textContent.trim()) {
278
+ firstTextNode = walker.nextNode();
279
+ }
280
+
281
+ const originalButtonText = firstTextNode ? firstTextNode.textContent.trim() : button.textContent.trim();
282
+
283
+ // Function to update all text nodes in button
284
+ function updateButtonText(newText) {
285
+ // Find all text nodes and update them
286
+ const walker = document.createTreeWalker(
287
+ button,
288
+ NodeFilter.SHOW_TEXT,
289
+ null,
290
+ false
291
+ );
292
+
293
+ const textNodes = [];
294
+ let node;
295
+ while (node = walker.nextNode()) {
296
+ if (node.textContent.trim()) {
297
+ textNodes.push(node);
298
+ }
299
+ }
300
+
301
+ textNodes.forEach(textNode => {
302
+ textNode.textContent = newText;
303
+ });
304
+ }
305
+
306
+ button.setAttribute('id', buttonId);
307
+ button.setAttribute('aria-expanded', 'false');
308
+ button.setAttribute('aria-controls', contentId);
309
+ button.setAttribute('aria-label', 'View Summary');
310
+
311
+ contentWrapper.setAttribute('id', contentId);
312
+ contentWrapper.setAttribute('aria-hidden', 'true');
313
+ contentWrapper.setAttribute('role', 'region');
314
+ contentWrapper.setAttribute('aria-labelledby', buttonId);
315
+
316
+ // Summary is closed by default - no need to check initial state
317
+
318
+ function toggleSummary() {
319
+ const isOpen = button.getAttribute('aria-expanded') === 'true';
320
+
321
+ if (isOpen) {
322
+ // Closing
323
+ button.setAttribute('aria-expanded', 'false');
324
+ button.setAttribute('aria-label', 'View Summary');
325
+ updateButtonText(originalButtonText);
326
+ contentWrapper.setAttribute('aria-hidden', 'true');
327
+ } else {
328
+ // Opening
329
+ button.setAttribute('aria-expanded', 'true');
330
+ button.setAttribute('aria-label', 'Close Summary');
331
+ updateButtonText('Close');
332
+ contentWrapper.setAttribute('aria-hidden', 'false');
333
+ }
334
+
335
+ }
336
+
337
+ button.addEventListener('click', toggleSummary);
338
+
339
+ });
340
+ }
341
+
342
+ function setupRichTextAccessibility() {
343
+ const contentAreas = document.querySelectorAll('[data-hs-a11y="rich-content"]');
344
+ const tocLists = document.querySelectorAll('[data-hs-a11y="rich-toc"]');
345
+
346
+
347
+ contentAreas.forEach((contentArea) => {
348
+
349
+ // Since there's only 1 content area and 1 TOC list per page, use the first TOC list
350
+ const tocList = tocLists[0];
351
+
352
+ if (!tocList) {
353
+ return;
354
+ }
355
+
356
+ if (tocList.children.length === 0) {
357
+ return;
358
+ }
359
+
360
+
361
+ const template = tocList.children[0];
362
+ tocList.innerHTML = "";
363
+ const h2Headings = contentArea.querySelectorAll("h2");
364
+
365
+
366
+ // Create sections and wrap content
367
+ h2Headings.forEach((heading) => {
368
+ const sectionId = heading.textContent
369
+ .toLowerCase()
370
+ .replace(/[^a-z0-9]+/g, "-")
371
+ .replace(/(^-|-$)/g, "");
372
+
373
+ const section = document.createElement("div");
374
+ section.id = sectionId;
375
+ heading.parentNode.insertBefore(section, heading);
376
+ section.appendChild(heading);
377
+ let nextElement = section.nextElementSibling;
378
+ while (nextElement && nextElement.tagName !== "H2") {
379
+ const elementToMove = nextElement;
380
+ nextElement = nextElement.nextElementSibling;
381
+ section.appendChild(elementToMove);
382
+ }
383
+ });
384
+
385
+ // Create TOC entries
386
+ h2Headings.forEach((heading, index) => {
387
+ const tocItem = template.cloneNode(true);
388
+ const link = tocItem.querySelector("a");
389
+ const sectionId = heading.parentElement.id;
390
+ link.href = "#" + sectionId;
391
+
392
+
393
+ // Bold numbered text
394
+ const number = document.createElement("strong");
395
+ number.textContent = index + 1 + ". ";
396
+
397
+ // Clear the link and add the number + text
398
+ link.innerHTML = "";
399
+ link.appendChild(number);
400
+ link.appendChild(document.createTextNode(heading.textContent));
401
+
402
+ // Add click handler for smooth scrolling
403
+ link.addEventListener("click", (e) => {
404
+ e.preventDefault();
405
+
406
+ const targetSection = document.getElementById(sectionId);
407
+ if (targetSection) {
408
+ targetSection.scrollIntoView({ behavior: "smooth" });
409
+ // Focus on the section for accessibility (will only show outline for keyboard users due to CSS)
410
+ setTimeout(() => {
411
+ targetSection.focus();
412
+ }, 100);
413
+ }
414
+ });
415
+
416
+ // Ensure sections are focusable for keyboard users but use CSS to control focus visibility
417
+ const targetSection = document.getElementById(sectionId);
418
+ if (targetSection) {
419
+ targetSection.setAttribute("tabindex", "-1");
420
+ // Use focus-visible to only show outline for keyboard focus
421
+ targetSection.style.outline = "none";
422
+ targetSection.style.setProperty("outline", "none", "important");
423
+ }
424
+
425
+ // Add item to the TOC list
426
+ tocList.appendChild(tocItem);
427
+ });
428
+
258
429
  });
259
430
  }
260
431
 
@@ -252,15 +252,6 @@ function setupDynamicDropdowns() {
252
252
  }
253
253
  });
254
254
 
255
- toggle.addEventListener("click", function(e) {
256
- if (e.isTrusted) {
257
- // This is a real user click - prevent it
258
- e.preventDefault();
259
- e.stopPropagation();
260
- return false;
261
- }
262
- // Programmatic clicks (from hover/keyboard) proceed normally
263
- });
264
255
 
265
256
  document.addEventListener("click", function (e) {
266
257
  if (!wrapper.contains(e.target) && isOpen) {
@@ -0,0 +1,118 @@
1
+ // Page Transition Module
2
+ const API_NAME = "hsmain";
3
+ let initialized = false;
4
+
5
+ export async function init() {
6
+ console.log('[Transition] Module init called');
7
+
8
+ // Wait for Webflow to be ready before initializing transitions
9
+ window[API_NAME].afterWebflowReady(() => {
10
+ console.log('[Transition] afterWebflowReady callback fired');
11
+ setTimeout(() => {
12
+ console.log('[Transition] Attempting to initialize transitions');
13
+ initTransitions();
14
+ initialized = true;
15
+ }, 50);
16
+ });
17
+
18
+ return { result: "anim-transition initialized" };
19
+ }
20
+
21
+ function initTransitions() {
22
+ console.log('[Transition] initTransitions called');
23
+ const transitionTrigger = document.querySelector(".transition-trigger");
24
+ const transitionElement = document.querySelector(".transition");
25
+ console.log('[Transition] transitionTrigger found:', transitionTrigger);
26
+ console.log('[Transition] transitionElement found:', transitionElement);
27
+
28
+ let excludedClass = "no-transition";
29
+
30
+ // Page Load - Trigger entrance animation with optional delay
31
+ if (transitionTrigger) {
32
+ // Check if this is the first page load of the session
33
+ const isFirstLoad = !sessionStorage.getItem('transition-loaded');
34
+ const delayAttr = transitionElement?.getAttribute('data-hs-delay');
35
+ const delaySeconds = delayAttr ? parseFloat(delayAttr) : 0;
36
+
37
+ console.log('[Transition] First load:', isFirstLoad);
38
+ console.log('[Transition] Delay attribute:', delayAttr);
39
+ console.log('[Transition] Delay seconds:', delaySeconds);
40
+
41
+ const triggerAnimation = () => {
42
+ console.log('[Transition] Triggering page load transition');
43
+ Webflow.push(function () {
44
+ transitionTrigger.click();
45
+ });
46
+ };
47
+
48
+ if (isFirstLoad && delaySeconds > 0) {
49
+ console.log(`[Transition] Delaying first load by ${delaySeconds} seconds`);
50
+ setTimeout(triggerAnimation, delaySeconds * 1000);
51
+ sessionStorage.setItem('transition-loaded', 'true');
52
+ } else {
53
+ triggerAnimation();
54
+ if (isFirstLoad) {
55
+ sessionStorage.setItem('transition-loaded', 'true');
56
+ }
57
+ }
58
+ }
59
+
60
+ // Monitor for transition completion
61
+ function waitForTransitionComplete(callback) {
62
+ if (!transitionElement) return;
63
+
64
+ const checkComplete = () => {
65
+ if (transitionElement.classList.contains('transition-done')) {
66
+ console.log('[Transition] Animation complete detected');
67
+ callback();
68
+ } else {
69
+ setTimeout(checkComplete, 50);
70
+ }
71
+ };
72
+ checkComplete();
73
+ }
74
+
75
+ // On Link Click
76
+ console.log('[Transition] Setting up click handler');
77
+ document.addEventListener("click", function (e) {
78
+ const link = e.target.closest("a");
79
+
80
+ if (
81
+ link &&
82
+ link.hostname === window.location.hostname &&
83
+ link.getAttribute("href") &&
84
+ link.getAttribute("href").indexOf("#") === -1 &&
85
+ !link.classList.contains(excludedClass) &&
86
+ link.getAttribute("target") !== "_blank" &&
87
+ transitionTrigger
88
+ ) {
89
+ console.log('[Transition] Triggering exit transition to:', link.getAttribute("href"));
90
+ e.preventDefault();
91
+
92
+ let transitionURL = link.getAttribute("href");
93
+
94
+ // Trigger exit animation
95
+ transitionTrigger.click();
96
+
97
+ // Wait for animation to complete before navigating
98
+ waitForTransitionComplete(() => {
99
+ console.log('[Transition] Navigating to:', transitionURL);
100
+ window.location = transitionURL;
101
+ });
102
+ }
103
+ });
104
+
105
+ // On Back Button Tap
106
+ window.onpageshow = function (event) {
107
+ if (event.persisted) {
108
+ window.location.reload();
109
+ }
110
+ };
111
+
112
+ // Hide Transition on Window Width Resize
113
+ window.addEventListener("resize", function () {
114
+ if (transitionElement) {
115
+ transitionElement.style.display = "none";
116
+ }
117
+ });
118
+ }
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // Version:1.5.1
1
+ // Version:1.6.0
2
2
 
3
3
  const API_NAME = "hsmain";
4
4
 
@@ -13,25 +13,19 @@ const initializeHsMain = async () => {
13
13
 
14
14
  const queuedModules = Array.isArray(window[API_NAME]) ? window[API_NAME] : [];
15
15
 
16
- const animationModules = {
17
- "data-hs-anim-text": true,
18
- "data-hs-anim-hero": true,
19
- "data-hs-anim-transition": true,
20
- };
16
+ const animationModules = {};
21
17
 
22
18
  const utilityModules = {
23
- "data-hs-util-toc": true,
24
- "data-hs-util-progress": true,
25
19
  "data-hs-util-ba": true,
26
20
  };
27
21
 
28
22
  const autoInitModules = {
29
23
  "smooth-scroll": true,
30
- modal: true,
31
24
  navbar: true,
32
25
  accessibility: true,
33
26
  counter: true,
34
27
  form: true,
28
+ transition: true,
35
29
  };
36
30
 
37
31
  const allDataAttributes = { ...animationModules, ...utilityModules };
@@ -43,14 +37,9 @@ const initializeHsMain = async () => {
43
37
  });
44
38
 
45
39
  const moduleMap = {
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"),
40
+ transition: () => import("./autoInit/transition.js"),
51
41
  "data-hs-util-ba": () => import("./utils/before-after.js"),
52
42
  "smooth-scroll": () => import("./autoInit/smooth-scroll.js"),
53
- modal: () => import("./autoInit/modal.js"),
54
43
  navbar: () => import("./autoInit/navbar.js"),
55
44
  accessibility: () => import("./autoInit/accessibility.js"),
56
45
  counter: () => import("./autoInit/counter.js"),
@@ -255,13 +244,7 @@ const initializeHsMain = async () => {
255
244
  loadModule(moduleName);
256
245
  }
257
246
 
258
- if (
259
- !scripts.some((script) => script.hasAttribute("data-hs-anim-transition"))
260
- ) {
261
- document.querySelectorAll(".transition").forEach((element) => {
262
- element.style.display = "none";
263
- });
264
- }
247
+ // Transition is now auto-loaded, so no need to hide transition elements
265
248
  };
266
249
 
267
250
  document.querySelectorAll(".w-richtext").forEach((richtext) => {
@@ -274,6 +257,14 @@ const initializeHsMain = async () => {
274
257
  processModules();
275
258
  await waitForWebflow();
276
259
 
260
+ // Small delay to ensure all scripts are fully initialized
261
+ await new Promise(resolve => setTimeout(resolve, 10));
262
+
263
+ // Force Webflow to reinitialize (not redraw) - rescans DOM and rebinds interactions
264
+ if (window.Webflow && typeof window.Webflow.ready === 'function') {
265
+ window.Webflow.ready();
266
+ }
267
+
277
268
  window[API_NAME].loaded = true;
278
269
  readyCallbacks.forEach((callback) => {
279
270
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hortonstudio/main",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "Animation and utility library for client websites",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -1,158 +0,0 @@
1
- # Before-After Component Attributes Guide
2
-
3
- This is a temporary reference for the `data-hs-ba` attributes used in the before-after utility.
4
-
5
- ## Required Structure
6
-
7
- ### Main Container
8
- ```html
9
- <div data-hs-ba="wrapper">
10
- <!-- All before-after content goes here -->
11
- </div>
12
- ```
13
-
14
- ### Individual Slide/Item
15
- Each comparison should be a direct child of the wrapper:
16
- ```html
17
- <div data-hs-ba="wrapper">
18
- <div class="slide-1">
19
- <!-- First comparison -->
20
- </div>
21
- <div class="slide-2">
22
- <!-- Second comparison -->
23
- </div>
24
- </div>
25
- ```
26
-
27
- ## Image Elements
28
-
29
- ### Image Wrapper Container
30
- ```html
31
- <div data-hs-ba="image-wrapper">
32
- <!-- Before and after images go inside here -->
33
- </div>
34
- ```
35
-
36
- ### Before Image
37
- ```html
38
- <img src="before.jpg" data-hs-ba="image-before" alt="Before image">
39
- ```
40
-
41
- ### After Image
42
- ```html
43
- <img src="after.jpg" data-hs-ba="image-after" alt="After image">
44
- ```
45
-
46
- ## Interactive Controls
47
-
48
- ### Mode Buttons
49
- ```html
50
- <!-- Show only before image -->
51
- <button data-hs-ba="mode-before">Before</button>
52
-
53
- <!-- Show split view with slider -->
54
- <button data-hs-ba="mode-split">Split</button>
55
-
56
- <!-- Show only after image -->
57
- <button data-hs-ba="mode-after">After</button>
58
- ```
59
-
60
- ### Navigation Arrows
61
- ```html
62
- <!-- Previous slide -->
63
- <button data-hs-ba="left">←</button>
64
-
65
- <!-- Next slide -->
66
- <button data-hs-ba="right">→</button>
67
- ```
68
-
69
- ### Slider Handle (for split mode)
70
- ```html
71
- <div data-hs-ba="slider"></div>
72
- ```
73
-
74
- ## Pagination
75
-
76
- ### Pagination Container with Template
77
- ```html
78
- <div data-hs-ba="pagination">
79
- <!-- Single template dot - system will clone this for each slide -->
80
- <div class="ba_1_pagination_dot"></div>
81
- </div>
82
- ```
83
-
84
- **How it works**:
85
- - Place ONE template dot inside the pagination container
86
- - The system will automatically clone this template for each slide
87
- - Each generated dot gets proper accessibility attributes
88
- - The `is-active` class is automatically managed
89
-
90
- **Generated Structure** (example with 3 slides):
91
- ```html
92
- <div data-hs-ba="pagination">
93
- <div class="ba_1_pagination_dot is-active" role="button" tabindex="0" aria-label="Go to slide 1" aria-current="true"></div>
94
- <div class="ba_1_pagination_dot" role="button" tabindex="0" aria-label="Go to slide 2" aria-current="false"></div>
95
- <div class="ba_1_pagination_dot" role="button" tabindex="0" aria-label="Go to slide 3" aria-current="false"></div>
96
- </div>
97
- ```
98
-
99
- ## Complete Example
100
-
101
- ```html
102
- <div data-hs-ba="wrapper">
103
- <!-- Slide 1 -->
104
- <div class="ba-slide">
105
- <div data-hs-ba="image-wrapper" class="ba-image_wrap">
106
- <img src="before1.jpg" data-hs-ba="image-before" alt="Before">
107
- <img src="after1.jpg" data-hs-ba="image-after" alt="After">
108
- <div data-hs-ba="slider"></div>
109
- </div>
110
-
111
- <!-- Mode controls -->
112
- <div class="controls">
113
- <button data-hs-ba="mode-before">Before</button>
114
- <button data-hs-ba="mode-split" class="is-active">Split</button>
115
- <button data-hs-ba="mode-after">After</button>
116
- </div>
117
-
118
- <!-- Navigation -->
119
- <button data-hs-ba="left">Previous</button>
120
- <button data-hs-ba="right">Next</button>
121
-
122
- <!-- Pagination -->
123
- <div data-hs-ba="pagination">
124
- <div class="ba_1_pagination_dot"></div> <!-- Template dot -->
125
- </div>
126
- </div>
127
-
128
- <!-- Slide 2 -->
129
- <div class="ba-slide">
130
- <!-- Similar structure... -->
131
- </div>
132
-
133
- <!-- Slide 3 -->
134
- <div class="ba-slide">
135
- <!-- Similar structure... -->
136
- </div>
137
- </div>
138
- ```
139
-
140
- ## Default Behavior
141
-
142
- - **Default mode**: Split view at 50% position
143
- - **Active states**: Elements with `is-active` class are highlighted
144
- - **Keyboard support**: Arrow keys navigate slides or move slider
145
- - **Touch support**: Dragging works on the slider handle
146
- - **Accessibility**: Full ARIA support with proper labels
147
-
148
- ## Notes
149
-
150
- - Each slide can have its own controls (mode buttons, navigation, pagination)
151
- - The wrapper image container should have `data-hs-ba="image-wrapper"` for proper click handling
152
- - Pagination dots are clicked to jump to specific slides
153
- - Mode buttons toggle between before, split, and after views
154
- - The slider only appears in split mode
155
-
156
- ---
157
-
158
- *This is a temporary file for development reference. Delete when no longer needed.*
@@ -1,82 +0,0 @@
1
- // Page Transition Module
2
- const API_NAME = "hsmain";
3
- export async function init() {
4
- // Register the transition logic to run after library is ready
5
- window[API_NAME].afterReady(() => {
6
- // Only run if jQuery is available
7
- if (typeof $ !== "undefined") {
8
- initTransitions();
9
- }
10
- });
11
-
12
- return { result: "anim-transition initialized" };
13
- }
14
-
15
- function initTransitions() {
16
- let transitionTrigger = $(".transition-trigger");
17
- let introDurationMS = 800;
18
- let exitDurationMS = 400;
19
- let excludedClass = "no-transition";
20
-
21
- // On Page Load
22
- if (transitionTrigger.length > 0) {
23
- function triggerTransition() {
24
- if (window.Webflow && window.Webflow.push) {
25
- Webflow.push(function () {
26
- transitionTrigger.click();
27
- });
28
- } else {
29
- // Non-Webflow initialization
30
- setTimeout(() => {
31
- transitionTrigger.click();
32
- }, 100);
33
- }
34
- $("body").addClass("no-scroll-transition");
35
- setTimeout(() => {
36
- $("body").removeClass("no-scroll-transition");
37
- }, introDurationMS);
38
- }
39
-
40
- // Wait for full page load (images, fonts, etc.) for Safari
41
- if (document.readyState === "complete") {
42
- triggerTransition();
43
- } else {
44
- window.addEventListener("load", triggerTransition, { once: true });
45
- }
46
- }
47
-
48
- // On Link Click
49
- $("a").on("click", function (e) {
50
- if (
51
- $(this).prop("hostname") == window.location.host &&
52
- $(this).attr("href").indexOf("#") === -1 &&
53
- !$(this).hasClass(excludedClass) &&
54
- $(this).attr("target") !== "_blank" &&
55
- transitionTrigger.length > 0
56
- ) {
57
- e.preventDefault();
58
- $("body").addClass("no-scroll-transition");
59
- let transitionURL = $(this).attr("href");
60
- transitionTrigger.click();
61
- setTimeout(function () {
62
- window.location = transitionURL;
63
- }, exitDurationMS);
64
- }
65
- });
66
-
67
- // On Back Button Tap
68
- window.onpageshow = function (event) {
69
- if (event.persisted) {
70
- window.location.reload();
71
- }
72
- };
73
-
74
- // Hide Transition on Window Width Resize
75
- setTimeout(() => {
76
- $(window).on("resize", function () {
77
- setTimeout(() => {
78
- $(".transition").css("display", "none");
79
- }, 50);
80
- });
81
- }, introDurationMS);
82
- }
package/styles.css DELETED
@@ -1,24 +0,0 @@
1
- /* transition */
2
- body .transition {display: block}
3
- .w-editor .transition {display: none;}
4
- .no-scroll-transition {overflow: hidden; position: relative;}
5
-
6
- /* splittext */
7
- .line-mask, .word-mask, .char-mask {
8
- padding-bottom: .1em;
9
- margin-bottom: -.1em;
10
- padding-inline: .1em;
11
- margin-inline: -.1em;
12
- will-change: transform, opacity;
13
- }
14
-
15
-
16
- /* TOC focus handling - only show focus outline for keyboard navigation */
17
- [data-hs-toc] div[id]:focus:not(:focus-visible) {
18
- outline: none !important;
19
- }
20
-
21
- [data-hs-toc] div[id]:focus-visible {
22
- outline: 2px solid #007bff !important;
23
- outline-offset: 2px;
24
- }
package/test.json DELETED
File without changes
@@ -1,34 +0,0 @@
1
- export async function init() {
2
- const progressBar = document.querySelector('[data-hs-progress="bar"]');
3
- const progressContent = document.querySelector(
4
- '[data-hs-progress="wrapper"]',
5
- );
6
-
7
- // Check if elements exist before using them
8
- if (!progressBar || !progressContent) {
9
- return {
10
- result: "util-scroll-progress initialized",
11
- };
12
- }
13
-
14
- // Check if gsap exists before using it
15
- if (typeof gsap !== "undefined") {
16
- gsap.set(progressBar, { width: "0%" });
17
-
18
- // Create the scroll progress animation
19
- gsap.to(progressBar, {
20
- width: "100%",
21
- ease: "none",
22
- scrollTrigger: {
23
- trigger: progressContent,
24
- start: "top bottom",
25
- end: "bottom bottom",
26
- scrub: true,
27
- },
28
- });
29
- }
30
-
31
- return {
32
- result: "util-scroll-progress initialized",
33
- };
34
- }
package/utils/toc.js DELETED
@@ -1,84 +0,0 @@
1
- export async function init() {
2
- const contentArea = document.querySelector('[data-hs-toc="content"]');
3
- const tocList = document.querySelector('[data-hs-toc="list"]');
4
-
5
- // Check main elements
6
- if (!contentArea) {
7
- return;
8
- }
9
- if (!tocList) {
10
- return;
11
- }
12
- if (tocList.children.length === 0) {
13
- return;
14
- }
15
-
16
- const template = tocList.children[0];
17
- tocList.innerHTML = "";
18
- const h2Headings = contentArea.querySelectorAll("h2");
19
-
20
- // Create sections and wrap content
21
- h2Headings.forEach((heading, index) => {
22
- const sectionId = heading.textContent
23
- .toLowerCase()
24
- .replace(/[^a-z0-9]+/g, "-")
25
- .replace(/(^-|-$)/g, "");
26
- const section = document.createElement("div");
27
- section.id = sectionId;
28
- heading.parentNode.insertBefore(section, heading);
29
- section.appendChild(heading);
30
- let nextElement = section.nextElementSibling;
31
- while (nextElement && nextElement.tagName !== "H2") {
32
- const elementToMove = nextElement;
33
- nextElement = nextElement.nextElementSibling;
34
- section.appendChild(elementToMove);
35
- }
36
- });
37
-
38
- // Create TOC entries
39
- h2Headings.forEach((heading, index) => {
40
- const tocItem = template.cloneNode(true);
41
- const link = tocItem.querySelector("a");
42
- const sectionId = heading.parentElement.id;
43
- link.href = "#" + sectionId;
44
-
45
- // Bold numbered text
46
- const number = document.createElement("strong");
47
- number.textContent = index + 1 + ". ";
48
-
49
- // Clear the link and add the number + text
50
- link.innerHTML = "";
51
- link.appendChild(number);
52
- link.appendChild(document.createTextNode(heading.textContent));
53
-
54
- // Add click handler for smooth scrolling
55
- link.addEventListener("click", (e) => {
56
- e.preventDefault();
57
-
58
- const targetSection = document.getElementById(sectionId);
59
- if (targetSection) {
60
- targetSection.scrollIntoView({ behavior: "smooth" });
61
- // Focus on the section for accessibility (will only show outline for keyboard users due to CSS)
62
- setTimeout(() => {
63
- targetSection.focus();
64
- }, 100);
65
- }
66
- });
67
-
68
- // Ensure sections are focusable for keyboard users but use CSS to control focus visibility
69
- const targetSection = document.getElementById(sectionId);
70
- if (targetSection) {
71
- targetSection.setAttribute("tabindex", "-1");
72
- // Use focus-visible to only show outline for keyboard focus
73
- targetSection.style.outline = "none";
74
- targetSection.style.setProperty("outline", "none", "important");
75
- }
76
-
77
- // Add item to the TOC list
78
- tocList.appendChild(tocItem);
79
- });
80
-
81
- return {
82
- result: "util-toc initialized",
83
- };
84
- }
File without changes
File without changes
File without changes