1/** 2 * b247-sidebar.js 3 * Charges carousel + mobile deck video overlay. 4 */ 5(function (w, d) { 6 'use strict'; 7 8 /* ââ Charges carousel ââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 9 function initCarousel(el) { 10 var slides = el.querySelectorAll('.b247-charges-slide'); 11 var dots = el.querySelectorAll('.b247-charges-dot'); 12 var prev = el.querySelector('.b247-charges-prev'); 13 var next = el.querySelector('.b247-charges-next'); 14 var total = slides.length; 15 var cur = 0; 16 var timer; 17 18 if (!total) return; 19 if (total === 1) { slides[0].classList.add('b247-charges-slide--active'); return; } 20 21 function show(n) { 22 slides[cur].classList.remove('b247-charges-slide--active'); 23 if (dots[cur]) dots[cur].classList.remove('b247-charges-dot--active'); 24 cur = (n + total) % total; 25 slides[cur].classList.add('b247-charges-slide--active'); 26 if (dots[cur]) dots[cur].classList.add('b247-charges-dot--active'); 27 } 28 29 function resetTimer() { 30 clearInterval(timer); 31 timer = setInterval(function () { show(cur + 1); }, 5000); 32 } 33 34 if (prev) prev.addEventListener('click', function () { show(cur - 1); resetTimer(); }); 35 if (next) next.addEventListener('click', function () { show(cur + 1); resetTimer(); }); 36 37 dots.forEach(function (dot, i) { 38 dot.addEventListener('click', function () { show(i); resetTimer(); }); 39 }); 40 41 var sx = 0; 42 el.addEventListener('touchstart', function (e) { sx = e.touches[0].clientX; }, { passive: true }); 43 el.addEventListener('touchend', function (e) { 44 var dx = sx - e.changedTouches[0].clientX; 45 if (Math.abs(dx) > 30) { show(dx > 0 ? cur + 1 : cur - 1); resetTimer(); } 46 }, { passive: true }); 47 48 show(0); 49 resetTimer(); 50 } 51 52 /* ââ Mobile deck video overlay âââââââââââââââââââââââââââââââââââââââââââ */ 53 function initDeck() { 54 // The deck button is the original entry point; .b247-video-trigger lets 55 // any other surface (article channels box, homepage CTA) open the same 56 // overlay. One overlay, one player instance, whoever opens it. 57 var triggers = d.querySelectorAll('#b247-deck-video-btn, .b247-video-trigger'); 58 var overlay = d.getElementById('b247-video-overlay'); 59 var closeBtn = d.getElementById('b247-video-overlay-close'); 60 var container = d.getElementById('b247-video-container'); 61 var loaded = false; 62 63 if (!triggers.length || !overlay) return; 64 65 function openOverlay() { 66 overlay.removeAttribute('hidden'); 67 d.body.style.overflow = 'hidden'; 68 history.pushState({ b247Video: true }, ''); 69 if (!loaded) { 70 loaded = true; 71 var s = d.createElement('script'); 72 s.setAttribute('data-cfasync', 'false'); 73 s.async = true; 74 s.src = 'https://go.trvdp.com/init/15165.js?vpid=11878'; 75 if (container) container.appendChild(s); 76 } 77 } 78 79 function closeOverlay(skipHistory) { 80 if (overlay.hasAttribute('hidden')) return; 81 overlay.setAttribute('hidden', ''); 82 d.body.style.overflow = ''; 83 if (!skipHistory) history.back(); 84 } 85 86 Array.prototype.forEach.call(triggers, function (t) { 87 t.addEventListener('click', openOverlay); 88 }); 89 if (closeBtn) closeBtn.addEventListener('click', function () { closeOverlay(false); }); 90 91 overlay.addEventListener('click', function (e) { 92 if (e.target === overlay) closeOverlay(false); 93 }); 94 95 d.addEventListener('keydown', function (e) { 96 if (e.key === 'Escape' && !overlay.hasAttribute('hidden')) closeOverlay(false); 97 }); 98 99 w.addEventListener('popstate', function () { 100 closeOverlay(true); 101 }); 102 } 103 104 /* ââ Scroll-hide header + deck âââââââââââââââââââââââââââââââââââââââââââ */ 105 function initScrollHide() { 106 var lastY = w.scrollY || w.pageYOffset || 0; 107 var ticking = false; 108 109 w.addEventListener('scroll', function () { 110 if (ticking) return; 111 // Never hide header while the nav overlay is open â hamburger must stay reachable. 112 if (d.querySelector('.wp-block-navigation__responsive-container.is-menu-open')) return; 113 ticking = true; 114 w.requestAnimationFrame(function () { 115 var y = w.scrollY || w.pageYOffset || 0; 116 if (y < 80) { 117 d.body.classList.remove('b247-scrolled-down'); 118 } else if (y > lastY + 8) { 119 d.body.classList.add('b247-scrolled-down'); 120 } else if (y < lastY - 8) { 121 d.body.classList.remove('b247-scrolled-down'); 122 } 123 lastY = y; 124 ticking = false; 125 }); 126 }, { passive: true });
127 } 128 129 /* ââ Search modal ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 130 function initSearchModal() { 131 var btn = d.getElementById('b247-search-btn'); 132 var modal = d.getElementById('b247-search-modal'); 133 var closeBt = d.getElementById('b247-search-close'); 134 var input = d.getElementById('b247-search-input'); 135 136 if (!btn || !modal) return; 137 138 function open() { 139 modal.removeAttribute('hidden'); 140 d.body.style.overflow = 'hidden'; 141 if (input) setTimeout(function () { input.focus(); }, 30); 142 } 143 144 function close() { 145 modal.setAttribute('hidden', ''); 146 d.body.style.overflow = ''; 147 btn.focus(); 148 } 149 150 btn.addEventListener('click', open); 151 if (closeBt) closeBt.addEventListener('click', close); 152 153 modal.addEventListener('click', function (e) { 154 if (e.target === modal) close(); 155 }); 156 157 d.addEventListener('keydown', function (e) { 158 if (e.key === 'Escape' && !modal.hasAttribute('hidden')) close(); 159 }); 160 } 161 162 /* ââ TV 247 schedule: highlight the program currently on air âââââââââââââ */ 163 function initTvSchedule() { 164 var list = d.querySelector('.b247-tv-schedule-list'); 165 if (!list) return; 166 167 var items = list.querySelectorAll('.b247-tv-item[data-start]'); 168 if (!items.length) return; 169 170 // The live player in the same widget. The program on air drives which 171 // video it shows; programs without their own video keep the default 172 // (channel live) embed captured here. 173 var liveFrame = d.querySelector('.b247-live-embed-frame'); 174 var defaultEmbed = liveFrame 175 ? (liveFrame.getAttribute('data-src') || liveFrame.getAttribute('src') || '') 176 : ''; 177 178 // Point the live iframe at the given embed URL. Updates data-src while the 179 // frame is still deferred (initLiveEmbed promotes it later), or src once 180 // it has loaded â only when the URL actually changes, so it doesn't reload 181 // mid-program. 182 function applyLiveVideo(idx) { 183 if (!liveFrame) return; 184 var embed = (idx >= 0 && items[idx].getAttribute('data-embed')) 185 ? items[idx].getAttribute('data-embed') 186 : defaultEmbed; 187 if (!embed) return; 188 if (liveFrame.hasAttribute('data-src')) { 189 if (liveFrame.getAttribute('data-src') !== embed) liveFrame.setAttribute('data-src', embed); 190 } else if (liveFrame.getAttribute('src') !== embed) { 191 liveFrame.setAttribute('src', embed); 192 } 193 } 194 195 // Current time in Brazil, independent of the visitor's own timezone, 196 // so the highlight is right even on edge-cached HTML. 197 function nowMinutes() { 198 try { 199 // Build the formatter once (cached on the fn) â constructing it per 200 // 60s tick is 5-20ms on low-end mobile and can land in a tap's INP window. 201 var fmt = nowMinutes._fmt || (nowMinutes._fmt = new Intl.DateTimeFormat('en-GB', { 202 timeZone: 'America/Sao_Paulo', 203 hour: '2-digit', 204 minute: '2-digit', 205 hour12: false 206 })); 207 var parts = fmt.format(new Date()).split(':'); 208 return (parseInt(parts[0], 10) % 24) * 60 + parseInt(parts[1], 10); 209 } catch (e) { 210 var n = new Date(); 211 return n.getHours() * 60 + n.getMinutes(); 212 } 213 } 214 215 function update() { 216 var mins = nowMinutes(); 217 var liveIndex = -1; 218 for (var i = 0; i < items.length; i++) { 219 var start = parseInt(items[i].getAttribute('data-start'), 10); 220 var next = (i + 1 < items.length) 221 ? parseInt(items[i + 1].getAttribute('data-start'), 10) 222 : 24 * 60; 223 if (mins >= start && mins < next) liveIndex = i; 224 } 225 for (var j = 0; j < items.length; j++) { 226 items[j].classList.toggle('is-live', j === liveIndex); 227 } 228 applyLiveVideo(liveIndex); 229 } 230 231 update(); 232 w.setInterval(update, 60000); 233 } 234 235 /* ââ Ao vivo embed: defer the YouTube iframe until it nears the viewport â */ 236 function initLiveEmbed() { 237 var frames = d.querySelectorAll('.b247-live-embed-frame[data-src]'); 238 if (!frames.length) return; 239 240 function load(frame) { 241 var src = frame.getAttribute('data-src'); 242 if (!src) return; 243 frame.setAttribute('src', src); 244 frame.removeAttribute('data-src'); 245 } 246 247 if (!('IntersectionObserver' in w)) {
248 frames.forEach(load); 249 return; 250 } 251 252 var io = new IntersectionObserver(function (entries) { 253 entries.forEach(function (entry) { 254 if (entry.isIntersecting) { 255 load(entry.target); 256 io.unobserve(entry.target); 257 } 258 }); 259 }, { rootMargin: '300px' }); 260 261 frames.forEach(function (frame) { io.observe(frame); }); 262 } 263 264 /* ââ Ao vivo player: TV 247 / Cortes 247 toggle ââââââââââââââââââââââââââ */ 265 /* 266 * Swaps the visible panel and STOPS the hidden one â symmetric for both the 267 * live stream and the Cortes playlist. There's no reliable way to "pause" a 268 * cross-origin YouTube iframe without the IFrame Player API, so we use the 269 * standard trick: clear the hidden frame's src (stashing the URL in 270 * data-paused-src) to stop playback, and restore it when its tab reopens. 271 * The live frame's element is kept (only its src is cleared) so its existing 272 * lazy-load + on-air program swap keep working; if that swap reloads it while 273 * hidden it comes back muted (autoplay=1&mute=1), i.e. silent. 274 */ 275 function initLiveToggle() { 276 var toggles = d.querySelectorAll('.b247-live-toggle'); 277 if (!toggles.length) return; 278 279 // Restore a frame's URL from whichever stash holds it: the Cortes lazy 280 // stash (first open) or the paused stash (re-open). Leaves an unloaded 281 // live frame's data-src alone so initLiveEmbed still promotes it. 282 function showFrame(frame) { 283 if (!frame) return; 284 var url = frame.getAttribute('data-cortes-src') || frame.getAttribute('data-paused-src'); 285 if (url && frame.getAttribute('src') !== url) frame.setAttribute('src', url); 286 frame.removeAttribute('data-cortes-src'); 287 frame.removeAttribute('data-paused-src'); 288 } 289 290 // Stop playback by clearing src, stashing the URL so it can be restored. 291 function hideFrame(frame) { 292 if (!frame) return; 293 var cur = frame.getAttribute('src'); 294 if (cur) { 295 frame.setAttribute('data-paused-src', cur); 296 frame.setAttribute('src', ''); 297 } 298 } 299 300 toggles.forEach(function (toggle) { 301 var scope = toggle.parentNode; 302 if (!scope) return; 303 var btns = toggle.querySelectorAll('.b247-live-toggle-btn'); 304 305 toggle.addEventListener('click', function (e) { 306 var btn = e.target.closest ? e.target.closest('.b247-live-toggle-btn') : null; 307 if (!btn) return; 308 var tab = btn.getAttribute('data-live-tab'); 309 310 btns.forEach(function (b) { 311 var active = b === btn; 312 b.classList.toggle('is-active', active); 313 b.setAttribute('aria-pressed', active ? 'true' : 'false'); 314 }); 315 316 scope.querySelectorAll('.b247-live-panel').forEach(function (panel) { 317 var show = panel.getAttribute('data-live-panel') === tab; 318 panel.hidden = !show; 319 var frame = panel.querySelector('iframe'); 320 if (show) { showFrame(frame); } else { hideFrame(frame); } 321 }); 322 }); 323 }); 324 } 325 326 /* ââ Hamburger menu enhancement ââââââââââââââââââââââââââââââââââââââââââ */ 327 /* 328 * Injects a brand header, search shortcut, and footer (Apoio CTA + socials) 329 * into the core navigation overlay the first time it opens. Keeps the WP 330 * markup intact â the user still edits the menu in Site Editor â while 331 * giving the overlay a magazine front-page feel. 332 */ 333 function initNavOverlay() { 334 var nav = d.querySelector('.b247-nav-hamburger'); 335 if (!nav) return; 336 var container = nav.querySelector('.wp-block-navigation__responsive-container'); 337 var content = nav.querySelector('.wp-block-navigation__responsive-container-content'); 338 if (!container || !content) return; 339 340 function createTextEl(tag, className, text) { 341 var el = d.createElement(tag); 342 if (className) el.className = className; 343 el.textContent = text; 344 return el; 345 } 346 347 function createSvg(pathData) { 348 var svg = d.createElementNS('http://www.w3.org/2000/svg', 'svg'); 349 var path = d.createElementNS('http://www.w3.org/2000/svg', 'path'); 350 svg.setAttribute('viewBox', '0 0 24 24'); 351 svg.setAttribute('aria-hidden', 'true'); 352 svg.setAttribute('focusable', 'false'); 353 path.setAttribute('d', pathData); 354 svg.appendChild(path);
355 return svg; 356 } 357 358 function createSocialLink(href, label, pathData) { 359 var link = d.createElement('a'); 360 link.href = href; 361 link.target = '_blank'; 362 link.rel = 'noopener'; 363 link.setAttribute('aria-label', label); 364 link.appendChild(createSvg(pathData)); 365 return link; 366 } 367 368 var enriched = false; 369 function enrich() { 370 if (enriched) return; 371 enriched = true; 372 373 // ââ Top: brand mark + search shortcut ââââââââââââââââââââââââ 374 var top = d.createElement('div'); 375 top.className = 'b247-nav-top'; 376 377 var search = d.createElement('button'); 378 search.type = 'button'; 379 search.className = 'b247-nav-search'; 380 search.setAttribute('data-action', 'open-search'); 381 search.appendChild(createSvg('M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z')); 382 search.appendChild(createTextEl('span', '', 'Buscar no 247')); 383 top.appendChild(search); 384 385 var apoioTop = d.createElement('a'); 386 apoioTop.className = 'b247-nav-apoio b247-nav-apoio--top'; 387 apoioTop.href = '/apoio/'; 388 apoioTop.appendChild(createTextEl('span', '', 'Apoiar o 247')); 389 var arrow = d.createElement('span'); 390 arrow.setAttribute('aria-hidden', 'true'); 391 arrow.textContent = 'â'; 392 apoioTop.appendChild(arrow); 393 top.appendChild(apoioTop); 394 395 var quemSomos = d.createElement('a'); 396 quemSomos.className = 'b247-nav-quem-somos'; 397 quemSomos.href = '/quem-somos/'; 398 quemSomos.appendChild(createTextEl('span', '', 'Quem somos')); 399 top.appendChild(quemSomos); 400 401 content.insertBefore(top, content.firstChild); 402 403 // Wire the search shortcut to the existing search modal. 404 search.addEventListener('click', function () { 405 // Closing the menu first lets the search modal own focus cleanly. 406 var closeBtn = nav.querySelector('.wp-block-navigation__responsive-container-close'); 407 if (closeBtn) closeBtn.click(); 408 setTimeout(function () { 409 var s = d.getElementById('b247-search-btn'); 410 if (s) s.click(); 411 }, 80); 412 }); 413 414 // ââ Bottom: Apoio CTA + social pills âââââââââââââââââââââââââ 415 var bottom = d.createElement('div'); 416 bottom.className = 'b247-nav-bottom'; 417 var apoio = d.createElement('a'); 418 apoio.className = 'b247-nav-apoio'; 419 apoio.href = '/apoio/'; 420 apoio.appendChild(createTextEl('span', '', 'Apoiar o 247')); 421 var arrow = createTextEl('span', '', String.fromCharCode(0x2192)); 422 arrow.setAttribute('aria-hidden', 'true'); 423 apoio.appendChild(arrow); 424 bottom.appendChild(apoio); 425 426 var socials = d.createElement('div'); 427 socials.className = 'b247-nav-socials'; 428 socials.setAttribute('aria-label', 'Redes sociais'); 429 socials.appendChild(createSocialLink('https://www.youtube.com/c/brasil247?sub_confirmation=1', 'YouTube', 'M23.5 6.2a3 3 0 0 0-2.1-2.1C19.6 3.6 12 3.6 12 3.6s-7.6 0-9.4.5a3 3 0 0 0-2.1 2.1C0 8.1 0 12 0 12s0 3.9.5 5.8a3 3 0 0 0 2.1 2.1c1.9.5 9.4.5 9.4.5s7.6 0 9.4-.5a3 3 0 0 0 2.1-2.1c.5-1.9.5-5.8.5-5.8s0-3.9-.5-5.8zM9.5 15.6V8.4L15.8 12l-6.3 3.6z')); 430 socials.appendChild(createSocialLink('https://x.com/brasil247', 'X', 'M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.737-8.835L1.254 2.25H8.08l4.258 5.635 5.906-5.635z')); 431 socials.appendChild(createSocialLink('https://www.facebook.com/brasil247', 'Facebook', 'M24 12.07C24 5.44 18.63.07 12 .07S0 5.44 0 12.07c0 6 4.39 10.95 10.13 11.85v-8.38H7.08v-3.47h3.05V9.43c0-3 1.79-4.67 4.53-4.67 1.31 0 2.69.24 2.69.24v2.95H15.83c-1.49 0-1.96.93-1.96 1.87v2.25h3.33l-.53 3.47h-2.8v8.38C19.61 23.02 24 18.06 24 12.07z')); 432 socials.appendChild(createSocialLink('https://instagram.com/brasil_247', 'Instagram', 'M12 2.16c3.2 0 3.58.01 4.85.07 3.25.15 4.77 1.69 4.92 4.92.06 1.27.07 1.65.07 4.85 0 3.2-.01 3.58-.07 4.85-.15 3.23-1.66 4.77-4.92 4.92-1.27.06-1.65.07-4.85.07s-3.58-.01-4.85-.07c-3.26-.15-4.77-1.7-4.92-4.92-.06-1.27-.07-1.64-.07-4.85 0-3.2.01-3.58.07-4.85.15-3.23 1.66-4.77 4.92-4.92C8.42 2.17 8.8 2.16 12 2.16zm0-2.16C8.74 0 8.33.01 7.05.07 2.7.27.27 2.69.07 7.05.01 8.33 0 8.74 0 12c0 3.26.01 3.67.07 4.95.2 4.36 2.62 6.78 6.98 6.98 1.28.06 1.69.07 4.95.07s3.67-.01 4.95-.07c4.35-.2 6.78-2.62 6.98-6.98.06-1.28.07-1.69.07-4.95s-.01-3.67-.07-4.95c-.2-4.35-2.62-6.78-6.98-6.98C15.67.01 15.26 0 12 0zm0 5.84a6.16 6.16 0 1 0 0 12.32 6.16 6.16 0 0 0 0-12.32zm0 10.16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm6.41-11.85a1.44 1.44 0 1 0 0 2.88 1.44 1.44 0 0 0 0-2.88z')); 433 socials.appendChild(createSocialLink('https://t.me/brasil247oficial', 'Telegram', 'M11.94 0a12 12 0 1 0 0 24 12 12 0 0 0 0-24zm5 7.22c.1 0 .32.02.47.14a.5.5 0 0 1 .17.33c.02.09.04.3.02.47-.18 1.9-.96 6.5-1.36 8.63-.17.9-.5 1.2-.82 1.23-.7.07-1.22-.46-1.9-.9-1.06-.7-1.66-1.13-2.68-1.8-1.19-.78-.42-1.2.26-1.91.17-.18 3.25-2.98 3.3-3.23.01-.03.02-.15-.05-.21s-.18-.04-.25-.02c-.1.02-1.8 1.14-5.06 3.34-.48.33-.92.49-1.3.48-.43 0-1.26-.24-1.87-.44-.75-.25-1.34-.38-1.3-.79.03-.21.32-.44.9-.66 3.5-1.53 5.83-2.53 7-3.02 3.33-1.39 4.02-1.63 4.47-1.64z')); 434 bottom.appendChild(socials); 435 436 var footer = d.createElement('div'); 437 footer.className = 'b247-nav-footer-links'; 438 var about = d.createElement('a'); 439 about.href = '/quem-somos'; 440 about.textContent = 'Quem somos'; 441 footer.appendChild(about); 442 var privacy = d.createElement('a'); 443 privacy.href = '/politica-de-privacidade'; 444 privacy.textContent = 'Privacidade'; 445 footer.appendChild(privacy); 446 bottom.appendChild(footer);
447 content.appendChild(bottom); 448 } 449 450 // Anchor the dropdown panel directly below the header so the header (and 451 // the hamburger) never move when the menu opens. Measure the FIXED bar 452 // that holds the hamburger: on mobile that's .b247-header-shell (fixed), 453 // on desktop it's .b247-header-brand (fixed; the shell scrolls). Drop the 454 // scroll-hide class first so the bar is at the top when measured, and 455 // clamp to >= 0 so the panel can never anchor off-screen. 456 function setMenuTop() { 457 d.body.classList.remove('b247-scrolled-down'); 458 // Defer the layout read (getBoundingClientRect) + property write to the 459 // next frame so opening the menu doesn't force a synchronous reflow of 460 // the whole listing inside the tap's INP window. The panel anchors one 461 // frame later â imperceptible. 462 w.requestAnimationFrame(function () { 463 var bar = nav.closest('.b247-header-brand') || d.querySelector('.b247-header-brand') || d.querySelector('.b247-header-shell'); 464 var top = bar ? Math.max(0, Math.round(bar.getBoundingClientRect().bottom)) : 56; 465 container.style.setProperty('--b247-menu-top', top + 'px'); 466 }); 467 } 468 469 // Collapse every section so the menu always opens tidy (collapse-by-default). 470 function collapseAccordion() { 471 var open = content.querySelectorAll('.b247-acc-open'); 472 for (var j = 0; j < open.length; j++) { 473 open[j].classList.remove('b247-acc-open'); 474 var ic = open[j].querySelector('.wp-block-navigation__submenu-icon'); 475 if (ic) ic.setAttribute('aria-expanded', 'false'); 476 } 477 } 478 479 // Click accordion: each parent section toggles its own submenu open. 480 // Capture-phase + stopImmediatePropagation so the core nav block's own 481 // submenu handler never fires â we fully own the expand/collapse. 482 function wireAccordion() { 483 var parents = content.querySelectorAll('.wp-block-navigation-item.has-child'); 484 for (var i = 0; i < parents.length; i++) { 485 (function (li) { 486 if (li.getAttribute('data-b247-acc')) return; 487 li.setAttribute('data-b247-acc', '1'); 488 // The chevron may be a direct child of the <li> or nested inside 489 // the parent's content link depending on the core render path â 490 // a descendant lookup matches either. 491 var icon = li.querySelector('.wp-block-navigation__submenu-icon'); 492 var label = li.querySelector(':scope > .wp-block-navigation-item__content'); 493 function toggle(e) { 494 e.preventDefault(); 495 e.stopImmediatePropagation(); 496 var isOpen = li.classList.toggle('b247-acc-open'); 497 if (icon) icon.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); 498 } 499 if (label) label.addEventListener('click', toggle, true); 500 if (icon) icon.addEventListener('click', toggle, true); 501 })(parents[i]); 502 } 503 } 504 505 // Keep the panel anchored below the header across orientation/resize. 506 window.addEventListener('resize', function () { 507 if (container.classList.contains('is-menu-open')) setMenuTop(); 508 }); 509 510 // MutationObserver catches the moment WP toggles is-menu-open on the 511 // container â at that point the contents are real DOM and we can enrich. 512 var mo = new MutationObserver(function () { 513 if (container.classList.contains('is-menu-open')) { 514 enrich(); 515 // Tag the navigation list so CSS can target it specifically â the 516 // core block doesn't ship a stable hook for the open state inside 517 // the <ul> tree. 518 var ul = nav.querySelector('.wp-block-navigation__container'); 519 if (ul) ul.classList.add('b247-nav-open'); 520 setMenuTop(); 521 wireAccordion(); 522 collapseAccordion(); 523 } 524 }); 525 mo.observe(container, { attributes: true, attributeFilter: ['class'] }); 526 527 // When the menu opens, ensure the header is visible (undo scroll-hide) 528 // so the X close button is always reachable. 529 var navMo = new MutationObserver(function () { 530 if (container.classList.contains('is-menu-open')) { 531 d.body.classList.remove('b247-scrolled-down'); 532 } 533 }); 534 navMo.observe(container, { attributes: true, attributeFilter: ['class'] }); 535 536 // Click outside the panel (on the dim scrim) closes the menu. Core wraps 537 // the content in __responsive-dialog / __responsive-close layers that fill 538 // the scrim, so a target===container check never matches; instead close 539 // whenever the click is NOT inside the content card. Ignore the close 540 // button itself so its own (re-dispatched) click can't loop. 541 container.addEventListener('click', function (e) { 542 if (!container.classList.contains('is-menu-open')) return; 543 if (e.target.closest('.wp-block-navigation__responsive-container-content')) return; 544 if (e.target.closest('.wp-block-navigation__responsive-container-close')) return; 545 var closeBtn = container.querySelector('.wp-block-navigation__responsive-container-close'); 546 if (closeBtn) closeBtn.click(); 547 }); 548
549 // Some browsers paint with is-menu-open already set (back-button navigation). 550 if (container.classList.contains('is-menu-open')) { 551 enrich(); 552 setMenuTop(); 553 wireAccordion(); 554 collapseAccordion(); 555 } 556 } 557 558 /* ââ Hamburger toggle â make open-btn also close the menu âââââââââââââââ */ 559 // The WP nav block uses two separate buttons (open / close). With the CSS 560 // X animation the open-btn is visible above the overlay when the menu is 561 // open; intercept those clicks and delegate to the close button so the 562 // single button works as a true toggle. 563 function initHamburgerToggle() { 564 var nav = d.querySelector('.b247-nav-hamburger'); 565 if (!nav) return; 566 var openBtn = nav.querySelector('.wp-block-navigation__responsive-container-open'); 567 var container = nav.querySelector('.wp-block-navigation__responsive-container'); 568 if (!openBtn || !container) return; 569 570 // Whether the menu was open when the finger landed. The click alone 571 // cannot be trusted: core's overlay closes itself on focusout when 572 // focus leaves for nowhere (relatedTarget null), which is what a tap 573 // on a button does on iOS, where buttons take no focus. So a tap on 574 // the X closed the menu before the click arrived, the click found it 575 // closed, this handler stepped aside, and core's open handler put the 576 // menu straight back â the reopen-on-close stutter. Latched on 577 // pointerdown (touchstart/mousedown for older engines), cleared soon 578 // after so an abandoned press cannot swallow a later keyboard click. 579 var wasOpen = false; 580 var latchTimer = 0; 581 function latch() { 582 wasOpen = container.classList.contains('is-menu-open'); 583 clearTimeout(latchTimer); 584 latchTimer = setTimeout(function () { wasOpen = false; }, 800); 585 } 586 openBtn.addEventListener('pointerdown', latch, true); 587 openBtn.addEventListener('touchstart', latch, { capture: true, passive: true }); 588 openBtn.addEventListener('mousedown', latch, true); 589 590 openBtn.addEventListener('click', function (e) { 591 var closing = wasOpen || container.classList.contains('is-menu-open'); 592 wasOpen = false; 593 if (!closing) return; 594 e.preventDefault(); 595 e.stopImmediatePropagation(); 596 if (!container.classList.contains('is-menu-open')) return; // focusout already closed it 597 var closeBtn = container.querySelector('.wp-block-navigation__responsive-container-close'); 598 if (closeBtn) closeBtn.click(); 599 }, true); // capture phase â runs before WP's own handler 600 } 601 602 /* ââ Boot ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 603 function boot() { 604 // Attach interactive handlers immediately â user may tap right after paint. 605 initDeck(); 606 initScrollHide(); 607 initSearchModal(); 608 initTvSchedule(); 609 initNavOverlay(); 610 initHamburgerToggle(); 611 initLiveEmbed(); 612 initLiveToggle(); 613 614 // Carousel sets up listeners + a 5s setInterval; defer to idle so it 615 // doesn't compete with LCP work or first input. 616 var initCarousels = function () { 617 d.querySelectorAll('.b247-charges-carousel').forEach(initCarousel); 618 }; 619 if ('requestIdleCallback' in w) { 620 w.requestIdleCallback(initCarousels, { timeout: 2000 }); 621 } else { 622 setTimeout(initCarousels, 200); 623 } 624 } 625 626 if (d.readyState === 'loading') { 627 d.addEventListener('DOMContentLoaded', boot); 628 } else { 629 boot(); 630 } 631 632}(window, document));
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.