1/** 2 * Pollsmax manual AdSense placements. 3 * PC: page-top 2-up fixed 250px rectangles (logoâDonate) + home/latest/race/forecast mid duals + footer multiplex â never collapse. 4 * Mobile: existing lazy shells (may collapse empty). 5 */ 6(function (global) { 7 'use strict'; 8 9 var AD_CLIENT = ''; 10 var SLOT_DISPLAY = '7546728775'; 11 var SLOT_MULTIPLEX = '6233647108'; 12 var SLOT_INFEED = '4495536942'; 13 var MOBILE_MQ = '(max-width: 767px)'; 14 var PUSHED = 'data-pollsmax-ad-pushed'; 15 var ET = global.ET || (global.ET = {}); 16 var IO_ROOT_MARGIN = '700px 0px 700px 0px'; 17 var lazyObserver = null; 18 var priorityScheduled = false; 19 20 function isMobile() { 21 try { 22 return !!(global.matchMedia && global.matchMedia(MOBILE_MQ).matches); 23 } catch (e) { 24 return false; 25 } 26 } 27 28 function isEmbed() { 29 try { 30 return /[?&]embed=1(?:&|$)/.test(location.search || ''); 31 } catch (e2) { 32 return false; 33 } 34 } 35 36 function pathNorm() { 37 var p = String((location && location.pathname) || ''); 38 p = p.replace(/\/index\.html$/i, '/').replace(/\/+$/, '') || '/'; 39 if (p.indexOf('/dynamic/') === 0) p = p.slice('/dynamic'.length) || '/'; 40 return p; 41 } 42 43 function pageKind() { 44 var path = pathNorm(); 45 var body = document.body; 46 var isForecast = /\/forecast(?:-premium)?\/?$/i.test((location && location.pathname) || ''); 47 var isPrimaryPath = 48 /\/(?:democratic-primary|special-democratic-primary|special-republican-primary|republican-primary|republican-primary-runoff|open-primary)\/?$/i.test(path); 49 return { 50 path: path, 51 home: path === '/' || !!(body && body.classList.contains('pollsmax-home-page')), 52 senateHub: path === '/senate' || path === '/2026-us-senate' || !!(body && body.classList.contains('senate-hub-page')), 53 govHub: path === '/governor' || !!(body && body.classList.contains('gov-hub-page')), 54 houseHub: path === '/2026-us-house' || !!(body && body.classList.contains('house-hub-page')), 55 forecast: isForecast, 56 primaryPoll: isPrimaryPath, 57 gePoll: !isForecast && !isPrimaryPath && ( 58 /^\/(?:state|senate|governor)\/[^/]+$/i.test(path) || 59 /^\/2026-us-house\/[^/]+$/i.test(path) 60 ), 61 approval: path.indexOf('/approval/') === 0 || 62 !!(body && ( 63 body.classList.contains('national-approval-polls-page') || 64 body.classList.contains('generic-ballot-page') 65 )), 66 primary2028: path === '/2028-democratic-primary' || 67 path === '/2028-republican-primary' || 68 !!(body && body.classList.contains('primary-2028-approval-format')), 69 latestPolls: path === '/latest-polls' || !!(body && body.classList.contains('all-polls-page')), 70 upcomingElections: path === '/upcoming-elections' || 71 !!(body && body.classList.contains('upcoming-elections-page')) 72 }; 73 } 74 75 function ensureAdsense() { 76 return; 77 if (typeof global.PollsmaxEnsureAdsenseScript === 'function') { 78 global.PollsmaxEnsureAdsenseScript(); 79 } 80 } 81 82 function shellOf(ins) { 83 return ins && ins.closest 84 ? ins.closest('.pollsmax-ad-banner, .pollsmax-ad-row, .pollsmax-ad-single, aside.pollsmax-site-multiplex, .pollsmax-ad-shell, #home-after-opening-maps') 85 : null; 86 } 87 88 function measureMobilePanelWidth() { 89 var sels = [ 90 '.senate-hub-mob-panel--map', 91 '.senate-hub-mob-panel--chart', 92 '.senate-hub-mob-panel--races', 93 '.senate-hub-mob-panel--search', 94 '.senate-hub-mob-panel', 95 'body.race-forecast-mob-panels .senate-hub-mob-panel--chart', 96 'body.pollsmax-home-page .sleek-maps', 97 'body.pollsmax-home-page .sleek-map-pane', 98 '#homeUpcomingElectionsSection', 99 '#searchAllPollsSection' 100 ]; 101 var best = 0; 102 var i; 103 var j; 104 var vw = window.innerWidth || document.documentElement.clientWidth || 0; 105 for (i = 0; i < sels.length; i++) { 106 var list = document.querySelectorAll(sels[i]); 107 for (j = 0; j < list.length; j++) { 108 var el = list[j]; 109 if (!el || (el.classList && el.classList.contains('pollsmax-ad-shell'))) continue; 110 var r = el.getBoundingClientRect(); 111 var w = r && r.width ? r.width : 0; 112 if (w >= 200 && w <= vw + 1 && w > best) best = w; 113 } 114 } 115 if (best >= 200) return Math.round(best); 116 var root = document.documentElement; 117 var cs = root && window.getComputedStyle ? window.getComputedStyle(root) : null; 118 var stdW = cs ? parseFloat(cs.getPropertyValue('--et-mobile-standard-width')) : NaN; 119 if (stdW >= 200) return Math.round(stdW); 120 var pl = cs ? parseFloat(cs.getPropertyValue('--et-mob-channel-pl')) : NaN; 121 var pr = cs ? parseFloat(cs.getPropertyValue('--et-mob-channel-pr')) : NaN; 122 if (!(pl >= 0)) pl = 16; 123 if (!(pr >= 0)) pr = 16; 124 return Math.max(200, Math.round(vw - pl - pr)); 125 } 126 127 function usableWidth(ins) { 128 try { 129 var shell = shellOf(ins); 130 if (isMobile() && shell && (isMobileContentMidShell(shell) || 131 (shell.classList && shell.classList.contains('pollsmax-site-multiplex')))) { 132 var panelW = measureMobilePanelWidth(); 133 if (panelW > 50) return panelW; 134 }
135 /* Collapsed shells report 0Ã0 â use parent / channel / viewport width. */ 136 if (shell && ( 137 shell.getAttribute('data-ad-state') === 'waiting' || 138 shell.getAttribute('data-ad-state') === 'loading' 139 )) { 140 var ch = measureHeaderAdChannel(); 141 if (ch && ch.width > 50) return ch.width; 142 var parent = shell.parentElement; 143 if (parent) { 144 var pw = parent.getBoundingClientRect().width || parent.clientWidth || 0; 145 if (pw > 50) return pw; 146 } 147 return document.documentElement.clientWidth || window.innerWidth || 0; 148 } 149 var rect = ins.getBoundingClientRect(); 150 var w = rect.width; 151 if (w < 50 && ins.parentElement) { 152 w = ins.parentElement.getBoundingClientRect().width || 0; 153 } 154 if (w < 50 && shell) w = shell.getBoundingClientRect().width || 0; 155 return w; 156 } catch (eW) { 157 return 0; 158 } 159 } 160 161 /* Collapse empty slots once Google is clearly done â not before fills can land. */ 162 var EMPTY_SOFT_MS = 1200; 163 var EMPTY_HARD_MS = 6000; 164 var EMPTY_HARD_MS_PAGE_TOP = 15000; 165 var EMPTY_HARD_MS_MULTIPLEX = 20000; 166 167 function clearShellLayoutInline(shell) { 168 if (!shell || !shell.style) return; 169 try { 170 shell.style.removeProperty('height'); 171 shell.style.removeProperty('max-height'); 172 shell.style.removeProperty('min-height'); 173 shell.style.removeProperty('overflow'); 174 shell.style.removeProperty('opacity'); 175 shell.style.removeProperty('margin'); 176 shell.style.removeProperty('padding'); 177 } catch (eClr) { 178 shell.style.height = ''; 179 shell.style.maxHeight = ''; 180 shell.style.minHeight = ''; 181 shell.style.overflow = ''; 182 } 183 } 184 185 /** 186 * Never collapse / hide: 187 * - Multiplex (PC + mobile) â must stay full-size for AdSense fills 188 * - Desktop page-top 2-up 189 */ 190 function isStickyPcAd(shell) { 191 if (!shell || !shell.classList) return false; 192 if (shell.classList.contains('pollsmax-site-multiplex')) return true; 193 if (!isMobile() && shell.classList.contains('pollsmax-ad-row--page-top')) return true; 194 return false; 195 } 196 197 /** Hard-collapse reserved space â CSS alone loses to Google placeholder heights. */ 198 function setShellCollapsed(shell) { 199 if (!shell || isStickyPcAd(shell)) return; 200 if (isMobile() && isMobileContentMidShell(shell)) { 201 setShellLoading(shell); 202 return; 203 } 204 try { 205 shell.style.setProperty('height', '1px', 'important'); 206 shell.style.setProperty('max-height', '1px', 'important'); 207 shell.style.setProperty('min-height', '0', 'important'); 208 shell.style.setProperty('margin', '0', 'important'); 209 shell.style.setProperty('padding', '0', 'important'); 210 shell.style.setProperty('overflow', 'hidden', 'important'); 211 shell.style.setProperty('opacity', '0', 'important'); 212 } catch (eCol) { /* ignore */ } 213 } 214 215 function isHubMobileMidShell(shell) { 216 if (!shell || !shell.id) return false; 217 return ( 218 shell.id === 'senateHubAdAfterMapMobile' || 219 shell.id === 'senateHubAdBeforeRacesMobile' || 220 shell.id === 'govHubAdAfterMapMobile' || 221 shell.id === 'govHubAdBeforeRacesMobile' || 222 shell.id === 'houseHubAdAfterMapMobile' || 223 shell.id === 'houseHubAdBeforeRacesMobile' 224 ); 225 } 226 227 /** 228 * Mobile in-content singles (hub / home / polls / forecast / approval / lists). 229 * These often sit collapsed (0â1px) until Google marks filled â treat like 230 * page-top for retries / soft-empty so a premature `done` does not kill them. 231 * Legacy #hubAdAfterMapKeyMobile is intentionally excluded (always removed). 232 */ 233 function isMobileContentMidShell(shell) { 234 if (!shell) return false;
235 if (isHubMobileMidShell(shell)) return true; 236 if (shell.classList && shell.classList.contains('pollsmax-ad-single--poll-mid-mobile')) { 237 return true; 238 } 239 var id = shell.id || ''; 240 return ( 241 id === 'homeAdBetweenGovAndSearch' || 242 id === 'homeAdBetweenGbAndDemPrimary' || 243 id === 'raceGePollAdBeforeTableMobile' || 244 id === 'raceGePollAdAfterTableMobile' || 245 id === 'racePrimaryPollAdBeforeTableMobile' || 246 id === 'racePrimaryPollAdAfterTableMobile' || 247 id === 'raceForecastAdTopOfSimMobile' || 248 id === 'raceForecastAdAfterOddsMobile' || 249 id === 'raceForecastAdUnderPollsMobile' || 250 id === 'approvalAdBeforeTableMobile' || 251 id === 'primary2028AdAfterChartMobile' || 252 id === 'latestPollsAdUnderListMobile' || 253 id === 'upcomingElectionsAdUnderListMobile' 254 ); 255 } 256 257 function setShellProbing(shell) { 258 if (!shell || isStickyPcAd(shell)) return; 259 /* Hub mids: never reserve 90px â stay collapsed until Google marks filled. */ 260 if (isHubMobileMidShell(shell)) { 261 setShellLoading(shell); 262 return; 263 } 264 shell.setAttribute('data-ad-state', 'probing'); 265 try { 266 shell.style.setProperty('height', '90px', 'important'); 267 shell.style.setProperty('max-height', '90px', 'important'); 268 shell.style.setProperty('min-height', '0', 'important'); 269 shell.style.setProperty('margin', '0', 'important'); 270 shell.style.setProperty('overflow', 'hidden', 'important'); 271 shell.style.setProperty('opacity', '0', 'important'); 272 void shell.offsetWidth; 273 } catch (eProbe) { /* ignore */ } 274 } 275 276 function setShellLoading(shell) { 277 if (!shell || isStickyPcAd(shell)) return; 278 shell.setAttribute('data-ad-state', 'loading'); 279 /* Mobile in-content mids: no height cap â Google sizes the creative. */ 280 if (isMobile() && isMobileContentMidShell(shell)) { 281 try { 282 shell.style.removeProperty('display'); 283 shell.style.setProperty('min-height', '0', 'important'); 284 shell.style.setProperty('height', 'auto', 'important'); 285 shell.style.setProperty('max-height', 'none', 'important'); 286 shell.style.setProperty('opacity', '1', 'important'); 287 shell.style.setProperty('overflow', 'visible', 'important'); 288 shell.style.setProperty('pointer-events', 'auto', 'important'); 289 shell.style.setProperty('background', 'transparent', 'important'); 290 } catch (eHubLoad) { /* ignore */ } 291 return; 292 } 293 setShellCollapsed(shell); 294 } 295 296 297 /** Mobile mock white boxes are off unless ?mockads=1. */ 298 function wantMockMobileAds() { 299 try { 300 var q = location.search || ''; 301 if (/[?&]mockads=1(?:&|$)/i.test(q)) return true; 302 return false; 303 } catch (eWant) { 304 return false; 305 } 306 } 307 308 function isMockAdShell(shell) { 309 return !!(shell && shell.getAttribute && shell.getAttribute('data-pollsmax-ad-mock') === '1'); 310 } 311 312 function isMockableMobileAdShell(shell) { 313 if (!shell || !shell.classList) return false; 314 if (shell.classList.contains('pollsmax-ad-row--page-top')) return false; 315 if (shell.classList.contains('pollsmax-ad-single--page-top')) return false; 316 if (shell.classList.contains('pollsmax-site-multiplex')) return true; 317 if (shell.classList.contains('pollsmax-ad-single')) return true; 318 if (shell.classList.contains('pollsmax-ad-row')) return true; 319 return false; 320 } 321 322 function notifyGePollMidAdChartTableGap(shell) { 323 if (!shell || shell.id !== 'raceGePollAdBeforeTableMobile') return; 324 if (typeof ET.scheduleGePollMidAdChartTableGapSync === 'function') { 325 try { ET.scheduleGePollMidAdChartTableGapSync(); } catch (eGap) { /* ignore */ } 326 } else if (typeof ET.syncStateSenGovPollMobileChartTableLayout === 'function') { 327 try { ET.syncStateSenGovPollMobileChartTableLayout(); } catch (eSync) { /* ignore */ } 328 } 329 } 330 331 function paintMockFilledShell(shell) { 332 if (!shell || !isMockableMobileAdShell(shell)) return; 333 shell.setAttribute('data-pollsmax-ad-mock', '1'); 334 shell.setAttribute('data-ad-state', 'filled'); 335 try { 336 shell.classList.remove('is-collapsed'); 337 shell.classList.remove('is-empty'); 338 } catch (eMockCls) { /* ignore */ } 339 shell.removeAttribute('data-pollsmax-ad-empty'); 340 shell.setAttribute('data-pollsmax-ad-ready', '1'); 341 shell.style.visibility = ''; 342 shell.style.position = ''; 343 shell.style.left = ''; 344 shell.style.top = ''; 345 try { shell.style.removeProperty('display'); } catch (eDisp) { /* ignore */ } 346 clearShellLayoutInline(shell); 347 notifyGePollMidAdChartTableGap(shell); 348 349 var nodes = shell.querySelectorAll('ins.adsbygoogle'); 350 var i; 351 for (i = 0; i < nodes.length; i++) { 352 nodes[i].setAttribute('data-ad-status', 'filled'); 353 try { 354 nodes[i].style.setProperty('display', 'none', 'important'); 355 nodes[i].style.setProperty('height', '0', 'important'); 356 nodes[i].style.setProperty('min-height', '0', 'important'); 357 } catch (eIns) { /* ignore */ } 358 } 359 var cells = shell.querySelectorAll('.pollsmax-ad-cell'); 360 for (i = 0; i < cells.length; i++) { 361 cells[i].removeAttribute('data-pollsmax-ad-empty'); 362 cells[i].setAttribute('data-ad-state', 'filled'); 363 } 364 365 var host = shell.querySelector('.pollsmax-ad-single__inner') || 366 shell.querySelector('.pollsmax-ad-row__grid') || 367 shell; 368 var box = shell.querySelector('.pollsmax-ad-mock-box'); 369 if (!box) { 370 box = document.createElement('div'); 371 box.className = 'pollsmax-ad-mock-box' + 372 (shell.classList.contains('pollsmax-site-multiplex') 373 ? ' pollsmax-ad-mock-box--multiplex' 374 : ''); 375 box.setAttribute('aria-hidden', 'true'); 376 host.appendChild(box); 377 } 378 } 379 380 function applyMockMobileAds() { 381 if (!wantMockMobileAds() || !isMobile()) return; 382 var shells = document.querySelectorAll('.pollsmax-ad-shell, aside.pollsmax-site-multiplex'); 383 var i; 384 for (i = 0; i < shells.length; i++) { 385 paintMockFilledShell(shells[i]); 386 } 387 } 388 389 function markShellEmpty(shell) { 390 if (!shell) return; 391 if (isMockAdShell(shell)) return; 392 if (isMobile() && isMobileContentMidShell(shell)) { 393 var midIns = shell.querySelector && shell.querySelector('ins.adsbygoogle'); 394 var midSt = midIns && (midIns.getAttribute('data-ad-status') || ''); 395 if (midSt !== 'unfilled' && midSt !== 'unfill-optimized') { 396 setShellLoading(shell); 397 return; 398 } 399 } 400 if (isStickyPcAd(shell)) { 401 shell.removeAttribute('data-pollsmax-ad-empty'); 402 if (!shell.getAttribute('data-ad-state') || 403 shell.getAttribute('data-ad-state') === 'unfilled') { 404 shell.setAttribute('data-ad-state', 'live'); 405 } 406 clearShellLayoutInline(shell); 407 try { shell.style.removeProperty('display'); } catch (eShow) { /* ignore */ } 408 return; 409 } 410 shell.setAttribute('data-ad-state', 'unfilled'); 411 shell.setAttribute('data-pollsmax-ad-empty', '1'); 412 shell.removeAttribute('data-pollsmax-ad-ready'); 413 try { 414 shell.classList.add('is-collapsed'); 415 shell.classList.add('is-empty'); 416 } catch (eCls) { /* ignore */ } 417 shell.style.marginLeft = ''; 418 shell.style.width = ''; 419 shell.style.maxWidth = ''; 420 shell.style.visibility = ''; 421 shell.style.position = ''; 422 shell.style.left = ''; 423 shell.style.top = ''; 424 clearShellLayoutInline(shell); 425 try { 426 shell.style.setProperty('display', 'none', 'important'); 427 } catch (eHide) { /* ignore */ } 428 } 429 430 function syncRowAfterCellCollapse(shell) { 431 if (!shell || !shell.classList.contains('pollsmax-ad-row')) return; 432 var alive = shell.querySelectorAll('.pollsmax-ad-cell:not([data-pollsmax-ad-empty="1"])'); 433 if (!alive.length) { 434 markShellEmpty(shell); 435 return; 436 } 437 shell.removeAttribute('data-pollsmax-ad-empty'); 438 shell.setAttribute('data-ad-state', 'filled'); 439 shell.setAttribute('data-pollsmax-ad-ready', '1'); 440 var grid = shell.querySelector('.pollsmax-ad-row__grid'); 441 if (grid) { 442 grid.style.gridTemplateColumns = alive.length === 1 ? 'minmax(0, 1fr)' : 'minmax(0, 1fr) minmax(0, 1fr)'; 443 } 444 if (shell.classList.contains('pollsmax-ad-row--page-top')) { 445 requestAnimationFrame(function () { 446 try { syncHeaderWidthAds(); } catch (eR) { /* ignore */ } 447 }); 448 } 449 } 450 451 /** Collapse one ins (and its cell); only nuke the whole row if every cell is empty. */ 452 function markInsEmpty(ins) { 453 if (!ins) return; 454 var shell = shellOf(ins); 455 if (isStickyPcAd(shell)) return; 456 var cell = ins.closest ? ins.closest('.pollsmax-ad-cell') : null; 457 if (cell) { 458 cell.setAttribute('data-ad-state', 'unfilled'); 459 cell.setAttribute('data-pollsmax-ad-empty', '1'); 460 } 461 if (!shell) return; 462 if (shell.classList.contains('pollsmax-ad-row') && cell) { 463 syncRowAfterCellCollapse(shell); 464 return; 465 } 466 markShellEmpty(shell); 467 } 468 469 function isEmptyAdStatus(status) { 470 return status === 'unfilled' || status === 'unfill-optimized'; 471 } 472 473 function insLooksFilled(ins) { 474 if (!ins) return false;
475 if (ins.getAttribute('data-ad-status') === 'filled') return true; 476 if (isEmptyAdStatus(ins.getAttribute('data-ad-status'))) return false; 477 try { 478 var iframe = ins.querySelector('iframe'); 479 if (!iframe) return false; 480 var r = iframe.getBoundingClientRect(); 481 return !!(r && r.height >= 20 && r.width >= 20); 482 } catch (eF) { 483 return false; 484 } 485 } 486 487 function applyInsStatus(ins) { 488 var shell = shellOf(ins); 489 if (!shell || !ins) return; 490 var status = ins.getAttribute('data-ad-status'); 491 /* 492 * Only expand when Google explicitly says filled. 493 * Blank iframes / placeholders look "filled" by size and were causing 494 * huge mobile top gaps with no creative. 495 */ 496 if (status === 'filled') { 497 var cell = ins.closest ? ins.closest('.pollsmax-ad-cell') : null; 498 if (cell) { 499 cell.removeAttribute('data-pollsmax-ad-empty'); 500 cell.setAttribute('data-ad-state', 'filled'); 501 } 502 shell.setAttribute('data-ad-state', 'filled'); 503 shell.removeAttribute('data-pollsmax-ad-empty'); 504 shell.setAttribute('data-pollsmax-ad-ready', '1'); 505 shell.style.visibility = ''; 506 shell.style.position = ''; 507 shell.style.left = ''; 508 shell.style.top = ''; 509 try { shell.style.removeProperty('display'); } catch (eDisp) { /* ignore */ } 510 clearShellLayoutInline(shell); 511 if (isMobile() && (isMobileContentMidShell(shell) || 512 (shell.classList && shell.classList.contains('pollsmax-site-multiplex')))) { 513 pinMobileAdToStandardWidth(shell); 514 setTimeout(function () { pinMobileAdToStandardWidth(shell); }, 200); 515 setTimeout(function () { pinMobileAdToStandardWidth(shell); }, 800); 516 } 517 notifyGePollMidAdChartTableGap(shell); 518 if (shell.classList && ( 519 shell.classList.contains('pollsmax-ad--header-width') || 520 shell.classList.contains('pollsmax-ad-row--page-top') || 521 shell.classList.contains('pollsmax-ad-single--page-top') 522 )) { 523 requestAnimationFrame(function () { 524 try { syncHeaderWidthAds(); } catch (eS) { /* ignore */ } 525 }); 526 } 527 return; 528 } 529 var ag = ins.getAttribute('data-adsbygoogle-status'); 530 /* Explicit Google no-fill â collapse (unless sticky PC top/multiplex). */ 531 if (isEmptyAdStatus(status)) { 532 if (isStickyPcAd(shell)) { 533 shell.setAttribute('data-ad-state', 'live'); 534 shell.removeAttribute('data-pollsmax-ad-empty'); 535 clearShellLayoutInline(shell); 536 return; 537 } 538 markInsEmpty(ins); 539 return; 540 } 541 /* 542 * `done` without a status is ambiguous â defer to the empty timer so late 543 * fills (Display, multiplex) are not wiped immediately. 544 * iframe-size hints alone must not expand the shell. 545 */ 546 if (ag === 'done' && status !== 'filled') { 547 if (isStickyPcAd(shell)) { 548 shell.setAttribute('data-ad-state', 'live'); 549 return; 550 } 551 scheduleEmptyTimeout(ins); 552 } 553 } 554 555 function emptyHardMsFor(ins, shell) { 556 shell = shell || shellOf(ins); 557 if (shell && shell.classList && shell.classList.contains('pollsmax-site-multiplex')) { 558 return EMPTY_HARD_MS_MULTIPLEX; 559 } 560 if (shell && (isPageTopShell(shell) || isMobileContentMidShell(shell))) { 561 return EMPTY_HARD_MS_PAGE_TOP; 562 } 563 return EMPTY_HARD_MS; 564 } 565 566 function scheduleEmptyTimeout(ins) { 567 if (!ins || ins.getAttribute('data-pollsmax-empty-timer') === '1') return; 568 var shellEarly = shellOf(ins); 569 if (isStickyPcAd(shellEarly)) return; 570 ins.setAttribute('data-pollsmax-empty-timer', '1'); 571 var started = Date.now(); 572 var shell = shellEarly; 573 var hardMs = emptyHardMsFor(ins, shell); 574 575 function check(hard) { 576 try { 577 if (ins.getAttribute('data-ad-status') === 'filled') { 578 applyInsStatus(ins); 579 return true; 580 } 581 var status = ins.getAttribute('data-ad-status'); 582 var ag = ins.getAttribute('data-adsbygoogle-status'); 583 /* Explicit Google no-fill â collapse immediately. */ 584 if (isEmptyAdStatus(status)) { 585 markInsEmpty(ins); 586 return true; 587 } 588 /* 589 * Ambiguous `done` / no status: wait for hard timeout. 590 * Placeholder iframes still count as "not settled". 591 */ 592 if (ag === 'done' && !hard) { 593 return false;
594 } 595 if (hard) { 596 /* 597 * Page-top / multiplex / mobile mid singles: leave collapsed 598 * (loading) on ambiguous done so a late filled status can 599 * still expand. Only collapse when Google explicitly marks 600 * empty. Mid singles were dying at 6s while still filling. 601 */ 602 var softShell = shellOf(ins); 603 if ( 604 softShell && 605 (isPageTopShell(softShell) || 606 isMobileContentMidShell(softShell) || 607 softShell.classList.contains('pollsmax-site-multiplex')) 608 ) { 609 if ( 610 softShell.getAttribute('data-ad-state') === 'probing' || 611 softShell.getAttribute('data-ad-state') === 'waiting' 612 ) { 613 setShellLoading(softShell); 614 } 615 return true; 616 } 617 markInsEmpty(ins); 618 return true; 619 } 620 } catch (eT) { /* ignore */ } 621 return false; 622 } 623 624 setTimeout(function () { 625 if (check(false)) return; 626 var left = Math.max(50, hardMs - (Date.now() - started)); 627 setTimeout(function () { check(true); }, left); 628 }, EMPTY_SOFT_MS); 629 630 var polls = 0; 631 var maxPolls = Math.ceil(hardMs / 150) + 2; 632 var pollId = setInterval(function () { 633 polls++; 634 if (check(false) || polls >= maxPolls || Date.now() - started >= hardMs) { 635 clearInterval(pollId); 636 } 637 }, 150); 638 } 639 640 function watchInsStatus(ins) { 641 if (!ins || ins.getAttribute('data-pollsmax-status-watch') === '1') return; 642 if (typeof MutationObserver !== 'function') return; 643 ins.setAttribute('data-pollsmax-status-watch', '1'); 644 try { 645 var obs = new MutationObserver(function () { 646 applyInsStatus(ins); 647 var status = ins.getAttribute('data-ad-status'); 648 /* Keep watching through ambiguous `done` â status often lands after. */ 649 if (status === 'filled' || isEmptyAdStatus(status)) { 650 try { obs.disconnect(); } catch (eD) { /* ignore */ } 651 } 652 }); 653 obs.observe(ins, { 654 attributes: true, 655 attributeFilter: ['data-ad-status', 'data-adsbygoogle-status'] 656 }); 657 } catch (eObs) { /* ignore */ } 658 } 659 660 /** Replace a dead/stale ins so we can push a fresh unit. */ 661 function refreshInsForRetry(ins) { 662 if (!ins || !ins.parentNode) return null; 663 var next = ins.cloneNode(false); 664 next.removeAttribute('data-adsbygoogle-status'); 665 next.removeAttribute('data-ad-status'); 666 next.removeAttribute('data-pollsmax-empty-timer'); 667 next.removeAttribute('data-pollsmax-status-watch'); 668 next.removeAttribute(PUSHED); 669 delete next.dataset.pollsmaxInitialized; 670 next.innerHTML = ''; 671 ins.parentNode.replaceChild(next, ins); 672 return next; 673 } 674 675 function initializeAdElement(ins) { 676 if (!(ins instanceof HTMLElement)) return false; 677 if (!ins.classList.contains('adsbygoogle')) return false; 678 /* Multiplex is owned exclusively by initializeAdSenseUnit / observe path. */ 679 var muxShell = shellOf(ins); 680 if (muxShell && muxShell.classList && muxShell.classList.contains('pollsmax-site-multiplex')) { 681 return false; 682 } 683 if (ins.dataset && ins.dataset.pollsmaxPushed === '1') return false; 684 if (ins.dataset.pollsmaxInitialized === 'true') return false; 685 if (ins.getAttribute('data-ad-status') === 'filled') return false; 686 if (isEmptyAdStatus(ins.getAttribute('data-ad-status'))) return false; 687 688 /* 689 * If Google marked the tag `done` before we pushed (common when the 690 * shell was height:0), refresh the node and push once instead of 691 * permanently collapsing page-top / multiplex / mobile mid singles. 692 */ 693 if (ins.getAttribute('data-adsbygoogle-status') === 'done') { 694 if (ins.getAttribute('data-pollsmax-retry') === '1') { 695 if (!isStickyPcAd(shellOf(ins))) markInsEmpty(ins); 696 return false;
697 } 698 var shellDone = shellOf(ins); 699 if ( 700 shellDone && 701 (isPageTopShell(shellDone) || 702 isMobileContentMidShell(shellDone) || 703 shellDone.classList.contains('pollsmax-site-multiplex')) 704 ) { 705 ins = refreshInsForRetry(ins); 706 if (!ins) return false; 707 ins.setAttribute('data-pollsmax-retry', '1'); 708 } else { 709 markInsEmpty(ins); 710 return false; 711 } 712 } 713 714 var pushShell = shellOf(ins); 715 if (isMobile() && pushShell) pinMobileAdToStandardWidth(pushShell); 716 var width = usableWidth(ins); 717 if (width < 50) { 718 /* Page-top / multiplex / mobile mids: use document width as a last resort. */ 719 var shellW = shellOf(ins); 720 if ( 721 shellW && 722 (isPageTopShell(shellW) || 723 isMobileContentMidShell(shellW) || 724 shellW.classList.contains('pollsmax-site-multiplex')) 725 ) { 726 width = Math.max( 727 width, 728 measureMobilePanelWidth(), 729 shellW.clientWidth || 0 730 ); 731 } 732 } 733 if (width < 50) return false; 734 735 ins.dataset.pollsmaxInitialized = 'true'; 736 ins.setAttribute(PUSHED, '1'); 737 var shell = shellOf(ins); 738 var sticky = isStickyPcAd(shell); 739 if (shell) { 740 if (sticky) { 741 /* PC top/multiplex: stay in-flow at full size â never collapse. */ 742 shell.setAttribute('data-ad-state', 'live'); 743 shell.removeAttribute('data-pollsmax-ad-empty'); 744 clearShellLayoutInline(shell); 745 try { shell.style.removeProperty('display'); } catch (eLive) { /* ignore */ } 746 } else { 747 setShellProbing(shell); 748 } 749 } 750 751 try { 752 global.adsbygoogle = global.adsbygoogle || []; 753 void 0; 754 watchInsStatus(ins); 755 if (!sticky) scheduleEmptyTimeout(ins); 756 if (!sticky && shell && shell.getAttribute('data-ad-state') === 'probing') { 757 setShellLoading(shell); 758 } 759 if (sticky && shell && shell.classList.contains('pollsmax-ad-row--page-top')) { 760 requestAnimationFrame(function () { 761 try { syncHeaderWidthAds(); } catch (eW) { /* ignore */ } 762 }); 763 } 764 return true; 765 } catch (error) { 766 delete ins.dataset.pollsmaxInitialized; 767 try { ins.removeAttribute(PUSHED); } catch (eR) { /* ignore */ } 768 if (!sticky) markInsEmpty(ins); 769 if (typeof console !== 'undefined' && console.error) { 770 console.error('Pollsmax AdSense initialization failed', error); 771 } 772 return false; 773 } 774 } 775 776 function initializeShell(shell) { 777 if (!shell) return false; 778 if (shell.getAttribute('data-pollsmax-ad-empty') === '1' && !isStickyPcAd(shell)) return false; 779 var nodes = shell.querySelectorAll('ins.adsbygoogle'); 780 var ok = false; 781 var i; 782 for (i = 0; i < nodes.length; i++) { 783 if (initializeAdElement(nodes[i])) ok = true; 784 } 785 return ok; 786 } 787 788 function tryInitializeShell(shell, attemptsLeft) { 789 if (!shell) return; 790 if (initializeShell(shell)) return; 791 if (attemptsLeft <= 0) { 792 /* Multiplex / sticky PC top: keep retrying via later ensure, never nuke. */ 793 if (isStickyPcAd(shell)) return; 794 markShellEmpty(shell); 795 return; 796 } 797 requestAnimationFrame(function () { 798 tryInitializeShell(shell, attemptsLeft - 1); 799 }); 800 } 801 802 function getLazyObserver() { 803 if (lazyObserver) return lazyObserver; 804 if (typeof IntersectionObserver !== 'function') { 805 lazyObserver = { 806 observe: function (el) { tryInitializeShell(el, 16); }, 807 unobserve: function () {}, 808 disconnect: function () {} 809 }; 810 return lazyObserver; 811 } 812 lazyObserver = new IntersectionObserver(function (entries) { 813 var i; 814 for (i = 0; i < entries.length; i++) { 815 var entry = entries[i]; 816 if (!entry.isIntersecting) continue; 817 var shell = entry.target; 818 try { lazyObserver.unobserve(shell); } catch (eU) { /* ignore */ } 819 tryInitializeShell(shell, 16); 820 } 821 }, { 822 root: null, 823 rootMargin: IO_ROOT_MARGIN, 824 threshold: 0.01 825 }); 826 return lazyObserver; 827 } 828 829 function schedulePriorityInit(shell) { 830 if (!shell) return; 831 /* 832 * PC mid duals (page-top chrome, not under-header): wait for Multiplex 833 * to claim its push first, otherwise AdSense fills mids and Multiplex 834 * stays empty above the footer. 835 */ 836 if ( 837 !isMobile() && 838 isPageTopShell(shell) &&
839 !isTrueHeaderPageTop(shell) && 840 multiplexPushPending() 841 ) { 842 var deferTries = shell._pmMuxDeferTries || 0; 843 if (deferTries < 40) { 844 shell._pmMuxDeferTries = deferTries + 1; 845 setTimeout(function () { 846 schedulePriorityInit(shell); 847 }, 120); 848 return; 849 } 850 } 851 var attempts = 0; 852 function tick() { 853 attempts += 1; 854 if (initializeShell(shell)) return; 855 if (attempts < 12) { 856 requestAnimationFrame(tick); 857 return; 858 } 859 /* Layout never gained width â fall back to IO. */ 860 shell.setAttribute('data-pollsmax-observed', '1'); 861 getLazyObserver().observe(shell); 862 } 863 requestAnimationFrame(function () { 864 requestAnimationFrame(tick); 865 }); 866 } 867 868 function observeShell(shell, opts) { 869 opts = opts || {}; 870 if (!shell || !shell.querySelector) return; 871 if (!shell.querySelector('ins.adsbygoogle')) return; 872 /* Multiplex: never lazy-IO / priority path â direct snippet push only. */ 873 if (shell.classList && shell.classList.contains('pollsmax-site-multiplex')) { 874 shell.setAttribute('data-pollsmax-observed', '1'); 875 return; 876 } 877 if (shell.getAttribute('data-pollsmax-observed') === '1') return; 878 if (shell.querySelector('ins.adsbygoogle[data-pollsmax-initialized="true"]')) { 879 shell.setAttribute('data-pollsmax-observed', '1'); 880 return; 881 } 882 shell.classList.add('pollsmax-ad-shell'); 883 if (!shell.getAttribute('data-ad-state')) shell.setAttribute('data-ad-state', 'waiting'); 884 885 if (opts.priority) { 886 shell.setAttribute('data-pollsmax-observed', '1'); 887 shell.classList.add('pollsmax-home-ad--priority'); 888 schedulePriorityInit(shell); 889 return; 890 } 891 892 /* Above-fold page-top on home: defer observe so maps/LCP settle first. */ 893 if (opts.deferIdle) { 894 shell.setAttribute('data-pollsmax-observed', '1'); 895 var start = function () { getLazyObserver().observe(shell); }; 896 if (typeof global.requestIdleCallback === 'function') { 897 global.requestIdleCallback(start, { timeout: 2200 }); 898 } else { 899 setTimeout(start, 1); 900 } 901 return; 902 } 903 904 shell.setAttribute('data-pollsmax-observed', '1'); 905 getLazyObserver().observe(shell); 906 } 907 908 function isPriorityShell(shell) { 909 if (!shell) return false; 910 if (shell.id === 'home-after-opening-maps') return true; 911 if (shell.getAttribute('data-ad-place') === 'home-after-opening-maps') return true; 912 /* Mobile mid singles: priority push â lazy IO starves 0-height collapsed shells. */ 913 if (isMobile() && isMobileContentMidShell(shell)) return true; 914 return false; 915 } 916 917 function isPageTopShell(shell) { 918 return !!(shell && shell.classList && ( 919 shell.classList.contains('pollsmax-ad-row--page-top') || 920 shell.classList.contains('pollsmax-ad-single--page-top') 921 )); 922 } 923 924 function registerAllShells(root) { 925 var scope = root && root.querySelectorAll ? root : document; 926 var shells = scope.querySelectorAll( 927 '.pollsmax-ad-banner, .pollsmax-ad-row, .pollsmax-ad-single, aside.pollsmax-site-multiplex, #home-after-opening-maps' 928 ); 929 var i; 930 for (i = 0; i < shells.length; i++) { 931 var shell = shells[i]; 932 if (isPriorityShell(shell)) { 933 observeShell(shell, { priority: true }); 934 continue; 935 } 936 if (isPageTopShell(shell)) { 937 /* Page-top is always priority â idle defer was missing fills. */ 938 observeShell(shell, { priority: true }); 939 continue; 940 } 941 if (shell.classList.contains('pollsmax-site-multiplex')) { 942 observeShell(shell, { priority: true }); 943 continue; 944 } 945 observeShell(shell, {}); 946 } 947 } 948 949 /* Back-compat: callers that used to push every ins now only register lazy shells. */ 950 function initAds(root) { 951 registerAllShells(root); 952 } 953 954 function makeIns(format, opts) { 955 var inner = document.createElement('div'); 956 inner.className = 'pollsmax-ad-single__inner pm-ad-slot'; 957 return inner; 958 } 959 960 /** 961 * Ads nested in a chart/content column â keep parent width. 962 * Hub-tall units sit under hub charts beside the races list; never 963 * break out to logoâDonate or they overlap the sidebar. 964 */ 965 function isChartColumnExtra(extraClass) { 966 var s = String(extraClass || ''); 967 return ( 968 s.indexOf('in-col') !== -1 || 969 s.indexOf('forecast-trend') !== -1 || 970 s.indexOf('hub-tall') !== -1 971 ); 972 } 973 974 /** 975 * True under-header page-top dual/single (not mid duals that reuse page-top chrome). 976 * These keep legacy PC placement/spacing â shared PC banner spacing must not touch them. 977 */
978 function isTrueHeaderPageTop(shell, extraClass) { 979 if (!shell || !shell.classList) return false; 980 var extra = String(extraClass || shell.className || ''); 981 var looksPageTop = 982 extra.indexOf('page-top') !== -1 || 983 shell.classList.contains('pollsmax-ad-row--page-top') || 984 shell.classList.contains('pollsmax-ad-single--page-top'); 985 if (!looksPageTop) return false; 986 if ( 987 shell.classList.contains('pollsmax-ad-row--home-mid') || 988 shell.classList.contains('pollsmax-ad-row--race-mid') || 989 shell.classList.contains('pollsmax-ad-row--forecast-mid') || 990 shell.classList.contains('pollsmax-ad-row--latest-polls-infeed') || 991 shell.classList.contains('pollsmax-ad-row--upcoming-elections-infeed') || 992 extra.indexOf('home-mid') !== -1 || 993 extra.indexOf('race-mid') !== -1 || 994 extra.indexOf('forecast-mid') !== -1 || 995 extra.indexOf('latest-polls-infeed') !== -1 || 996 extra.indexOf('upcoming-elections-infeed') !== -1 997 ) { 998 return false; 999 } 1000 return true; 1001 } 1002 1003 function clearAdAdjacentContentMarks(shell) { 1004 if (!shell || !shell.parentNode) return; 1005 var prev = shell.previousElementSibling; 1006 var next = shell.nextElementSibling; 1007 if (prev && prev.classList) { 1008 prev.classList.remove('pm-content-before-ad'); 1009 prev.classList.remove('pm-content-after-ad'); 1010 } 1011 if (next && next.classList) { 1012 next.classList.remove('pm-content-before-ad'); 1013 next.classList.remove('pm-content-after-ad'); 1014 } 1015 } 1016 1017 /** 1018 * Shared spacing boundary classes (see pm-ad-spacing.css). 1019 * Mobile mids â pm-mobile-ad-boundary; PC mid banners â pm-pc-banner-ad-boundary. 1020 * True page-top under header is never a PC banner boundary. 1021 */ 1022 function decorateAdSpacingBoundary(aside, opts) { 1023 if (!aside || !aside.classList) return; 1024 opts = opts || {}; 1025 var extra = String(opts.extraClass || aside.className || ''); 1026 var mobileMid = 1027 opts.mobileOnly === true || 1028 extra.indexOf('poll-mid-mobile') !== -1 || 1029 aside.classList.contains('pollsmax-ad-single--poll-mid-mobile'); 1030 var raceOrForecastMid = 1031 extra.indexOf('race-mid') !== -1 || 1032 extra.indexOf('forecast-mid') !== -1 || 1033 aside.classList.contains('pollsmax-ad-row--race-mid') || 1034 aside.classList.contains('pollsmax-ad-row--forecast-mid'); 1035 var headerPageTop = isTrueHeaderPageTop(aside, extra); 1036 var isBanner = 1037 aside.classList.contains('pollsmax-ad-banner') || 1038 raceOrForecastMid || 1039 aside.classList.contains('pollsmax-ad-row--home-mid') || 1040 aside.classList.contains('pollsmax-ad-row--latest-polls-infeed') || 1041 aside.classList.contains('pollsmax-ad-row--upcoming-elections-infeed'); 1042 1043 if (mobileMid) { 1044 aside.classList.add('pm-mobile-ad-boundary'); 1045 } 1046 /* Forecast PC keeps legacy spacing â do not attach PC boundary class. */ 1047 var onForecastPc = false; 1048 try { 1049 onForecastPc = !!( 1050 document.body && 1051 document.body.classList.contains('race-view-forecast') && 1052 !isMobile() 1053 ); 1054 } catch (eFc) { 1055 onForecastPc = false; 1056 } 1057 if (isBanner && !headerPageTop && !onForecastPc) { 1058 aside.classList.add('pm-pc-banner-ad-boundary'); 1059 } 1060 if (headerPageTop) { 1061 aside.classList.remove('pm-pc-banner-ad-boundary'); 1062 clearAdAdjacentContentMarks(aside); 1063 } 1064 1065 var slot = null; 1066 var kid = aside.firstElementChild; 1067 while (kid) { 1068 if ( 1069 kid.classList && 1070 (kid.classList.contains('pollsmax-ad-single__inner') || 1071 kid.classList.contains('pollsmax-ad-row__grid') || 1072 kid.classList.contains('pm-ad-slot')) 1073 ) { 1074 slot = kid; 1075 break; 1076 } 1077 kid = kid.nextElementSibling; 1078 } 1079 if (!slot) { 1080 slot = 1081 aside.querySelector('.pollsmax-ad-single__inner') || 1082 aside.querySelector('.pollsmax-ad-row__grid'); 1083 } 1084 if (slot && slot.classList) { 1085 slot.classList.add('pm-ad-slot'); 1086 } 1087 } 1088 1089 function markAdAdjacentContent(shell) { 1090 if (!shell || !shell.parentNode) return; 1091 /* PC/mobile shared spacing must not rewrite gaps around under-header page-top ads. */
1092 if (isTrueHeaderPageTop(shell)) { 1093 clearAdAdjacentContentMarks(shell); 1094 return; 1095 } 1096 var prev = shell.previousElementSibling; 1097 var next = shell.nextElementSibling; 1098 while (prev && prev.nodeType === 1) { 1099 if (prev.classList && prev.classList.contains('pollsmax-ad-shell')) { 1100 prev = prev.previousElementSibling; 1101 continue; 1102 } 1103 if (prev.offsetParent === null && prev.getAttribute && 1104 (prev.getAttribute('hidden') !== null || 1105 (prev.style && prev.style.display === 'none') || 1106 (prev.classList && prev.classList.contains('home-key-races-grid')))) { 1107 /* Skip obviously hidden nodes; keep walking for Key Races wrapper. */ 1108 if (prev.id === 'homeKeyRacesSection') { 1109 prev = prev.previousElementSibling; 1110 continue; 1111 } 1112 } 1113 if (prev.classList) prev.classList.add('pm-content-before-ad'); 1114 break; 1115 } 1116 while (next && next.nodeType === 1) { 1117 if (next.classList && next.classList.contains('pollsmax-ad-shell')) { 1118 next = next.nextElementSibling; 1119 continue; 1120 } 1121 if (next.id === 'homeKeyRacesSection') { 1122 next = next.nextElementSibling; 1123 continue; 1124 } 1125 if (next.classList) next.classList.add('pm-content-after-ad'); 1126 break; 1127 } 1128 } 1129 1130 function makeBanner(id, opts) { 1131 opts = opts || {}; 1132 var aside = document.createElement('aside'); 1133 var extra = opts.extraClass || ''; 1134 aside.id = id; 1135 aside.className = 'pollsmax-ad-banner pollsmax-ad-shell' + 1136 (extra ? ' ' + extra : '') + 1137 (!isChartColumnExtra(extra) ? ' pollsmax-ad--header-width' : ''); 1138 aside.setAttribute('aria-hidden', 'true'); 1139 aside.setAttribute('data-ad-state', 'waiting'); 1140 var inner = document.createElement('div'); 1141 inner.className = 'pollsmax-ad-single__inner pm-ad-slot'; 1142 inner.appendChild(makeIns(opts.format || 'auto')); 1143 aside.appendChild(inner); 1144 decorateAdSpacingBoundary(aside, opts); 1145 return aside; 1146 } 1147 1148 function makeRow(id, opts) { 1149 opts = opts || {}; 1150 var aside = document.createElement('aside'); 1151 var extra = opts.extraClass || ''; 1152 var shortTop = extra.indexOf('page-top-short') !== -1; 1153 var isPageTop = extra.indexOf('page-top') !== -1; 1154 aside.id = id; 1155 aside.className = 'pollsmax-ad-row pollsmax-ad-shell' + 1156 (extra ? ' ' + extra : '') + 1157 /* Page-top already syncs; other 2-up rows use header channel width. */ 1158 (!isPageTop ? ' pollsmax-ad--header-width' : ''); 1159 aside.setAttribute('aria-hidden', 'true'); 1160 aside.setAttribute('data-ad-state', 'waiting'); 1161 var grid = document.createElement('div'); 1162 grid.className = 'pollsmax-ad-row__grid pm-ad-slot'; 1163 var i; 1164 for (i = 0; i < 2; i++) { 1165 var cell = document.createElement('div'); 1166 cell.className = 'pollsmax-ad-cell' + (shortTop ? ' pollsmax-ad-cell--short' : ''); 1167 cell.appendChild(makeIns( 1168 opts.format || 'rectangle', 1169 /* 1170 * Dual PC rows: fixed 250px rectangle boxes. FWR/auto was still 1171 * serving short natives into a tall empty (white) shell. 1172 */ 1173 { 1174 fullWidthResponsive: false, 1175 slotH: opts.slotH != null ? opts.slotH : 250 1176 } 1177 )); 1178 grid.appendChild(cell); 1179 } 1180 aside.appendChild(grid); 1181 decorateAdSpacingBoundary(aside, opts); 1182 return aside; 1183 } 1184 1185 function makeSingle(id, opts) { 1186 opts = opts || {}; 1187 var aside = document.createElement('aside'); 1188 aside.id = id; 1189 aside.className = 'pollsmax-ad-single pollsmax-ad-shell' + 1190 (opts.extraClass ? ' ' + opts.extraClass : ''); 1191 aside.setAttribute('aria-hidden', 'true'); 1192 aside.setAttribute('data-ad-state', 'waiting'); 1193 var inner = document.createElement('div'); 1194 inner.className = 'pollsmax-ad-single__inner pm-ad-slot'; 1195 var midMob = isMobile() && String(opts.extraClass || '').indexOf('poll-mid-mobile') !== -1; 1196 inner.appendChild(makeIns( 1197 opts.format || 'auto', 1198 { 1199 fullWidthResponsive: midMob ? false : opts.fullWidthResponsive, 1200 slotH: opts.slotH 1201 } 1202 )); 1203 aside.appendChild(inner); 1204 decorateAdSpacingBoundary(aside, opts); 1205 return aside; 1206 } 1207 1208 function placeOnce(id, build, insert) { 1209 var existing = document.getElementById(id); 1210 if (existing) { 1211 decorateAdSpacingBoundary(existing, {}); 1212 if (typeof insert === 'function') insert(existing); 1213 markAdAdjacentContent(existing); 1214 return existing; 1215 } 1216 var el = build(); 1217 if (!el) return null; 1218 if (typeof insert === 'function') insert(el); 1219 else return null; 1220 decorateAdSpacingBoundary(el, {}); 1221 markAdAdjacentContent(el); 1222 return el; 1223 } 1224 1225 function insertAfter(anchor, el) { 1226 if (!anchor || !anchor.parentNode || !el) return; 1227 if (el.parentNode === anchor.parentNode && el.previousElementSibling === anchor) return;
1228 anchor.parentNode.insertBefore(el, anchor.nextSibling); 1229 } 1230 1231 /** 1232 * Mobile panel pages: mid-ads must be siblings BETWEEN panels, never nested 1233 * inside .senate-hub-mob-panel / home card panes (that forces in-panel width 1234 * and wrecks the equal gap rhythm). 1235 */ 1236 /** 1237 * Match painted panel OUTER width. Full-width-responsive ads ignore the 1238 * parent and size to the viewport â turn that off and lock px width. 1239 */ 1240 function pinMobileAdToStandardWidth(el) { 1241 if (!isMobile() || !el) return; 1242 if ( 1243 el.getAttribute('data-ad-state') === 'unfilled' || 1244 el.getAttribute('data-pollsmax-ad-empty') === '1' 1245 ) { 1246 return; 1247 } 1248 if ( 1249 el.classList && 1250 (el.classList.contains('pollsmax-ad-row--page-top') || 1251 el.classList.contains('pollsmax-ad-single--page-top')) 1252 ) { 1253 return; 1254 } 1255 var isMux = !!(el.classList && el.classList.contains('pollsmax-site-multiplex')); 1256 if (!isMux && !isMobileContentMidShell(el)) return; 1257 var w = measureMobilePanelWidth(); 1258 if (!(w > 50)) return; 1259 try { 1260 document.documentElement.style.setProperty('--et-mobile-standard-width', w + 'px'); 1261 el.style.setProperty('box-sizing', 'border-box', 'important'); 1262 el.style.setProperty('display', 'block', 'important'); 1263 el.style.setProperty('width', w + 'px', 'important'); 1264 el.style.setProperty('min-width', w + 'px', 'important'); 1265 el.style.setProperty('max-width', w + 'px', 'important'); 1266 el.style.setProperty('flex', '0 0 ' + w + 'px', 'important'); 1267 el.style.setProperty('align-self', 'center', 'important'); 1268 el.style.setProperty('margin-left', 'auto', 'important'); 1269 el.style.setProperty('margin-right', 'auto', 'important'); 1270 el.style.setProperty('padding-left', '0', 'important'); 1271 el.style.setProperty('padding-right', '0', 'important'); 1272 var ins = el.querySelector('ins.adsbygoogle'); 1273 if (ins && !isMux) { 1274 ins.setAttribute('data-full-width-responsive', 'false'); 1275 ins.style.setProperty('display', 'block', 'important'); 1276 ins.style.setProperty('width', '100%', 'important'); 1277 ins.style.setProperty('max-width', '100%', 'important'); 1278 ins.style.setProperty('min-width', '0', 'important'); 1279 ins.style.setProperty('margin-left', '0', 'important'); 1280 ins.style.setProperty('margin-right', '0', 'important'); 1281 ins.style.setProperty('left', 'auto', 'important'); 1282 ins.style.setProperty('right', 'auto', 'important'); 1283 ins.style.setProperty('position', 'relative', 'important'); 1284 } 1285 var inner = el.querySelector('.pollsmax-ad-single__inner'); 1286 if (inner) { 1287 inner.style.setProperty('width', '100%', 'important'); 1288 inner.style.setProperty('max-width', '100%', 'important'); 1289 inner.style.setProperty('box-sizing', 'border-box', 'important'); 1290 } 1291 var iframe = el.querySelector('iframe'); 1292 if (iframe && !isMux) { 1293 iframe.removeAttribute('width'); 1294 iframe.style.setProperty('width', '100%', 'important'); 1295 iframe.style.setProperty('max-width', '100%', 'important'); 1296 iframe.style.setProperty('min-width', '0', 'important'); 1297 } 1298 } catch (ePin) { /* ignore */ } 1299 } 1300 1301 function syncMidAdPanelOuterWidths() { 1302 if (!isMobile()) return; 1303 var nodes = document.querySelectorAll( 1304 '.pollsmax-ad-single--poll-mid-mobile, aside.pollsmax-site-multiplex, .pm-mobile-ad-boundary.pollsmax-ad-shell' 1305 ); 1306 var ids = [ 1307 'homeAdBetweenGovAndSearch', 1308 'homeAdBetweenGbAndDemPrimary', 1309 'senateHubAdAfterMapMobile', 1310 'senateHubAdBeforeRacesMobile', 1311 'govHubAdAfterMapMobile', 1312 'govHubAdBeforeRacesMobile', 1313 'houseHubAdAfterMapMobile', 1314 'houseHubAdBeforeRacesMobile', 1315 'raceGePollAdBeforeTableMobile', 1316 'raceGePollAdAfterTableMobile', 1317 'racePrimaryPollAdBeforeTableMobile', 1318 'racePrimaryPollAdAfterTableMobile', 1319 'raceForecastAdTopOfSimMobile', 1320 'raceForecastAdAfterOddsMobile', 1321 'raceForecastAdUnderPollsMobile', 1322 'approvalAdBeforeTableMobile', 1323 'primary2028AdAfterChartMobile', 1324 'latestPollsAdUnderListMobile', 1325 'upcomingElectionsAdUnderListMobile' 1326 ]; 1327 var seen = []; 1328 var i; 1329 for (i = 0; i < nodes.length; i++) { 1330 if (nodes[i] && seen.indexOf(nodes[i]) < 0) seen.push(nodes[i]); 1331 } 1332 for (i = 0; i < ids.length; i++) { 1333 var el = document.getElementById(ids[i]); 1334 if (el && seen.indexOf(el) < 0) seen.push(el); 1335 } 1336 for (i = 0; i < seen.length; i++) pinMobileAdToStandardWidth(seen[i]); 1337 } 1338 1339 function scheduleMidAdPanelOuterWidthSync() { 1340 function run() { 1341 ejectMidAdsFromPanels(); 1342 syncMidAdPanelOuterWidths(); 1343 } 1344 run(); 1345 requestAnimationFrame(run); 1346 setTimeout(run, 120); 1347 setTimeout(run, 500); 1348 setTimeout(run, 1600); 1349 } 1350 1351 function ejectMidAdsFromPanels() { 1352 if (!isMobile()) return; 1353 var i; 1354 var govSearch = document.getElementById('homeAdBetweenGovAndSearch'); 1355 if (govSearch) { 1356 var upcoming = document.getElementById('homeUpcomingElectionsSection'); 1357 var search = document.getElementById('searchAllPollsSection'); 1358 var trappedHome = 1359 (govSearch.closest && 1360 (govSearch.closest('#homeUpcomingElectionsSection') || 1361 govSearch.closest('#searchAllPollsSection') || 1362 govSearch.closest('.sleek-map-pane'))) || 1363 null; 1364 if (trappedHome || (upcoming && govSearch.previousElementSibling !== upcoming)) { 1365 if (upcoming && upcoming.parentNode) insertAfter(upcoming, govSearch); 1366 else if (search) insertBefore(search, govSearch); 1367 } 1368 } 1369 var gbDem = document.getElementById('homeAdBetweenGbAndDemPrimary'); 1370 if (gbDem && gbDem.closest) { 1371 var trappedGb = gbDem.closest('.sleek-map-pane, .senate-hub-mob-panel'); 1372 if (trappedGb) { 1373 var approvalRow = document.querySelector('.sleek-maps--home-approval'); 1374 if (approvalRow && approvalRow.parentNode) insertAfter(approvalRow, gbDem); 1375 else if (trappedGb.parentNode) insertAfter(trappedGb, gbDem); 1376 } 1377 } 1378 var hubIds = [ 1379 'senateHubAdAfterMapMobile', 1380 'senateHubAdBeforeRacesMobile', 1381 'govHubAdAfterMapMobile', 1382 'govHubAdBeforeRacesMobile', 1383 'houseHubAdAfterMapMobile', 1384 'houseHubAdBeforeRacesMobile', 1385 'raceForecastAdTopOfSimMobile', 1386 'raceForecastAdAfterOddsMobile', 1387 'raceForecastAdUnderPollsMobile' 1388 ]; 1389 for (i = 0; i < hubIds.length; i++) { 1390 var el = document.getElementById(hubIds[i]); 1391 if (!el || !el.parentNode || !el.closest) continue; 1392 var panel = el.closest('.senate-hub-mob-panel, .sleek-map-pane'); 1393 if (!panel || panel === el) continue; 1394 if (panel.parentNode) insertAfter(panel, el); 1395 } 1396 } 1397 1398 function insertBefore(anchor, el) { 1399 if (!anchor || !anchor.parentNode || !el) return; 1400 if (el.parentNode === anchor.parentNode && el.nextElementSibling === anchor) return;
1401 anchor.parentNode.insertBefore(el, anchor); 1402 } 1403 1404 function appendChild(host, el) { 1405 if (!host || !el) return; 1406 if (el.parentNode !== host) host.appendChild(el); 1407 } 1408 1409 /* ---------- Global: top of page ---------- */ 1410 1411 /** 1412 * Insert top ads as the first content in <main> â under #mainHeader and 1413 * #site-primary-nav (those sit outside main), above hero / page-head sections. 1414 */ 1415 function insertPageTopAd(el) { 1416 if (!el) return; 1417 var main = document.querySelector('main'); 1418 if (!main) return; 1419 var parent = main.parentNode; 1420 if (!parent) return; 1421 1422 /* 1423 * National approval (Trump / generic ballot): keep page-top outside <main>, 1424 * after the select bar when present â same slot on both pages so the gap 1425 * under the ad to the H1 matches. 1426 */ 1427 if (document.body && document.body.classList.contains('national-approval-polls-page')) { 1428 var bar = document.getElementById('stateRaceSelectBar'); 1429 if (bar && bar.parentNode === parent) { 1430 if (el.previousElementSibling !== bar || el.nextElementSibling !== main) { 1431 parent.insertBefore(el, main); 1432 } 1433 return; 1434 } 1435 } 1436 1437 /* Prefer mounting right after the sitelinks strip when it is a sibling before main. */ 1438 var strip = document.getElementById('site-primary-nav') || 1439 document.querySelector('nav.sleek-sitelinks-strip, .sleek-sitelinks-strip'); 1440 if (strip && strip.parentNode === parent) { 1441 if (el.previousElementSibling !== strip || el.nextElementSibling !== main) { 1442 parent.insertBefore(el, main); 1443 } 1444 return; 1445 } 1446 1447 /* Fallback: first child of main, above heroes. */ 1448 if (main.firstElementChild !== el) { 1449 main.insertBefore(el, main.firstChild); 1450 } 1451 } 1452 1453 /** 1454 * Content channel for page-top + wide ads: POLLSMAX logo left â Donate right. 1455 * Maps are not used â homepage map columns can diverge from the header chrome. 1456 */ 1457 /** Page-top ad host chrome â always site dark canvas (never light/white). */ 1458 var PAGE_TOP_AD_BG = '#050608'; 1459 1460 /** Live site canvas color â whatever html/body is actually painted with. */ 1461 function resolvePageBackgroundColor() { 1462 try { 1463 var root = document.documentElement; 1464 /* Prefer computed body paint (covers page-local overrides like #0b0e13). */ 1465 var bodyBg = global.getComputedStyle(document.body).backgroundColor; 1466 if ( 1467 bodyBg && 1468 bodyBg !== 'transparent' && 1469 bodyBg !== 'rgba(0, 0, 0, 0)' 1470 ) { 1471 return bodyBg; 1472 } 1473 var htmlBg = global.getComputedStyle(root).backgroundColor; 1474 if ( 1475 htmlBg && 1476 htmlBg !== 'transparent' && 1477 htmlBg !== 'rgba(0, 0, 0, 0)' 1478 ) { 1479 return htmlBg; 1480 } 1481 var token = ( 1482 global.getComputedStyle(root).getPropertyValue('--color-page-bg') || 1483 '' 1484 ).trim(); 1485 if (token) return token; 1486 } catch (eBg) { /* ignore */ } 1487 return PAGE_TOP_AD_BG; 1488 } 1489 1490 function paintPageBg(el, pageBg) { 1491 if (!el || !pageBg) return; 1492 try { 1493 el.style.setProperty('background', pageBg, 'important'); 1494 el.style.setProperty('background-color', pageBg, 'important'); 1495 } catch (ePaint) { /* ignore */ } 1496 } 1497 1498 function measureHeaderAdChannel() { 1499 var vw = document.documentElement.clientWidth || window.innerWidth || 0; 1500 var left = null; 1501 var right = null; 1502 1503 /* Pin Donate first so its live right edge is the ad-channel right. */ 1504 if (global.ET && typeof global.ET.syncHeaderDonateToContentRight === 'function') { 1505 try { 1506 global.ET.syncHeaderDonateToContentRight({ skipAds: true, force: true }); 1507 } catch (eSyncD) { /* ignore */ } 1508 } 1509 1510 var hdr = document.getElementById('mainHeader'); 1511 var donate = null; 1512 if (hdr) { 1513 /* Prefer real logo link â never img[alt*=Pollsmax] (matches tiny social icon). */ 1514 var logoImg = hdr.querySelector('img.sleek-home-header-logo'); 1515 var logo = 1516 (logoImg && (logoImg.closest('a') || logoImg)) ||
1517 hdr.querySelector('a.sleek-home-header-logo') || 1518 hdr.querySelector('a[href="/"].flex.items-center') || 1519 hdr.querySelector('a[href="/"]'); 1520 donate = 1521 document.getElementById('pollsmax-header-donate') || 1522 hdr.querySelector('a.pollsmax-header-donate') || 1523 hdr.querySelector('a[href*="/donate"]'); 1524 if (logo) { 1525 left = logo.getBoundingClientRect().left; 1526 } 1527 /* 1528 * Right edge MUST be Donateâs painted right â never come up short 1529 * of the button (content-target alone was leaving ads too far left 1530 * on some pages). 1531 */ 1532 if (donate) { 1533 right = donate.getBoundingClientRect().right; 1534 } 1535 if (!(right > 80) && global.ET && global.ET._headerAlignTarget > 80) { 1536 right = global.ET._headerAlignTarget; 1537 } 1538 if (!(right > 80) && 1539 global.ET && 1540 typeof global.ET.measureContentAlignRightEdge === 'function') { 1541 try { 1542 var contentR = global.ET.measureContentAlignRightEdge(); 1543 if (contentR > 80) right = contentR; 1544 } catch (eCR) { /* ignore */ } 1545 } 1546 if (!(right > left)) { 1547 var wrap = hdr.querySelector('.w-full.px-4') || hdr.firstElementChild; 1548 if (wrap) { 1549 var wr = wrap.getBoundingClientRect(); 1550 var cs = global.getComputedStyle(wrap); 1551 if (!(left > 0)) left = wr.left + (parseFloat(cs.paddingLeft) || 0); 1552 right = wr.right - (parseFloat(cs.paddingRight) || 0); 1553 } 1554 } 1555 } 1556 if (!(right > left) && vw > 0) { 1557 var pad = vw >= 1280 ? 56 : vw >= 1024 ? 40 : vw >= 640 ? 24 : 16; 1558 if (!(left > 0)) left = pad; 1559 right = vw - pad; 1560 } 1561 if (!(vw > 0) || !(right > left)) return null; 1562 var padL = Math.max(0, Math.round(left)); 1563 /* Width from logo â Donate; round so painted right never sits left of Donate. */ 1564 var width = Math.max(0, Math.round(right - padL)); 1565 var padR = Math.max(0, Math.round(vw - padL - width)); 1566 /* If rounding still undershoots Donate, steal 1px from padR. */ 1567 if (donate) { 1568 var wantR = donate.getBoundingClientRect().right; 1569 var paintedR = padL + width; 1570 if (wantR - paintedR > 0.25) { 1571 width = Math.max(0, Math.ceil(wantR - padL)); 1572 padR = Math.max(0, Math.round(vw - padL - width)); 1573 } 1574 } 1575 return { 1576 vw: vw, 1577 padL: padL, 1578 padR: padR, 1579 width: width 1580 }; 1581 } 1582 1583 /** 1584 * Nonâchart-column ads (poll tables, EH, etc.): match page-top width 1585 * even when nested inside narrower padded sections. 1586 */ 1587 function syncHeaderWidthAds() { 1588 try { 1589 if (isMobile()) return; 1590 if (syncHeaderWidthAds._lock) return; 1591 syncHeaderWidthAds._lock = true; 1592 var ch = measureHeaderAdChannel(); 1593 if (!ch) { 1594 syncHeaderWidthAds._lock = false; 1595 return; 1596 } 1597 var root = document.documentElement; 1598 root.style.setProperty('--pollsmax-top-ad-pad-left', ch.padL + 'px'); 1599 root.style.setProperty('--pollsmax-top-ad-pad-right', ch.padR + 'px'); 1600 root.style.setProperty('--pollsmax-top-ad-width', ch.width + 'px'); 1601 1602 var tops = document.querySelectorAll('.pollsmax-ad-row--page-top'); 1603 var ti; 1604 for (ti = 0; ti < tops.length; ti++) { 1605 var top = tops[ti]; 1606 if (!top) continue; 1607 /* 1608 * Nested mid rows (latest in-feed, race polling, forecast) sit inside 1609 * padded content columns. Break out to logoâDonate â do not also apply 1610 * padL (that double-insets and leaves a middle gap / short width). 1611 */ 1612 /* 1613 * Nested breakout: latest in-feed + forecast mids inside padded 1614 * forecast columns. Above-table race mids use --mid-bleed between 1615 * <section>s (page-top padL path â same as home mid) so they span 1616 * logoâDonate without double-inset or clipped breakout. 1617 */ 1618 var isNestedBreakout = 1619 top.classList.contains('pollsmax-ad-row--latest-polls-infeed') || 1620 top.classList.contains('pollsmax-ad-row--forecast-mid') || 1621 (top.classList.contains('pollsmax-ad-row--race-mid') && 1622 !top.classList.contains('pollsmax-ad-row--mid-bleed')); 1623 /* Upcoming Elections in-feed: fill the date-divider column (not logoâDonate). */ 1624 var isUpcomingInfeed = 1625 top.classList.contains('pollsmax-ad-row--upcoming-elections-infeed'); 1626 var spanW = ch.width; 1627 if (isNestedBreakout) { 1628 top.style.setProperty('padding-left', '0', 'important'); 1629 top.style.setProperty('padding-right', '0', 'important'); 1630 top.style.setProperty('margin-left', '0', 'important'); 1631 top.style.setProperty('margin-right', '0', 'important'); 1632 top.style.setProperty('width', '', 'important'); 1633 top.style.setProperty('max-width', '', 'important'); 1634 void top.offsetWidth; 1635 var infeedRect = top.getBoundingClientRect(); 1636 var infeedShift = ch.padL - Math.round(infeedRect.left); 1637 top.style.setProperty('margin-left', infeedShift + 'px', 'important'); 1638 top.style.setProperty('width', ch.width + 'px', 'important'); 1639 top.style.setProperty('max-width', ch.width + 'px', 'important'); 1640 spanW = ch.width; 1641 } else if (isUpcomingInfeed) { 1642 top.style.setProperty('padding-left', '0', 'important'); 1643 top.style.setProperty('padding-right', '0', 'important'); 1644 top.style.setProperty('margin-left', '0', 'important'); 1645 top.style.setProperty('margin-right', '0', 'important'); 1646 top.style.setProperty('width', '100%', 'important'); 1647 top.style.setProperty('max-width', '100%', 'important'); 1648 void top.offsetWidth; 1649 spanW = Math.max(0, Math.round(top.getBoundingClientRect().width)); 1650 if (!(spanW > 0)) spanW = ch.width; 1651 } else { 1652 top.style.setProperty('padding-left', ch.padL + 'px', 'important'); 1653 top.style.setProperty('padding-right', ch.padR + 'px', 'important'); 1654 top.style.setProperty('width', '100%', 'important'); 1655 top.style.setProperty('max-width', '100%', 'important'); 1656 top.style.setProperty('margin-left', '0', 'important'); 1657 top.style.setProperty('margin-right', '0', 'important'); 1658 spanW = ch.width; 1659 } 1660 top.style.setProperty('box-sizing', 'border-box', 'important'); 1661 /* 1662 * Padded dual-ad bands: keep â¥250px creatives; pad above/below so 1663 * page canvas fills hairlineâcreative (CSS alone loses to these locks). 1664 * Home uses 0.75rem; Latest/Upcoming in-feed uses 24px mid-banner gap. 1665 */ 1666 var isHomeBand = 1667 (document.body && document.body.classList.contains('pollsmax-home-page')) && 1668 (top.id === 'pollsmaxPageTopAdDesktop' || 1669 top.id === 'homeAdAfterUpcomingElections' || 1670 top.id === 'homeAdAfterSearchAllPolls' || 1671 top.id === 'homeAdAfterKeyRaces' || 1672 top.classList.contains('pollsmax-ad-row--home-mid')); 1673 var isInfeedBand = 1674 top.classList.contains('pollsmax-ad-row--latest-polls-infeed') || 1675 top.classList.contains('pollsmax-ad-row--upcoming-elections-infeed'); 1676 if (isHomeBand) { 1677 top.style.setProperty('height', 'calc(250px + 1.5rem)', 'important'); 1678 top.style.setProperty('max-height', 'calc(250px + 1.5rem)', 'important'); 1679 top.style.setProperty('min-height', 'calc(250px + 1.5rem)', 'important'); 1680 top.style.setProperty('padding-top', '0.75rem', 'important'); 1681 top.style.setProperty('padding-bottom', '0.75rem', 'important'); 1682 top.style.setProperty('margin-top', '0', 'important'); 1683 top.style.setProperty('margin-bottom', '0', 'important'); 1684 } else if (isInfeedBand) { 1685 top.style.setProperty('height', 'calc(250px + 48px)', 'important'); 1686 top.style.setProperty('max-height', 'calc(250px + 48px)', 'important'); 1687 top.style.setProperty('min-height', 'calc(250px + 48px)', 'important'); 1688 top.style.setProperty('padding-top', '24px', 'important'); 1689 top.style.setProperty('padding-bottom', '24px', 'important'); 1690 top.style.setProperty('margin-top', '0', 'important'); 1691 top.style.setProperty('margin-bottom', '0', 'important'); 1692 } else { 1693 /* Exact 250px slot â matches rectangle request; no empty white below. */ 1694 top.style.setProperty('height', '250px', 'important'); 1695 top.style.setProperty('max-height', '250px', 'important'); 1696 top.style.setProperty('min-height', '250px', 'important'); 1697 top.style.setProperty('padding-top', '0', 'important'); 1698 top.style.setProperty('padding-bottom', '0', 'important'); 1699 } 1700 top.style.setProperty('overflow', 'hidden', 'important'); 1701 top.style.setProperty('border-top', '0', 'important'); 1702 top.style.setProperty('border-bottom', '0', 'important'); 1703 /* Host chrome is always dark site canvas â not light/white body paint. */ 1704 var pageBg = isHomeBand ? '#0b0f13' : PAGE_TOP_AD_BG; 1705 paintPageBg(top, pageBg); 1706 var grid = top.querySelector('.pollsmax-ad-row__grid'); 1707 if (grid) { 1708 /* 1709 * Exact logoâDonate span. Two flush halves, zero middle gap 1710 * (no CSS 1rem gap, no space-between). Odd leftover px on 1711 * the right cell so the row right edge lands on Donate. 1712 */ 1713 var cellL = Math.max(0, Math.floor(spanW / 2)); 1714 var cellR = Math.max(0, spanW - cellL); 1715 grid.style.setProperty('display', 'flex', 'important'); 1716 grid.style.setProperty('flex-direction', 'row', 'important'); 1717 grid.style.setProperty('flex-wrap', 'nowrap', 'important'); 1718 grid.style.setProperty('align-items', 'stretch', 'important'); 1719 grid.style.setProperty('justify-content', 'flex-start', 'important'); 1720 grid.style.setProperty('gap', '0', 'important'); 1721 grid.style.setProperty('column-gap', '0', 'important'); 1722 grid.style.setProperty('row-gap', '0', 'important'); 1723 grid.style.setProperty('width', spanW + 'px', 'important'); 1724 grid.style.setProperty('max-width', spanW + 'px', 'important'); 1725 grid.style.setProperty('margin-left', '0', 'important'); 1726 grid.style.setProperty('margin-right', '0', 'important'); 1727 grid.style.setProperty('padding', '0', 'important'); 1728 grid.style.gridTemplateColumns = 'none'; 1729 grid.style.setProperty('height', '250px', 'important'); 1730 grid.style.setProperty('max-height', '250px', 'important'); 1731 grid.style.setProperty('min-height', '250px', 'important'); 1732 grid.style.setProperty('overflow', 'hidden', 'important'); 1733 paintPageBg(grid, pageBg); 1734 var cells = grid.querySelectorAll('.pollsmax-ad-cell'); 1735 var ci; 1736 for (ci = 0; ci < cells.length; ci++) { 1737 var cellW = ci === 0 ? cellL : cellR; 1738 cells[ci].style.setProperty('flex', '0 0 ' + cellW + 'px', 'important'); 1739 cells[ci].style.setProperty('width', cellW + 'px', 'important'); 1740 cells[ci].style.setProperty('min-width', cellW + 'px', 'important'); 1741 cells[ci].style.setProperty('max-width', cellW + 'px', 'important'); 1742 cells[ci].style.setProperty('box-sizing', 'border-box', 'important'); 1743 cells[ci].style.setProperty('display', 'flex', 'important'); 1744 cells[ci].style.setProperty('align-items', 'stretch', 'important'); 1745 cells[ci].style.setProperty('justify-content', 'flex-start', 'important'); 1746 cells[ci].style.setProperty('margin', '0', 'important'); 1747 cells[ci].style.setProperty('padding', '0', 'important'); 1748 cells[ci].style.setProperty('height', '250px', 'important'); 1749 cells[ci].style.setProperty('max-height', '250px', 'important'); 1750 cells[ci].style.setProperty('min-height', '250px', 'important'); 1751 cells[ci].style.setProperty('overflow', 'hidden', 'important'); 1752 paintPageBg(cells[ci], pageBg); 1753 } 1754 var topIns = grid.querySelectorAll('ins.adsbygoogle'); 1755 for (ci = 0; ci < topIns.length; ci++) { 1756 topIns[ci].style.setProperty('display', 'block', 'important'); 1757 topIns[ci].style.setProperty('width', '100%', 'important'); 1758 topIns[ci].style.setProperty('max-width', '100%', 'important'); 1759 topIns[ci].style.setProperty('min-width', '0', 'important'); 1760 topIns[ci].style.setProperty('box-sizing', 'border-box', 'important'); 1761 topIns[ci].style.setProperty('text-align', 'left', 'important'); 1762 topIns[ci].style.setProperty('height', '250px', 'important'); 1763 topIns[ci].style.setProperty('max-height', '250px', 'important'); 1764 topIns[ci].style.setProperty('min-height', '250px', 'important'); 1765 topIns[ci].style.setProperty('margin', '0', 'important'); 1766 topIns[ci].style.setProperty('padding', '0', 'important'); 1767 paintPageBg(topIns[ci], pageBg); 1768 } 1769 var iframes = grid.querySelectorAll('iframe'); 1770 for (ci = 0; ci < iframes.length; ci++) { 1771 iframes[ci].style.setProperty('width', '100%', 'important'); 1772 iframes[ci].style.setProperty('max-width', '100%', 'important'); 1773 iframes[ci].style.setProperty('min-width', '0', 'important'); 1774 iframes[ci].style.setProperty('margin', '0', 'important'); 1775 iframes[ci].style.setProperty('padding', '0', 'important'); 1776 iframes[ci].style.setProperty('border', '0', 'important'); 1777 iframes[ci].style.setProperty('box-sizing', 'border-box', 'important'); 1778 iframes[ci].style.setProperty('height', '250px', 'important'); 1779 iframes[ci].style.setProperty('max-height', '250px', 'important'); 1780 iframes[ci].style.setProperty('min-height', '250px', 'important'); 1781 paintPageBg(iframes[ci],
1781 pageBg); 1782 try { iframes[ci].removeAttribute('width'); } catch (eW) { /* ignore */ } 1783 } 1784 1785 /* Final paint check: grow the row if it still sits left of Donate. */ 1786 var donateEl = 1787 document.getElementById('pollsmax-header-donate') || 1788 document.querySelector('#mainHeader a.pollsmax-header-donate'); 1789 if (donateEl) { 1790 var want = donateEl.getBoundingClientRect().right; 1791 var got = grid.getBoundingClientRect().right; 1792 var shortBy = Math.round(want - got); 1793 if (shortBy > 0) { 1794 ch.width += shortBy; 1795 ch.padR = Math.max(0, ch.padR - shortBy); 1796 top.style.setProperty('padding-right', ch.padR + 'px', 'important'); 1797 cellL = Math.max(0, Math.floor(ch.width / 2)); 1798 cellR = Math.max(0, ch.width - cellL); 1799 grid.style.setProperty('width', ch.width + 'px', 'important'); 1800 grid.style.setProperty('max-width', ch.width + 'px', 'important'); 1801 if (cells[0]) { 1802 cells[0].style.setProperty('flex', '0 0 ' + cellL + 'px', 'important'); 1803 cells[0].style.setProperty('width', cellL + 'px', 'important'); 1804 cells[0].style.setProperty('min-width', cellL + 'px', 'important'); 1805 cells[0].style.setProperty('max-width', cellL + 'px', 'important'); 1806 } 1807 if (cells[1]) { 1808 cells[1].style.setProperty('flex', '0 0 ' + cellR + 'px', 'important'); 1809 cells[1].style.setProperty('width', cellR + 'px', 'important'); 1810 cells[1].style.setProperty('min-width', cellR + 'px', 'important'); 1811 cells[1].style.setProperty('max-width', cellR + 'px', 'important'); 1812 } 1813 root.style.setProperty('--pollsmax-top-ad-pad-right', ch.padR + 'px'); 1814 root.style.setProperty('--pollsmax-top-ad-width', ch.width + 'px'); 1815 } 1816 } 1817 } 1818 } 1819 1820 var nodes = document.querySelectorAll('.pollsmax-ad--header-width'); 1821 var i; 1822 for (i = 0; i < nodes.length; i++) { 1823 var el = nodes[i]; 1824 if (el.classList.contains('pollsmax-ad-row--page-top')) continue; 1825 /* Hub chart ads must stay in the right column â clear any prior breakout. */ 1826 if (el.classList.contains('pollsmax-ad-banner--hub-tall')) { 1827 el.style.marginLeft = ''; 1828 el.style.marginRight = ''; 1829 el.style.width = ''; 1830 el.style.maxWidth = ''; 1831 el.style.paddingLeft = ''; 1832 el.style.paddingRight = ''; 1833 el.classList.remove('pollsmax-ad--header-width'); 1834 continue; 1835 } 1836 /* Reset then measure natural inset so we can break out to header channel. */ 1837 el.style.marginLeft = '0'; 1838 el.style.marginRight = '0'; 1839 el.style.width = ''; 1840 el.style.maxWidth = ''; 1841 el.style.paddingLeft = '0'; 1842 el.style.paddingRight = '0'; 1843 var rect = el.getBoundingClientRect(); 1844 var shift = ch.padL - Math.round(rect.left); 1845 var nextML = shift + 'px'; 1846 var nextW = ch.width + 'px'; 1847 if (el.style.marginLeft !== nextML || el.style.width !== nextW) { 1848 el.style.marginLeft = nextML; 1849 el.style.width = nextW; 1850 el.style.maxWidth = nextW; 1851 el.style.boxSizing = 'border-box'; 1852 } 1853 } 1854 syncHeaderWidthAds._lock = false;
1855 } catch (eSync) { 1856 syncHeaderWidthAds._lock = false; 1857 } 1858 } 1859 1860 /* PC top duals keep natural height â no short clamp. */ 1861 function fitPageTopCreatives() { /* no-op */ } 1862 function watchPageTopClamp() { /* no-op */ } 1863 1864 function syncTopAdContentBounds() { 1865 syncHeaderWidthAds(); 1866 } 1867 1868 /* Back-compat alias */ 1869 var syncTopAdHeaderBounds = syncTopAdContentBounds; 1870 1871 function removeShell(el, force) { 1872 if (!el || !el.parentNode) return false; 1873 if (!force && el.querySelector('ins.adsbygoogle[data-pollsmax-initialized="true"]')) return false; 1874 el.parentNode.removeChild(el); 1875 return true; 1876 } 1877 1878 function removeUninitShell(el) { 1879 removeShell(el, false); 1880 } 1881 1882 function placePageTopAds() { 1883 var mobile = isMobile(); 1884 var desktopId = 'pollsmaxPageTopAdDesktop'; 1885 var mobileId = 'pollsmaxPageTopAdMobile'; 1886 1887 /* Mobile: no top banner. Desktop: 2-up fixed 250px rectangles. */ 1888 if (mobile) { 1889 removeShell(document.getElementById(desktopId), true); 1890 removeShell(document.getElementById(mobileId), true); 1891 return; 1892 } 1893 removeShell(document.getElementById(mobileId), true); 1894 1895 var staleDesk = document.getElementById(desktopId); 1896 if (staleDesk) { 1897 var deskIns = staleDesk.querySelector('ins.adsbygoogle'); 1898 var deskFmt = deskIns && deskIns.getAttribute('data-ad-format'); 1899 var deskFwr = deskIns && deskIns.getAttribute('data-full-width-responsive'); 1900 /* Remount anything that isn't the fixed 250px rectangle 2
1900-up PC top. */ 1901 if ( 1902 staleDesk.classList.contains('pollsmax-ad-row--page-top-short') || 1903 !staleDesk.classList.contains('pollsmax-ad-row--page-top-wide') || 1904 deskFmt !== 'rectangle' || 1905 deskFwr === 'true' || 1906 staleDesk.getAttribute('data-pollsmax-ad-empty') === '1' 1907 ) { 1908 removeShell(staleDesk, true); 1909 } 1910 } 1911 1912 placeOnce(desktopId, function () { 1913 return makeRow(desktopId, { 1914 /* Two fixed 250px rectangles spanning logo â Donate; never collapse. */ 1915 extraClass: 'pollsmax-ad-row--page-top pollsmax-ad-row--page-top-wide', 1916 format: 'rectangle', 1917 slotH: 250 1918 }); 1919 }, insertPageTopAd); 1920 var desk = document.getElementById(desktopId); 1921 if (desk) { 1922 desk.setAttribute('data-ad-state', 'live'); 1923 desk.removeAttribute('data-pollsmax-ad-empty'); 1924 } 1925 syncTopAdContentBounds(); 1926 requestAnimationFrame(syncTopAdContentBounds); 1927 setTimeout(syncTopAdContentBounds, 120); 1928 setTimeout(syncTopAdContentBounds, 500); 1929 } 1930 1931 /** Homepage Priority 1: first high-value Display after opening Senate/House maps (PC only). */ 1932 function placeHomeAfterOpeningMaps(kind) { 1933 if (!kind || !kind.home) return; 1934 /* Mobile: no ad under the opening maps hero. */ 1935 if (isMobile()) { 1936 removeShell(document.getElementById('home-after-opening-maps'), true); 1937 return; 1938 } 1939 var maps = document.querySelector('.sleek-maps--home-senate-house'); 1940 if (!maps || !maps.parentNode) return; 1941 placeOnce('home-after-opening-maps', function () { 1942 var aside = makeSingle('home-after-opening-maps', { 1943 extraClass: 'pollsmax-home-ad--priority pollsmax-ad--header-width', 1944 format: 'rectangle', 1945 fullWidthResponsive: false, 1946 slotH: 250 1947 }); 1948 aside.setAttribute('data-ad-place', 'home-after-opening-maps'); 1949 aside.setAttribute('data-ad-priority', '1'); 1950 return aside; 1951 }, function (el) { insertAfter(maps, el); }); 1952 } 1953 1954 /** Mobile: strip side-by-side under-hero ads (vote/odds pair, 2-up rows, top banners). */ 1955 function scrubMobileUnderHeroAds() { 1956 if (!isMobile()) return; 1957 var killIds = [ 1958 'pollsmaxPageTopAdMobile', 1959 'pollsmaxPageTopAdDesktop', 1960 'home-after-opening-maps', 1961 'raceForecastAdUnderVote', 1962 'raceForecastAdUnderOdds', 1963 /* Legacy under-map-key unit â would stack a 2nd ad under the hub map. */ 1964 'hubAdAfterMapKeyMobile' 1965 /* Never kill intended mobile mids (home/hub/poll/forecast/approval/2028/latest). */ 1966 ]; 1967 var keepIds = { 1968 homeAdBetweenGovAndSearch: 1, 1969 homeAdBetweenGbAndDemPrimary: 1, 1970 senateHubAdAfterMapMobile: 1, 1971 senateHubAdBeforeRacesMobile: 1, 1972 govHubAdAfterMapMobile: 1, 1973 govHubAdBeforeRacesMobile: 1, 1974 houseHubAdAfterMapMobile: 1, 1975 houseHubAdBeforeRacesMobile: 1, 1976 raceGePollAdBeforeTableMobile: 1, 1977 raceGePollAdAfterTableMobile: 1, 1978 racePrimaryPollAdBeforeTableMobile: 1, 1979 racePrimaryPollAdAfterTableMobile: 1, 1980 raceForecastAdTopOfSimMobile: 1, 1981 raceForecastAdAfterOddsMobile: 1, 1982 raceForecastAdUnderPollsMobile: 1, 1983 approvalAdBeforeTableMobile: 1, 1984 primary2028AdAfterChartMobile: 1, 1985 latestPollsAdUnderListMobile: 1, 1986 upcomingElectionsAdUnderListMobile: 1 1987 }; 1988 var i; 1989 for (i = 0; i < killIds.length; i++) { 1990 removeShell(document.getElementById(killIds[i]), true); 1991 } 1992 var rows = document.querySelectorAll('.pollsmax-ad-row'); 1993 for (i = 0; i < rows.length; i++) { 1994 var row = rows[i]; 1995 if (row && row.id && keepIds[row.id]) continue; 1996 removeShell(row, true); 1997 } 1998 /* Clear stale inline display:none on allowlisted mids (from older CSS path). */
1999 Object.keys(keepIds).forEach(function (id) { 2000 var el = document.getElementById(id); 2001 if (!el || !el.style) return; 2002 try { 2003 if (el.style.getPropertyValue('display') === 'none') { 2004 el.style.removeProperty('display'); 2005 } 2006 } catch (eKeep) { /* ignore */ } 2007 }); 2008 } 2009 2010 /* ---------- Global: multiplex above footer ---------- */ 2011 2012 function findFooter() { 2013 return document.querySelector('footer[data-et-site-footer="1"]') || 2014 document.querySelector('footer'); 2015 } 2016 2017 function hasOverflowClipAncestor(el) { 2018 var node = el && el.parentElement; 2019 while (node && node !== document.documentElement) { 2020 try { 2021 var cs = global.getComputedStyle(node); 2022 if (cs) { 2023 var oy = cs.overflowY; 2024 /* 2025 * Only vertical clip blocks tall multiplex units. 2026 * body { overflow-x: hidden } is common and must not count. 2027 */ 2028 if (oy === 'hidden' || oy === 'clip') return true; 2029 } 2030 } catch (e) { /* ignore */ } 2031 node = node.parentElement; 2032 } 2033 return false; 2034 } 2035 2036 /** Actual mobile scrollport â body/html oy:auto, not the layout viewport. */ 2037 function scrollRootForAdsIO() { 2038 try { 2039 var body = document.body; 2040 if (body) { 2041 var bs = global.getComputedStyle(body); 2042 if (bs && (bs.overflowY === 'auto' || bs.overflowY === 'scroll')) return body; 2043 } 2044 var html = document.documentElement; 2045 if (html) { 2046 var hs = global.getComputedStyle(html); 2047 if (hs && (hs.overflowY === 'auto' || hs.overflowY === 'scroll')) return html; 2048 } 2049 } catch (eR) { /* ignore */ } 2050 return null; 2051 } 2052 2053 function isMobileRaceMuxPage() { 2054 if (!isMobile()) return false; 2055 var k = pageKind(); 2056 return !!(k.forecast || k.gePoll || k.primaryPoll); 2057 } 2058 2059 function waitForAdSense(timeout) { 2060 timeout = typeof timeout === 'number' ? timeout : 10000; 2061 return new Promise(function (resolve, reject) { 2062 var start = Date.now(); 2063 function scriptReady() { 2064 var loader = 2065 document.getElementById('pollsmax-adsense-loader') || 2066 document.querySelector( 2067 'script[src*="pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"]' 2068 ); 2069 if (!loader) return false; 2070 if (loader.getAttribute('data-pollsmax-loaded') === '1') return true; 2071 try { 2072 if (loader.dataset && loader.dataset.loaded === '1') return true; 2073 } catch (eD) { /* ignore */ } 2074 /* After Google boots, adsbygoogle is no longer a plain Array. */ 2075 try { 2076 if ( 2077 global.adsbygoogle && 2078 typeof global.adsbygoogle.push === 'function' && 2079 !Array.isArray(global.adsbygoogle) 2080 ) { 2081 return true; 2082 } 2083 } catch (eAg) { /* ignore */ } 2084 return false; 2085 } 2086 function queueReady() { 2087 try { 2088 return ( 2089 Array.isArray(global.adsbygoogle) || 2090 (global.adsbygoogle && typeof global.adsbygoogle.push === 'function') 2091 ); 2092 } catch (eQ) { 2093 return false; 2094 } 2095 } 2096 function check() { 2097 if (scriptReady() || (queueReady() && Date.now() - start > 400 && document.readyState === 'complete')) { 2098 resolve(); 2099 return; 2100 } 2101 if (Date.now() - start >= timeout) { 2102 if (queueReady()) { 2103 resolve(); 2104 return; 2105 } 2106 reject(new Error('AdSense script did not become ready')); 2107 return; 2108 } 2109 requestAnimationFrame(check); 2110 } 2111 check(); 2112 }); 2113 } 2114 2115 function isAdVisibleAndSized(ad) { 2116 if (!ad || !ad.isConnected) return false;
2117 var element = ad; 2118 while (element && element !== document.documentElement) { 2119 try { 2120 var style = global.getComputedStyle(element); 2121 var rect = element.getBoundingClientRect(); 2122 if ( 2123 style.display === 'none' || 2124 style.visibility === 'hidden' || 2125 rect.width <= 0 2126 ) { 2127 return false; 2128 } 2129 /* Empty <ins> can report height 0 before fill â only reject ancestors. */ 2130 if (element !== ad && rect.height <= 0) { 2131 return false; 2132 } 2133 } catch (eVis) { 2134 return false; 2135 } 2136 element = element.parentElement; 2137 } 2138 var parentRect = 2139 ad.parentElement && ad.parentElement.getBoundingClientRect 2140 ? ad.parentElement.getBoundingClientRect() 2141 : null; 2142 return !!(parentRect && parentRect.width > 0); 2143 } 2144 2145 function logMultiplexDiagnostics(ad, note) { 2146 try { 2147 var loader = 2148 document.getElementById('pollsmax-adsense-loader') || 2149 document.querySelector( 2150 'script[src*="pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"]' 2151 ); 2152 var cs = ad ? global.getComputedStyle(ad) : null; 2153 var parent = ad && ad.parentElement; 2154 var parentW = 2155 parent && parent.getBoundingClientRect 2156 ? parent.getBoundingClientRect().width 2157 : 0; 2158 if (typeof console !== 'undefined' && console.info) { 2159 console.info('[Pollsmax Ads] Multiplex diagnostics' + (note ? ' â ' + note : ''), { 2160 adsenseScript: !!(loader), 2161 adsenseLoaded: !!(loader && loader.getAttribute('data-pollsmax-loaded') === '1'), 2162 adExists: !!ad, 2163 connected: !!(ad && ad.isConnected), 2164 parentWidth: Math.round(parentW || 0), 2165 display: cs ? cs.display : null, 2166 visibility: cs ? cs.visibility : null, 2167 adStatus: ad ? ad.getAttribute('data-ad-status') : null, 2168 adsbygoogleStatus: ad ? ad.getAttribute('data-adsbygoogle-status') : null, 2169 pollsmaxPushed: ad ? ad.dataset.pollsmaxPushed : null, 2170 slot: ad ? ad.getAttribute('data-ad-slot') : null, 2171 format: ad ? ad.getAttribute('data-ad-format') : null 2172 }); 2173 } 2174 } catch (eLog) { /* ignore */ } 2175 } 2176 2177 function watchAdStatus(ad) { 2178 if (!ad || ad.dataset.pollsmaxStatusWatch === '1') return; 2179 ad.dataset.pollsmaxStatusWatch = '1'; 2180 try { 2181 if (typeof MutationObserver !== 'function') return; 2182 var observer = new MutationObserver(function () { 2183 var status = ad.dataset.adStatus || ad.getAttribute('data-ad-status'); 2184 var googleStatus = ad.getAttribute('data-adsbygoogle-status'); 2185 if (status || googleStatus === 'done') { 2186 if (typeof console !== 'undefined' && console.info) { 2187 console.info('[Pollsmax Ads] Ad processed', { 2188 slot: ad.dataset.adSlot || ad.getAttribute('data-ad-slot'), 2189 status: status, 2190 googleStatus: googleStatus 2191 }); 2192 } 2193 if (status === 'filled' || (googleStatus === 'done' && status !== 'unfilled')) { 2194 settleMultiplexHeight(ad); 2195 setTimeout(function () { settleMultiplexHeight(ad); }, 300); 2196 setTimeout(function () { settleMultiplexHeight(ad); }, 1000); 2197 } 2198 try { observer.disconnect(); } catch (eD) { /* ignore */ } 2199 if (status === 'unfilled' || status === 'unfill-optimized') { 2200 collapseUnfilledAd(ad); 2201 } 2202 } 2203 }); 2204 observer.observe(ad, { 2205 attributes: true, 2206 attributeFilter: ['data-ad-status', 'data-adsbygoogle-status', 'style'] 2207 }); 2208 try { 2209 var childObs = new MutationObserver(function () { 2210 if (ad.querySelector('iframe') && ad.getAttribute('data-ad-status') === 'filled') {
2211 settleMultiplexHeight(ad); 2212 try { childObs.disconnect(); } catch (eC) { /* ignore */ } 2213 } 2214 }); 2215 childObs.observe(ad, { childList: true, subtree: true }); 2216 } catch (eChild) { /* ignore */ } 2217 } catch (eWatch) { /* ignore */ } 2218 } 2219 2220 function collapseUnfilledAd(ad) { 2221 if (!ad) return; 2222 function handleStatus() { 2223 var status = ad.dataset.adStatus || ad.getAttribute('data-ad-status'); 2224 if (status !== 'unfilled' && status !== 'unfill-optimized') return; 2225 var shell = 2226 ad.closest('#pollsmaxSiteMultiplex, aside.pollsmax-site-multiplex, .pollsmax-ad-shell') || 2227 ad.closest('.pollsmax-ad-shell'); 2228 if (!shell) return; 2229 /* Multiplex (and PC page-top) must stay reserved for late fills. */ 2230 if (isStickyPcAd(shell)) { 2231 shell.setAttribute('data-ad-state', 'live'); 2232 shell.removeAttribute('data-pollsmax-ad-empty'); 2233 clearShellLayoutInline(shell); 2234 try { shell.style.removeProperty('display'); } catch (eShow) { /* ignore */ } 2235 revealMultiplexShell(shell); 2236 return; 2237 } 2238 shell.setAttribute('data-pollsmax-ad-empty', '1'); 2239 shell.setAttribute('data-ad-state', 'unfilled'); 2240 try { 2241 shell.style.setProperty('display', 'none', 'important'); 2242 } catch (eHide) { /* ignore */ } 2243 } 2244 try { 2245 if (typeof MutationObserver === 'function') { 2246 var observer = new MutationObserver(handleStatus); 2247 observer.observe(ad, { 2248 attributes: true, 2249 attributeFilter: ['data-ad-status'] 2250 }); 2251 } 2252 } catch (eObs) { /* ignore */ } 2253 handleStatus(); 2254 } 2255 2256 /** 2257 * Shared push helper â one source of truth for manual unit push. 2258 * Marks data-pollsmax-pushed only after push({}) returns without throwing. 2259 */ 2260 function initializeAdSenseUnit(ad) { 2261 if (!ad || !ad.isConnected) return Promise.resolve(false); 2262 2263 if ( 2264 ad.dataset.adStatus || 2265 ad.getAttribute('data-ad-status') || 2266 ad.getAttribute('data-adsbygoogle-status') === 'done' || 2267 ad.dataset.pollsmaxPushed === '1' 2268 ) { 2269 return Promise.resolve(false); 2270 } 2271 2272 if (!isAdVisibleAndSized(ad)) { 2273 logMultiplexDiagnostics(ad, 'not visible/sized'); 2274 return Promise.resolve(false); 2275 } 2276 2277 return waitForAdSense() 2278 .then(function () { 2279 if ( 2280 ad.dataset.adStatus || 2281 ad.getAttribute('data-ad-status') || 2282 ad.getAttribute('data-adsbygoogle-status') === 'done' || 2283 ad.dataset.pollsmaxPushed === '1' 2284 ) { 2285 return false; 2286 } 2287 if (!ad.isConnected || !isAdVisibleAndSized(ad)) { 2288 logMultiplexDiagnostics(ad, 'lost visibility before push'); 2289 return false; 2290 } 2291 var parentWidth = 2292 (ad.parentElement && 2293 ad.parentElement.getBoundingClientRect && 2294 ad.parentElement.getBoundingClientRect().width) || 2295 0; 2296 if (parentWidth <= 0) { 2297 logMultiplexDiagnostics(ad, 'parent width 0'); 2298 return false; 2299 } 2300 2301 /* 2302 * AdSense assigns each push({}) to the next unprocessed ins in 2303 * document order. Claim earlier manual units first so multiplex 2304 * is not skipped / stolen. 2305 */ 2306 claimEarlierManualAds(ad); 2307 2308 if ( 2309 ad.getAttribute('data-adsbygoogle-status') === 'done' || 2310 ad.dataset.pollsmaxPushed === '1' 2311 ) { 2312 return false;
2313 } 2314 2315 /* Refresh Multiplex grid/title attrs for this viewport right before push. */ 2316 if (ad.getAttribute('data-ad-slot') === SLOT_MULTIPLEX) { 2317 applyMultiplexLayoutAttrs(ad); 2318 } 2319 2320 watchAdStatus(ad); 2321 global.adsbygoogle = global.adsbygoogle || []; 2322 void 0; 2323 ad.dataset.pollsmaxPushed = '1'; 2324 ad.setAttribute(PUSHED, '1'); 2325 2326 if (typeof console !== 'undefined' && console.info) { 2327 console.info('[Pollsmax Ads] Ad pushed', { 2328 slot: ad.dataset.adSlot || ad.getAttribute('data-ad-slot'), 2329 format: ad.dataset.adFormat || ad.getAttribute('data-ad-format'), 2330 uiType: ad.getAttribute('data-matched-content-ui-type'), 2331 rows: ad.getAttribute('data-matched-content-rows-num'), 2332 cols: ad.getAttribute('data-matched-content-columns-num'), 2333 parentWidth: Math.round(parentWidth) 2334 }); 2335 } 2336 logMultiplexDiagnostics(ad, 'push ok'); 2337 return true; 2338 }) 2339 .catch(function (error) { 2340 try { delete ad.dataset.pollsmaxPushed; } catch (eDel) { /* ignore */ } 2341 try { ad.removeAttribute(PUSHED); } catch (eR) { /* ignore */ } 2342 if (typeof console !== 'undefined' && console.error) { 2343 console.error('[Pollsmax Ads] Ad push failed', { 2344 slot: ad ? (ad.dataset.adSlot || ad.getAttribute('data-ad-slot')) : null, 2345 error: error 2346 }); 2347 } 2348 logMultiplexDiagnostics(ad, 'push failed'); 2349 return false; 2350 }); 2351 } 2352 2353 function isManualAdIns(ins) { 2354 if (!ins || !ins.getAttribute) return false; 2355 if (ins.closest && ins.closest('.google-auto-placed')) return false; 2356 if (ins.closest && ins.closest('aside.pollsmax-site-multiplex')) return false; 2357 var slot = ins.getAttribute('data-ad-slot'); 2358 return !!(slot && String(slot).length > 3); 2359 } 2360 2361 function claimEarlierManualAds(targetIns) { 2362 if (!targetIns || !document.querySelectorAll) return; 2363 var all = document.querySelectorAll('ins.adsbygoogle'); 2364 var i; 2365 for (i = 0; i < all.length; i++) { 2366 var el = all[i]; 2367 if (el === targetIns) break; 2368 if (!isManualAdIns(el)) continue; 2369 if ( 2370 el.dataset.pollsmaxPushed === '1' || 2371 el.getAttribute('data-adsbygoogle-status') === 'done' || 2372 el.getAttribute('data-ad-status') 2373 ) { 2374 continue; 2375 } 2376 if (!el.isConnected || !isAdVisibleAndSized(el)) continue; 2377 try { 2378 global.adsbygoogle = global.adsbygoogle || []; 2379 void 0; 2380 el.dataset.pollsmaxPushed = '1'; 2381 el.setAttribute(PUSHED, '1'); 2382 el.dataset.pollsmaxInitialized = 'true'; 2383 } catch (eClaim) { /* ignore */ } 2384 } 2385 } 2386 2387 /** 2388 * Claim only the under-header page-top pair so multiplex can push next. 2389 * Mid duals reuse page-top chrome â claiming them here starves Multiplex 2390 * (AdSense fills document-order display units first). 2391 */ 2392 function claimPageTopAdsNow() { 2393 var tops = document.querySelectorAll( 2394 '#pollsmaxPageTopAdDesktop.pollsmax-ad-row--page-top, .pollsmax-ad-row--page-top, .pollsmax-ad-single--page-top' 2395 ); 2396 var ti; 2397 for (ti = 0; ti < tops.length; ti++) { 2398 var top = tops[ti]; 2399 if (!top || !isTrueHeaderPageTop(top)) continue; 2400 var nodes = top.querySelectorAll('ins.adsbygoogle'); 2401 var i; 2402 for (i = 0; i < nodes.length; i++) { 2403 var el = nodes[i]; 2404 if ( 2405 el.dataset.pollsmaxPushed === '1' || 2406 el.getAttribute('data-adsbygoogle-status') === 'done' || 2407 el.getAttribute('data-ad-status') 2408 ) { 2409 continue; 2410 } 2411 if (!isAdVisibleAndSized(el)) continue; 2412 try { 2413 global.adsbygoogle = global.adsbygoogle || []; 2414 void 0; 2415 el.dataset.pollsmaxPushed = '1'; 2416 el.setAttribute(PUSHED, '1'); 2417 el.dataset.pollsmaxInitialized = 'true'; 2418 } catch (eTop) { /* ignore */ } 2419 } 2420 } 2421 } 2422 2423 function multiplexInsIsSettled(ins) { 2424 if (!ins) return true; 2425 return !!( 2426 ins.dataset.pollsmaxPushed === '1' || 2427 ins.getAttribute('data-adsbygoogle-status') === 'done' || 2428 ins.getAttribute('data-ad-status') 2429 ); 2430 } 2431 2432 /** True while footer Multiplex still needs its first push. */ 2433 function multiplexPushPending() { 2434 if (isEmbed()) return false;
2435 try { 2436 if (document.body && document.body.getAttribute('data-pm-no-multiplex') === '1') { 2437 return false; 2438 } 2439 } catch (eNo) { /* ignore */ } 2440 var aside = 2441 document.getElementById('homeAdMultiplex') || 2442 document.getElementById('pollsmaxSiteMultiplex') || 2443 document.querySelector('aside.pollsmax-site-multiplex'); 2444 /* Shell not mounted yet (footer late) â still treat as pending. */ 2445 if (!aside) return true; 2446 var ins = 2447 aside.querySelector('ins.adsbygoogle[data-ad-slot="' + SLOT_MULTIPLEX + '"]') || 2448 aside.querySelector('ins.adsbygoogle'); 2449 if (!ins) return true; 2450 return !multiplexInsIsSettled(ins); 2451 } 2452 2453 function scheduleMultiplexPushRetries(ad) { 2454 if (!ad || ad.getAttribute('data-pollsmax-mux-retry') === '1') return; 2455 ad.setAttribute('data-pollsmax-mux-retry', '1'); 2456 var attempts = 0; 2457 function tick() { 2458 if ( 2459 !ad.isConnected || 2460 ad.dataset.pollsmaxPushed === '1' || 2461 ad.getAttribute('data-adsbygoogle-status') === 'done' || 2462 ad.getAttribute('data-ad-status') 2463 ) { 2464 return; 2465 } 2466 initializeAdSenseUnit(ad).then(function () { 2467 attempts += 1; 2468 if ( 2469 ad.dataset.pollsmaxPushed === '1' || 2470 ad.getAttribute('data-ad-status') || 2471 attempts >= 28 2472 ) { 2473 return; 2474 } 2475 setTimeout(tick, attempts < 10 ? 250 : 500); 2476 }).catch(function () { 2477 attempts += 1; 2478 if (attempts < 28) setTimeout(tick, 400); 2479 }); 2480 } 2481 waitForAdSense() 2482 .then(function () { 2483 requestAnimationFrame(function () { 2484 requestAnimationFrame(tick); 2485 }); 2486 }) 2487 .catch(function () { /* ignore */ }); 2488 } 2489 2490 function observeAdSenseUnit(ad) { 2491 if (!ad || ad.getAttribute('data-pollsmax-mux-observed') === '1') return; 2492 ad.setAttribute('data-pollsmax-mux-observed', '1'); 2493 2494 if ( 2495 ad.dataset.pollsmaxPushed === '1' || 2496 ad.getAttribute('data-adsbygoogle-status') === 'done' || 2497 ad.getAttribute('data-ad-status') 2498 ) { 2499 collapseUnfilledAd(ad); 2500 return; 2501 } 2502 2503 /* 2504 * Always eager-push Multiplex (PC + mobile). Waiting on IO lets mid 2505 * duals claim AdSense first; mobile race pages also need this because 2506 * body is the scrollport and viewport-root IO may never fire. 2507 */ 2508 if (ad.getAttribute('data-ad-slot') === SLOT_MULTIPLEX) { 2509 scheduleMultiplexPushRetries(ad); 2510 } 2511 2512 if (typeof IntersectionObserver !== 'function') return; 2513 2514 var observer = new IntersectionObserver( 2515 function (entries) { 2516 var i; 2517 for (i = 0; i < entries.length; i++) { 2518 if (!entries[i].isIntersecting) continue; 2519 claimPageTopAdsNow(); 2520 initializeAdSenseUnit(ad).then(function (initialized) { 2521 if ( 2522 initialized || 2523 ad.dataset.adStatus || 2524 ad.getAttribute('data-ad-status') || 2525 ad.getAttribute('data-adsbygoogle-status') === 'done' || 2526 ad.dataset.pollsmaxPushed === '1' 2527 ) { 2528 try { observer.disconnect(); } catch (eDisc) { /* ignore */ } 2529 } 2530 }); 2531 } 2532 }, 2533 { 2534 root: scrollRootForAdsIO(), 2535 rootMargin: '900px 0px', 2536 threshold: 0 2537 } 2538 ); 2539 observer.observe(ad); 2540 } 2541 2542 function multiplexLayoutSpec() { 2543 /* 2544 * Single-value attrs for the current viewport (comma mobile,desktop pairs 2545 * are often ignored by modern Multiplex). PC: 2Ã4 = 8 with titles. 2546 * https://support.google.com/adsense/answer/7533385 2547 */ 2548 var mobile = isMobile(); 2549 return { 2550 uiType: 'image_card_stacked', 2551 rows: mobile ? '4' : '2', 2552 cols: mobile ? '1' : '4' 2553 }; 2554 } 2555 2556 function applyMultiplexLayoutAttrs(ins) { 2557 if (!ins) return; 2558 var spec = multiplexLayoutSpec(); 2559 ins.setAttribute('data-matched-content-ui-type', spec.uiType); 2560 ins.setAttribute('data-matched-content-rows-num', spec.rows); 2561 ins.setAttribute('data-matched-content-columns-num', spec.cols); 2562 /* 2563 * Google sizes the Multiplex iframe from the <ins> box at push time. 2564 * A short box yields a short iframe that clips titles under the images. 2565 */ 2566 /* Mobile: tall min-height so stacked Multiplex is not sized to a short iframe. */ 2567 var slotH = isMobile() ? 2200 : 720; 2568 try { 2569 ins.style.display = 'block'; 2570 ins.style.setProperty('min-height', slotH + 'px', 'important'); 2571 ins.style.setProperty('height', 'auto', 'important'); 2572 ins.style.setProperty('max-height', 'none', 'important'); 2573 ins.style.setProperty('overflow', 'visible', 'important'); 2574 } catch (eH) { /* ignore */ } 2575 } 2576 2577 /** 2578 * After Google fills, drop our prefill height floors. 2579 * Forcing the iframe taller than the 2Ã4 creatives left an empty white 2580 * "ghost" third row under the real ads. 2581 */
2582 function settleMultiplexHeight(ad) { 2583 if (!ad || ad.getAttribute('data-ad-slot') !== SLOT_MULTIPLEX) return; 2584 var iframe = ad.querySelector('iframe'); 2585 var shell = 2586 ad.closest('#pollsmaxSiteMultiplex, aside.pollsmax-site-multiplex') || 2587 null; 2588 /* Mobile stacked Multiplex is clipped if we hug Google's short iframe. */ 2589 if (isMobile()) { 2590 var minH = 2200; 2591 var attrH = iframe ? parseInt(iframe.getAttribute('height'), 10) : 0; 2592 var h = Math.max(minH, attrH || 0, (iframe && iframe.offsetHeight) || 0); 2593 try { 2594 ad.style.setProperty('min-height', h + 'px', 'important'); 2595 ad.style.setProperty('height', 'auto', 'important'); 2596 ad.style.setProperty('max-height', 'none', 'important'); 2597 ad.style.setProperty('overflow', 'visible', 'important'); 2598 } catch (eInsM) { /* ignore */ } 2599 if (iframe) { 2600 try { 2601 iframe.style.setProperty('height', h + 'px', 'important'); 2602 iframe.style.setProperty('min-height', h + 'px', 'important'); 2603 iframe.style.setProperty('max-height', 'none', 'important'); 2604 iframe.style.setProperty('overflow', 'visible', 'important'); 2605 } catch (eFrameM) { /* ignore */ } 2606 } 2607 if (shell) { 2608 try { 2609 shell.style.setProperty('min-height', '0', 'important'); 2610 shell.style.setProperty('height', 'auto', 'important'); 2611 shell.style.setProperty('max-height', 'none', 'important'); 2612 shell.style.setProperty('overflow', 'visible', 'important'); 2613 shell.style.setProperty('background', 'transparent', 'important'); 2614 shell.setAttribute('data-pollsmax-mux-settled', '1'); 2615 } catch (eShellM) { /* ignore */ } 2616 } 2617 pinMobileAdToStandardWidth(shell || ad); 2618 return; 2619 } 2620 try { 2621 ad.style.removeProperty('height'); 2622 ad.style.removeProperty('min-height'); 2623 ad.style.setProperty('min-height', '0', 'important'); 2624 ad.style.setProperty('height', 'auto', 'important'); 2625 } catch (eIns) { /* ignore */ } 2626 if (iframe) { 2627 try { 2628 iframe.style.removeProperty('min-height'); 2629 iframe.style.removeProperty('height'); 2630 iframe.style.removeProperty('max-height'); 2631 } catch (eFrame) { /* ignore */ } 2632 } 2633 if (shell) { 2634 try { 2635 shell.style.setProperty('min-height', '0', 'important'); 2636 shell.style.setProperty('height', 'auto', 'important'); 2637 shell.style.setProperty('background', 'transparent', 'important'); 2638 shell.setAttribute('data-pollsmax-mux-settled', '1'); 2639 } catch (eShell) { /* ignore */ } 2640 } 2641 var inner = ad.closest('.pollsmax-ad-single__inner'); 2642 if (inner) { 2643 try { 2644 inner.style.setProperty('min-height', '0', 'important'); 2645 inner.style.setProperty('height', 'auto', 'important'); 2646 } catch (eInner) { /* ignore */ } 2647 } 2648 } 2649 2650 function buildMultiplexIns() { 2651 var inner = document.createElement('div'); 2652 inner.className = 'pollsmax-ad-single__inner pm-ad-slot'; 2653 return inner; 2654 } 2655 2656 function multiplexInsNeedsLayoutUpgrade(ins) { 2657 if (!ins) return true; 2658 var spec = multiplexLayoutSpec(); 2659 return ( 2660 ins.getAttribute('data-ad-slot') !== SLOT_MULTIPLEX || 2661 ins.getAttribute('data-ad-format') !== 'autorelaxed' || 2662 ins.getAttribute('data-matched-content-ui-type') !== spec.uiType || 2663 ins.getAttribute('data-matched-content-rows-num') !== spec.rows || 2664 ins.getAttribute('data-matched-content-columns-num') !== spec.cols 2665 ); 2666 } 2667 2668 function mountFreshMultiplexIns(aside) { 2669 if (!aside) return null; 2670 /* Never wipe after push, Google processing, or an active IO/observer. */ 2671 var existing = aside.querySelector('ins.adsbygoogle'); 2672 if ( 2673 existing && 2674 (existing.dataset.pollsmaxPushed === '1' || 2675 existing.getAttribute('data-adsbygoogle-status') === 'done' || 2676 existing.getAttribute('data-ad-status') || 2677 existing.getAttribute('data-pollsmax-mux-observed') === '1') 2678 ) { 2679 applyMultiplexLayoutAttrs(existing); 2680 return existing; 2681 } 2682 aside.innerHTML = ''; 2683 aside.removeAttribute('data-pollsmax-observed'); 2684 aside.removeAttribute('data-pollsmax-ad-empty'); 2685 aside.removeAttribute('data-pollsmax-ad-ready'); 2686 var inner = document.createElement('div'); 2687 inner.className = 'pollsmax-ad-single__inner'; 2688 var ins = buildMultiplexIns(); 2689 inner.appendChild(ins); 2690 aside.appendChild(inner); 2691 return ins; 2692 } 2693 2694 function revealMultiplexShell(aside) { 2695 if (!aside) return; 2696 aside.classList.add('pollsmax-ad-shell'); 2697 aside.classList.add('pollsmax-site-multiplex'); 2698 aside.setAttribute('data-ad-priority', '4'); 2699 aside.setAttribute('data-ad-state', 'live'); 2700 aside.setAttribute('data-pollsmax-observed', '1'); 2701 aside.removeAttribute('data-pollsmax-ad-empty'); 2702 aside.removeAttribute('aria-hidden'); 2703 clearShellLayoutInline(aside); 2704 try { 2705 aside.style.removeProperty('display'); 2706 aside.style.setProperty('visibility', 'visible', 'important'); 2707 aside.style.setProperty('opacity', '1', 'important'); 2708 aside.style.setProperty( 2709 'min-height', 2710 isMobile() ? '0' : '720px', 2711 'important' 2712 ); 2713 aside.style.setProperty('height', 'auto', 'important'); 2714 aside.style.setProperty('max-height', 'none', 'important'); 2715 aside.style.setProperty('overflow', 'visible', 'important'); 2716 aside.style.setProperty('contain', 'none', 'important'); 2717 aside.style.setProperty( 2718 'width', 2719 isMobile() 2720 ? 'var(--et-mobile-standard-width, calc(100vw - 2rem))' 2721 : '100%', 2722 'important' 2723 ); 2724 if (isMobile()) { 2725 pinMobileAdToStandardWidth(aside); 2726 } 2727 aside.style.setProperty('pointer-events', 'auto', 'important'); 2728 aside.style.setProperty('background', 'transparent', 'important'); 2729 aside.style.removeProperty('color'); 2730 aside.style.removeProperty('color-scheme'); 2731 } catch (eRev) { /* ignore */ } 2732 } 2733 2734 function ensureSiteMultiplex() { 2735 if (isEmbed()) return; 2736 ensureAdsense(); 2737 2738 var footer = findFooter(); 2739 if (!footer || !footer.parentNode) { 2740 if (!ensureSiteMultiplex._footerRetry) { 2741 ensureSiteMultiplex._footerRetry = setTimeout(function () { 2742 ensureSiteMultiplex._footerRetry = null; 2743 ensureSiteMultiplex(); 2744 }, 400); 2745 } 2746 return; 2747 } 2748 2749 var home = document.getElementById('homeAdMultiplex'); 2750 var aside = home || document.getElementById('pollsmaxSiteMultiplex'); 2751 if (!aside) { 2752 aside = document.createElement('aside'); 2753 aside.id = 'pollsmaxSiteMultiplex'; 2754 aside.className = 'pollsmax-site-multiplex'; 2755 } 2756 if (!aside.id) aside.id = 'pollsmaxSiteMultiplex'; 2757 2758 var ins = aside.querySelector('ins.adsbygoogle[data-ad-slot="' + SLOT_MULTIPLEX + '"]') || 2759 aside.querySelector('ins.adsbygoogle'); 2760 var alreadyPushed = !!( 2761 ins && 2762 (ins.dataset.pollsmaxPushed === '1' || 2763 ins.getAttribute('data-adsbygoogle-status') === 'done' || 2764 ins.getAttribute('data-ad-status')) 2765 ); 2766 2767 /* 2768 * Do not move / recreate the <ins> after it has been pushed or 2769 * processed by Google (requirement #6). 2770 */ 2771 if (!alreadyPushed) { 2772 if (!ins || multiplexInsNeedsLayoutUpgrade(ins)) { 2773 ins = mountFreshMultiplexIns(aside); 2774 } 2775 if ( 2776 (aside.nextElementSibling !== footer || hasOverflowClipAnce
2776stor(aside)) && 2777 aside.getAttribute('data-pollsmax-mux-placed') !== '1' 2778 ) { 2779 footer.parentNode.insertBefore(aside, footer); 2780 aside.setAttribute('data-pollsmax-mux-placed', '1'); 2781 } else if (!aside.parentNode) { 2782 footer.parentNode.insertBefore(aside, footer); 2783 aside.setAttribute('data-pollsmax-mux-placed', '1'); 2784 } 2785 } 2786 2787 revealMultiplexShell(aside); 2788 ins = aside.querySelector('ins.adsbygoogle[data-ad-slot="' + SLOT_MULTIPLEX + '"]'); 2789 if (!ins) { 2790 logMultiplexDiagnostics(null, 'missing ins after mount'); 2791 return; 2792 } 2793 2794 logMultiplexDiagnostics(ins, 'ensure'); 2795 collapseUnfilledAd(ins); 2796 2797 /* IO near-viewport (+ eager retries â always for Multiplex). */ 2798 if (ins.getAttribute('data-pollsmax-mux-observed') === '1') { 2799 if ( 2800 ins.dataset.pollsmaxPushed !== '1' && 2801 !ins.getAttribute('data-ad-status') 2802 ) { 2803 /* Clear retry gate so late forecast mount can re-kick push. */ 2804 try { ins.removeAttribute('data-pollsmax-mux-retry'); } catch (eRetry) { /* ignore */ } 2805 scheduleMultiplexPushRetries(ins); 2806 } 2807 } else { 2808 observeAdSenseUnit(ins); 2809 } 2810 } 2811 2812 function watchForecastReadyForMultiplex() { 2813 if (!pageKind().forecast) return; 2814 var root = document.documentElement; 2815 function kick() { 2816 ensureSiteMultiplex(); 2817 } 2818 if (root.classList.contains('race-view-forecast-ready')) { 2819 kick(); 2820 return; 2821 } 2822 if (typeof MutationObserver !== 'function') { 2823 setTimeout(kick, 2000); 2824 return; 2825 } 2826 var mo = new MutationObserver(function () { 2827 if (!root.classList.contains('race-view-forecast-ready')) return; 2828 try { mo.disconnect(); } catch (eMo) { /* ignore */ } 2829 kick(); 2830 }); 2831 mo.observe(root, { attributes: true, attributeFilter: ['class'] }); 2832 } 2833 2834 global.PollsmaxEnsureSiteMultiplex = ensureSiteMultiplex; 2835 global.PollsmaxInitializeAdSenseUnit = initializeAdSenseUnit; 2836 global.PollsmaxWaitForAdSense = waitForAdSense; 2837 2838 /* ---------- Homepage ---------- */ 2839 2840 /** PC only: dual horizontals like page-top â after Upcoming / Search / Key Races. */ 2841 function placeHomeDesktopDualRows(kind) { 2842 var idUpcoming = 'homeAdAfterUpcomingElections'; 2843 var idSearch = 'homeAdAfterSearchAllPolls'; 2844 var idKeyRaces = 'homeAdAfterKeyRaces'; 2845 if (!kind || !kind.home || isMobile()) { 2846 removeShell(document.getElementById(idUpcoming), true); 2847 removeShell(document.getElementById(idSearch), true); 2848 removeShell(document.getElementById(idKeyRaces), true); 2849 return; 2850 } 2851 2852 function makeHomeMidRow(id) { 2853 return makeRow(id, { 2854 extraClass: 'pollsmax-ad-row--page-top pollsmax-ad-row--page-top-wide pollsmax-ad-row--home-mid', 2855 format: 'rectangle' 2856 }); 2857 } 2858 2859 function mountHomeMid(id, beforeEl) { 2860 if (!beforeEl || !beforeEl.parentNode) return; 2861 placeOnce(id, function () { 2862 return makeHomeMidRow(id); 2863 }, function (el) { 2864 if (el.parentNode !== beforeEl.parentNode || el.nextElementSibling !== beforeEl) { 2865 beforeEl.parentNode.insertBefore(el, beforeEl); 2866 } 2867 }); 2868 var mounted = document.getElementById(id); 2869 if (mounted) { 2870 mounted.setAttribute('data-ad-state', 'live'); 2871 mounted.removeAttribute('data-pollsmax-ad-empty'); 2872 } 2873 } 2874 2875 var upcoming = document.getElementById('homeUpcomingElectionsSection'); 2876 var govDonate = document.querySelector('.sleek-maps--home-gov-donate'); 2877 if (upcoming && govDonate) mountHomeMid(idUpcoming, govDonate); 2878 2879 var search = document.getElementById('searchAllPollsSection'); 2880 var trumpCanvas = document.getElementById('trumpHomeApprovalChart'); 2881 var approvalRow = trumpCanvas && trumpCanvas.closest ? trumpCanvas.closest('.sleek-maps') : null; 2882 if (search && approvalRow) mountHomeMid(idSearch, approvalRow); 2883 2884 var keyRaces = document.getElementById('homeKeyRacesSection'); 2885 var primaries = document.querySelector('.sleek-maps--home-primaries'); 2886 if (keyRaces && primaries) mountHomeMid(idKeyRaces, primaries); 2887 } 2888 2889 function placeHomeAds(kind) { 2890 if (!kind.home) return; 2891 var mobile = isMobile(); 2892 2893 if (!mobile) { 2894 [['trumpHomeApprovalChart', 'homeAdTrumpChart'], 2895 ['gbHomeBallotChart', 'homeAdGbChart'], 2896 ['demHomePrimaryChart', 'homeAdDemPrimaryChart'], 2897 ['gopHomePrimaryChart', 'homeAdGopPrimaryChart']].forEac
2897h(function (pair) { 2898 var canvas = document.getElementById(pair[0]); 2899 var stage = canvas && canvas.closest ? canvas.closest('.sleek-chart-stage') : null; 2900 var host = stage && stage.parentElement; 2901 if (!host) return; 2902 placeOnce(pair[1], function () { 2903 return makeBanner(pair[1], { 2904 desktopOnly: true, 2905 extraClass: 'pollsmax-ad-banner--in-col', 2906 format: 'rectangle' 2907 }); 2908 }, function (el) { appendChild(host, el); }); 2909 }); 2910 } else { 2911 var govMaps = document.querySelector('.sleek-maps--home-gov-donate'); 2912 var search = document.getElementById('searchAllPollsSection'); 2913 if (govMaps && search) { 2914 placeOnce('homeAdBetweenGovAndSearch', function () { 2915 return makeSingle('homeAdBetweenGovAndSearch', { 2916 mobileOnly: true, 2917 format: 'auto', 2918 extraClass: 'pollsmax-ad-single--poll-mid-mobile' 2919 }); 2920 }, function (el) { 2921 /* Prefer after Upcoming (true between-panels sibling); fallback before Search. */ 2922 var upcoming = document.getElementById('homeUpcomingElectionsSection'); 2923 if (upcoming && upcoming.parentNode) insertAfter(upcoming, el); 2924 else insertBefore(search, el); 2925 }); 2926 var govSearchAd = document.getElementById('homeAdBetweenGovAndSearch'); 2927 if (govSearchAd) { 2928 var upcomingPin = document.getElementById('homeUpcomingElectionsSection'); 2929 if ( 2930 upcomingPin && 2931 upcomingPin.parentNode && 2932 (govSearchAd.previousElementSibling !== upcomingPin || 2933 govSearchAd.parentNode !== upcomingPin.parentNode) 2934 ) { 2935 insertAfter(upcomingPin, govSearchAd); 2936 } 2937 observeShell(govSearchAd, { priority: true }); 2938 } 2939 } 2940 2941 /* 2942 * Between Generic Ballot and 2028 Dem Primary (home mobile). 2943 * Mount immediately after the approval maps row â not after the hidden 2944 * Key Races block â so the shell stays in normal flow for IO + AdSense. 2945 */ 2946 var approvalRow = document.querySelector('.sleek-maps--home-approval'); 2947 var demRow = document.querySelector('.sleek-maps--home-primaries') || 2948 (function () { 2949 var demCanvas = document.getElementById('demHomePrimaryChart'); 2950 return demCanvas && demCanvas.closest ? demCanvas.closest('.sleek-maps') : null; 2951 })(); 2952 var gbDemAnchor = approvalRow || demRow; 2953 if (gbDemAnchor) { 2954 placeOnce('homeAdBetweenGbAndDemPrimary', function () { 2955 var aside = makeSingle('homeAdBetweenGbAndDemPrimary', { 2956 mobileOnly: true, 2957 format: 'auto', 2958 extraClass: 'pollsmax-ad-single--poll-mid-mobile' 2959 }); 2960 aside.setAttribute('data-ad-priority', '1'); 2961 return aside; 2962 }, function (el) { 2963 /* Prefer immediately before 2028 Dem row so no hidden Key Races 2964 * sibling sits between the ad and the next panel (extra margin-top). */ 2965 if (demRow && demRow.parentNode) { 2966 insertBefore(demRow, el); 2967 } else if (approvalRow && approvalRow.parentNode) { 2968 insertAfter(approvalRow, el); 2969 } 2970 }); 2971 var gbDemAd = document.getElementById('homeAdBetweenGbAndDemPrimary'); 2972 if (gbDemAd) { 2973 if ( 2974 demRow && 2975 demRow.parentNode && 2976 (gbDemAd.nextElementSibling !== demRow || 2977 gbDemAd.parentNode !== demRow.parentNode) 2978 ) { 2979 insertBefore(demRow, gbDemAd); 2980 } 2981 observeShell(gbDemAd, { priority: true }); 2982 } 2983 } 2984 } 2985 } 2986 2987 /* ---------- Hub pages ---------- */ 2988 2989 function placeHubAds(kind) { 2990 if (!(kind.senateHub || kind.govHub || kind.houseHub)) return; 2991 var mobile = isMobile(); 2992 2993 if (mobile) { 2994 removeShell(document.getElementById('hubAdAfterRacesListMobile'), true); 2995 removeShell(document.getElementById('hubAdAfterSearchPollsMobile'), true); 2996 2997 /* 2998 * Senate / Governor / House hub mobile: only two dis
2998play mids â 2999 * 1) directly under the map block 3000 * 2) under last sim chart / before All ⦠Races 3001 * Never above the map. Strip map-key + chart interstitials. 3002 */ 3003 if (kind.senateHub || kind.govHub || kind.houseHub) { 3004 var afterMapId = kind.senateHub 3005 ? 'senateHubAdAfterMapMobile' 3006 : kind.govHub 3007 ? 'govHubAdAfterMapMobile' 3008 : 'houseHubAdAfterMapMobile'; 3009 var afterOddsId = kind.senateHub 3010 ? 'senateHubAdBeforeRacesMobile' 3011 : kind.govHub 3012 ? 'govHubAdBeforeRacesMobile' 3013 : 'houseHubAdBeforeRacesMobile'; 3014 var chartsSecId = kind.senateHub 3015 ? 'senateHubSimulationChartsSection' 3016 : kind.govHub 3017 ? 'govHubSimulationChartsSection' 3018 : 'houseHubSimulationChartsSection'; 3019 3020 removeShell(document.getElementById('hubAdAfterMapKeyMobile'), true); 3021 removeShell(document.getElementById('senateHubAdAfterDistribution'), true); 3022 removeShell(document.getElementById('senateHubAdAfterProjected'), true); 3023 removeShell(document.getElementById('senateHubAdAfterControlOdds'), true); 3024 removeShell(document.getElementById('govHubAdAfterDistribution'), true); 3025 removeShell(document.getElementById('govHubAdAfterProjected'), true); 3026 var hubTalls = document.querySelectorAll( 3027 '.hub-simulation-charts-section .pollsmax-ad-banner--hub-tall, ' + 3028 '#' + chartsSecId + ' .pollsmax-ad-shell' 3029 ); 3030 var ti; 3031 for (ti = 0; ti < hubTalls.length; ti++) { 3032 var tall = hubTalls[ti]; 3033 if (!tall || !tall.id) { 3034 removeShell(tall, true); 3035 continue; 3036 } 3037 if (tall.id === afterMapId || tall.id === afterOddsId) { 3038 continue; 3039 } 3040 removeShell(tall, true); 3041 } 3042 3043 /* Collapse only true unfilled stubs â waiting/loading must stay sized. */ 3044 function scrubDeadHubMid(id) { 3045 var dead = document.getElementById(id); 3046 if (!dead) return; 3047 if ( 3048 dead.getAttribute('data-pollsmax-ad-empty') === '1' || 3049 dead.getAttribute('data-ad-state') === 'unfilled' 3050 ) { 3051 try { 3052 dead.style.setProperty('display', 'none', 'important'); 3053 dead.style.setProperty('height', '0', 'important'); 3054 dead.style.setProperty('max-height', '0', 'important'); 3055 dead.style.setProperty('margin', '0', 'important'); 3056 } catch (eDead) { /* ignore */ } 3057 } 3058 } 3059 scrubDeadHubMid(afterMapId); 3060 scrubDeadHubMid(afterOddsId); 3061 3062 var chartsSec = 3063 document.getElementById(chartsSecId) || 3064 document.querySelector('.hub-simulation-charts-section'); 3065 var mapEl = 3066 document.querySelector('.senate-right-content > .sleek-maps.senate-map-divider') || 3067 document.querySelector('.senate-right-content > .sleek-maps') || 3068 document.querySelector('.senate-right-content > .senate-map-divider') || 3069 document.querySelector('.sleek-maps.senate-map-divider'); 3070 3071 function isHubKeepMid(el) { 3072 return !!(el && (el.id === afterMapId || el.id === afterOddsId)); 3073 } 3074 3075 function hideAutoAd(el) { 3076 if (!el || !el.style) return; 3077 try { 3078 el.style.setProperty('display', 'none', 'important'); 3079 el.style.setProperty('height', '0', 'important'); 3080 el.style.setProperty('max-height', '0', 'important'); 3081 el.style.setProperty('margin', '0', 'important'); 3082 el.style.setProperty('padding', '0', 'important'); 3083 el.style.setProperty('overflow', 'hidden', 'important'); 3084 el.setAttribute('data-pollsmax-hub-ad-scrubbed', '1'); 3085 } catch (eHide) { /* ignore */ } 3086 } 3087 3088 /* 3089 * Anything that paints above the map (Auto ads, leftover shells, 3090 * misplaced mids) must go â then we pin afterMap under the map. 3091 */ 3092 function scrubAdsAboveMap(map) { 3093 if (!map) return; 3094 var scope = document.querySelector('main') || document.body; 3095 if (!scope || !scope.querySelectorAll) return; 3096 var nodes = scope.querySelectorAll( 3097 '.google-auto-placed, .pollsmax-ad-shell, .pollsmax-ad-single, ' + 3098 '.pollsmax-ad-banner, .pollsmax-ad-row, aside.pollsmax-site-multiplex' 3099 ); 3100 var i; 3101 for (i = 0; i < nodes.length; i++) { 3102 var el = nodes[i]; 3103 if (!el || el === map) continue; 3104 if (el.classList && el.classList.contains('pollsmax-site-multiplex')) continue; 3105 if (isHubKeepMid(el)) continue; 3106 /* PRECEDING = el is above the map in document order. */ 3107 if (!(map.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_PRECEDING)) { 3108 continue; 3109 } 3110 if (el.classList && el.classList.contains('google-auto-placed')) { 3111 hideAutoAd(el); 3112 continue; 3113 } 3114 removeShell(el, true); 3115 } 3116 /* Also clear previous siblings inside the right column. */ 3117 if (map.parentNode) { 3118 var pre = map.parentNode.firstElementChild; 3119 while (pre && pre !== map) { 3120 var preNext = pre.nextElementSibling; 3121 if (isHubKeepMid(pre)) { 3122 /* Misplaced keep-mid â move under map below. */ 3123 pre = preNext; 3124 continue; 3125 } 3126 if ( 3127 pre.classList && 3128 (pre.classList.contains('google-auto-placed') || 3129 pre.classList.contains('pollsmax-ad-shell') || 3130 pre.classList.contains('pollsmax-ad-single') || 3131 pre.classList.contains('pollsmax-ad-banner') || 3132 pre.classList.contains('pollsmax-ad-row')) 3133 ) { 3134 if (pre.classList.contains('google-auto-placed')) hideAutoAd(pre); 3135 else removeShell(pre, true); 3136 } 3137 pre = preNext; 3138 } 3139 } 3140 } 3141 3142 /** 3143 * Only one unit may sit under the map. Strip Auto ads / leftover 3144 * shells between map â charts, and never leave the before-races mid 3145 * as a second under-map sibling when charts aren't mounted yet. 3146 */ 3147 function scrubAdsUnderMap(map, charts) { 3148 if (!map || !map.parentNode) return; 3149 var node = map.nextElementSibling; 3150 while (node) { 3151 var next = node.nextElementSibling; 3152 if ( 3153 node === charts || 3154 (node.id && node.id === chartsSecId) || 3155 (node.classList && node.classList.contains('hub-simulation-charts-section')) 3156 ) { 3157 break; 3158 } 3159 if (node.id === afterMapId) { 3160 node = next; 3161 continue; 3162 } 3163 /* Second mid belongs after charts â park it, don't fill here. */ 3164 if (node.id === afterOddsId) { 3165 if (charts && charts.parentNode) insertAfter(charts, node); 3166 else { 3167 setShellCollapsed(node); 3168 try { node.style.setProperty('display', 'none', 'important'); } catch (ePark) { /* ignore */ } 3169 } 3170 node = next; 3171 continue; 3172 } 3173 if ( 3174 node.classList && 3175 (node.classList.contains('google-auto-placed') || 3176 node.classList.contains('pollsmax-ad-shell') || 3177 node.classList.contains('pollsmax-ad-single') || 3178 node.classList.contains('pollsmax-ad-banner') || 3179 node.classList.contains('pollsmax-ad-row')) 3180 ) { 3181 if (node.classList.contains('google-auto-placed')) hideAutoAd(node); 3182 else if (!node.classList.contains('pollsmax-site-multiplex')) { 3183 removeShell(node, true); 3184 } 3185 } 3186 node = next; 3187 } 3188 } 3189 3190 function pinUnderMap(el, map) { 3191 if (!el || !map || !map.parentNode) return; 3192 if (el.previousElementSibling !== map || el.parentNode !== map.parentNode) { 3193 map.parentNode.insertBefore(el, map.nextSibling); 3194 } 3195 /* Map uses flex order:1; default order:0 paints ads above it. */ 3196 try { el.style.setProperty('order', '2', 'important'); } catch (eOrd) { /* ignore */ } 3197 } 3198 3199 function kickHubMid(el) { 3200 if (!el) return; 3201 var ins = el.querySelector && el.querySelector('ins.adsbygoogle'); 3202 var insStatus = ins && (ins.getAttribute('data-ad-status') || ''); 3203 /* Only Google unfilled kills the slot â never stay collapsed waiting. */ 3204 if (insStatus === 'unfilled' || insStatus === 'unfill-optimized') return; 3205 if (el.getAttribute('data-ad-state') === 'filled' && insStatus === 'filled') return; 3206 if (el.querySelector('ins.adsbygoogle[data-pollsmax-initialized="true"]') && insStatus === 'filled') return; 3207 el.removeAttribute('data-pollsmax-observed'); 3208 el.removeAttribute('data-pollsmax-ad-empty'); 3209 try { el.style.removeProperty('display'); } catch (eKick) { /* ignore */ } 3210 if ( 3211 !el.getAttribute('data-ad-state') || 3212 el.getAttribute('data-ad-state') === 'loading' 3213 ) { 3214 el.setAttribute('data-ad-state', 'waiting'); 3215 } 3216 clearShellLayoutInline(el); 3217 setShellLoading(el); 3218 pinMobileAdToStandardWidth(el); 3219 observeShell(el, { priority: true }); 3220 applyMockMobileAds(); 3221 } 3222 3223 function hubChartsReady(sec) { 3224 if (!sec) return false;
3225 return !!( 3226 sec.querySelector('.senate-hub-mob-panel--chart') || 3227 sec.querySelector('.sleek-chart-stage') || 3228 sec.querySelector('.hub-sim-hist-stage') || 3229 sec.querySelector('canvas') 3230 ); 3231 } 3232 3233 scrubAdsAboveMap(mapEl); 3234 3235 /* 1) Directly under the map block â forced next sibling of the map. */ 3236 if (mapEl) { 3237 var afterMap = placeOnce(afterMapId, function () { 3238 return makeSingle(afterMapId, { 3239 mobileOnly: true, 3240 format: 'auto', 3241 extraClass: 'pollsmax-ad-single--poll-mid-mobile' 3242 }); 3243 }, function (el) { pinUnderMap(el, mapEl); }); 3244 pinUnderMap(afterMap, mapEl); 3245 kickHubMid(afterMap); 3246 scrubAdsUnderMap(mapEl, chartsSec); 3247 } 3248 3249 /* 3250 * 2) Under the bottom sim chart block: 3251 * Senate â Odds of Winning; Governor â Projected / last chart panel. 3252 * Wait until charts exist â otherwise this mid stacks under the map. 3253 */ 3254 function lastChartPanelIn(sec) { 3255 if (!sec) return null; 3256 var panels = sec.querySelectorAll('.senate-hub-mob-panel--chart'); 3257 if (panels.length) return panels[panels.length - 1]; 3258 var stages = sec.querySelectorAll( 3259 '.sleek-chart-stage, .hub-sim-hist-stage, .race-forecast-sim-stage' 3260 ); 3261 return stages.length ? stages[stages.length - 1] : null; 3262 } 3263 var chartCanvasId = kind.senateHub 3264 ? 'senateHubControlOddsChart' 3265 : kind.govHub 3266 ? 'govHubExpectedSeatsChart' 3267 : 'houseHubControlOddsChart'; 3268 var chartCanvas = document.getElementById(chartCanvasId); 3269 var chartAnchor = 3270 (chartCanvas && chartCanvas.closest('.senate-hub-mob-panel--chart')) || 3271 (chartCanvas && chartCanvas.closest('.sleek-chart-stage')) || 3272 lastChartPanelIn(chartsSec) || 3273 null; 3274 var racesSec = 3275 document.getElementById('hubMobileRacesSection') || 3276 document.querySelector('.hub-mobile-races-section'); 3277 var chartsReady = !!(chartAnchor || hubChartsReady(chartsSec)); 3278 if (!chartsReady) { 3279 /* Park â do not destroy. Re-place when charts mount. */ 3280 var parked = document.getElementById(afterOddsId); 3281 if (parked && racesSec && racesSec.parentNode) { 3282 insertBefore(racesSec, parked); 3283 try { parked.style.setProperty('order', '4', 'important'); } catch (eParkOrd) { /* ignore */ } 3284 } 3285 if (mapEl) scrubAdsUnderMap(mapEl, chartsSec); 3286 return; 3287 } 3288 if (chartAnchor || racesSec || chartsSec) { 3289 var afterChart = placeOnce(afterOddsId, function () { 3290 return makeSingle(afterOddsId, { 3291 mobileOnly: true, 3292 format: 'auto', 3293 extraClass: 'pollsmax-ad-single--poll-mid-mobile' 3294 }); 3295 }, function (el) { 3296 /* Sibling of the charts host (order 4) â never nest inside it. */ 3297 if (racesSec && racesSec.parentNode) { 3298 insertBefore(racesSec, el); 3299 } else if (chartsSec && chartsSec.parentNode) { 3300 insertAfter(chartsSec, el); 3301 } 3302 }); 3303 if (afterChart && racesSec && racesSec.parentNode) { 3304 if ( 3305 afterChart.nextElementSibling !== racesSec || 3306 afterChart.parentNode !== racesSec.parentNode 3307 ) { 3308 insertBefore(racesSec, afterChart); 3309 } 3310 try { afterChart.style.setProperty('order', '4', 'important'); } catch (eOrd2) { /* ignore */ } 3311 } 3312 if (mapEl) scrubAdsUnderMap(mapEl, chartsSec); 3313 /* Guard: never kick a before-races mid that still sits under the map. */ 3314 if ( 3315 afterChart && 3316 mapEl && 3317 afterChart.parentNode === mapEl.parentNode && 3318 (afterChart.previousElementSibling === mapEl || 3319 afterChart.previousElementSibling === document.getElementById(afterMapId)) && 3320 !(chartsSec && ( 3321 afterChart.compareDocumentPosition(ch
3321artsSec) & 3322 Node.DOCUMENT_POSITION_PRECEDING 3323 )) 3324 ) { 3325 if (chartsSec && chartsSec.parentNode) insertAfter(chartsSec, afterChart); 3326 else { 3327 setShellCollapsed(afterChart); 3328 return; 3329 } 3330 } 3331 kickHubMid(afterChart); 3332 } 3333 return; 3334 } 3335 } 3336 3337 /* Desktop: chart ads are injected by hub-charts-page.js; house race ratings here. */ 3338 if (kind.houseHub) { 3339 var buckets = document.getElementById('houseForecastBucketsSection'); 3340 if (buckets) { 3341 placeOnce('houseHubAdAfterRaceRatings', function () { 3342 return makeBanner('houseHubAdAfterRaceRatings', { 3343 desktopOnly: true, 3344 extraClass: 'pollsmax-ad-banner--hub-tall', 3345 format: 'auto' 3346 }); 3347 }, function (el) { appendChild(buckets, el); }); 3348 } 3349 } 3350 } 3351 3352 /** PC only runs page-top + multiplex â no hub chart ads. */ 3353 ET.hubChartSlotAdHtml = function () { 3354 return ''; 3355 }; 3356 3357 ET.initHubChartAds = function (root) { 3358 registerAllShells(root); 3359 }; 3360 3361 /* ---------- Race polling / forecast / approval / 2028 ---------- */ 3362 3363 function sectionOf(el) { 3364 return el && el.closest ? (el.closest('section') || el) : el; 3365 } 3366 3367 function placeAfterStatsOrChart(adId, mobile) { 3368 var stats = 3369 document.querySelector('.mi-senate-chart-stat-head') || 3370 document.querySelector('.national-approval-chart-stat-head') || 3371 document.querySelector('.approval-stat-grid'); 3372 var chart = 3373 document.getElementById('avgChart') || 3374 document.getElementById('senLineViz') || 3375 document.getElementById('govLineViz') || 3376 document.querySelector('.sleek-chart-stage'); 3377 var anchor = stats || chart; 3378 if (!anchor) return null; 3379 var sec = sectionOf(anchor); 3380 return placeOnce(adId, function () { 3381 return mobile 3382 ? makeSingle(adId, { 3383 mobileOnly: true, 3384 format: 'auto', 3385 extraClass: 'pollsmax-ad-single--poll-mid-mobile' 3386 }) 3387 : makeRow(adId, { desktopOnly: true, format: 'rectangle' }); 3388 }, function (el) { 3389 if (sec && sec.parentNode) insertAfter(sec, el); 3390 else insertAfter(anchor, el); 3391 }); 3392 } 3393 3394 /** 3395 * Mobile primary polling: bar chart (â¤2 polls) â no Google ad above the table. 3396 * Uses body.et-state-poll-bar-chart-active when set; falls back to #primaryBarViz visibility. 3397 */ 3398 function primaryPollMobileUsesBarChart() { 3399 if (!isMobile()) return false; 3400 if (document.body && document.body.classList.contains('et-state-poll-bar-chart-active')) { 3401 return true; 3402 } 3403 var bar = document.getElementById('primaryBarViz') || 3404 document.querySelector('[id$="PrimaryBarViz"]'); 3405 if (!bar || !bar.isConnected) return false; 3406 if (bar.style && bar.style.display === 'none') return false; 3407 try { 3408 var cs = window.getComputedStyle(bar); 3409 if (cs.display === 'none' || cs.visibility === 'hidden') return false; 3410 } catch (eBar) { /* ignore */ } 3411 var line = document.getElementById('primaryLineViz') || 3412 document.querySelector('[id$="PrimaryLineViz"]'); 3413 if (line && line.isConnected) { 3414 if (line.style && line.style.display === 'none') return true; 3415 try { 3416 var lcs = window.getComputedStyle(line); 3417 if (lcs.display === 'none') return true; 3418 } catch (eLine) { /* ignore */ } 3419 } 3420 return false; 3421 } 3422 3423 /** Remove Google above-table mid on mobile primary bar-chart pages (Mediavine untouched). */ 3424 function scrubPrimaryPollMobileBarChartAboveTableAd() { 3425 if (!isMobile()) return; 3426 if (!pageKind().primaryPoll) return; 3427 if (!primaryPollMobileUsesBarChart()) return; 3428 removeShell(document.getElementById('racePrimaryPollAdBeforeTableMobile'), true); 3429 } 3430 3431 /** Chart init can land after ads boot â re-scrub when bar class / viz flips. */ 3432 function schedulePrimaryPollMobileBarChartAdScrub() { 3433 if (!isMobile() || !pageKind().primaryPoll) return; 3434 scrubPrimaryPollMobileBarChartAboveTableAd(); 3435 setTimeout(scrubPrimaryPollMobileBarChartAboveTableAd, 120); 3436 setTimeout(scrubPrimaryPollMobileBarChartAboveTableAd, 500); 3437 setTimeout(scrubPrimaryPollMobileBarChartAboveTableAd, 1600); 3438 if (schedulePrimaryPollMobileBarChartAdScrub._watching) return; 3439 schedulePrimaryPollMobileBarChartAdScrub._watching = true; 3440 try { 3441 var mo = new MutationObserver(function () { 3442 scrubPrimaryPollMobileBarChartAboveTableAd(); 3443 }); 3444 if (document.body) { 3445 mo.observe(document.body, { attributes: true, attributeFilter: ['class'] }); 3446 } 3447 var bar = document.getElementById('primaryBarViz'); 3448 var line = document.getElementById('primaryLineViz'); 3449 if (bar) mo.observe(bar, { attributes: true, attributeFilter: ['style', 'class'] }); 3450 if (line) mo.observe(line, { attributes: true, attributeFilter: ['style', 'class'] }); 3451 } catch (eWatch) { /* ignore */ } 3452 } 3453 3454 /** 3455 * Mobile: single unit immediately above the polls table 3456 * (below GE 4-rectangle stats, or below the chart on primary / 2028 pages). 3457 */ 3458 function placeBeforePollsTableMobile(adId) { 3459 if (!isMobile()) return null; 3460 var shell = document.getElementById('pollsTableShell'); 3461 if (shell) { 3462 var tableSec = (shell.closest && shell.closest('section')) || pollTableOuterBo
3462x(shell); 3463 if (tableSec && tableSec.parentNode) { 3464 return placeOnce(adId, function () { 3465 return makeSingle(adId, { 3466 mobileOnly: true, 3467 format: 'auto', 3468 extraClass: 'pollsmax-ad-single--poll-mid-mobile' 3469 }); 3470 }, function (el) { 3471 insertBefore(tableSec, el); 3472 notifyGePollMidAdChartTableGap(el); 3473 }); 3474 } 3475 } 3476 return placeAfterStatsOrChart(adId, true); 3477 } 3478 3479 /** Outer bordered table chrome â ads go after this, never inside it. */ 3480 function pollTableOuterBox(shell) { 3481 if (!shell) return null; 3482 return (shell.closest && ( 3483 shell.closest('.state-polls-shell') || 3484 shell.closest('.race-forecast-polls-table') || 3485 shell.closest('[class*="polls-shell"]') 3486 )) || shell; 3487 } 3488 3489 /** PC mid-page duals â same chrome as page-top / Latest Polls in-feed. */ 3490 function makeDesktopPageTopDualRow(id, extraClass) { 3491 return makeRow(id, { 3492 extraClass: 'pollsmax-ad-row--page-top pollsmax-ad-row--page-top-wide' + 3493 (extraClass ? ' ' + extraClass : ''), 3494 format: 'rectangle' 3495 }); 3496 } 3497 3498 function activateDesktopDualRow(el) { 3499 if (!el) return; 3500 el.setAttribute('data-ad-state', 'live'); 3501 el.removeAttribute('data-pollsmax-ad-empty'); 3502 } 3503 3504 /** Sum of next sibling margin-top + padding-top that sits between ad and content. */ 3505 function midAdNextInsetPx(el) { 3506 if (!el) return 0; 3507 var next = el.nextElementSibling; 3508 var total = 0; 3509 var hops = 0; 3510 while (next && hops < 3) { 3511 hops++; 3512 /* Zero-height full-bleed rules don't count as content â skip past them. */ 3513 var isRule = next.classList && ( 3514 next.classList.contains('race-forecast-trends-rule') || 3515 next.classList.contains('race-forecast-eh-bottom-rule') || 3516 next.classList.contains('race-related-races-rule') || 3517 next.classList.contains('race-forecast-polling-data-rule') 3518 ); 3519 try { 3520 var cs = global.getComputedStyle(next); 3521 var mt = parseFloat(cs.marginTop) || 0; 3522 var pt = parseFloat(cs.paddingTop) || 0; 3523 /* Allow negative (e.g. race-polling-page table pull) so mb can compensate. */ 3524 total += Math.round(mt + pt); 3525 /* 3526 * Section wrappers are often 0-inset; the real top inset lives on the 3527 * first child (pt-7 / margin-top:-20px on poll-table hosts). 3528 */ 3529 if (!isRule && next.firstElementChild) { 3530 var inner = next.firstElementChild; 3531 var ics = global.getComputedStyle(inner); 3532 total += Math.round( 3533 (parseFloat(ics.marginTop) || 0) + (parseFloat(ics.paddingTop) || 0) 3534 ); 3535 /* sleek-home .state-polls-shell uses margin-top:-14px â count it too. */ 3536 var shell = 3537 (inner.classList && inner.classList.contains('state-polls-shell') && inner) || 3538 (inner.querySelector && inner.querySelector('.state-polls-shell')); 3539 if (shell && shell !== inner) { 3540 var scs = global.getComputedStyle(shell); 3541 total += Math.round(parseFloat(scs.marginTop) || 0); 3542 } 3543 } 3544 } catch (eCs) { /* ignore */ } 3545 if (!isRule) break; 3546 next = next.nextElementSibling; 3547 } 3548 /* Related Races lives outside the poll section â include its margin-top. */ 3549 if (el.id === 'raceForecastAdUnderPolls') { 3550 var related = 3551 document.getElementById('raceRelatedRaces') || 3552 document.querySelector('.race-related-races'); 3553 if (related && !el.parentNode.contains(related)) { 3554 try { 3555 var rcs = global.getComputedStyle(related); 3556 total += Math.max(0, Math.round(parseFloat(rcs.marginTop) || 0)); 3557 } catch (eR) { /* ignore */ } 3558 } 3559 } 3560 return total; 3561 } 3562 3563 /** 3564 * PC-only per-placement tweaks vs the canonical mid gap. 3565 * mtDelta / mbDelta adjust space above / below the ad row. 3566 * 3567 * Senate/Gov/House GE + race primary above-table duals: equal +1 so the 3568 * visual gap matches Montana Senate's below-ad spacing (~55px = 1.5rem+30px+1). 3569 */ 3570 function midContentAdGapDeltas(el) { 3571 if (!el) return { mt: 0, mb: 0 }; 3572 var id = el.id || ''; 3573 var kind = pageKind(); 3574 3575 /* Forecast: under Projected Vote Share / Odds of Winning. */ 3576 if (id === 'raceForecastAdUnderTrends') { 3577 return { mt: -32, mb: 32 }; 3578 } 3579 /* Forecast: under polls table (Polling Data section). */ 3580 if (id === 'raceForecastAdUnderPolls') { 3581 return { mt: 0, mb: 33 }; 3582 } 3583 /* GE + race primary polling: above polls table (Montana below-ad reference). */ 3584 if ( 3585 id === 'raceGePollAdBeforeTable' || 3586 id === 'racePrimaryPollAdBeforeTable' || 3587 ((kind.gePoll || kind.primaryPoll) && el.classList.contains('pollsmax-ad-row--mid-bleed')) 3588 ) { 3589 return { mt: 1, mb: 1 }; 3590 } 3591 /* 2028 Dem / GOP primary: above polls table. */ 3592 if (id === 'primary2028AdBeforeTable' || (kind.primary2028 && el.classList.contains('pollsmax-ad-row--mid-bleed'))) { 3593 return { mt: 10, mb: 14 }; 3594 } 3595 /* Trump approval + generic ballot: above polls table. */ 3596 if (id === 'approvalAdBeforeTable' || (kind.approval && el.classList.contains('pollsmax-ad-row--mid-bleed'))) { 3597 return { mt: -85, mb: 14 }; 3598 } 3599 return { mt: 0, mb: 0 }; 3600 } 3601 3602 /** 3603 * PC: equal visual gap above/below each mid dual, plus page-specific deltas. 3604 * margin-top = GAP + mtDelta; margin-bottom = GAP â next-heading inset + mbDelta 3605 * so Electoral History / Polling Data / Related Races keep their own 3606 * padding and still sit the intended distance from the ad edge. 3607 */ 3608 function syncMidContentAdGaps() { 3609 if (isMobile()) return; 3610 var gapPx = 54; 3611 try { 3612 var rootFs = parseFloat(global.getComputedStyle(document.documentElement).fontSize) || 16; 3613 gapPx = Math.round(rootFs * 1.5 + 30); 3614 } catch (eGap) { /* keep fallback */ } 3615 3616 var nodes = document.querySelectorAll( 3617 '.pollsmax-ad-row--race-mid, .pollsmax-ad-row--forecast-mid' 3618 ); 3619 var i; 3620 for (i = 0; i < nodes.length; i++) { 3621 var el = nodes[i]; 3622 var nextInset = midAdNextInsetPx(el); 3623 var delta = midContentAdGapDeltas(el); 3624 var mt = Math.max(0, gapPx + (delta.mt || 0)); 3625 var mb = Math.max(0, gapPx - nextInset + (delta.mb || 0)); 3626 el.style.setProperty('margin-top', mt + 'px', 'important'); 3627 el.style.setProperty('margin-bottom', mb + 'px', 'important'); 3628 } 3629 } 3630 3631 function scheduleMidContentAdGapSync() { 3632 function run() { 3633 syncMidContentAdGaps(); 3634 syncTopAdContentBounds(); 3635 } 3636 run(); 3637 requestAnimationFrame(run); 3638 setTimeout(run, 120); 3639 setTimeout(run, 500); 3640 setTimeout(run, 1600); 3641 } 3642 3643 function placeUnderPollTable(adId, mobile) { 3644 var shell = document.getElementById('pollsTableShell'); 3645 if (!shell) return null;
3646 var box = pollTableOuterBox(shell); 3647 if (!box || !box.parentNode) return null; 3648 return placeOnce(adId, function () { 3649 return mobile 3650 ? makeSingle(adId, { 3651 mobileOnly: true, 3652 format: 'auto', 3653 extraClass: 'pollsmax-ad-single--poll-mid-mobile' 3654 }) 3655 : makeDesktopPageTopDualRow(adId, 'pollsmax-ad-row--race-mid'); 3656 }, function (el) { 3657 /* Sibling under the table shell â not a child inside the bordered box. */ 3658 insertAfter(box, el); 3659 if (!mobile) activateDesktopDualRow(el); 3660 }); 3661 } 3662 3663 /** 3664 * Walk up from the polls table until the block that is a direct child of 3665 * <main> (or #racePollingBottom). Above-table duals must sit there â 3666 * nesting inside px-* sections double-insets and leaves a middle gap. 3667 */ 3668 function abovePollTableAnchor(box) { 3669 if (!box) return null; 3670 var pollingBottom = document.getElementById('racePollingBottom'); 3671 var mainEl = document.querySelector('main'); 3672 var host = 3673 (pollingBottom && pollingBottom.contains(box) && pollingBottom) || 3674 (mainEl && mainEl.contains(box) && mainEl) || 3675 null; 3676 if (!host) { 3677 var section = box.closest ? box.closest('section') : null; 3678 return section && section.parentNode ? section : box; 3679 } 3680 var cur = box; 3681 var hops = 0; 3682 while (cur && cur.parentNode && hops < 12) { 3683 hops++; 3684 if (cur.parentNode === host) return cur; 3685 cur = cur.parentNode; 3686 } 3687 var fallback = box.closest ? box.closest('section') : null; 3688 return fallback && fallback.parentNode ? fallback : box; 3689 } 3690 3691 /** 3692 * PC only: 2-up horizontal above the polls table. 3693 * Sit between chart block and table block (main child) so width sync 3694 * can use logoâDonate padding like home mid â never nest inside 3695 * the table section's px-* pad (that double-insets and clips). 3696 */ 3697 function placeAbovePollTable(adId) { 3698 if (isMobile()) return null; 3699 var shell = document.getElementById('pollsTableShell'); 3700 if (!shell) return null; 3701 var box = pollTableOuterBox(shell); 3702 if (!box || !box.parentNode) return null; 3703 var anchor = abovePollTableAnchor(box); 3704 if (!anchor || !anchor.parentNode) return null; 3705 return placeOnce(adId, function () { 3706 return makeDesktopPageTopDualRow( 3707 adId, 3708 'pollsmax-ad-row--race-mid pollsmax-ad-row--mid-bleed' 3709 ); 3710 }, function (el) { 3711 /* Upgrade legacy nested placements to between-section bleed. */ 3712 el.classList.add('pollsmax-ad-row--race-mid', 'pollsmax-ad-row--mid-bleed'); 3713 insertBefore(anchor, el); 3714 activateDesktopDualRow(el); 3715 }); 3716 } 3717 3718 ET.placeRacePollingPageAds = function () { 3719 if (ET.racePageViewMode && ET.racePageViewMode() === 'forecast') return; 3720 var kind = pageKind(); 3721 if (!(kind.gePoll || kind.primaryPoll)) return; 3722 var mobile = isMobile(); 3723 3724 if (kind.gePoll) { 3725 if (mobile) { 3726 /* Under the 4 chart rectangles, above the polls table. */ 3727 placeBeforePollsTableMobile('raceGePollAdBeforeTableMobile'); 3728 placeUnderPollTable('raceGePollAdAfterTableMobile', true); 3729 } else { 3730 /* PC: 2-up horizontal above the polls table only. */ 3731 removeShell(document.getElementById('raceGePollAdAfterTable'), true); 3732 placeAbovePollTable('raceGePollAdBeforeTable'); 3733 } 3734 } else if (kind.primaryPoll) { 3735 if (mobile) { 3736 /* Under chart, above the polls table â skip when bar chart (â¤2 polls). */ 3737 if (primaryPollMobileUsesBarChart()) { 3738 removeShell(document.getElementById('racePrimaryPollAdBeforeTableMobile'), true); 3739 } else { 3740 placeBeforePollsTableMobile('racePrimaryPollAdBeforeTableMobile'); 3741 } 3742 placeUnderPollTable('racePrimaryPollAdAfterTableMobile', true); 3743 } else { 3744 /* PC: 2-up horizontal above the polls table only. */ 3745 removeShell(document.getElementById('racePrimaryPollAdAfterChart'), true); 3746 removeShell(document.getElementById('racePrimaryPollAdAfterTable'), true); 3747 placeAbovePollTable('racePrimaryPollAdBeforeTable'); 3748 } 3749 } 3750 registerAllShells(); 3751 ensureSiteMultiplex(); 3752 if (!mobile) scheduleMidContentAdGapSync(); 3753 }; 3754 3755 function placeForecastDesktopAds() { 3756 /* Retire legacy in-column banners + EH row. */ 3757 removeShell(document.getElementById('raceForecastAdUnderVote'), true); 3758 removeShell(document.getElementById('raceForecastAdUnderOdds'), true); 3759 removeShell(document.getElementById('raceForecastAdUnderEh'), true); 3760 3761 /* 3762 * PC: one 2-up horizontal under Projected Vote Share / Odds of Winning, 3763 * still inside the 3-chart block â immediately after the trends grid, 3764 * BEFORE the trends-rule / Electoral History block. 3765 */ 3766 var trends = 3767 document.querySelector('.rf-premium-trends') || 3768 document.querySelector('.race-forecast-trends-grid'); 3769 if (trends) { 3770 placeOnce('raceForecastAdUnderTrends', function () { 3771 return makeDesktopPageTopDualRow( 3772 'raceForecastAdUnderTrends', 3773 'pollsmax-ad-row--forecast-mid' 3774 ); 3775 }, function (el) { 3776 insertAfter(trends, el); 3777 activateDesktopDualRow(el); 3778 }); 3779 } 3780 3781 /* Polling Data: 2-up under the polls table when it exists. */ 3782 placeUnderPollTable('raceForecastAdUnderPolls', false); 3783 /* House/other forecasts: table can land in #racePollingBottom after mount. */ 3784 if (!document.getElementById('raceForecastAdUnderPolls')) { 3785 var pollingBottom = document.getElementById('racePollingBottom'); 3786 var bottomShell = pollingBottom && pollingBottom.querySelector('#pollsTableShell, .state-polls-shell'); 3787 if (bottomShell) {
3788 var bottomBox = pollTableOuterBox( 3789 bottomShell.id === 'pollsTableShell' ? bottomShell : bottomShell.querySelector('#pollsTableShell') || bottomShell 3790 ); 3791 if (bottomBox && bottomBox.parentNode) { 3792 placeOnce('raceForecastAdUnderPolls', function () { 3793 return makeDesktopPageTopDualRow( 3794 'raceForecastAdUnderPolls', 3795 'pollsmax-ad-row--forecast-mid' 3796 ); 3797 }, function (el) { 3798 insertAfter(bottomBox, el); 3799 activateDesktopDualRow(el); 3800 }); 3801 } 3802 } 3803 } 3804 /* Nested mid duals â logoâDonate channel + canonical equal gaps. */ 3805 scheduleMidContentAdGapSync(); 3806 } 3807 3808 function placeForecastMobileAds() { 3809 /* Drop desktop-style in-col trend banners; keep dedicated mobile placements. */ 3810 removeShell(document.getElementById('raceForecastAdUnderVote'), true); 3811 removeShell(document.getElementById('raceForecastAdUnderOdds'), true); 3812 3813 var panelsMode = !!( 3814 document.body && 3815 document.body.classList.contains('race-forecast-mob-panels') && 3816 !document.body.classList.contains('race-forecast-premium') 3817 ); 3818 3819 var simBlock = 3820 document.querySelector('.race-forecast-sim-block') || 3821 document.querySelector('.rf-premium-panel--sim'); 3822 var oddsCanvas = document.getElementById('raceForecastOddsCanvas'); 3823 var oddsCard = oddsCanvas && oddsCanvas.closest 3824 ? oddsCanvas.closest('.rf-premium-panel--trend, .race-forecast-trend-card') 3825 : null; 3826 var oddsStage = oddsCard 3827 ? oddsCard.querySelector('.sleek-chart-stage, .race-forecast-chart-stage, .rf-premium-panel__chart') 3828 : null; 3829 var trendsSection = 3830 document.querySelector('.race-forecast-trends-grid') || 3831 document.querySelector('.rf-premium-trends'); 3832 var pollPanel = 3833 document.getElementById('raceForecastPollingData') || 3834 document.querySelector('.race-forecast-polling-data'); 3835 var related = 3836 document.getElementById('raceRelatedRaces') || 3837 document.querySelector('main > .race-related-races'); 3838 3839 /* 3840 * Panels trial (Iowa etc.): place mids as siblings BETWEEN panels 3841 * (channel-centered via CSS) â same rhythm as hub (hero â map â ad): 3842 * 1) Sim â Vote (under first panel, like hub under-map mid) 3843 * 2) Odds â EH 3844 * No Polling Data â Related Races mid on panels mobile. 3845 */ 3846 if (panelsMode) { 3847 removeShell(document.getElementById('raceForecastAdUnderPollsMobile'), true); 3848 3849 if (simBlock && simBlock.parentNode) { 3850 var afterSim = placeOnce('raceForecastAdTopOfSimMobile', function () { 3851 return makeSingle('raceForecastAdTopOfSimMobile', { 3852 mobileOnly: true, 3853 format: 'auto', 3854 extraClass: 'pollsmax-ad-single--poll-mid-mobile' 3855 }); 3856 }, function (el) { 3857 insertAfter(simBlock, el); 3858 }); 3859 if (afterSim) { 3860 afterSim.setAttribute('data-ad-state', 'waiting'); 3861 setShellLoading(afterSim); 3862 observeShell(afterSim, { priority: true }); 3863 } 3864 } 3865 3866 if (trendsSection && trendsSection.parentNode) { 3867 var afterOdds = placeOnce('raceForecastAdAfterOddsMobile', function () { 3868 return makeSingle('raceForecastAdAfterOddsMobile', { 3869 mobileOnly: true, 3870 format: 'auto', 3871 extraClass: 'pollsmax-ad-single--poll-mid-mobile' 3872 }); 3873 }, function (el) { 3874 insertAfter(trendsSection, el); 3875 }); 3876 if (afterOdds) { 3877 afterOdds.setAttribute('data-ad-state', 'waiting'); 3878 setShellLoading(afterOdds); 3879 observeShell(afterOdds, { priority: true }); 3880 } 3881 } else if (oddsCard && oddsCard.parentNode) { 3882 var afterOddsCard = placeOnce('raceForecastAdAfterOddsMobile', function () { 3883 return makeSingle('raceForecastAdAfterOddsMobile', { 3884 mobileOnly: true, 3885 format: 'auto', 3886 extraClass: 'pollsmax-ad-single--poll-mid-mobile' 3887 }); 3888 }, function (el) { 3889 insertAfter(oddsCard, el); 3890 }); 3891 if (afterOddsCard) { 3892 afterOddsCard.setAttribute('data-ad-state', 'waiting'); 3893 setShellLoading(afterOddsCard); 3894 observeShell(afterOddsCard, { priority: true }); 3895 } 3896 } 3897 applyMockMobileAds(); 3898 return; 3899 } 3900 3901 /* 3902 * Legacy mobile forecast (non-panels): 3903 * 1) Top of Simulated Results box 3904 * 2) Under Odds chart stage 3905 * 3) Under polls table 3906 */ 3907 if (simBlock) { 3908 placeOnce('raceForecastAdTopOfSimMobile', function () { 3909 return makeSingle('raceForecastAdTopOfSimMobile', { 3910 mobileOnly: true, 3911 format: 'auto', 3912 extraClass: 'pollsmax-ad-single--poll-mid-mobile' 3913 }); 3914 }, function (el) { 3915 if (simBlock.firstElementChild !== el) { 3916 simBlock.insertBefore(el, simBlock.firstChild); 3917 } 3918 }); 3919 } 3920 3921 if (oddsStage || oddsCard || trendsSection) { 3922 placeOnce('raceForecastAdAfterOddsMobile', function () { 3923 return makeSingle('raceForecastAdAfterOddsMobile', { 3924 mobileOnly: true, 3925 format: 'auto', 3926 extraClass: 'pollsmax-ad-single--poll-mid-mobile' 3927 }); 3928 }, function (el) { 3929 if (oddsStage && oddsStage.parentNode) insertAfter(oddsStage, el); 3930 else if (oddsCard) appendChild(oddsCard, el); 3931 else if (trendsSection) insertAfter(trendsSection, el); 3932 }); 3933 } 3934 3935 placeUnderPollTable('raceForecastAdUnderPollsMobile', true); 3936 } 3937 3938 ET.placeRaceForecastPageAds = function () { 3939 if (ET.racePageViewMode && ET.racePageViewMode() !== 'forecast') return; 3940 if (isMobile()) { 3941 placeForecastMobileAds(); 3942 } else { 3943 /* Prefer Multiplex before forecast mid duals when called late (after mount). */ 3944 ensureSiteMultiplex(); 3945 placeForecastDesktopAds(); 3946 } 3947 registerAllShells(); 3948 applyMockMobileAds(); 3949 ensureSiteMultiplex(); 3950 }; 3951 3952 /* Back-compat hooks used by older call sites */ 3953 ET.placeRaceForecastPollsTableAd = function () { 3954 ET.placeRaceForecastPageAds(); 3955 }; 3956 ET.placeRaceForecastEhAd = function () { 3957 ET.placeRaceForecastPageAds(); 3958 }; 3959 3960 function placeApprovalAds(kind) { 3961 if (!kind || !kind.approval) return; 3962 var mobile = isMobile(); 3963 if (mobile) { 3964 /* Drop any stale PC dual so it cannot sit between mobile mid-ad and table. */ 3965 removeShell(document.getElementById('approvalAdBeforeTable'), true); 3966 /* Under the 4 rectangles / issue buttons, above the polls table. */ 3967 placeBeforePollsTableMobile('approvalAdBeforeTableMobile'); 3968 } else { 3969 /* PC: same 2-up horizontal above the polls table as race polling pages. */ 3970 removeShell(document.getElementById('approvalAdBeforeTableMobile'), true); 3971 removeShell(document.getElementById('approvalAdBeforeTable'), true); 3972 placeAbovePollTable('approvalAdBeforeTable'); 3973 scheduleMidContentAdGapSync(); 3974 } 3975 registerAllShells(); 3976 } 3977 3978 function placeLatestPollsAds(kind) { 3979 if (!kind || !kind.latestPolls) return; 3980 var content = document.getElementById('allPollsContent'); 3981 var wrap = document.querySelector('.all-polls-table-wrap') || content; 3982 if (!wrap && !content) return; 3983 var mobile = isMobile(); 3984 3985 /* 1-based date ordinals â 0-based indexes among visible date blocks. */ 3986 var infeedOrd = [6, 11, 16, 21, 26, 31, 36, 41]; 3987 var infeedIds = infeedOrd.map(function (n) { return 'latestPollsAdBeforeDate' + n; }); 3988 /* Legacy 3/5/7 slots â always remove. */ 3989 ['latestPollsAdBeforeDate3', 'latestPollsAdBeforeDate5', 'latestPollsAdBeforeDate7'] 3990 .concat(infeedIds) 3991 .forEach(function (id) { 3992 if (mobile) removeShell(document.getElementById(id), true); 3993 }); 3994 3995 if (mobile) { 3996 var host = wrap && wrap.id === 'allPollsContent' && wrap.parentElement ? wrap.parentElement : wrap; 3997 if (!host) return; 3998 placeOnce('latestPollsAdUnderListMobile', function () { 3999 return makeSingle('latestPollsAdUnderListMobile', { 4000 mobileOnly: true, 4001 format: 'auto', 4002 extraClass: 'pollsmax-ad-single--poll-mid-mobile' 4003 }); 4004 }, function (el) { insertAfter(host, el); }); 4005 registerAllShells(); 4006 return; 4007 } 4008 4009 removeShell(document.getElementById('latestPollsAdUnderListMobile'), true); 4010 removeShell(document.getElementById('latestPollsAdUnderList'), true); 4011 /* Clear retired ordinals so they cannot linger between filter changes. */ 4012 ['latestPollsAdBeforeDate3', 'latestPollsAdBeforeDate5', 'latestPollsAdBeforeDate7'].forEach(function (id) { 4013 removeShell(document.getElementById(id), true); 4014 }); 4015 4016 if (!content) return; 4017 var allBlocks = content.querySelectorAll('.all-polls-date-block'); 4018 var blocks = []; 4019 var bi; 4020 for (bi = 0; bi < allBlocks.length; bi++) { 4021 if (allBlocks[bi].style.display === 'none') continue; 4022 blocks.push(allBlocks[bi]); 4023 } 4024 4025 /* Same 2-up horizontal as page-top â before 6th / 11th / 16th / ⦠date blocks. */ 4026 var slots = infeedOrd.map(function (n) { 4027 return { id: 'latestPollsAdBeforeDate' + n, index: n - 1 }; 4028 });
4029 slots.forEach(function (slot) { 4030 var block = blocks[slot.index]; 4031 if (!block || !block.parentNode) { 4032 removeShell(document.getElementById(slot.id), true); 4033 return; 4034 } 4035 placeOnce(slot.id, function () { 4036 return makeRow(slot.id, { 4037 extraClass: 'pollsmax-ad-row--page-top pollsmax-ad-row--page-top-wide pollsmax-ad-row--latest-polls-infeed', 4038 format: 'rectangle' 4039 }); 4040 }, function (el) { 4041 if (el.parentNode !== block.parentNode || el.nextElementSibling !== block) { 4042 block.parentNode.insertBefore(el, block); 4043 } 4044 el.setAttribute('data-ad-state', 'live'); 4045 el.removeAttribute('data-pollsmax-ad-empty'); 4046 }); 4047 }); 4048 registerAllShells(); 4049 syncTopAdContentBounds(); 4050 requestAnimationFrame(syncTopAdContentBounds); 4051 setTimeout(syncTopAdContentBounds, 120); 4052 } 4053 4054 function placeUpcomingElectionsAds(kind) { 4055 if (!kind || !kind.upcomingElections) return; 4056 var content = document.getElementById('upcomingElectionsPageRoot'); 4057 if (!content) return; 4058 var mobile = isMobile(); 4059 /* Every 5 date groups: before 6th, 11th, 16th, ⦠*/ 4060 var infeedOrd = [6, 11, 16, 21, 26, 31, 36, 41]; 4061 var infeedIds = infeedOrd.map(function (n) { return 'upcomingElectionsAdBeforeDate' + n; }); 4062 4063 infeedIds.forEach(function (id) { 4064 if (mobile) removeShell(document.getElementById(id), true); 4065 }); 4066 4067 if (mobile) { 4068 var wrap = content.parentElement || content; 4069 placeOnce('upcomingElectionsAdUnderListMobile', function () { 4070 return makeSingle('upcomingElectionsAdUnderListMobile', { 4071 mobileOnly: true, 4072 format: 'auto', 4073 extraClass: 'pollsmax-ad-single--poll-mid-mobile' 4074 }); 4075 }, function (el) { insertAfter(wrap, el); }); 4076 registerAllShells(); 4077 return; 4078 } 4079 4080 removeShell(document.getElementById('upcomingElectionsAdUnderListMobile'), true); 4081 4082 var allBlocks = content.querySelectorAll('.upcoming-elections-page__day'); 4083 var blocks = []; 4084 var bi; 4085 for (bi = 0; bi < allBlocks.length; bi++) { 4086 if (allBlocks[bi].hidden || allBlocks[bi].style.display === 'none') continue; 4087 blocks.push(allBlocks[bi]); 4088 } 4089 4090 var slots = infeedOrd.map(function (n) { 4091 return { id: 'upcomingElectionsAdBeforeDate' + n, index: n - 1 }; 4092 }); 4093 slots.forEach(function (slot) { 4094 var block = blocks[slot.index]; 4095 if (!block || !block.parentNode) { 4096 removeShell(document.getElementById(slot.id), true); 4097 return; 4098 } 4099 placeOnce(slot.id, function () { 4100 return makeRow(slot.id, { 4101 extraClass: 'pollsmax-ad-row--page-top pollsmax-ad-row--page-top-wide pollsmax-ad-row--upcoming-elections-infeed', 4102 format: 'rectangle' 4103 }); 4104 }, function (el) { 4105 if (el.parentNode !== block.parentNode || el.nextElementSibling !== block) { 4106 block.parentNode.insertBefore(el, block); 4107 } 4108 el.setAttribute('data-ad-state', 'live'); 4109 el.removeAttribute('data-pollsmax-ad-empty'); 4110 }); 4111 }); 4112 registerAllShells(); 4113 syncTopAdContentBounds(); 4114 requestAnimationFrame(syncTopAdContentBounds); 4115 setTimeout(syncTopAdContentBounds, 120); 4116 } 4117 4118 function place2028PrimaryAds(kind) { 4119 if (!kind || !kind.primary2028) return; 4120 var mobile = isMobile(); 4121 if (mobile) { 4122 /* Under chart box, above the polls table. */ 4123 placeBeforePollsTableMobile('primary2028AdAfterChartMobile'); 4124 } else { 4125 /* PC: same 2-up horizontal above the polls table as race polling pages. */ 4126 removeShell(document.getElementById('primary2028AdAfterChart'), true); 4127 removeShell(document.getElementById('primary2028AdAfterTable'), true); 4128 placeAbovePollTable('primary2028AdBeforeTable'); 4129 scheduleMidContentAdGapSync(); 4130 } 4131 registerAllShells(); 4132 } 4133 4134 4135 /* ---------- Boot ---------- */ 4136 4137 function scrubWrongSlots() { 4138 var bad = document.querySelectorAll( 4139 'ins.adsbygoogle[data-ad-slot="5773578144"],' + 4140 'ins.adsbygoogle[data-ad-slot="8152742160"]' 4141 ); 4142 var i; 4143 for (i = 0; i < bad.length; i++) { 4144 var shell = bad[i].closest( 4145 '.pollsmax-ad-banner, .pollsmax-ad-row, .pollsmax-ad-single, aside.pollsmax-site-multiplex, .pollsmax-home-ad, #home-after-opening-maps' 4146 ); 4147 if (shell && shell.parentNode && 4148 !shell.querySelector('ins.adsbygoogle[data-pollsmax-initialized="true"]')) { 4149 shell.parentNode.removeChild(shell); 4150 } 4151 } 4152 } 4153 4154 /** Undo logoâDonate breakout on hub chart ads (they sit beside the races list). */ 4155 function scrubHubTallBreakout() { 4156 var hubs = document.querySelectorAll('.pollsmax-ad-banner--hub-tall'); 4157 var i; 4158 for (i = 0; i < hubs.length; i++) { 4159 var el = hubs[i]; 4160 el.classList.remove('pollsmax-ad--header-width'); 4161 el.style.marginLeft = ''; 4162 el.style.marginRight = ''; 4163 el.style.width = ''; 4164 el.style.maxWidth = ''; 4165 el.style.paddingLeft = ''; 4166 el.style.paddingRight = ''; 4167 el.style.left = ''; 4168 el.style.right = ''; 4169 el.style.position = ''; 4170 } 4171 } 4172 4173 /** PC: strip ads that aren't page-top duals (incl. mid page-top rows) or multiplex. */ 4174 function scrubDesktopExtraAds() { 4175 if (isMobile()) return; 4176 var shells = document.querySelectorAll( 4177 '.pollsmax-ad-banner, .pollsmax-ad-row, .pollsmax-ad-single, aside.pollsmax-site-multiplex, #home-after-opening-maps' 4178 ); 4179 var i; 4180 for (i = 0; i < shells.length; i++) { 4181 var el = shells[i]; 4182 if (!el || !el.parentNode) continue; 4183 if (el.id === 'pollsmaxPageTopAdDesktop') continue; 4184 if (el.classList && el.classList.contains('pollsmax-ad-row--page-top')) continue; 4185 if (el.classList && el.classList.contains('pollsmax-site-multiplex')) continue; 4186 if (el.id === 'homeAdMultiplex' || el.id === 'pollsmaxSiteMultiplex') continue; 4187 removeShell(el, true); 4188 } 4189 } 4190 4191 function placeShellsOnly() { 4192 if (isEmbed()) return; 4193 ensureAdsense(); 4194 scrubWrongSlots(); 4195 /* Desktop: under-header dual + Multiplex first; mid duals after mux push. */ 4196 if (!isMobile()) { 4197 var deskKind = pageKind(); 4198 scrubDesktopExtraAds(); 4199 placePageTopAds(); 4200 claimPageTopAdsNow(); 4201 ensureSiteMultiplex(); 4202 4203 function placeDesktopMidContentAds() { 4204 placeHomeDesktopDualRows(deskKind); 4205 placeLatestPollsAds(deskKind); 4206 placeUpcomingElectionsAds(deskKind); 4207 if (deskKind.gePoll || deskKind.primaryPoll) ET.placeRacePollingPageAds(); 4208 if (deskKind.forecast) ET.placeRaceForecastPageAds(); 4209 if (deskKind.approval) placeApprovalAds(deskKind); 4210 if (deskKind.primary2028) place2028PrimaryAds(deskKind); 4211 ensureSiteMultiplex(); 4212 var mux = document.querySelector('aside.pollsmax-site-multiplex'); 4213 if (mux) { 4214 mux.setAttribute('data-ad-state', 'live'); 4215 mux.removeAttribute('data-pollsmax-ad-empty'); 4216 clearShellLayoutInline(mux); 4217 } 4218 registerAllShells(); 4219 syncTopAdContentBounds(); 4220 scheduleMidContentAdGapSync(); 4221 } 4222 4223 /* 4224 * Wait for Multiplex push (or ~2.5s) before mounting mid duals so 4225 * AdSense does not burn the page quota on mids and leave the footer empty. 4226 */ 4227 var muxWaitStart = Date.now(); 4228 function waitMuxThenMids() { 4229 if (!multiplexPushPending() || Date.now() - muxWaitStart >= 2500) { 4230 placeDesktopMidContentAds(); 4231 return; 4232 } 4233 setTimeout(waitMuxThenMids, 100); 4234 } 4235 waitMuxThenMids(); 4236 return; 4237 } 4238 scrubHubTallBreakout(); 4239 scrubMobileUnderHeroAds(); 4240 scrubPrimaryPollMobileBarChartAboveTableAd(); 4241 var kind = pageKind(); 4242 placeHomeDesktopDualRows(kind); 4243 placePageTopAds(); 4244 placeHomeAfterOpeningMaps(kind); 4245 placeHomeAds(kind); 4246 placeHubAds(kind); 4247 if (kind.gePoll || kind.primaryPoll) ET.placeRacePollingPageAds(); 4248 if (kind.forecast) ET.placeRaceForecastPageAds(); 4249 ejectMidAdsFromPanels(); 4250 placeApprovalAds(kind); 4251 place2028PrimaryAds(kind); 4252 placeLatestPollsAds(kind); 4253 placeUpcomingElectionsAds(kind); 4254 scrubMobileUnderHeroAds(); 4255 scrubPrimaryPollMobileBarChartAboveTableAd(); 4256 ensureSiteMultiplex(); 4257 registerAllShells(); 4258 syncTopAdContentBounds(); 4259 applyMockMobileAds(); 4260 scheduleMidAdPanelOuterWidthSync(); 4261 schedulePrimaryPollMobileBarChartAdScrub(); 4262 } 4263 4264 /* Alias kept for race/hub hooks. */ 4265 function placeAll() { 4266 placeShellsOnly(); 4267 } 4268 4269 function boot() { 4270 placeShellsOnly(); 4271 watchForecastReadyForMultiplex(); 4272 /* Re-place latest-polls in-feed duals after the date list paints / filters. */ 4273 document.addEventListener('pollsmax-all-polls-rendered', function () { 4274 try { 4275 placeLatestPollsAds(pageKind()); 4276 claimPageTopAdsNow(); 4277 syncTopAdContentBounds(); 4278 } catch (eLp) { /* ignore */ } 4279 }); 4280 document.addEventListener('pollsmax-upcoming-elections-rendered', function () { 4281 try { 4282 placeUpcomingElectionsAds(pageKind()); 4283 claimPageTopAdsNow(); 4284 syncTopAdContentBounds(); 4285 } catch (eUe) { /* ignore */ } 4286 }); 4287 function replaceHubMidsAfterCharts() { 4288 try { 4289 var k = pageKind(); 4290 if (!(k.senateHub || k.govHub || k.houseHub) || !isMobile()) return; 4291 placeHubAds(k); 4292 applyMockMobileAds(); 4293 scheduleMidAdPanelOuterWidthSync(); 4294 } catch (eHubCharts) { /* ignore */ } 4295 } 4296 document.addEventListener('pollsmax-hub-simulation-charts-drawn', replaceHubMidsAfterCharts); 4297 document.addEventListener('pollsmax-house-hub-charts-mounted', replaceHubMidsAfterCharts); 4298 document.addEventListener('pollsmax-hub-charts-ready', replaceHubMidsAfterCharts); 4299 /* Limited retries for late-mounted forecast/hub charts / races â not a long spam loop. 4300 * Hub mobile: shorter schedule so map zoom / races aren't fighting 8s of re-place. */ 4301 var kindBoot = pageKind(); 4302 var retries = (isMobile() && (kindBoot.houseHub || kindBoot.senateHub || kindBoot.govHub)) 4303 ? [500, 2200] 4304 : [400, 1600, 4000, 8000];
4305 retries.forEach(function (ms) { 4306 setTimeout(placeShellsOnly, ms); 4307 }); 4308 global.addEventListener('pagehide', function () { 4309 if (lazyObserver && typeof lazyObserver.disconnect === 'function') { 4310 try { lazyObserver.disconnect(); } catch (e) { /* ignore */ } 4311 } 4312 }, { once: true }); 4313 } 4314 4315 if (document.readyState === 'loading') { 4316 document.addEventListener('DOMContentLoaded', boot, { once: true }); 4317 } else { 4318 boot(); 4319 } 4320 global.addEventListener('load', function () { 4321 placeShellsOnly(); 4322 syncTopAdHeaderBounds(); 4323 scheduleMidAdPanelOuterWidthSync(); 4324 }, { once: true }); 4325 global.addEventListener('resize', function () { 4326 if (placePageTopAds._resizeT) clearTimeout(placePageTopAds._resizeT); 4327 placePageTopAds._resizeT = setTimeout(function () { 4328 syncTopAdHeaderBounds(); 4329 syncMidAdPanelOuterWidths(); 4330 }, 80); 4331 }); 4332 4333 global.PollsmaxPageAds = { 4334 placeAll: placeAll, 4335 initAds: initAds, 4336 registerAllShells: registerAllShells, 4337 initializeAdElement: initializeAdElement, 4338 ensureSiteMultiplex: ensureSiteMultiplex, 4339 syncTopAdContentBounds: syncTopAdContentBounds, 4340 syncHeaderWidthAds: syncHeaderWidthAds, 4341 fitPageTopCreatives: fitPageTopCreatives 4342 }; 4343})(typeof window !== 'undefined' ? window : this);
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.