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.