1// The desktop quick-actions widget is dismissible: the injected close button hides it for the 2// rest of the page view. 3(function () { 4 "use strict"; 5 document.addEventListener("click", function (e) { 6 var btn = e.target && e.target.closest ? e.target.closest(".desktop-cta-close") : null; 7 if (!btn) return; 8 var widget = document.getElementById("desktopCtaWidget"); 9 if (widget) widget.style.display = "none"; 10 }); 11})(); 12 13// Native quote dialog controller. showModal() supplies top-layer placement, modal inertness, 14// Escape handling, and browser-managed focus containment; this controller only owns triggers, 15// backdrop dismissal, body scroll locking, initial focus, and explicit focus restoration. 16(function () { 17 "use strict"; 18 19 var dialog = document.getElementById("fullScreenModal"); 20 if (!dialog || dialog.tagName !== "DIALOG" || typeof dialog.showModal !== "function") return; 21 22 var returnFocus = null; 23 24 function openDialog(trigger) { 25 if (dialog.open) return; 26 returnFocus = trigger instanceof HTMLElement ? trigger : document.activeElement; 27 document.body.classList.add("overflow-hidden"); 28 dialog.showModal(); 29 window.requestAnimationFrame(function () { 30 var first = dialog.querySelector(".modal-close, button, [href], input, select, textarea, iframe, [tabindex]:not([tabindex=\"-1\"])"); 31 if (first && typeof first.focus === "function") first.focus(); 32 }); 33 } 34 35 function closeDialog() { 36 if (dialog.open) dialog.close(); 37 } 38 39 document.addEventListener("click", function (event) { 40 var trigger = event.target && event.target.closest 41 ? event.target.closest(".quote-trigger, #instantQuoteBtn") 42 : null; 43 if (trigger) { 44 event.preventDefault(); 45 openDialog(trigger); 46 return; 47 } 48 49 var closer = event.target && event.target.closest 50 ? event.target.closest('[data-modal-toggle="fullScreenModal"]') 51 : null; 52 if (closer && dialog.contains(closer)) { 53 event.preventDefault(); 54 closeDialog(); 55 } 56 }); 57 58 dialog.addEventListener("click", function (event) { 59 if (event.target === dialog) closeDialog(); 60 }); 61 62 document.addEventListener("keydown", function (event) { 63 if (event.key !== "Escape" || !dialog.open) return; 64 event.preventDefault(); 65 closeDialog(); 66 }); 67 68 dialog.addEventListener("close", function () { 69 document.body.classList.remove("overflow-hidden"); 70 var target = returnFocus; 71 returnFocus = null; 72 if (target && target.isConnected && typeof target.focus === "function") target.focus(); 73 }); 74})(); 75 76// One shared controller owns both the mobile drawer and the navigation disclosures. Destination 77// links always navigate; on mobile each adjacent disclosure becomes the full labeled parent row. 78(function () { 79 "use strict"; 80 81 var nav = document.getElementById("primaryNav"); 82 var button = document.querySelector(".hbg[aria-controls=\"primaryNav\"]"); 83 var navWrap = document.querySelector("#nav .nav-wrap"); 84 var desktopQuery = window.matchMedia("(min-width: 1025px)"); 85 var suppressFocusOpen = false; 86 if (!nav || !button || !navWrap) return; 87 88 function isMobile() { 89 return !desktopQuery.matches; 90 } 91 92 function getSubmenu(item) { 93 return item ? item.querySelector(":scope > .nav-dropdown") : null; 94 } 95 96 function getToggle(item) { 97 return item ? item.querySelector( 98 ":scope > .nav-submenu-toggle, :scope > .nav-main-group > .nav-submenu-toggle" 99 ) : null; 100 } 101 102 function setDrawerState(isOpen) { 103 button.classList.toggle("active", isOpen); 104 button.setAttribute("aria-expanded", isOpen ? "true" : "false"); 105 button.setAttribute("aria-label", isOpen ? "Close mobile menu" : "Open mobile menu"); 106 nav.classList.toggle("open", isOpen); 107 document.body.classList.toggle("nav-open", isOpen); 108 } 109 110 function setSubmenuState(item, isOpen) { 111 var toggle = getToggle(item); 112 var menu = getSubmenu(item); 113 if (!item || !toggle || !menu) return; 114 item.classList.toggle("open", isOpen); 115 toggle.setAttribute("aria-expanded", isOpen ? "true" : "false"); 116 menu.classList.toggle("open", isOpen); 117 } 118 119 function closeAllSubmenus(exceptItem) { 120 Array.prototype.forEach.call(nav.querySelectorAll(".nav-item.has-submenu"), function (item) { 121 if (item !== exceptItem) setSubmenuState(item, false); 122 }); 123 } 124 125 function closeDrawer() { 126 setDrawerState(false); 127 closeAllSubmenus(); 128 } 129 130 button.setAttribute("aria-label", "Open mobile menu"); 131 button.addEventListener("click", function () { 132 var willOpen = !nav.classList.contains("open"); 133 if (!willOpen) closeAllSubmenus(); 134 setDrawerState(willOpen); 135 }); 136 137 Array.prototype.forEach.call(nav.querySelectorAll(".nav-item.has-submenu"), function (item, index) { 138 var toggle = getToggle(item); 139 var menu = getSubmenu(item); 140 var openedByPointerHover = false;
141 if (!toggle || !menu) return; 142 if (!menu.id) menu.id = "nav-submenu-" + (index + 1); 143 toggle.setAttribute("aria-controls", menu.id); 144 toggle.setAttribute("aria-expanded", "false"); 145 146 item.addEventListener("mouseenter", function () { 147 if (isMobile()) return; 148 openedByPointerHover = !item.classList.contains("open"); 149 closeAllSubmenus(item); 150 setSubmenuState(item, true); 151 }); 152 153 item.addEventListener("mouseleave", function () { 154 openedByPointerHover = false; 155 if (isMobile() || item.matches(":focus-within")) return; 156 setSubmenuState(item, false); 157 }); 158 159 item.addEventListener("focusin", function (event) { 160 if (isMobile() || suppressFocusOpen) return; 161 // The disclosure button owns its activation state. Opening it on focus and then toggling it 162 // again in the click handler made a first pointer click immediately close the panel. The 163 // adjacent destination link still opens the disclosure for desktop keyboard exploration. 164 if (event.target === toggle) return; 165 openedByPointerHover = false; 166 closeAllSubmenus(item); 167 setSubmenuState(item, true); 168 }); 169 170 item.addEventListener("focusout", function (event) { 171 if (isMobile() || item.contains(event.relatedTarget)) return; 172 setSubmenuState(item, false); 173 }); 174 175 toggle.addEventListener("click", function (event) { 176 event.preventDefault(); 177 // A first pointer click is preceded by mouseenter, which already synchronized the visible 178 // panel and aria-expanded. Keep that first activation open; later activations still toggle. 179 if (!isMobile() && openedByPointerHover) { 180 openedByPointerHover = false; 181 setSubmenuState(item, true); 182 return; 183 } 184 var isOpen = item.classList.contains("open"); 185 closeAllSubmenus(item); 186 setSubmenuState(item, !isOpen); 187 }); 188 }); 189 190 Array.prototype.forEach.call(nav.querySelectorAll("a[href]"), function (link) { 191 link.addEventListener("click", function () { 192 if (isMobile()) closeDrawer(); 193 }); 194 }); 195 196 Array.prototype.forEach.call(nav.querySelectorAll("button.quote-trigger"), function (trigger) { 197 trigger.addEventListener("click", function () { 198 if (isMobile()) closeDrawer(); 199 }); 200 }); 201 202 document.addEventListener("click", function (event) { 203 if (navWrap.contains(event.target)) return; 204 closeAllSubmenus(); 205 if (isMobile()) closeDrawer(); 206 }); 207 208 document.addEventListener("keydown", function (event) { 209 if (event.key !== "Escape") return; 210 var openToggle = getToggle(nav.querySelector(".nav-item.has-submenu.open")); 211 closeDrawer(); 212 if (!isMobile() && openToggle) { 213 suppressFocusOpen = true; 214 openToggle.focus(); 215 window.setTimeout(function () { suppressFocusOpen = false; }, 0); 216 } 217 }); 218 219 window.addEventListener("resize", closeDrawer); 220})(); 221 222// Add focus containment, focus restoration, and inert background handling to the shared 223// controller so every generated mobile drawer exposes the same accessible modal behavior. 224(function () { 225 "use strict"; 226 227 var nav = document.getElementById("primaryNav"); 228 var button = document.querySelector(".hbg[aria-controls=\"primaryNav\"]"); 229 var navWrap = document.querySelector("#nav .nav-wrap"); 230 var desktopQuery = window.matchMedia("(min-width: 1025px)"); 231 if (!nav || !button || !navWrap || !("inert" in HTMLElement.prototype)) return; 232 233 var isEnhancedOpen = false; 234 var returnFocus = null; 235 var activationClose = false; 236 var managedInert = []; 237 var overlayStyle = document.createElement("style"); 238 overlayStyle.textContent = "body.nav-open > [data-nav-drawer-overlay]{visibility:hidden!important}"; 239 document.head.appendChild(overlayStyle); 240 241 function isMenuOpen() { 242 return !desktopQuery.matches && nav.classList.contains("open"); 243 } 244 245 function isVisible(element) { 246 if (!element || element.closest("[inert]")) return false; 247 var style = window.getComputedStyle(element); 248 return style.display !== "none" && 249 style.visibility !== "hidden" && 250 element.getClientRects().length > 0; 251 } 252 253 function getNavFocusables() { 254 var selector = "a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not
254([tabindex=\"-1\"])"; 255 return Array.prototype.filter.call(nav.querySelectorAll(selector), isVisible); 256 } 257 258 function getFocusLoop() { 259 var focusables = getNavFocusables(); 260 focusables.unshift(button); 261 return focusables; 262 } 263 264 function rememberInert(element) { 265 if (!element || element.inert || element.hasAttribute("data-nav-drawer-inert")) return; 266 element.inert = true; 267 element.setAttribute("data-nav-drawer-inert", ""); 268 if (element.parentElement === document.body) { 269 var position = window.getComputedStyle(element).position; 270 if (position === "fixed" || position === "sticky") { 271 element.setAttribute("data-nav-drawer-overlay", ""); 272 } 273 } 274 managedInert.push(element); 275 } 276 277 function applyBackgroundInert() { 278 Array.prototype.forEach.call(document.body.children, function (element) { 279 if (element.contains(nav) || /^(SCRIPT|STYLE|NOSCRIPT|TEMPLATE)$/.test(element.tagName)) return; 280 rememberInert(element); 281 }); 282 283 Array.prototype.forEach.call(navWrap.children, function (element) { 284 if (element === nav || element === button || element.contains(nav) || element.contains(button)) return; 285 rememberInert(element); 286 }); 287 } 288 289 function releaseBackgroundInert() { 290 managedInert.forEach(function (element) { 291 if (!element.hasAttribute("data-nav-drawer-inert")) return; 292 element.inert = false; 293 element.removeAttribute("data-nav-drawer-inert"); 294 element.removeAttribute("data-nav-drawer-overlay"); 295 }); 296 managedInert = []; 297 } 298 299 function focusFirstNavItem() { 300 window.requestAnimationFrame(function () { 301 if (!isMenuOpen()) return; 302 var focusables = getNavFocusables(); 303 (focusables[0] || button).focus(); 304 }); 305 } 306 307 function restoreMenuFocus() { 308 window.requestAnimationFrame(function () { 309 var current = document.activeElement; 310 var modalHasFocus = current && current !== document.body && !navWrap.contains(current); 311 if (modalHasFocus) return; 312 var target = returnFocus && returnFocus.isConnected ? returnFocus : button; 313 target.focus(); 314 }); 315 } 316 317 function syncAccessibleState() { 318 var open = isMenuOpen(); 319 if (open === isEnhancedOpen) return; 320 321 isEnhancedOpen = open; 322 if (open) { 323 returnFocus = document.activeElement; 324 activationClose = false; 325 applyBackgroundInert(); 326 focusFirstNavItem(); 327 return; 328 } 329 330 releaseBackgroundInert(); 331 if (!activationClose) restoreMenuFocus(); 332 activationClose = false; 333 } 334 335 // Release inert synchronously when a menu action launches another UI (for example, 336 // the quote modal). This lets that action move focus before the class observer runs. 337 document.addEventListener("click", function (event) { 338 if (!isEnhancedOpen) return; 339 var action = event.target && event.target.closest ? 340 event.target.closest("a[href], button.quote-trigger") : null; 341 if (!action || !nav.contains(action) || action.matches("[data-nav-toggle=\"submenu\"]")) return; 342 activationClose = true; 343 releaseBackgroundInert(); 344 window.setTimeout(function () { 345 if (isMenuOpen()) { 346 activationClose = false; 347 applyBackgroundInert(); 348 } 349 }, 0); 350 }, true); 351 352 document.addEventListener("keydown", function (event) { 353 if (event.key !== "Tab" || !isMenuOpen()) return; 354 var focusables = getFocusLoop(); 355 if (!focusables.length) return; 356 var currentIndex = focusables.indexOf(document.activeElement); 357 var nextIndex; 358 if (event.shiftKey) { 359 nextIndex = currentIndex <= 0 ? focusables.length - 1 : currentIndex - 1; 360 } else { 361 nextIndex = currentIndex < 0 || currentIndex === focusables.length - 1 ? 0 : currentIndex + 1; 362 } 363 event.preventDefault(); 364 focusables[nextIndex].focus(); 365 }); 366 367 var stateObserver = new MutationObserver(syncAccessibleState); 368 stateObserver.observe(nav, { attributes: true, attributeFilter: ["class"] }); 369 stateObserver.observe(button, { attributes: true, attributeFilter: ["aria-expanded"] }); 370 371 var backgroundObserver = new MutationObserver(function () {
372 if (isEnhancedOpen) applyBackgroundInert(); 373 }); 374 backgroundObserver.observe(document.body, { childList: true }); 375 backgroundObserver.observe(navWrap, { childList: true }); 376 377 syncAccessibleState(); 378})(); 379 380// Keep third-party quote-form microcopy cleanup scoped to the stable widget hosts. The widget 381// can inject duplicate consent text or its own sharing sentence; process only initial content 382// and added nodes, then disconnect after the widget's bounded startup window. 383(function () { 384 "use strict"; 385 386 var sharingSentence = /we will never share your information\.?/gi; 387 var hostSelector = "#fullScreenModal, #booking-form-modal-container, #hero-quote, #instant-quote"; 388 389 function normalize(element) { 390 return (element.textContent || "").replace(/\s+/g, " ").trim().toLowerCase(); 391 } 392 393 function stripSharingSentence(root) { 394 if (!root) return; 395 if (root.nodeType === Node.TEXT_NODE) { 396 root.nodeValue = (root.nodeValue || "").replace(sharingSentence, "").replace(/\s{2,}/g, " "); 397 sharingSentence.lastIndex = 0; 398 return; 399 } 400 if (root.nodeType !== Node.ELEMENT_NODE) return; 401 var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT); 402 var node; 403 while ((node = walker.nextNode())) { 404 node.nodeValue = (node.nodeValue || "").replace(sharingSentence, "").replace(/\s{2,}/g, " "); 405 sharingSentence.lastIndex = 0; 406 } 407 } 408 409 function dedupeConsent(container) { 410 var candidates = container.querySelectorAll("p, small, div"); 411 var seen = Object.create(null); 412 for (var i = 0; i < candidates.length; i++) { 413 var element = candidates[i]; 414 if (element.children.length > 0 && !/^(p|small)$/i.test(element.tagName)) continue; 415 var text = normalize(element); 416 if (text.indexOf("by submitting this form") !== 0) continue; 417 if (seen[text]) { 418 if (element.style.display !== "none") element.style.display = "none"; 419 } else { 420 seen[text] = true; 421 } 422 } 423 } 424 425 function repairBookingFormLabels(container) { 426 var mappings = { 427 "first-name": { selector: '[name="pickup"]', autocomplete: "section-pickup address-level2" }, 428 "last-name": { selector: '[name="dropoff"]', autocomplete: "section-dropoff address-level2" }, 429 mobile: { selector: '[name="mobile_number"]', autocomplete: "tel", inputMode: "tel" }, 430 email: { selector: '[name="email_address"]', autocomplete: "email", inputMode: "email" } 431 }; 432 Object.keys(mappings).forEach(function (originalFor) { 433 var labels = container.querySelectorAll('label[for="' + originalFor + '"]'); 434 Array.prototype.forEach.call(labels, function (label, labelIndex) { 435 var form = label.closest("form") || container; 436 if (form.querySelector("#" + originalFor)) return; 437 var config = mappings[originalFor]; 438 var control = form.querySelector(config.selector); 439 if (!control) return; 440 var candidate = originalFor; 441 if (document.getElementById(candidate) && document.getElementById(candidate) !== control) { 442 candidate = originalFor + "-booking-" + (labelIndex + 1); 443 var suffix = 1; 444 while (document.getElementById(candidate) && document.getElementById(candidate) !== control) { 445 suffix += 1; 446 candidate = originalFor + "-booking-" + suffix; 447 } 448 } 449 control.id = candidate; 450 label.htmlFor = candidate; 451 if (!control.getAttribute("autocomplete")) control.setAttribute("autocomplete", config.autocomplete); 452 if (config.inputMode && !control.getAttribute("inputmode")) { 453 control.setAttribute("inputmode", config.inputMode); 454 } 455 }); 456 }); 457 } 458 459 function cleanupScope(scope) { 460 stripSharingSentence(scope); 461 dedupeConsent(scope); 462 repairBookingFormLabels(scope); 463 } 464 465 var hosts = Array.prototype.slice.call(document.querySelectorAll(hostSelector)); 466 if (!hosts.length) return; 467 hosts.forEach(cleanupScope); 468 if (!("MutationObserver" in window)) return; 469 470 var observer = new MutationObserver(function (records) { 471 var scopes = []; 472 records.forEach(function (record) { 473 Array.prototype.forEach.call(record.addedNodes, stripSharingSentence); 474 var form = record.target.nodeType === Node.ELEMENT_NODE && record.target.closest ? 475 record.target.closest("form") : null; 476 var scope = form || hosts.find(function (host) { return host.contains(record.target); }); 477 if (scope && scopes.indexOf(scope) === -1) scopes.push(scope); 478 });
479 scopes.forEach(function (scope) { 480 dedupeConsent(scope); 481 repairBookingFormLabels(scope); 482 }); 483 }); 484 hosts.forEach(function (host) { 485 observer.observe(host, { childList: true, subtree: true }); 486 }); 487 window.setTimeout(function () { observer.disconnect(); }, 15000); 488})(); 489 490// One state controller owns floating CTA visibility, on two INDEPENDENT signals: 491// DESKTOP widget -> the hero. Hidden while the page hero intersects the viewport; revealed 492// once it has scrolled fully out. Pages with no hero at all fall back to 493// the one-viewport threshold rather than appearing at load. 494// MOBILE actions + "Back to Top" -> the one-viewport threshold, on every page, always. 495// These used to be coupled: mobile rode the hero observer, so its reveal point silently 496// depended on whether the hero lookup happened to succeed. IntersectionObserver owns 497// visibility when available; geometry-based scroll handling is the compatibility fallback. 498(function () { 499 "use strict"; 500 501 var desktopWidget = document.getElementById("desktopCtaWidget"); 502 var mobileWidget = document.querySelector(".mobile-cta-widget"); 503 var footer = document.querySelector("footer"); 504 //: three-tier hero lookup. Order is deliberate and must not be reordered: 505 // 1. [data-page-hero="true"] -- the stable contract the generator stamps on the OUTER 506 // hero band of newly built NON-NATIONAL home/city pages. (National builds strip the 507 // VARIANT:hero markers the stamper keys on, so they resolve via #hero at step 2.) 508 // 2. #hero -- legacy nested hero. Kept SECOND so an already-shipped page that receives 509 // only the new site-ui.js keeps observing exactly the element it observed before. 510 // 3. #intro-layout -- the outer band, present on every hero variant. This is what fixes 511 // sanmarcoscharterbuscompany.com, whose hero variant 512 // (hero-mclaren-golf-lower-left-overlay.html) is the one approved variant with no 513 // nested #hero: the lookup used to return null, the else branch below stripped 514 // is-hero-hidden at load, and the desktop CTA painted at scrollY 0. 515 // The explicit ="true" match is required so data-page-hero="false" or an empty value can 516 // never silently become an active controller target. 517 var hero = document.querySelector('[data-page-hero="true"]') || 518 document.getElementById("hero") || 519 document.getElementById("intro-layout"); 520 var backToTopControls = Array.prototype.slice.call(document.querySelectorAll( 521 ".back-to-top, .mobile-cta-top[href='#top']" 522 )); 523 var mobileReady = false; 524 525 if (!desktopWidget && !mobileWidget) return; 526 527 function intersectsViewport(element) { 528 var rect = element.getBoundingClientRect(); 529 return rect.bottom > 0 && rect.top < window.innerHeight; 530 } 531
532 function setFooterHidden(isHidden) { 533 if (desktopWidget) desktopWidget.classList.toggle("is-footer-hidden", isHidden); 534 if (mobileWidget) mobileWidget.classList.toggle("is-footer-hidden", isHidden); 535 } 536 537 function syncResponsiveVisibility() { 538 if (desktopWidget) { 539 desktopWidget.classList.toggle("is-visible", window.innerWidth > 1024); 540 } 541 if (mobileWidget) { 542 mobileWidget.classList.toggle("is-visible", window.innerWidth <= 1024 && mobileReady); 543 } 544 } 545 546 function setMobileReady(isReady) { 547 mobileReady = !!isReady; 548 syncResponsiveVisibility(); 549 } 550 551 //: the hero observer drives the DESKTOP widget only. Mobile used to ride this 552 // same signal, which meant mobile's reveal point silently depended on whether the hero 553 // lookup happened to succeed. The two contracts are now decoupled: desktop follows the 554 // hero, mobile follows the one-viewport threshold, always. 555 function setHeroIntersecting(isIntersecting) { 556 if (desktopWidget) desktopWidget.classList.toggle("is-hero-hidden", isIntersecting); 557 } 558 559 function setPastPageThreshold(isPastThreshold) { 560 backToTopControls.forEach(function (control) { 561 control.classList.toggle("is-scroll-ready", !!isPastThreshold); 562 }); 563 // Mobile is threshold-driven unconditionally now -- no longer gated on `!hero`. 564 setMobileReady(isPastThreshold); 565 // A page with NO hero at all (e.g. /contact-us/) still must not show the desktop CTA at 566 // load; it falls back to the same one-viewport threshold rather than appearing instantly. 567 if (!hero && desktopWidget) { 568 desktopWidget.classList.toggle("is-hero-hidden", !isPastThreshold); 569 } 570 } 571 572 function bindVisibility(element, setter, rootMargin) { 573 if (!element) return; 574 setter(intersectsViewport(element)); 575 if ("IntersectionObserver" in window) { 576 var observer = new IntersectionObserver(function (entries) { 577 setter(entries.some(function (entry) { return entry.isIntersecting; })); 578 }, { threshold: 0, rootMargin: rootMargin || "0px" }); 579 observer.observe(element); 580 return; 581 } 582 583 function sync() { 584 setter(intersectsViewport(element)); 585 } 586 window.addEventListener("scroll", sync, { passive: true }); 587 window.addEventListener("resize", sync); 588 window.addEventListener("load", sync, { once: true }); 589 } 590 591 function bindPageThreshold() { 592 var marker = document.createElement("span"); 593 marker.setAttribute("aria-hidden", "true"); 594 marker.className = "page-scroll-threshold-sentinel"; 595 marker.style.cssText = "position:absolute;top:100vh;inset-inline-start:0;width:1px;height:1px;pointer-events:none"; 596 document.body.appendChild(marker); 597 598 function sync() { 599 setPastPageThreshold(marker.getBoundingClientRect().top <= 0); 600 } 601 602 sync(); 603 if ("IntersectionObserver" in window) { 604 var observer = new IntersectionObserver(function (entries) { 605 entries.forEach(function (entry) { 606 setPastPageThreshold(entry.boundingClientRect.top <= 0); 607 }); 608 }, { threshold: 0 }); 609 observer.observe(marker); 610 return; 611 } 612 613 window.addEventListener("scroll", sync, { passive: true }); 614 window.addEventListener("resize", sync); 615 window.addEventListener("load", sync, { once: true }); 616 } 617 618 syncResponsiveVisibility(); 619 window.addEventListener("resize", syncResponsiveVisibility); 620 621 if (footer) { 622 bindVisibility(footer, setFooterHidden, "0px 0px -1px 0px"); 623 } 624 625 bindPageThreshold(); 626 627 //: the old `else` branch removed is-hero-hidden the moment the lookup failed, 628 // which is exactly how the desktop CTA came to paint at scrollY 0. Hero-less pages are now 629 // handled by the threshold fallback inside setPastPageThreshold() instead. 630 if (hero) { 631 bindVisibility(hero, setHeroIntersecting); 632 } 633})();
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.