PageSourceSearch

https://www.nationaltravels.co.in/cms_builds/380/national-travels-…/javascript/searchResultsAccessibility.js

js nationaltravels.co.in collected 2026-10-02 01:56:43 UTC 37,644 bytes, 945 lines download raw bytes

1function isMobile() {
2  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
3    ('ontouchstart' in window) ||
4    (navigator.maxTouchPoints > 0);
5}
6
7function initSrpContainerSetFocused() {
8  // Only add intelligence for mobile devices
9  if (isMobile()) {
10    // Enhanced state management
11    let isUserEngagedWithSearch = false;
12    let visibilityCheckInterval;
13    let searchWidgetEngagementTimeout;
14    let engagementScore = 0;
15    let interactionHistory = [];
16    let userScrollDirection = null;
17    let lastScrollTop = 0;
18    let touchStartX = 0, touchStartY = 0;
19    let touchEndX = 0, touchEndY = 0;
20    let srpObserver = null;
21
22    // Utility functions
23    const debounce = (func, wait) => {
24      let timeout;
25      return function executedFunction(...args) {
26        const later = () => {
27          clearTimeout(timeout);
28          func(...args);
29        };
30        clearTimeout(timeout);
31        timeout = setTimeout(later, wait);
32      };
33    };
34
35    // Haptic feedback for mobile interactions
36    const addHapticFeedback = (pattern = [50]) => {
37      if (navigator.vibrate && typeof navigator.vibrate === 'function') {
38        navigator.vibrate(pattern);
39      }
40    };
41
42    // Visual focus indicators
43    const addFocusIndicator = () => {
44      const srpContainer = document.querySelector('.srp_container');
45      if (srpContainer) {
46        srpContainer.classList.add('srp-focused');
47        addHapticFeedback([30]); // Subtle feedback
48      }
49    };
50
51    const removeFocusIndicator = () => {
52      const srpContainer = document.querySelector('.srp_container');
53      if (srpContainer) {
54        srpContainer.classList.remove('srp-focused');
55      }
56    };
57
58    // Accessibility improvements
59    const announceToScreenReader = (message) => {
60      const announcement = document.createElement('div');
61      announcement.setAttribute('aria-live', 'polite');
62      announcement.setAttribute('aria-atomic', 'true');
63      announcement.style.position = 'absolute';
64      announcement.style.left = '-10000px';
65      announcement.style.width = '1px';
66      announcement.style.height = '1px';
67      announcement.style.overflow = 'hidden';
68
69      announcement.textContent = message;
70      document.body.appendChild(announcement);
71
72      setTimeout(() => {
73        if (document.body.contains(announcement)) {
74          document.body.removeChild(announcement);
75        }
76      }, 1000);
77    };
78
79    // Smart engagement scoring
80    const updateEngagementScore = (action, weight = 1) => {
81      const timestamp = Date.now();
82      interactionHistory.push({ action, timestamp, weight });
83
84      // Remove interactions older than 30 seconds
85      interactionHistory = interactionHistory.filter(item => timestamp - item.timestamp < 30000);
86
87      // Calculate weighted engagement score
88      engagementScore = interactionHistory.reduce((score, item) => score + item.weight, 0);
89
90      // Adjust focus behavior based on engagement
91      if (engagementScore > 8) {
92        // High engagement - be less aggressive with auto-scrolling
93        if (visibilityCheckInterval) {
94          clearInterval(visibilityCheckInterval);
95          visibilityCheckInterval = null;
96        }
97      }
98    };
99
100    // Progressive scroll behavior tracking (enhanced to detect user intent)
101    const trackScrollBehavior = debounce(() => {
102      const currentScrollTop = window.pageYOffset || document.documentElement.scrollTop;
103      const previousDirection = userScrollDirection;
104      userScrollDirection = currentScrollTop > lastScrollTop ? 'down' : 'up';
105      lastScrollTop = currentScrollTop;
106
107      // If user is actively scrolling down, mark them as actively interacting to prevent interference
108      if (userScrollDirection === 'down' && previousDirection !== 'down') {
109        markUserAsActivelyInteracting(); // Prevent auto-repositioning when user scrolls down
110      }
111
112      updateEngagementScore('scroll', 0.5);
113    }, 150); // Slightly longer debounce for better detection
114
115    // Enhanced gesture recognition
116    const handleGestureRecognition = () => {
117      const minSwipeDistance = 50;
118      const maxVerticalDeviation = 100;
119
120      const horizontalDistance = Math.abs(touchEndX - touchStartX);
121      const verticalDistance = Math.abs(touchEndY - touchStartY);
122
123      // Detect meaningful gestures
124      if (horizontalDistance > minSwipeDistance && verticalDistance < maxVerticalDeviation) {
125        updateEngagementScore('gesture_swipe', 2);
126        addHapticFeedback([40, 20, 40]); // Pattern feedback for gesture
127
128        if (touchEndX > touchStartX) {
129          // Swipe right - focus on results
130          scrollSrpToTopWithAnimation();
131        }
132      } else if (verticalDistance > minSwipeDistance && horizontalDistance < maxVerticalDeviation) {
133        if (touchStartY > touchEndY) {
134          // Swipe up - user wants to see more content
135          updateEngagementScore('swipe_up', 3);
136        }
137      }
138    };
139
140    // Performance optimized scroll function with animation and enhanced positioning
141    const scrollSrpToTopWithAnimation = () => {
142      const srpContainer = document.querySelector('.srp_container');
143      if (srpContainer) {
144        addFocusIndicator();
145        announceToScreenReader('Search results are now in focus');
146
147        // Check if container is already mostly visible
148        if (isSrpContainer75PercentVisible()) {
149          // If 75%+ visible, use precise positioning with viewport optimization
150          optimizeSrpContainerViewport();
151        } else {
152          // Standard scroll into view for less visible containers
153          srpContainer.scrollIntoView({
154            behavior: 'smooth',
155            block: 'start'
156          });
157
158          // After scroll, optimize viewport usage
159          setTimeout(() => {
160            optimizeSrpContainerViewport();
161          }, 800); // Wait for scroll animation to complete
162        }
163
164        // Add temporary highlight effect
165        setTimeout(() => {
166          removeFocusIndicator();
167        }, 2000);
168      }
169    };
170
171    document.querySelector('.srp_container').scrollIntoView({
172      behavior: 'smooth',
173      block: 'start'
174    });
175
176    // Enhanced click event listener with gesture tracking and srp_container positioning
177    let lastRepositionTime = 0;
178    let isUserActivelyInteracting = false;
179    let activeInteractionTimeout;
180
181    // Track active user interactions to prevent jarring repositioning
182    const markUserAsActivelyInteracting = () => {
183      isUserActivelyInteracting = true;
184      clearTimeout(activeInteractionTimeout);
185      activeInteractionTimeout = setTimeout(() => {
186        isUserActivelyInteracting = false;
187      }, 2000); // Consider user actively interacting for 2 seconds
188    };
189
190    document.addEventListener('click', function (event) {
191      const srpContainer = event.target.closest('.srp_container');
192      if (srpContainer) {
193        const isFromFilterButton = event.target.closest('.filters__mobile_button');
194        const isFromFiltersContainer = event.target.closest('#filters_container');
195        const isFromSortContainer = event.target.closest('#sort_container');
196        const isFromInteractiveElement = event.target.matches('button, a, input, select, textarea, [role="button"], [tabindex]');
197
198        updateEngagementScore('srp_click', 2);
199        markUserAsActivelyInteracting();
200
201        // Only reposition if:
202        // 1. Container is significantly out of position (not just slightly off)
203        // 2. User is not actively interacting with UI elements
204        // 3. Enough time has passed since last repositioning
205        // 4. Container visibility is poor (less than 60%)
206        const now = Date.now();
207        const timeSinceLastReposition = now - lastRepositionTime;
208
209        if (!isFromInteractiveElement &&
210          timeSinceLastReposition > 3000 &&
211          !isUserActivelyInteracting) {
212
213          setTimeout(() => {
214            const rect = srpContainer?.getBoundingClientRect();
215            const windowHeight = window.innerHeight || document.documentElement.clientHeight;
216            const visibilityRatio = Math.max(0, Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0)) / windowHeight;
217
218            // Only reposition if container is using less than 60% of viewport OR is significantly off-screen
219            if ((visibilityRatio < 0.60 || rect.top > 150 || rect.top < -100) && !isUserActivelyInteracting) {
220              lastRepositionTime = now;
221              if (isSrpContainer75PercentVisible()) {
222                optimizeSrpContainerViewport();
223              } else {
224                scrollSrpToTopWithAnimation();
225              }
226              announceToScreenReader('Search results repositioned for better viewing');
227            }
228          }, 500); // Longer delay to ensure user interaction is complete
229        }
230
231        if (!isFromFilterButton && !isFromFiltersContainer && !isFromSortContainer) {
232          // Only scroll to top if user explicitly clicked on non-interactive elements AND engagement is very low
233          // Removed automatic scroll-to-top on down scroll - this was interfering with user intent
234          if (engagementScore < 2 && userScrollDirection === 'up' && event.target.matches('.srp_container, .result-item')) {
235            // Only if user is scrolling UP and clicked on result content (not down scrolling)
236            addHapticFeedback([60]);
237            window.scrollTo({
238              top: 0,
239              behavior: 'smooth'
240            });
241            announceToScreenReader('Scrolled to top of page');
242          }
243        }
244      }
245    });
246
247    // Add touch gesture tracking
248    document.addEventListener('touchstart', (event) => {
249      const srpContainer = event.target.closest('.srp_container');
250      if (srpContainer) {
251        touchStartX = event.touches[0].clientX;
252        touchStartY = event.touches[0].clientY;
253      }
254    });
255
256    document.addEventListener('touchend', (event) => {
257      const srpContainer = event.target.closest('.srp_container');
258      if (srpContainer && touchStartX !== 0 && touchStartY !== 0) {
259        touchEndX = event.changedTouches[0].clientX;
260        touchEndY = event.changedTouches[0].clientY;
261        handleGestureRecognition();
262        touchStartX = touchStartY = touchEndX = touchEndY = 0;
263      }
264    });
265
266    // Add scroll tracking with srp_container specific monitoring
267    document.addEventListener('scroll', trackScrollBehavior, { passive: true });
268
269    // Enhanced scroll detection specifically within srp_container (less aggressive positioning)
270    const handleSrpContainerScroll = debounce(() => {
271      const srpContainer = document.querySelector('.srp_container');
272      if (!srpContainer) return;
273
274      // Check if any scroll happened and container positioning is severely compromised
275      const rect = srpContainer?.getBoundingClientRect();
276      const windowHeight = window.innerHeight || document.documentElement.clientHeight;
277      const visibilityRatio = Math.max(0, Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0)) / rect.height;
278
279      if (visibilityRatio < 0.40) { // Only act if less than 40% visible
280        updateEngagementScore('srp_container_scroll_detected', 1);
281        markUserAsActivelyInteracting();
282
283        // Wait longer to see if user continues scrolling
284        setTimeout(() => {
285          if (!isUserEngagedWithSearch && !isUserActivelyInteracting && (Date.now() - lastRepositionTime) > 4000) {
286            // Only reposition if visibility is still poor and user seems done interacting
287            const currentRect = srpContainer?.getBoundingClientRect();
288            const currentVisibilityRatio = Math.max(0, Math.min(currentRect.bottom, windowHeight) - Math.max(currentRect.top, 0)) / currentRect.height;
289
290            if (currentVisibilityRatio < 0.40) {
291              lastRepositionTime = Date.now();
292              if (isSrpContainer75PercentVisible()) {
293                optimizeSrpContainerViewport();
294              } else {
295                scrollSrpToTopWithAnimation();
296              }
297            }
298          }
299        }, 3000); // Much longer delay - give user more time
300      }
301    }, 400); // Longer debounce
302
303    // Add specific scroll listener for srp_container interactions
304    document.addEventListener('scroll', (event) => {
305      // Check if scroll originated from or affects srp_container
306      const srpContainer = document.querySelector('.srp_container');
307      if (srpContainer) {
308        const rect = srpContainer?.getBoundingClientRect();
309        const windowHeight = window.innerHeight || document.documentElement.clientHeight;
310
311        // If container is in viewport and scroll occurred, monitor it
312        if (rect.top < windowHeight && rect.bottom > 0) {
313          handleSrpContainerScroll();
314        }
315      }
316    }, { passive: true });
317
318    // Performance optimized visibility checks using Intersection Observer
319    const observeSrpVisibility = () => {
320      const srpContainer = document.querySelector('.srp_container');
321      if (!srpContainer || srpObserver) return;
322
323      srpObserver = new IntersectionObserver((entries) => {
324        entries.forEach(entry => {
325          if (entry.isIntersecting) {
326            updateEngagementScore('srp_visible', 1);
327
328            // Only reposition if container is significantly out of position and user is not interacting
329            if (isSrpContainer75PercentVisible() && !isSrpContainerAtTop() && !isUserActivelyInteracting) {
330              // Container is mostly visible but not properly positioned
331              setTimeout(() => {
332                if (!isUserEngagedWithSearch && !isUserActivelyInteracting && (Date.now() - lastRepositionTime) > 3000) {
333                  lastRepositionTime = Date.now();
334                  optimizeSrpContainerViewport();
335                }
336              }, 1500); // Longer delay to avoid interrupting interactions
337            } else {
338              addFocusIndicator();
339            }
340          } else {
341            removeFocusIndicator();
342            // Only help refocus if user seems lost AND is not actively scrolling down
343            if (!isUserEngagedWithSearch && engagementScore < 3 && userScrollDirection !== 'down') {
344              // User might need help refocusing, but respect their scrolling intent
345              setTimeout(() => {
346                if (!isSrpContainerAtTop() && !isUserEngagedWithSearch && userScrollDirection !== 'down') {
347                  scrollSrpToTopWithAnimation();
348                }
349              }, 5000); // Longer delay, more respectful
350            }
351          }
352        });
353      }, { threshold: [0.3, 0.75] });
354
355      srpObserver.observe(srpContainer);
356    };
357
358    // Initialize visibility observer
359    setTimeout(observeSrpVisibility, 500);
360
361    // Check if search widget is visible (enhanced with engagement scoring)
362    const isSearchWidgetVisible = () => {
363      const searchWidget = document.querySelector('.search-widget');
364      if (!searchWidget) return false;
365
366      const rect = searchWidget.getBoundingClientRect();
367      const windowHeight = window.innerHeight || document.documentElement.clientHeight;
368
369      const visibleHeight = Math.max(0, Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0));
370      const totalHeight = rect.height;
371
372      if (totalHeight === 0) return false;
373
374      const visibilityRatio = visibleHeight / totalHeight;
375      const isVisible = visibilityRatio > 0.50;
376
377      if (isVisible) {
378        updateEngagementScore('search_widget_visible', 1);
379      }
380
381      return isVisible;
382    };
383
384    const isFooterWidgetVisible = () => {
385      const footerWidget = document.querySelector('[data-widget-id="footer"]');
386      if (!footerWidget) return false;
387
388      const rect = footerWidget.getBoundingClientRect();
389      const windowHeight = window.innerHeight || document.documentElement.clientHeight;
390
391      const visibleHeight = Math.max(0, Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0));
392      const totalHeight = rect.height;
393
394      if (totalHeight === 0) return false;
395
396      const visibilityRatio = visibleHeight / totalHeight;
397      return visibilityRatio > 0.20;
398    };
399
400    // Enhanced scroll function with 75% visibility check (respects user scrolling intent)
401    const scrollSrpToTop = () => {
402      // Don't interfere if user is actively scrolling or recently interacted
403      if (isUserActivelyInteracting || isUserEngagedWithSearch) {
404        return;
405      }
406
407      // Only act if search widget is visible AND footer is not visible (to avoid conflicts)
408      if (!isSearchWidgetVisible() && !isFooterWidgetVisible()) {
409        // Special case: if container is more than 75% visible, position it properly to top
410        if (isSrpContainer75PercentVisible()) {
411          // Don't auto-reposition if user is scrolling down (they might want to see more content)
412          if (userScrollDirection !== 'down') {
413            optimizeSrpContainerViewport();
414          }
415          return;
416        }
417
418        // Only auto-scroll if engagement is very low AND user is not scrolling down
419        if (userScrollDirection !== 'down' && engagementScore < 2) {
420          scrollSrpToTopWithAnimation();
421          updateEngagementScore('auto_scroll', 1);
422        }
423      }
424    };
425
426    // Check if srp_container is more than 75% visible in viewport
427    const isSrpContainer75PercentVisible = () => {
428      const srpContainer = document.querySelector('.srp_container');
429      if (!srpContainer) return false;
430
431      const rect = srpContainer?.getBoundingClientRect();
432      const windowHeight = window.innerHeight || document.documentElement.clientHeight;
433
434      // Calculate how much of the container is visible
435      const visibleTop = Math.max(0, rect.top);
436      const visibleBottom = Math.min(rect.bottom, windowHeight);
437      const visibleHeight = Math.max(0, visibleBottom - visibleTop);
438      const totalHeight = rect.height;
439
440      if (totalHeight === 0) return false;
441
442      const visibilityRatio = visibleHeight / totalHeight;
443      return visibilityRatio >= 0.75; // More than 75% visible
444    };
445
446    // Check if srp_container is visible at top of viewport (less aggressive optimization)
447    const isSrpContainerAtTop = () => {
448      const srpContainer = document.querySelector('.srp_container');
449      if (!srpContainer) return true;
450
451      const rect = srpContainer?.getBoundingClientRect();
452      const isAtTop = rect.top >= -50 && rect.top <= 100;
453
454      if (isAtTop) {
455        updateEngagementScore('srp_at_top', 1);
456
457        // Only optimize viewport if user is not actively interacting and enough time has passed
458        if (!isUserActivelyInteracting && (Date.now() - lastRepositionTime) > 5000) {
459          setTimeout(() => {
460            if (!isUserActivelyInteracting) {
461              optimizeSrpContainerViewport();
462            }
463          }, 1000); // Longer delay to avoid interrupting user interactions
464        }
465      }
466
467      return isAtTop;
468    };
469
470    // Check if footer or search widgets are encroaching on viewport
471    const isViewportEncroached = () => {
472      const windowHeight = window.innerHeight || document.documentElement.clientHeight;
473
474      // Check if search widget is partially visible at top
475      const searchWidget = document.querySelector('.search-widget');
476      if (searchWidget) {
477        const searchRect = searchWidget.getBoundingClientRect();
478        if (searchRect.bottom > 0 && searchRect.bottom < windowHeight * 0.25) {
479          return { type: 'search', element: searchWidget, rect: searchRect };
480        }
481      }
482
483      // Check if footer is partially visible at bottom
484      const footerWidget = document.querySelector('[data-widget-id="footer"]');
485      if (footerWidget) {
486        const footerRect = footerWidget.getBoundingClientRect();
487        if (footerRect.top < windowHeight && footerRect.top > windowHeight * 0.75) {
488          return { type: 'footer', element: footerWidget, rect: footerRect };
489        }
490      }
491
492      return null;
493    };
494
495    // Enhanced function to ensure SRP container gets maximum viewport space
496    const optimizeSrpContainerViewport = () => {
497      const srpContainer = document.querySelector('.srp_container');
498      if (!srpContainer) return;
499
500      const windowHeight = window.innerHeight || document.documentElement.clientHeight;
501      const rect = srpContainer?.getBoundingClientRect();
502
503      // Calculate how much of the viewport the container is using
504      const visibleTop = Math.max(0, rect.top);
505      const visibleBottom = Math.min(rect.bottom, windowHeight);
506      const visibleHeight = Math.max(0, visibleBottom - visibleTop);
507      const viewportUsage = visibleHeight / windowHeight;
508
509      // Check for encroaching elements
510      const encroachment = isViewportEncroached();
511
512      // If container is using less than 70% of viewport or there's encroachment, optimize
513      if (viewportUsage < 0.70 || encroachment) {
514        updateEngagementScore('viewport_optimization', 1);
515        positionSrpContainerToTop();
516
517        if (encroachment) {
518          announceToScreenReader(`Search results repositioned to avoid ${encroachment.type} interference`);
519        }
520      }
521    };
522
523    // Enhanced function to properly position SRP container to top with maximum viewport utilization
524    const positionSrpContainerToTop = () => {
525      const srpContainer = document.querySelector('.srp_container');
526      if (!srpContainer) return;
527
528      // Get the container's current position
529      const rect = srpContainer?.getBoundingClientRect();
530      const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
531      const windowHeight = window.innerHeight || document.documentElement.clientHeight;
532
533      // Calculate the absolute position of the container
534      const containerTop = rect.top + scrollTop;
535
536      // Check for interfering elements
537      const searchWidget = document.querySelector('.search-widget');
538      const footerWidget = document.querySelector('[data-widget-id="footer"]');
539
540      let headerOffset = 20; // Default offset for headers
541
542      // Adjust offset if search widget is partially visible at top
543      if (searchWidget) {
544        const searchRect = searchWidget.getBoundingClientRect();
545        if (searchRect.bottom > 0 && searchRect.bottom < windowHeight * 0.3) {
546          // Search widget is partially visible at top, adjust offset
547          headerOffset = Math.max(headerOffset, searchRect.height - searchRect.top + 10);
548        }
549      }
550
551      // Calculate optimal target position for maximum container visibility
552      const targetPosition = Math.max(0, containerTop - headerOffset);
553
554      // Ensure we're not positioning too low if footer is encroaching
555      let adjustedTargetPosition = targetPosition;
556      if (footerWidget) {
557        const footerRect = footerWidget.getBoundingClientRect();
558        const footerTop = footerRect.top + scrollTop;
559        const availableSpace = footerTop - targetPosition;
560
561        // If footer would interfere, adjust positioning
562        if (availableSpace < windowHeight * 0.8) {
563          adjustedTargetPosition = Math.max(0, footerTop - windowHeight * 0.85);
564        }
565      }
566
567      // Smooth scroll to the calculated position
568      window.scrollTo({
569        top: adjustedTargetPosition,
570        behavior: 'smooth'
571      });
572
573      addFocusIndicator();
574      announceToScreenReader('Search results optimally positioned for maximum visibility');
575      updateEngagementScore('positioned_to_top', 2);
576    };
577
578    // Enhanced monitoring with smart intervals (respects user scrolling intent)
579    const startVisibilityMonitoring = () => {
580      if (visibilityCheckInterval) clearInterval(visibilityCheckInterval);
581
582      // Adjust monitoring frequency based on engagement (longer intervals)
583      const monitoringInterval = engagementScore > 5 ? 10000 : 8000; // Increased intervals
584
585      visibilityCheckInterval = setInterval(() => {
586        if (!isUserEngagedWithSearch && !isUserActivelyInteracting) {
587          // Only check if significant time has passed since last repositioning
588          const timeSinceLastReposition = Date.now() - lastRepositionTime;
589
590          // Respect user scrolling intent - don't interfere if they're scrolling down
591          if (timeSinceLastReposition > 10000 && userScrollDirection !== 'down') {
592            // Check container visibility ratio instead of just position
593            const srpContainer = document.querySelector('.srp_container');
594            if (srpContainer) {
595              const rect = srpContainer?.getBoundingClientRect();
596              const windowHeight = window.innerHeight || document.documentElement.clientHeight;
597              const visibilityRatio = Math.max(0, Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0)) / windowHeight;
598
599              // Only reposition if container is using less than 40% of viewport (more restrictive)
600              if (visibilityRatio < 0.40) {
601                lastRepositionTime = Date.now();
602                if (isSrpContainer75PercentVisible()) {
603                  optimizeSrpContainerViewport();
604                } else {
605                  scrollSrpToTop();
606                }
607              }
608            }
609
610            // Check for viewport encroachment only if container needs repositioning and user isn't scrolling down
611            const encroachment = isViewportEncroached();
612            if (encroachment && isSrpContainer75PercentVisible() && timeSinceLastReposition > 12000 && userScrollDirection !== 'down') {
613              setTimeout(() => {
614                if (!isUserActivelyInteracting && userScrollDirection !== 'down') {
615                  lastRepositionTime = Date.now();
616                  optimizeSrpContainerViewport();
617                }
618              }, 1500);
619            }
620          }
621        }
622      }, monitoringInterval);
623    };
624
625    // Stop monitoring with cleanup
626    const stopVisibilityMonitoring = () => {
627      if (visibilityCheckInterval) {
628        clearInterval(visibilityCheckInterval);
629        visibilityCheckInterval = null;
630      }
631    };
632
633    // Enhanced search widget engagement handlers
634    const searchWidgetElements = document.querySelectorAll('.search-widget');
635    let activeTouchInSearchWidget = false;
636    let touchStartTime = 0;
637
638    searchWidgetElements.forEach(searchWidget => {
639      // Enhanced focus handlers with haptic feedback
640      searchWidget.addEventListener('focusin', () => {
641        isUserEngagedWithSearch = true;
642        updateEngagementScore('search_focus', 3);
643        addHapticFeedback([40]);
644        addFocusIndicator();
645        stopVisibilityMonitoring();
646        if (searchWidgetEngagementTimeout) {
647          clearTimeout(searchWidgetEngagementTimeout);
648        }
649        announceToScreenReader('Search widget is now focused');
650      });
651
652      searchWidget.addEventListener('touchstart', (e) => {
653        activeTouchInSearchWidget = true;
654        touchStartTime = Date.now();
655        isUserEngagedWithSearch = true;
656        updateEngagementScore('search_touch', 2);
657        addHapticFeedback([30]);
658        stopVisibilityMonitoring();
659        if (searchWidgetEngagementTimeout) {
660          clearTimeout(searchWidgetEngagementTimeout);
661        }
662      });
663
664      searchWidget.addEventListener('touchmove', (e) => {
665        if (activeTouchInSearchWidget) {
666          isUserEngagedWithSearch = true;
667          updateEngagementScore('search_interaction', 1);
668          stopVisibilityMonitoring();
669          if (searchWidgetEngagementTimeout) {
670            clearTimeout(searchWidgetEngagementTimeout);
671          }
672        }
673      });
674
675      searchWidget.addEventListener('mouseenter', () => {
676        if (isMobile()) {
677          isUserEngagedWithSearch = true;
678          updateEngagementScore('search_hover', 1);
679          stopVisibilityMonitoring();
680          if (searchWidgetEngagementTimeout) {
681            clearTimeout(searchWidgetEngagementTimeout);
682          }
683        }
684      });
685
686      // Enhanced input field interactions with engagement tracking
687      const inputElements = searchWidget.querySelectorAll('input, select, textarea, [contenteditable], div, span');
688      inputElements.forEach(input => {
689        input.addEventListener('focus', () => {
690          isUserEngagedWithSearch = true;
691          updateEngagementScore('input_focus', 3);
692          addHapticFeedback([35]);
693          stopVisibilityMonitoring();
694          if (searchWidgetEngagementTimeout) {
695            clearTimeout(searchWidgetEngagementTimeout);
696          }
697        });
698
699        input.addEventListener('input', () => {
700          isUserEngagedWithSearch = true;
701          updateEngagementScore('input_typing', 4);
702          stopVisibilityMonitoring();
703          if (searchWidgetEngagementTimeout) {
704            clearTimeout(searchWidgetEngagementTimeout);
705          }
706        });
707
708        input.addEventListener('click', () => {
709          isUserEngagedWithSearch = true;
710          updateEngagementScore('input_click', 2);
711          addHapticFeedback([25]);
712          stopVisibilityMonitoring();
713          if (searchWidgetEngagementTimeout) {
714            clearTimeout(searchWidgetEngagementTimeout);
715          }
716        });
717
718        // Enhanced touch interactions for form fields
719        input.addEventListener('touchstart', () => {
720          activeTouchInSearchWidget = true;
721          touchStartTime = Date.now();
722          isUserEngagedWithSearch = true;
723          updateEngagementScore('input_touch', 3);
724          addHapticFeedback([30]);
725          stopVisibilityMonitoring();
726          if (searchWidgetEngagementTimeout) {
727            clearTimeout(searchWidgetEngagementTimeout);
728          }
729        });
730      });
731
732      // Enhanced disengagement handlers with smart delays
733      searchWidget.addEventListener('focusout', () => {
734        if (!activeTouchInSearchWidget) {
735          const delay = engagementScore > 7 ? 2000 : 1000; // Longer delay for highly engaged users
736
737          searchWidgetEngagementTimeout = setTimeout(() => {
738            isUserEngagedWithSearch = false;
739            removeFocusIndicator();
740            startVisibilityMonitoring();
741            setTimeout(scrollSrpToTop, 500);
742            announceToScreenReader('Search widget is no longer focused');
743          }, delay);
744        }
745      });
746
747      searchWidget.addEventListener('touchend', () => {
748        activeTouchInSearchWidget = false;
749        const touchDuration = Date.now() - touchStartTime;
750        updateEngagementScore('search_touch_end', 1);
751
752        // Smart delay based on interaction duration and engagement
753        let delay = 2000;
754        if (touchDuration > 1000) delay = 3000;
755        if (engagementScore > 8) delay = 4000;
756
757        searchWidgetEngagementTimeout = setTimeout(() => {
758          const activeElement = document.activeElement;
759          const isInputFocused = activeElement &&
760            (activeElement.matches('input, select, textarea, [contenteditable], div, span') ||
761              searchWidget.contains(activeElement));
762
763          if (!isInputFocused && !activeTouchInSearchWidget) {
764            isUserEngagedWithSearch = false;
765            removeFocusIndicator();
766            startVisibilityMonitoring();
767            setTimeout(scrollSrpToTop, 500);
768          }
769        }, delay);
770      });
771
772      searchWidget.addEventListener('mouseleave', () => {
773        if (isMobile() && !activeTouchInSearchWidget) {
774          const delay = engagementScore > 6 ? 1500 : 1000;
775
776          searchWidgetEngagementTimeout = setTimeout(() => {
777            isUserEngagedWithSearch = false;
778            removeFocusIndicator();
779            startVisibilityMonitoring();
780            setTimeout(scrollSrpToTop, 500);
781          }, delay);
782        }
783      });
784    });
785
786    // Enhanced click handlers with smart engagement detection and srp positioning
787    document.addEventListener('click', (event) => {
788      const isClickInsideSearchWidget = event.target.closest('.search-widget');
789      const isClickOnFormElement = event.target.matches('input, select, textarea, button, [contenteditable], div, span');
790      const isClickInsideSrpContainer = event.target.closest('.srp_container');
791
792      // Track all clicks for engagement scoring
793      updateEngagementScore('global_click', 0.5);
794
795      // Special handling for clicks inside srp_container (less aggressive positioning)
796      if (isClickInsideSrpContainer && !isClickInsideSearchWidget) {
797        updateEngagementScore('srp_container_interaction', 2);
798        markUserAsActivelyInteracting();
799
800        // Only reposition if container visibility is severely compromised
801        setTimeout(() => {
802          const rect = isClickInsideSrpContainer?.getBoundingClientRect();
803          const windowHeight = window.innerHeight || document.documentElement.clientHeight;
804          const visibilityRatio = Math.max(0, Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0)) / rect.height;
805
806          // Only reposition if container is less than 50% visible AND user is not actively interacting
807          if (visibilityRatio < 0.50 && !isUserActivelyInteracting && (Date.now() - lastRepositionTime) > 2000) {
808            lastRepositionTime = Date.now();
809            if (isSrpContainer75PercentVisible()) {
810              optimizeSrpContainerViewport();
811            } else {
812              scrollSrpToTopWithAnimation();
813            }
814          }
815        }, 800); // Even longer delay for srp container interactions
816      }
817
818      if (!isClickInsideSearchWidget && !isClickOnFormElement && isUserEngagedWithSearch) {
819        const delay = engagementScore > 7 ? 1000 : 500; // More patient with engaged users
820
821        searchWidgetEngagementTimeout = setTimeout(() => {
822          isUserEngagedWithSearch = false;
823          removeFocusIndicator();
824          startVisibilityMonitoring();
825          setTimeout(scrollSrpToTop, 500);
826        }, delay);
827      } else if (isClickInsideSearchWidget) {
828        isUserEngagedWithSearch = true;
829        updateEngagementScore('search_widget_click', 3);
830        addHapticFeedback([35]);
831        addFocusIndicator();
832        stopVisibilityMonitoring();
833        if (searchWidgetEngagementTimeout) {
834          clearTimeout(searchWidgetEngagementTimeout);
835        }
836      }
837    });
838
839    // Enhanced initial monitoring with performance optimization
840    setTimeout(() => {
841      startVisibilityMonitoring();
842      announceToScreenReader('Search results focus assistance is active');
843    }, 1000);
844
845    // Enhanced page visibility change handler
846    document.addEventListener('visibilitychange', () => {
847      if (document.visibilityState === 'visible') {
848        updateEngagementScore('page_visible', 2);
849
850        if (!isUserEngagedWithSearch) {
851          setTimeout(() => {
852            if (!isSrpContainerAtTop()) {
853              scrollSrpToTopWithAnimation();
854              announceToScreenReader('Welcome back! Search results are now in focus');
855            }
856            startVisibilityMonitoring();
857          }, 500);
858        }
859      } else {
860        stopVisibilityMonitoring();
861        // Reset some engagement when user leaves page
862        engagementScore = Math.max(0, engagementScore - 2);
863      }
864    });
865
866    // Cleanup function for when component unmounts
867    const cleanup = () => {
868      stopVisibilityMonitoring();
869      if (srpObserver) {
870        srpObserver.disconnect();
871        srpObserver = null;
872      }
873      if (searchWidgetEngagementTimeout) {
874        clearTimeout(searchWidgetEngagementTimeout);
875      }
876      document.removeEventListener('scroll', trackScrollBehavior);
877    };
878
879    // Store cleanup function for potential later use
880    window.srpFocusCleanup = cleanup;
881
882    // Add CSS for focus indicators if not present
883    if (!document.getElementById('srp-focus-styles')) {
884      const style = document.createElement('style');
885      style.id = 'srp-focus-styles';
886      style.textContent = `
887        .srp-focused {
888          box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.3) !important;
889          transition: box-shadow 0.3s ease !important;
890        }
891        
892        .srp_container {
893          transition: box-shadow 0.3s ease;
894        }
895      `;
896      document.head.appendChild(style);
897    }
898
899    // Add scroll and touch event listeners for mobile devices (keep existing disabled functionality)
900    if (false) { // Disabled functionality
901      let scrollTimeout;
902      let isScrolling = false;
903
904      // Handle scroll events
905      document.addEventListener('scroll', function (event) {
906        const srpContainer = event.target.closest('.srp_container');
907        if (srpContainer && !isScrolling) {
908          clearTimeout(scrollTimeout);
909          scrollTimeout = setTimeout(() => {
910            window.scrollTo({
911              top: 0,
912              behavior: 'smooth'
913            });
914          }, 150); // Debounce scroll events
915        }
916      }, true);
917
918      // Handle touch events for swipe gestures
919      let touchStartY = 0;
920      document.addEventListener('touchstart', function (event) {
921        const srpContainer = event.target.closest('.srp_container');
922        if (srpContainer) {
923          touchStartY = event.touches[0].clientY;
924        }
925      });
926
927      document.addEventListener('touchend', function (event) {
928        const srpContainer = event.target.closest('.srp_container');
929        if (srpContainer && touchStartY > 0) {
930          const touchEndY = event.changedTouches[0].clientY;
931          const swipeDistance = touchStartY - touchEndY;
932
933          // If user scrolled/swiped within the container, scroll to top
934          if (Math.abs(swipeDistance) > 50) { // Minimum swipe distance
935            window.scrollTo({
936              top: 0,
937              behavior: 'smooth'
938            });
939          }
940          touchStartY = 0;
941        }
942      });
943    }
944  }
945}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.