PageSourceSearch

https://fortraders.org/wp-content/themes/ForTraderMaster/js/bybit-exact-menu.js?ver=2.20.34

js fortraders.org collected 2026-10-09 17:30:11 UTC 30,616 bytes, 874 lines download raw bytes

1/**
2 * Bybit Exact Menu JavaScript
3 * Точная функциональность меню как на Bybit.com
4 */
5
6(function($) {
7    'use strict';
8
9    if (typeof $ === 'undefined' || typeof jQuery === 'undefined') {
10        return;
11    }
12
13    function setCssIfChanged($element, property, value) {
14        if (!$element || !$element.length) {
15            return;
16        }
17
18        if ($element.css(property) !== value) {
19            $element.css(property, value);
20        }
21    }
22
23    function initializeMenu() {
24        try {
25            initBybitMenu();
26
27            setTimeout(function() {
28                const $toggle = $('.bybit-navbar__mobile-toggle');
29                const $mobileMenu = $('.bybit-navbar__mobile-menu');
30
31                if ($toggle.length > 0 && $mobileMenu.length > 0) {
32                    try {
33                        const events = $._data($toggle[0], 'events');
34                        if (!events || !events.click || !events.click.some(function(handler) {
35                            return handler.namespace === 'bybitMobileMenu' || handler.namespace === 'bybitMobileMenuDirect';
36                        })) {
37                            initMobileMenu();
38                        }
39                    } catch (error) {
40                        initMobileMenu();
41                    }
42                } else {
43                    setTimeout(function() {
44                        if ($('.bybit-navbar__mobile-toggle').length > 0 && $('.bybit-navbar__mobile-menu').length > 0) {
45                            initMobileMenu();
46                        }
47                    }, 1000);
48                }
49            }, 500);
50        } catch (error) {
51            return;
52        }
53    }
54
55    if (document.readyState === 'loading') {
56        $(document).ready(initializeMenu);
57    } else {
58        initializeMenu();
59    }
60
61    function initBybitMenu() {
62        try {
63            adjustForAdminBar();
64            initMobileMenu();
65            initSearch();
66            initDropdowns();
67            initUserMenu();
68            initAuthControls();
69            initLanguageSelector();
70            initOverflowMenu();
71            handleScroll();
72        } catch (error) {
73            return;
74        }
75
76        $(window).on('resize', function() {
77            setTimeout(adjustForAdminBar, 100);
78            initOverflowMenu();
79        });
80
81        let checkCount = 0;
82        const checkInterval = setInterval(function() {
83            adjustForAdminBar();
84            checkCount++;
85            if (checkCount >= 3) {
86                clearInterval(checkInterval);
87            }
88        }, 500);
89    }
90
91    // v2.17.940: Create auth controls in the browser so crawlers do not receive login links in HTML.
92    function initAuthControls() {
93        $('[data-auth-control]').each(function() {
94            const placeholder = this;
95            const label = placeholder.getAttribute('data-auth-label') || 'Log In';
96            const mode = placeholder.getAttribute('data-auth-control') || 'desktop';
97            const authUrl = buildAuthUrl();
98
99            if (!authUrl) {
100                return;
101            }
102
103            const button = document.createElement('button');
104            button.type = 'button';
105            button.className = mode === 'mobile' ? 'bybit-navbar__mobile-login' : 'bybit-navbar__login-btn';
106            button.textContent = label;
107            button.addEventListener('click', function() {
108                window.location.href = authUrl;
109            });
110
111            placeholder.replaceWith(button);
112        });
113    }
114
115    // v1: language prefix of the current URL ('/de'), '' on the default-language root
116    function getLanguagePrefix() {
117        const match = (window.location.pathname || '/').match(/^\/(en|ru|de|ar|lv)(?:\/|$)/);
118        return match ? '/' + match[1] : '';
119    }
120
121    // v2: prefix taken from the current URL, no language is assumed to be the default
122    function buildAuthUrl() {
123        return window.location.origin + getLanguagePrefix() + '/traders-access/?redirect_to=' + encodeURIComponent(window.location.href);
124    }
125
126    function adjustForAdminBar() {
127        let adminBar = $('#wpadminbar');
128        if (adminBar.length === 0) {
129            adminBar = $('.admin-bar #wpadminbar');
130        }
131        if (adminBar.length === 0) {
132            adminBar = $('body.admin-bar').find('#wpadminbar');
133        }
134
135        const $navbar = $('.bybit-navbar');
136        const $mobileMenu = $('.bybit-navbar__mobile-menu');
137        const $body = $('body');
138
139        if (adminBar.length > 0 && adminBar.is(':visible')) {
140            const adminBarHeight = adminBar.outerHeight() || 32;
141            const menuHeight = $navbar.outerHeight() || 64;
142            const totalOffset = adminBarHeight + menuHeight;
143
144            setCssIfChanged($navbar, 'top', adminBarHeight + 'px');
145            setCssIfChanged($navbar, 'z-index', '99999');
146            setCssIfChanged($body, 'padding-top', totalOffset + 'px');
147            setCssIfChanged($mobileMenu, 'top', adminBarHeight + 'px');
148            $body.addClass('bybit-admin-bar-detected');
149        } else {
150            const isMobile = $(window).width() <= 768;
151            const menuOffset = isMobile ? '56px' : '64px';
152            setCssIfChanged($navbar, 'top', '0px');
153            setCssIfChanged($navbar, 'z-index', '999');
154            setCssIfChanged($body, 'padding-top', menuOffset);
155            setCssIfChanged($mobileMenu, 'top', '0px');
156            $body.removeClass('bybit-admin-bar-detected');
157        }
158    }
159    
160    // Mobile Menu
161    // v2.18: mobile drawer with overlay, focus trap and scroll lock.
162    function initMobileMenu() {
163        const toggle = document.querySelector('.bybit-navbar__mobile-toggle');
164        const drawer = document.querySelector('.bybit-navbar__mobile-menu');
165
166        if (!toggle || !drawer) {
167            return;
168        }
169
170        if (toggle.dataset.drawerReady === '1') {
171            return;
172        }
173        toggle.dataset.drawerReady = '1';
174
175        const navbar = document.querySelector('.bybit-navbar');
176        const closeBtn = drawer.querySelector('.bybit-navbar__mobile-close');
177        const overlay = document.querySelector('[data-menu-overlay]');
178        const openLabel = toggle.getAttribute('aria-label') || 'Open menu';
179        const closeLabel = closeBtn ? (closeBtn.getAttribute('aria-label') || 'Close menu') : 'Close menu';
180        const focusableSelector = 'a[href], button:not([disabled]), input:not([disabled]), select, textarea, [tabindex]:not([tabindex="-1"])';
181
182        let lastFocused = null;
183        let scrollbarCompensation = 0;
184
185        function isOpen() {
186            return drawer.classList.contains('active');
187        }
188
189        function visibleFocusables() {
190            return Array.prototype.filter.call(
191                drawer.querySelectorAll(focusableSelector),
192                function(el) {
193                    return el.offsetWidth > 0 || el.offsetHeight > 0 || el === document.activeElement;
194                }
195            );
196        }
197
198        function lockScroll() {
199            scrollbarCompensation = window.innerWidth - document.documentElement.clientWidth;
200            document.body.classList.add('bybit-menu-open');
201            if (scrollbarCompensation > 0) {
202                document.body.style.paddingRight = scrollbarCompensation + 'px';
203            }
204        }
205
206        function unlockScroll() {
207            document.body.classList.remove('bybit-menu-open');
208            document.body.style.paddingRight = '';
209        }
210
211        function openDrawer() {
212            if (isOpen()) {
213                return;
214            }
215
216            lastFocused = document.activeElement;
217
218            if (overlay) {
219                overlay.hidden = false;
220                // force reflow so the fade-in actually runs
221                void overlay.offsetWidth;
222                overlay.classList.add('active');
223            }
224
225            drawer.classList.add('active');
226            toggle.classList.add('active');
227            toggle.setAttribute('aria-expanded', 'true');
228            toggle.setAttribute('aria-label', closeLabel);
229            lockScroll();
230
231            const focusTarget = closeBtn || visibleFocusables()[0];
232            if (focusTarget) {
233                window.setTimeout(function() { focusTarget.focus(); }, 60);
234            }
235        }
236
237        function closeDrawer(returnFocus) {
238            if (!isOpen()) {
239                return;
240            }
241
242            drawer.classList.remove('active');
243            toggle.classList.remove('active');
244            toggle.setAttribute('aria-expanded', 'false');
245            toggle.setAttribute('aria-label', openLabel);
246
247            if (overlay) {
248                overlay.classList.remove('active');
249                window.setTimeout(function() {
250                    if (!isOpen()) {
251                        overlay.hidden = true;
252                    }
253                }, 260);
254            }
255
256            unlockScroll();
257
258            drawer.querySelectorAll('.bybit-navbar__mobile-item.expanded').forEach(function(item) {
259                item.classList.remove('expanded');
260                const btn = item.querySelector('.bybit-navbar__mobile-toggle-btn');
261                if (btn) {
262                    btn.setAttribute('aria-expanded', 'false');
263                }
264            });
265
266            if (returnFocus !== false && lastFocused && typeof lastFocused.focus === 'function') {
267                lastFocused.focus();
268            }
269        }
270
271        toggle.addEventListener('click', function(event) {
272            event.preventDefault();
273            event.stopPropagation();
274            if (isOpen()) {
275                closeDrawer();
276            } else {
277                openDrawer();
278            }
279        });
280
281        if (closeBtn) {
282            closeBtn.addEventListener('click', function(event) {
283                event.preventDefault();
284                closeDrawer();
285            });
286        }
287
288        if (overlay) {
289            overlay.addEventListener('click', function() {
290                closeDrawer();
291            });
292        }
293
294        document.addEventListener('click', function(event) {
295            if (!isOpen()) {
296                return;
297            }
298            if (drawer.contains(event.target) || (navbar && navbar.contains(event.target))) {
299                return;
300            }
301            closeDrawer(false);
302        });
303
304        // Navigating away from the drawer should not leave it open behind the new page
305        drawer.addEventListener('click', function(event) {
306            const link = event.target.closest('a[href]');
307            if (link && !link.classList.contains('bybit-navbar__language-option')) {
308                closeDrawer(false);
309            }
310        });
311
312        document.addEventListener('keydown', function(event) {
313            if (!isOpen()) {
314                return;
315            }
316
317            if (event.key === 'Escape') {
318                event.preventDefault();
319                closeDrawer();
320                return;
321            }
322
323            if (event.key !== 'Tab') {
324                return;
325            }
326
327            const focusables = visibleFocusables();
328            if (!focusables.length) {
329                return;
330            }
331
332            const first = focusables[0];
333            const last = focusables[focusables.length - 1];
334
335            if (event.shiftKey && document.activeElement === first) {
336                event.preventDefault();
337                last.focus();
338            } else if (!event.shiftKey && document.activeElement === last) {
339                event.preventDefault();
340                first.focus();
341            } else if (!drawer.contains(document.activeElement)) {
342                event.preventDefault();
343                first.focus();
344            }
345        });
346
347        window.addEventListener('resize', function() {
348            if (window.innerWidth >= 900 && isOpen()) {
349                closeDrawer(false);
350            }
351        });
352
353        // Accordion disclosure
354        drawer.querySelectorAll('.bybit-navbar__mobile-toggle-btn').forEach(function(btn
354) {
355            btn.addEventListener('click', function(event) {
356                event.preventDefault();
357                event.stopPropagation();
358
359                const item = btn.closest('.bybit-navbar__mobile-item');
360                if (!item) {
361                    return;
362                }
363
364                const willExpand = !item.classList.contains('expanded');
365
366                drawer.querySelectorAll('.bybit-navbar__mobile-item.expanded').forEach(function(other) {
367                    if (other === item) {
368                        return;
369                    }
370                    other.classList.remove('expanded');
371                    const otherBtn = other.querySelector('.bybit-navbar__mobile-toggle-btn');
372                    if (otherBtn) {
373                        otherBtn.setAttribute('aria-expanded', 'false');
374                    }
375                });
376
377                item.classList.toggle('expanded', willExpand);
378                btn.setAttribute('aria-expanded', willExpand ? 'true' : 'false');
379            });
380        });
381    }
382    
383    // Search Functionality
384    function initSearch() {
385        const $searchBtn = $('.bybit-navbar__search-btn');
386        const $searchDropdown = $('.bybit-navbar__search-dropdown');
387        const $searchInput = $('.bybit-navbar__search-input');
388        const $mobileSearchInput = $('.bybit-navbar__mobile-search-input');
389        
390        $searchBtn.on('click', function(e) {
391            e.preventDefault();
392            e.stopPropagation();
393            
394            const isActive = $searchDropdown.hasClass('active');
395            
396            if (isActive) {
397                closeSearch();
398            } else {
399                openSearch();
400            }
401        });
402        
403        // Close on outside click
404        $(document).on('click', function(e) {
405            if (!$(e.target).closest('.bybit-navbar__search').length && $searchDropdown.hasClass('active')) {
406                closeSearch();
407            }
408        });
409        
410        // Close on escape
411        $(document).on('keydown', function(e) {
412            if (e.keyCode === 27 && $searchDropdown.hasClass('active')) {
413                closeSearch();
414            }
415        });
416        
417        // Handle search input (desktop)
418        $searchInput.on('keydown', function(e) {
419            if (e.keyCode === 13) { // Enter
420                e.preventDefault();
421                performSearch($(this).val());
422            }
423        });
424        
425        // Handle mobile search input
426        $mobileSearchInput.on('keydown', function(e) {
427            if (e.keyCode === 13) { // Enter
428                e.preventDefault();
429                performSearch($(this).val());
430            }
431        });
432        
433        function openSearch() {
434            $searchDropdown.addClass('active');
435            setTimeout(() => {
436                $searchInput.focus();
437            }, 100);
438        }
439        
440        function closeSearch() {
441            $searchDropdown.removeClass('active');
442            $searchInput.val('');
443        }
444        
445        // v2: keep whatever language prefix the current URL has
446        function performSearch(query) {
447            if (query.trim()) {
448                window.location.href = getLanguagePrefix() + '/?s=' + encodeURIComponent(query.trim());
449            }
450        }
451    }
452    
453    // Dropdown Menus
454    function initDropdowns() {
455        const $menuItems = $('.bybit-navbar__menu-item.has-dropdown');
456        let hoverTimeout;
457        
458        $menuItems.each(function() {
459            const $item = $(this);
460            const $dropdown = $item.find('.bybit-navbar__dropdown');
461            
462            $item.on('mouseenter', function() {
463                clearTimeout(hoverTimeout);
464                $dropdown.addClass('show');
465            });
466            
467            $item.on('mouseleave', function() {
468                hoverTimeout = setTimeout(() => {
469                    $dropdown.removeClass('show');
470                }, 150);
471            });
472            
473            // Handle keyboard navigation
474            $item.find('a').first().on('keydown', function(e) {
475                if (e.keyCode === 40) { // Arrow down
476                    e.preventDefault();
477                    $dropdown.addClass('show');
478                    $dropdown.find('a').first().focus();
479                }
480            });
481            
482            $dropdown.find('a').on('keydown', function(e) {
483                const $links = $dropdown.find('a');
484                const currentIndex = $links.index(this);
485                
486                if (e.keyCode === 38) { // Arrow up
487                    e.preventDefault();
488                    if (currentIndex > 0) {
489                        $links.eq(currentIndex - 1).focus();
490                    } else {
491                        $item.find('a').first().focus();
492                        $dropdown.removeClass('show');
493                    }
494                } else if (e.keyCode === 40) { // Arrow down
495                    e.preventDefault();
496                    if (currentIndex < $links.length - 1) {
497                        $links.eq(currentIndex + 1).focus();
498                    }
499                } else if (e.keyCode === 27) { // Escape
500                    e.preventDefault();
501                    $dropdown.removeClass('show');
502                    $item.find('a').first().focus();
503                }
504                });
505
506});
507        
508        // Close dropdowns on outside click
509        $(document).on('click', function(e) {
510            if (!$(e.target).closest('.bybit-navbar__menu-item').length) {
511                $('.bybit-navbar__dropdown').removeClass('show');
512            }
513        });
514    }
515    
516    // User Menu
517    function initUserMenu() {
518        const $userMenu = $('.bybit-navbar__user-menu');
519        const $userDropdown = $('.bybit-navbar__user-dropdown');
520        let userHoverTimeout;
521        
522        $userMenu.on('mouseenter', function() {
523            clearTimeout(userHoverTimeout);
524            $userDropdown.addClass('show');
525        });
526        
527        $userMenu.on('mouseleave', function() {
528            userHoverTimeout = setTimeout(() => {
529                $userDropdown.removeClass('show');
530            }, 150);
531        });
532        
533        // Close on outside click
534        $(document).on('click', function(e) {
535            if (!$(e.target).closest('.bybit-navbar__user-menu').length) {
536                $userDropdown.removeClass('show');
537            }
538        });
539    }
540    
541    // Language Selector
542    function initLanguageSelector() {
543        $('.bybit-navbar__language-toggle').off('click.languageSwitch');
544        $('.bybit-navbar__language-option').off('click.languageSwitch');
545        $(document).off('click.languageSwitch');
546        $(document).off('keydown.languageSwitch');
547
548        function closeLanguageMenus() {
549            $('.bybit-navbar__language.is-open, .bybit-navbar__mobile-language.is-open').removeClass('is-open');
550            $('.bybit-navbar__language-toggle').attr('aria-expanded', 'false');
551        }
552
553        $('.bybit-navbar__language-toggle').on('click.languageSwitch', function(e) {
554            e.preventDefault();
555            e.stopPropagation();
556
557            const $wrap = $(this).closest('.bybit-navbar__language, .bybit-navbar__mobile-language');
558            const isOpen = $wrap.hasClass('is-open');
559
560            closeLanguageMenus();
561
562            if (!isOpen) {
563                $wrap.addClass('is-open');
564                $(this).attr('aria-expanded', 'true');
565            }
566        });
567
568        $('.bybit-navbar__language-option.is-current').on('click.languageSwitch', function(e) {
569            e.preventDefault();
570            closeLanguageMenus();
571        });
572
573        $(document).on('click.languageSwitch', function(e) {
574            if (!$(e.target).closest('.bybit-navbar__language, .bybit-navbar__mobile-language').length) {
575                closeLanguageMenus();
576            }
577        });
578
579        $(document).on('keydown.languageSwitch', function(e) {
580            if (e.key === 'Escape' || e.keyCode === 27) {
581                closeLanguageMenus();
582            }
583        });
584    }
585    
586    // Scroll handling
587    function handleScroll() {
588        const $navbar = $('.bybit-navbar');
589        let isScrolled = false;
590        
591        $(window).on('scroll', function() {
592            const scrollTop = $(window).scrollTop();
593            
594            // Get admin bar height if exists
595            const adminBarHeight = $('body').hasClass('admin-bar') ? 
596                ($(window).width() > 782 ? 32 : 46) : 0;
597            
598            if (scrollTop > adminBarHeight && !isScrolled) {
599                $navbar.addClass('scrolled');
600                isScrolled = true;
601            } else if (scrollTop <= adminBarHeight && isScrolled) {
602                $navbar.removeClass('scrolled');
603                isScrolled = false;
604            }
605        });
606    }
607    
608    // Add smooth scrolling for anchor links
609    $('a[href^="#"]').on('click', function(e) {
610        const target = $(this.hash);
611        if (target.length) {
612            e.preventDefault();
613            
614            // Calculate offset based on admin bar and menu height
615            let offset = 64; // Default menu height
616            
617            // Check for admin bar
618            if ($('body').hasClass('admin-bar')) {
619                const adminBarHeight = $(window).width() > 782 ? 32 : 46;
620                offset += adminBarHeight;
621            }
622            
623            // Adjust for mobile
624            if ($(window).width() <= 768) {
625                offset = $('body').hasClass('admin-bar') ? 102 : 56;
626            }
627            
628            $('html, body').animate({
629                scrollTop: target.offset().top - offset
630            }, 600);
631        }
632    });
633    
634    // Prevent body scroll when mobile menu is open
635    const style = document.createElement('style');
636    style.textContent = `
637        body.bybit-menu-open {
638            overflow: hidden;
639            position: fixed;
640            width: 100%;
641        }
642    `;
643    document.head.appendChild(style);
644    
645    // Horizontal progress bar under logo with unique class
646    $(window).on('beforeunload', function() {
647        $('.bybit-navbar').addClass('bybit-logo-progress');
648        setTimeout(function() {
649            $('.bybit-navbar').removeClass('bybit-logo-progress');
650        }, 2200);
651    });
652    
653    // Aggressively remove old spinners on initialization
654    $(document).ready(function() {
655        // Remove all potential conflicting spinners
656        $('.nav_wrap.loading').removeClass('loading');
657        $('.loadingform').removeClass('loadingform');
658        $('.arcticmodal-loading, .owl-loading').hide();
659        
660        // Hide any spinners that might appear in navbar
661        $('.bybit-navbar').find('.spinner, .loader, .spinner-wrap, .loader-wrap, [class*="spinner"], [class*="loader"]').hide().remove();
662        
663        // Prevent future conflicts by intercepting old loading additions
664        var originalAddClass = $.fn.addClass;
665        $.fn.addClass = function(className) {
666            if (this.hasClass('bybit-navbar') && className === 'loading') {
667                return this; // Prevent adding 'loading' to bybit-navbar, only allow 'bybit-logo-progress'
668            }
669            return originalAddClass.call(this, className);
670        };
671    });
672    
673    // v2.18: priority+ navigation. Items that do not fit the row move into "More".
674    function initOverflowMenu() {
675        const nav = document.querySelector('.bybit-navbar__nav');
676        const menu = nav ? nav.querySelector('.bybit-navbar__menu') : null;
677        const overflowMenu = nav ? nav.querySelector('.bybit-navbar__overflow-menu') : null;
678        const dropdown = overflowMenu ? overflowMenu.querySelector('.bybit-navbar__overflow-dropdown') : null;
679        const toggle = overflowMenu ? overflowMenu.querySelector('.bybit-navbar__overflow-toggle') : null;
680
681        if (!nav || !menu || !overflowMenu || !dropdown || !toggle) {
682            return;
683        }
684
685        if (nav.dataset.overflowReady === '1') {
686            if (typeof nav._ftOverflowLayout === 'function') {
687                nav._ftOverflowLayout();
688            }
689            return;
690        }
691        nav.dataset.overflowReady = '1';
692
693        const items = Array.prototype.slice.call(menu.querySelectorAll('.bybit-navbar__menu-item'));
694        if (!items.length) {
695            return;
696        }
697
698        let metrics = null;
699        let lastVisibleCount = -1;
700        let frame = null;
701
702        function measureKey() {
703            const link = items[0].querySelector('.bybit-navbar__menu-link');
704            if (!link) {
705                return 'na';
706            }
707            const style = window.getComputedStyle(link);
708            return style.fontSize + '|' + style.paddingLeft;
709        }
710
711        function measure() {
712            const restore = items.map(function(item) { return item.hidden; });
713            const overflowWasHidden = overflowMenu.hidden;
714
715            items.forEach(function(item) { item.hidden = false; });
716            overflowMenu.hidden = false;
717
718            const widths = items.map(function(item) {
719                return Math.ceil(item.getBoundingClientRect().width);
720            });
721            const toggleWidth = Math.ceil(overflowMenu.getBoundingClientRect().width);
722            const gap = parseFloat(window.getComputedStyle(menu).columnGap) || 0;
723
724            items.forEach(function(item, index) { item.hidden = restore[index]; });
725            overflowMenu.hidden = overflowWasHidden;
726
727            return { widths: widths, toggleWidth: toggleWidth, gap: gap, key: measureKey() };
728        }
729
730        function getMetrics() {
731            if (!metrics || metrics.key !== measureKey() || !metrics.widths.length) {
732                metrics = measure();
733            }
734            return metrics;
735        }
736
737        function buildDropdown(hiddenItems) {
738            const fragment = document.createDocumentFragment();
739
740            hiddenItems.forEach(function(item) {
741                const group = document.createElement('div');
742                group.className = 'bybit-navbar__overflow-group';
743
744                const link = item.querySelector('.bybit-navbar__menu-link');
745                if (link) {
746                    const parentLink = document.createElement('a');
747                    parentLink.className = 'bybit-navbar__overflow-link bybit-navbar__overflow-link--parent';
748                    parentLink.href = link.getAttribute('href') || '#';
749                    parentLink.textContent = link.textContent.trim();
750                    group.appendChild(parentLink);
751                }
752
753                item.querySelectorAll('.bybit-navbar__dropdown-link').forEach(function(sub) {
754                    const childLink = document.createElement('a');
755                    childLink.className = 'bybit-navbar__overflow-link bybit-navbar__overflow-link--child';
756                    childLink.href = sub.getAttribute('href') || '#';
757                    childLink.textContent = sub.textContent.trim();
758                    group.appendChild(childLink);
759                });
760
761                fragment.appendChild(group);
762            });
763
764            dropdown.innerHTML = '';
765            dropdown.appendChild(fragment);
766        }
767
768        function closeOverflow() {
769            overflowMenu.classList.remove('is-open');
770            toggle.setAttribute('aria-expanded', 'false');
771        }
772
773        function apply(visibleCount) {
774            if (visibleCount === lastVisibleCount) {
775                return;
776            }
777            lastVisibleCount = visibleCount;
778
779            items.forEach(function(item, index) {
780                item.hidden = index >= visibleCount;
781            });
782
783            const hiddenItems = items.slice(visibleCount);
784            if (!hiddenItems.length) {
785                overflowMenu.hidden = true;
786                dropdown.innerHTML = '';
787                closeOverflow();
788                return;
789            }
790
791            overflowMenu.hidden = false;
792            buildDropdown(hiddenItems);
793        }
794
795        function layout() {
796            frame = null;
797
798            const available = nav.clientWidth;
799            if (!available) {
800                // Nav is hidden (drawer range): force a recalculation when it returns.
801                lastVisibleCount = -1;
802                return;
803            }
804
805            const data = getMetrics();
806            const totalAll = data.widths.reduce(function(sum, width) { return sum + width; }, 0)
807                + data.gap * Math.max(items.length - 1, 0);
808
809            if (totalAll <= available) {
810                apply(items.length);
811                return;
812            }
813
814            const budget = available - data.toggleWidth - data.gap;
815            let used = 0;
816            let visibleCount = 0;
817
818            for (let i = 0; i < items.length; i++) {
819                const width = data.widths[i] + (i > 0 ? data.gap : 0);
820                if (used + width > budget) {
821                    break;
822                }
823                used += width;
824                visibleCount++;
825            }
826
827            apply(Math.max(visibleCount, 1));
828        }
829
830        function scheduleLayout() {
831            if (frame) {
832                return;
833            }
834            frame = window.requestAnimationFrame(layout);
835        }
836
837        toggle.addEventListener('click', function(event) {
838            event.preventDefault();
839            event.stopPropagation();
840            const isOpen = overflowMenu.classList.toggle('is-open');
841            toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
842        });
843
844        document.addEventListener('click', function(event) {
845            if (!overflowMenu.contains(event.target)) {
846                closeOverflow();
847            }
848        });
849
850        document.addEventListener('keydown', function(event) {
851            if (event.key === 'Escape' && overflowMenu.classList.contains('is-open')) {
852                closeOverflow();
853                toggle.focus();
854            }
855        });
856
857        if (typeof ResizeObserver !== 'undefined') {
858            new ResizeObserver(scheduleLayout).observe(nav);
859        }
860        window.addEventListener('resize', scheduleLayout);
861
862        if (document.fonts && document.fonts.ready) {
863            document.fonts.ready.then(function() {
864                metrics = null;
865                lastVisibleCount = -1;
866                scheduleLayout();
867            });
868        }
869
870        nav._ftOverflowLayout = scheduleLayout;
871        scheduleLayout();
872    }
873    
874})(jQuery);

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.