1(() => { 2 'use strict'; 3 const themeButton = document.querySelector('.theme-toggle'); 4 const root = document.documentElement; 5 const syncTheme = theme => { 6 const dark = theme === 'dark'; 7 root.dataset.theme = dark ? 'dark' : 'light'; 8 root.style.colorScheme = dark ? 'dark' : 'light'; 9 themeButton?.setAttribute('aria-checked', String(dark)); 10 themeButton?.setAttribute('title', dark ? 'Switch to light mode' : 'Switch to dark mode'); 11 document.querySelector('meta[name="theme-color"]')?.setAttribute('content', dark ? '#050e32' : '#eef1f6'); 12 }; 13 syncTheme(root.dataset.theme); 14 themeButton?.addEventListener('click', () => { 15 const theme = root.dataset.theme === 'dark' ? 'light' : 'dark'; 16 syncTheme(theme); 17 try { localStorage.setItem('tooq-theme', theme); } catch { /* The switch works without storage. */ } 18 }); 19 window.addEventListener('storage', event => { 20 if (event.key === 'tooq-theme') syncTheme(event.newValue); 21 }); 22 const menuButton = document.querySelector('.menu-toggle'); 23 const nav = document.querySelector('.nav-links'); 24 const dropdowns = [...document.querySelectorAll('.nav-dropdown')]; 25 const closeMenu = () => { 26 nav?.classList.remove('is-open'); 27 menuButton?.setAttribute('aria-expanded', 'false'); 28 menuButton?.setAttribute('aria-label', 'Open navigation'); 29 dropdowns.forEach(item => { item.open = false; }); 30 }; 31 menuButton?.addEventListener('click', () => { 32 const isOpen = menuButton.getAttribute('aria-expanded') !== 'true'; 33 nav.classList.toggle('is-open', isOpen); 34 menuButton.setAttribute('aria-expanded', String(isOpen)); 35 menuButton.setAttribute('aria-label', isOpen ? 'Close navigation' : 'Open navigation'); 36 }); 37 nav?.addEventListener('click', event => { if (event.target.closest('a')) closeMenu(); }); 38 document.querySelector('.floating-links')?.addEventListener('click', event => { if (event.target.closest('a')) closeMenu(); }); 39 dropdowns.forEach(item => item.addEventListener('toggle', () => { 40 if (item.open) dropdowns.forEach(other => { if (other !== item) other.open = false; }); 41 })); 42 document.addEventListener('click', event => { 43 if (!event.target.closest('.site-header,.floating-header')) closeMenu(); 44 }); 45 document.addEventListener('keydown', event => { 46 if (event.key !== 'Escape') return; 47 const openDropdown = dropdowns.find(item => item.open); 48 if (openDropdown) { 49 openDropdown.open = false; 50 openDropdown.querySelector('summary').focus(); 51 } else if (nav?.classList.contains('is-open')) { 52 closeMenu(); 53 menuButton.focus(); 54 } 55 }); 56 const wideScreen = window.matchMedia('(min-width: 701px)'); 57 wideScreen.addEventListener('change', () => closeMenu()); 58 59 // Swap navigation only after the complete opening header has left the viewport. 60 // The same theme button moves between slots, preserving its state and listeners. 61 const header = document.querySelector('.site-header'); 62 const floatingHeader = document.querySelector('.floating-header'); 63 const themeSlot = document.querySelector('.theme-slot'); 64 const themeDock = document.querySelector('.theme-dock'); 65 if (header && floatingHeader && themeSlot && themeDock && themeButton) { 66 let floating = false; 67 const setFloating = next => { 68 if (next === floating) return; 69 const focused = document.activeElement; 70 const themeFocused = focused === themeButton; 71 const outgoing = next ? header : floatingHeader; 72 const incoming = next ? floatingHeader : header; 73 const moveFocus = outgoing.contains(focused) && !themeFocused; 74 const focusKey = focused?.dataset.navKey || 'home'; 75 floating = next; 76 closeMenu(); 77 (next ? themeDock : themeSlot).append(themeButton); 78 root.classList.toggle('nav-is-floating', next); 79 incoming.inert = false; 80 floatingHeader.setAttribute('aria-hidden', String(!next)); 81 if (themeFocused) themeButton.focus({ preventScroll: true }); 82 else if (moveFocus) (incoming.querySelector(`[data-nav-key="${focusKey}
82"]`) || incoming.querySelector('[data-nav-key="home"]'))?.focus({ preventScroll: true }); 83 outgoing.inert = true; 84 }; 85 const updateNavigation = () => setFloating(header.getBoundingClientRect().bottom <= 0); 86 if ('IntersectionObserver' in window) { 87 const observer = new IntersectionObserver(updateNavigation, { threshold: 0 }); 88 observer.observe(header); 89 } else { 90 window.addEventListener('scroll', updateNavigation, { passive: true }); 91 } 92 window.addEventListener('resize', updateNavigation, { passive: true }); 93 window.addEventListener('pageshow', updateNavigation); 94 updateNavigation(); 95 96 // The floating pill keeps its links and button on one row only while they fit, whatever the font size, 97 // zoom, or text. Otherwise it uses the two-row layout from phones (.is-stacked in brand-theme.css). 98 const floatingShell = floatingHeader.querySelector('.floating-shell'); 99 const floatingLinks = floatingHeader.querySelector('.floating-links'); 100 if (floatingShell && floatingLinks && 'ResizeObserver' in window) { 101 const fitFloating = () => { 102 floatingHeader.classList.remove('is-stacked'); 103 const edge = floatingShell.getBoundingClientRect().right - parseFloat(getComputedStyle(floatingShell).paddingRight); 104 // Compare the links themselves, not scrollWidth: their hover pills reach past the row by design. 105 const row = floatingLinks.getBoundingClientRect().right; 106 const overflows = [...floatingLinks.children].some(link => link.getBoundingClientRect().right > row + 1) || [...floatingShell.children].some(child => child.getBoundingClientRect().right > edge + 1); 107 floatingHeader.classList.toggle('is-stacked', overflows); 108 }; 109 new ResizeObserver(fitFloating).observe(document.documentElement); 110 document.fonts?.ready.then(fitFloating); 111 fitFloating(); 112 } 113 } 114 115 // Use navy text over the bright Signal Lime beginning of the page. 116 // This only measures document geometry; it never changes the background. 117 const home = document.querySelector('body.home-page'); 118 if (home) { 119 const inkTargets = [...home.querySelectorAll('.section-heading,.product-directory,.company-intro,.company-copy,.footer-top,.footer-grid,.footer-bottom')]; 120 const updateInk = () => { 121 const height = home.scrollHeight; 122 inkTargets.forEach(element => { 123 const rect = element.getBoundingClientRect(); 124 const center = rect.top + window.scrollY + rect.height / 2; 125 element.classList.toggle('is-on-bright', center / height < .18); 126 }); 127 }; 128 if ('ResizeObserver' in window) new ResizeObserver(updateInk).observe(home); 129 window.addEventListener('resize', updateInk, { passive: true }); 130 document.fonts?.ready.then(updateInk); 131 updateInk(); 132 } 133 134 const search = document.querySelector('#manual-search'); 135 if (search) { 136 const terms = [...document.querySelectorAll('.manual-term')]; 137 const parts = [...document.querySelectorAll('.manual-part')]; 138 const count = document.querySelector('#manual-count'); 139 const empty = document.querySelector('.manual-empty'); 140 const entries = terms.map(term => ({ 141 element: term, 142 link: document.querySelector(`.reading-sidebar a[href="#${term.id}"]`), 143 text: term.textContent.toLocaleLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '') 144 })); 145 const filter = () => { 146 const query = search.value.trim().toLocaleLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, ''); 147 let visible = 0; 148 entries.forEach(entry => { 149 const match = entry.text.includes(query); 150 entry.element.hidden = !match; 151 if (entry.link) entry.link.hidden = !match; 152 if (match) visible++; 153 }); 154 parts.forEach(part => { part.hidden = ![...part.querySelectorAll('.manual-term')].some(term => !term.hidden); }); 155 count.textContent = query ? `${visible} / ${terms.length} terms` : `${terms.length} terms`; 156 empty.hidden = visible !== 0; 157 }; 158 search.addEventListener('input', filter); 159 document.querySelector('[data-clear-search]')?.addEventListener('click', () => { 160 search.value = ''; 161 filter(); 162 search.focus(); 163 }); 164 filter(); 165 } 166 const readingLinks = [...document.querySelectorAll('.reading-sidebar nav a')]; 167 if (readingLinks.length && 'IntersectionObserver' in window) { 168 const observer = new IntersectionObserver(entries => { 169 const visible = entries.find(entry => entry.isIntersecting); 170 if (!visible) return; 171 readingLinks.forEach(link => { 172 if (link.hash === `#${visible.target.id}`) link.setAttribute('aria-current', 'true'); 173 else link.removeAttribute('aria-current'); 174 }); 175 }, { rootMargin: '-5% 0px -70% 0px', threshold: 0 }); 176 readingLinks.forEach(link => { 177 const target = document.getElementById(link.hash.slice(1)); 178 if (target) observer.observe(target); 179 }); 180 } 181})();
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.