1/** 2 * Senate / Governor forecast hub â bar caption, map key, Show Flips / More Detail. 3 * Requires ET from shared.js. Not used on the site home page maps. 4 */ 5(function () { 6 'use strict'; 7 var ET = window.ET; 8 if (!ET) return; 9 10 function $(id) { return document.getElementById(id); } 11 12 function syncFlipsToggleRow(checkboxId, rowId, on) { 13 var cb = $(checkboxId); 14 var row = rowId ? $(rowId) : null; 15 if (cb) cb.checked = !!on; 16 if (row) row.classList.toggle('eh-flips-toggle-row--off', !on); 17 } 18 19 var FORECAST_HUB_KEY_RANGE_POLLING = { 20 solid: '> 15%', 21 likely: '5 - 15%', 22 lean: '1 - 5%', 23 tilt: '< 1%' 24 }; 25 var FORECAST_HUB_KEY_RANGE_RATINGS = { 26 solid: '>95%', 27 likely: '80 - 95%', 28 lean: '65 - 80%', 29 tilt: '50 - 65%' 30 }; 31 32 function isHubKeyRangeCompactMobile() { 33 return window.matchMedia('(max-width: 767px)').matches; 34 } 35 36 function formatHubKeyRangeLabel(text) { 37 if (!text || !isHubKeyRangeCompactMobile()) return text; 38 return String(text).replace(/\s+-\s+/g, '-'); 39 } 40 41 function syncLegendKeyRangeLabels(key, ratingsMode) { 42 if (!key) return; 43 var ranges = ratingsMode ? FORECAST_HUB_KEY_RANGE_RATINGS : FORECAST_HUB_KEY_RANGE_POLLING; 44 ['solid', 'likely', 'lean', 'tilt'].forEach(function (tier) { 45 var col = key.querySelector('.forecast-hub-key-col[data-hub-col="' + tier + '"]'); 46 if (!col) return; 47 var rangeEl = col.querySelector('.forecast-hub-key-range'); 48 if (rangeEl) rangeEl.textContent = formatHubKeyRangeLabel(ranges[tier]); 49 }); 50 } 51 52 if (!window._hubKeyRangeResizeBound) { 53 window._hubKeyRangeResizeBound = true; 54 window.addEventListener('resize', function () { 55 document.querySelectorAll('.forecast-hub-map-key').forEach(function (key) { 56 syncLegendKeyRangeLabels(key, key.classList.contains('forecast-hub-map-key--ratings')); 57 }); 58 }); 59 } 60 61 function syncLegendKey(root, showFlips, showMargins, mapSource) { 62 if (!root) return; 63 var key = root.querySelector('.forecast-hub-map-key'); 64 if (!key) return; 65 var ratingsMode = mapSource === 'ratings'; 66 key.classList.toggle('forecast-hub-map-key--margins', !!showMargins); 67 key.classList.toggle('forecast-hub-map-key--flips', !!showFlips); 68 key.classList.toggle('forecast-hub-map-key--ratings', ratingsMode); 69 syncLegendKeyRangeLabels(key, ratingsMode); 70 } 71 72 ET.syncForecastHubMapLegendKey = syncLegendKey; 73 74 function syncMapSourceToggle(rowId, mapSource) { 75 var row = rowId ? $(rowId) : null; 76 if (!row) return; 77 var buttons = row.querySelectorAll('[data-map-source]'); 78 buttons.forEach(function (btn) { 79 var active = btn.getAttribute('data-map-source') === mapSource; 80 btn.classList.toggle('forecast-hub-map-source__btn--active', active); 81 btn.setAttribute('aria-pressed', active ? 'true' : 'false'); 82 }); 83 if (typeof ET.syncHubMobileRacesMapSource === 'function') { 84 ET.syncHubMobileRacesMapSource(rowId, mapSource); 85 } 86 } 87 88 function hubAssetUrl(path) { 89 if (typeof window.pollsmaxHubAssetUrl === 'function') return window.pollsmaxHubAssetUrl(path); 90 return path + (path.indexOf('?') >= 0 ? '&' : '?') + 'v=' + (window.POLLSMAX_HUB_UI_REV || '70'); 91 } 92 93 function hubHeaderLogoLeft() { 94 var logo = document.querySelector('#mainHeader .sleek-home-header-logo, #mainHeader img[alt*="POLLSMAX"]'); 95 return logo ? logo.getBoundingClientRect().left : 0; 96 } 97 98 function bleedLeftFromLogo(hostEl) { 99 if (!hostEl) return '0px'; 100 return (hubHeaderLogoLeft() - hostEl.getBoundingClientRect().left) + 'px'; 101 } 102 103 function syncForecastHubSearchRuleFromAside() { 104 var searchWrap = document.querySelector('.senate-search-polls-rule-wrap, .gov-search-polls-rule-wrap'); 105 var aside = document.querySelector('main .senate-races-aside, main .gov-hub-aside.senate-races-aside');
106 if (!searchWrap) return; 107 var prop = searchWrap.classList.contains('gov-search-polls-rule-wrap') 108 ? '--gov-search-rule-left' 109 : '--senate-search-rule-left'; 110 if (window.matchMedia('(min-width: 1024px)').matches && aside) { 111 var ar = aside.getBoundingClientRect(); 112 var wr = searchWrap.getBoundingClientRect(); 113 /* Aside frame border-right is the column vline (box extends through former +15px gutter). */ 114 var vlineLeft = ar.right - 1; 115 var sectionRuleLeft = null; 116 searchWrap.style.setProperty(prop, (vlineLeft - wr.left) + 'px'); 117 if (document.body.classList.contains('house-hub-page')) { 118 var grid = forecastHubPageGrid(); 119 var buckets = document.getElementById('houseForecastBucketsSection'); 120 var rightCol = document.querySelector('.senate-right-content'); 121 sectionRuleLeft = rightCol ? (vlineLeft - rightCol.getBoundingClientRect().left) : null; 122 if (grid) { 123 var gridLeft = grid.getBoundingClientRect().left; 124 searchWrap.style.setProperty('--house-search-rule-left-bleed', (gridLeft - wr.left) + 'px'); 125 } 126 if (sectionRuleLeft != null && isFinite(sectionRuleLeft)) { 127 var sectionRulePx = Math.round(sectionRuleLeft) + 'px'; 128 if (rightCol) rightCol.style.setProperty('--house-hub-section-rule-left', sectionRulePx); 129 if (buckets) buckets.style.setProperty('--house-hub-section-rule-left', sectionRulePx); 130 } 131 document.body.classList.add('house-hub-search-rule-synced'); 132 } else { 133 searchWrap.style.removeProperty('--house-search-rule-left-bleed'); 134 document.body.classList.remove('house-hub-search-rule-synced'); 135 } 136 var houseOdds = document.getElementById('houseHubSimulationChartsSection'); 137 if (houseOdds) { 138 if (document.body.classList.contains('house-hub-page') && sectionRuleLeft != null && isFinite(sectionRuleLeft)) { 139 houseOdds.style.setProperty('--house-hub-section-rule-left', Math.round(sectionRuleLeft) + 'px'); 140 } else { 141 houseOdds.style.setProperty('--house-hub-section-rule-left', (vlineLeft - houseOdds.getBoundingClientRect().left) + 'px'); 142 } 143 } 144 } else { 145 searchWrap.style.setProperty(prop, '0px'); 146 searchWrap.style.removeProperty('--house-search-rule-left-bleed'); 147 document.body.classList.remove('house-hub-search-rule-synced'); 148 var houseOddsMob = document.getElementById('houseHubSimulationChartsSection'); 149 if (houseOddsMob) houseOddsMob.style.setProperty('--house-hub-section-rule-left', '0px'); 150 } 151 if (document.body.classList.contains('house-hub-page') && typeof ET.syncHouseAsideScrollHeight === 'function') { 152 ET.syncHouseAsideScrollHeight(); 153 } 154 if (document.body.classList.contains('house-hub-page') && typeof ET.syncHouseHubColumnVline === 'function') { 155 ET.syncHouseHubColumnVline(); 156 } 157 if (document.body.classList.contains('house-hub-page') && typeof ET.syncHouseHubSearchRuleLayout === 'function') { 158 ET.syncHouseHubSearchRuleLayout(); 159 } 160 if (document.body.classList.contains('house-hub-page') && typeof ET.syncHouseHubMapZoomLayout === 'function') { 161 ET.syncHouseHubMapZoomLayout(); 162 } 163 } 164 165 function forecastHubPageGrid() { 166 return document.querySelector('main .forecast-hub-page-grid'); 167 } 168 169 /** Snapshot collapse-arrow Y and flag-column X while the aside is visible (document scroll, not viewport-fixed). */ 170 function captureAsideToggleAnchor() { 171 var grid = forecastHubPageGrid(); 172 var chevron = document.querySelector( 173 '.forecast-hub-aside-collapse-toggle--collapse .forecast-hub-aside-collapse-chevron' 174 ); 175 var flag = document.querySelector('.senate-races-aside .senate-race-flag'); 176 var aside = document.querySelector('main .senate-races-aside, main .gov-hub-aside.senate-races-aside'); 177 if (!grid || !chevron) return false;
178 179 var gridRect = grid.getBoundingClientRect(); 180 var chRect = chevron.getBoundingClientRect(); 181 var top = (chRect.top + chRect.height / 2) - gridRect.top + grid.scrollTop; 182 var left; 183 184 if (flag) { 185 left = flag.getBoundingClientRect().left - gridRect.left + grid.scrollLeft; 186 } else if (aside) { 187 var asideRect = aside.getBoundingClientRect(); 188 left = asideRect.left - gridRect.left + grid.scrollLeft; 189 } else { 190 left = 0; 191 } 192 193 grid.style.setProperty('--forecast-hub-aside-toggle-top', top + 'px'); 194 grid.style.setProperty('--forecast-hub-aside-toggle-left', left + 'px'); 195 return true; 196 } 197 198 function applyAsideToggleAnchor() { 199 var grid = forecastHubPageGrid(); 200 if (!grid) return; 201 /* vars already on grid from captureAsideToggleAnchor */ 202 } 203 204 function syncForecastHubAsideTogglePosition() { 205 if (document.body.classList.contains('forecast-hub-races-aside-collapsed')) { 206 applyAsideToggleAnchor(); 207 } else { 208 captureAsideToggleAnchor(); 209 } 210 } 211 212 /** Match Search polls / map rules: aside vertical line, or logo-left full bleed when race list hidden. */ 213 function syncForecastHubCollapsedMapRule(shell, ruleEl, divider) { 214 if (!shell || !ruleEl || !divider) return; 215 if (window.matchMedia('(min-width: 1024px)').matches) { 216 if (document.body.classList.contains('forecast-hub-races-aside-collapsed')) { 217 divider.style.removeProperty('--forecast-hub-map-below-rule-left'); 218 return; 219 } 220 var aside = document.querySelector('main .senate-races-aside, main .gov-hub-aside.senate-races-aside'); 221 if (aside) { 222 var ar = aside.getBoundingClientRect(); 223 var rr = ruleEl.getBoundingClientRect(); 224 /* Aside frame border-right is the column vline (box extends through former +15px gutter). */ 225 var vlineLeft = ar.right - 1; 226 divider.style.setProperty('--forecast-hub-map-below-rule-left', (vlineLeft - rr.left) + 'px'); 227 return; 228 } 229 } 230 divider.style.setProperty('--forecast-hub-map-below-rule-left', '0px'); 231 } 232 233 /** 234 * Race list hidden (lg+): align hero identity + more blurb to the seat bar rail above the map. 235 * Otherwise clear vars (default full-width head). 236 */ 237 function syncForecastHubHeadSeatLabelAlign() { 238 var root = document.documentElement; 239 var lg = window.matchMedia('(min-width: 1024px)').matches; 240 var collapsed = document.body.classList.contains('forecast-hub-races-aside-collapsed'); 241 var isHub = document.body.classList.contains('senate-hub-page') || 242 document.body.classList.contains('gov-hub-page') || 243 document.body.classList.contains('house-hub-page'); 244 245 if (!lg || !isHub) { 246 root.style.removeProperty('--forecast-hub-head-content-left'); 247 root.style.removeProperty('--forecast-hub-head-content-width'); 248 return; 249 } 250 251 if (!collapsed) { 252 root.style.removeProperty('--forecast-hub-head-content-left'); 253 root.style.removeProperty('--forecast-hub-head-content-width'); 254 return; 255 } 256 257 if (document.body.classList.contains('senate-hub-page') || 258 document.body.classList.contains('gov-hub-page')) { 259 root.style.removeProperty('--forecast-hub-head-content-left'); 260 root.style.removeProperty('--forecast-hub-head-content-width'); 261 requestAnimationFrame(syncForecastHubCollapsedHeadlineWrap); 262 return; 263 } 264 265 var seatBar = document.getElementById('senateHubSeatBar') || 266 document.getElementById('govHubSeatBar') || 267 document.getElementById('houseHubSeatBar'); 268 var headText = document.querySelector('.forecast-hub-page-head .forecast-hub-page-head-text'); 269 if (!seatBar || !headText) { 270 root.style.removeProperty('--forecast-hub-head-content-left'); 271 root.style.removeProperty('--forecast-hub-head-content-width'); 272 return; 273 } 274 275 var barRail = seatBar.querySelector('.forecast-hub-bar-rail') || 276 seatBar.querySelector('.pollsmax-hub-seat-bar-rail'); 277 if (!barRail) { 278 requestAnimationFrame(syncForecastHubHeadSeatLabelAlign); 279 return; 280 } 281 282 var barRect = barRail.getBoundingClientRect(); 283 if (barRect.width < 1) { 284 requestAnimationFrame(syncForecastHubHeadSeatLabelAlign); 285 return; 286 } 287 288 var refRect = headText.getBoundingClientRect(); 289 var left = Math.max(0, Math.round(barRect.left - refRect.left)); 290 var width = Math.max(0, Math.round(barRect.width)); 291 root.style.setProperty('--forecast-hub-head-content-left', left + 'px'); 292 root.style.setProperty('--forecast-hub-head-content-width', width + 'px'); 293 requestAnimationFrame(syncForecastHubCollapsedHeadlineWrap); 294 } 295 296 var syncForecastHubCollapsedHeadAlign = syncForecastHubHeadSeatLabelAlign; 297 298 function syncForecastHubCollapsedHeadlineWrap() { 299 if (typeof ET.syncStateRaceHeadlineWrap === 'function') { 300 requestAnimationFrame(function () { 301 if (document.getElementById('forecastHubRotatingHeadline')) { 302 ET.syncStateRaceHeadlineWrap('#forecastHubRotatingHeadline'); 303 } 304 if (document.getElementById('forecastGovHubRotatingHeadline')) { 305 ET.syncStateRaceHeadlineWrap('#forecastGovHubRotatingHeadline'); 306 } 307 }); 308 } 309 requestAnimationFrame(syncHouseHubForecastTitleFit); 310 } 311 312 function resetHouseHubForecastTitleFit() { 313 document.querySelectorAll('[data-forecast-hub-head="house"] .rf-head-state').forEach(function (h1) { 314 h1.style.removeProperty('max-width'); 315 h1.style.removeProperty('white-space'); 316 h1.classList.remove('rf-head-state--wrap'); 317 }); 318 document.querySelectorAll('[data-forecast-hub-head="house"] .rf-head-identity').forEach(function (el) { 319 el.classList.remove('rf-head-identity--title-fit'); 320 }); 321 } 322 323 function syncHouseHubForecastTitleFit() { 324 var lg = window.matchMedia('(min-width: 1024px)').matches; 325 var collapsed = document.body.classList.contains('forecast-hub-races-aside-collapsed'); 326 if (!document.body.classList.contains('house-hub-page') || !lg || !collapsed) { 327 resetHouseHubForecastTitleFit(); 328 return; 329 } 330 331 var h1 = document.querySelector('[data-forecast-hub-head="house"] .rf-head-state'); 332 var countdownBox = document.querySelector('[data-forecast-hub-head="house"] .forecast-hub-midterm-countdown__boxes') || 333 document.querySelector('[data-forecast-hub-head="house"] .forecast-hub-head-countdown-slot'); 334 var identity = document.querySelector('[data-forecast-hub-head="house"] .rf-head-identity'); 335 if (!h1 || !countdownBox || !identity) { 336 resetHouseHubForecastTitleFit(); 337 return; 338 } 339 340 identity.classList.add('rf-head-identity--title-fit'); 341 h1.classList.remove('rf-head-state--wrap'); 342 h1.style.whiteSpace = 'nowrap'; 343 h1.style.maxWidth = ''; 344 345 var h1Rect = h1.getBoundingClientRect(); 346 var cdRect = countdownBox.getBoundingClientRect(); 347 var maxW = Math.floor(cdRect.left - h1Rect.left - 5); 348 if (maxW < 48) { 349 h1.style.whiteSpace = ''; 350 h1.style.maxWidth = ''; 351 h1.classList.add('rf-head-state--wrap'); 352 return; 353 } 354 355 h1.style.maxWidth = maxW + 'px'; 356 if (h1.scrollWidth > maxW + 1) { 357 h1.style.whiteSpace = ''; 358 h1.classList.add('rf-head-state--wrap'); 359 } 360 } 361 362 ET.syncHouseHubForecastTitleFit = syncHouseHubForecastTitleFit; 363 364 ET.syncForecastHubHeadSeatLabelAlign = syncForecastHubHeadSeatLabelAlign; 365 ET.syncForecastHubCollapsedHeadAlign = syncForecastHubCollapsedHeadAlign; 366 ET.syncForecastHubCollapsedHeadlineWrap = syncForecastHubCollapsedHeadlineWrap; 367 368 ET.scheduleForecastHubHeadSeatLabelAlign = function () { 369 requestAnimationFrame(function () { 370 syncForecastHubHeadSeatLabelAlign(); 371 requestAnimationFrame(function () { 372 syncForecastHubHeadSeatLabelAlign(); 373 syncForecastHubCollapsedHeadlineWrap(); 374 }); 375 }); 376 }; 377 378 ET.syncForecastHubHubRules = function () { 379 syncForecastHubAsideTogglePosition(); 380 syncForecastHubHeadSeatLabelAlign(); 381 syncForecastHubCollapsedHeadlineWrap(); 382 var collapsed = document.body.classList.contains('forecast-hub-races-aside-collapsed'); 383 var lg = window.matchMedia('(min-width: 1024px)').matches; 384 385 if (!collapsed || !lg) { 386 document.querySelectorAll('.senate-map-divider, .senate-right-content > section.senate-right-divider-inset').forEach(function (el) { 387 el.style.removeProperty('--forecast-hub-rule-bleed-left'); 388 });
389 var searchWrap = document.querySelector('.senate-search-polls-rule-wrap, .gov-search-polls-rule-wrap'); 390 if (searchWrap) { 391 searchWrap.style.removeProperty('--senate-search-rule-left'); 392 searchWrap.style.removeProperty('--gov-search-rule-left'); 393 searchWrap.style.removeProperty('--house-search-rule-left-bleed'); 394 } 395 syncForecastHubSearchRuleFromAside(); 396 document.querySelectorAll('.forecast-hub-map-legend-shell--collapsed').forEach(function (shell) { 397 var rule = shell.querySelector('.forecast-hub-map-below-rule'); 398 var divider = shell.closest('.senate-map-divider'); 399 if (rule && divider) syncForecastHubCollapsedMapRule(shell, rule, divider); 400 }); 401 return; 402 } 403 404 document.querySelectorAll('.senate-map-divider, .senate-right-content > section.senate-right-divider-inset').forEach(function (el) { 405 el.style.removeProperty('--forecast-hub-rule-bleed-left'); 406 }); 407 var searchWrapCollapsed = document.querySelector('.senate-search-polls-rule-wrap, .gov-search-polls-rule-wrap'); 408 if (searchWrapCollapsed) { 409 searchWrapCollapsed.style.removeProperty('--senate-search-rule-left'); 410 searchWrapCollapsed.style.removeProperty('--gov-search-rule-left'); 411 searchWrapCollapsed.style.removeProperty('--house-search-rule-left-bleed'); 412 } 413 document.querySelectorAll('.forecast-hub-map-legend-shell--collapsed').forEach(function (shell) { 414 var rule = shell.querySelector('.forecast-hub-map-below-rule'); 415 var divider = shell.closest('.senate-map-divider'); 416 if (rule && divider) syncForecastHubCollapsedMapRule(shell, rule, divider); 417 }); 418 if (document.body.classList.contains('house-hub-page') && typeof ET.syncHouseHubSearchRuleLayout === 'function') { 419 ET.syncHouseHubSearchRuleLayout(); 420 } 421 }; 422 423 function clearForecastHubCollapsedMapRule(divider) { 424 if (!divider) return; 425 divider.style.removeProperty('--forecast-hub-map-below-rule-left'); 426 } 427 428 function ensureHubMapUiCss() { 429 if (!document.getElementById('pollsmax-toggle-css')) { 430 var toggleLink = document.createElement('link'); 431 toggleLink.id = 'pollsmax-toggle-css'; 432 toggleLink.rel = 'stylesheet'; 433 toggleLink.href = '/assets/pollsmax-toggle.css?v=4'; 434 document.head.appendChild(toggleLink); 435 } 436 if (!document.getElementById('forecast-hub-map-ui-css')) { 437 var cssPath = '/assets/forecast-hub-map-ui.css'; 438 var link = document.createElement('link'); 439 link.id = 'forecast-hub-map-ui-css'; 440 link.rel = 'stylesheet'; 441 link.href = hubAssetUrl(cssPath); 442 document.head.appendChild(link); 443 } 444 /* Keep full-bleed hide after hub CSS so mobile overrides stick. */ 445 var hideId = 'pm-mobile-hide-fullbleed-rules'; 446 var hide = document.getElementById(hideId); 447 if (!hide) { 448 hide = document.createElement('link'); 449 hide.id = hideId; 450 hide.rel = 'stylesheet'; 451 hide.href = '/assets/pm-mobile-hide-fullbleed-rules.css?v=3'; 452 } 453 document.head.appendChild(hide); 454 } 455 456 ET.initForecastHubMapUi = function (opts) { 457 opts = opts || {}; 458 var svgId = opts.svgId; 459 if (!svgId) return; 460 461 ensureHubMapUiCss(); 462 463 var ui = ET.getForecastHubMapUi(svgId); 464 if (opts.captionId) { 465 var count = opts.kind === 'gov' 466 ? (typeof ET.countGovSeatsOnBallot === 'function' ? ET.countGovSeatsOnBallot() : 0) 467 : (typeof ET.countSenateSeatsOnBallot === 'function' ? ET.countSenateSeatsOnBallot() : 0); 468 var suffix = opts.captionSuffix || (opts.kind === 'gov' 469 ? (count === 1 ? ' governorship up for election' : ' governorships up for election') 470 : ' seats up for election'); 471 if (typeof ET.setForecastHubBarCaption === 'function') { 472 ET.setForecastHubBarCaption(opts.captionId, count, suffix
472); 473 } 474 } 475 476 var toolbar = opts.toolbarId ? $(opts.toolbarId) : null; 477 478 function bindLegendCollapse() { 479 var shell = toolbar && toolbar.closest ? toolbar.closest('.forecast-hub-map-legend-shell') : null; 480 if (!shell) return; 481 var toggle = shell.querySelector('.forecast-hub-map-legend-toggle'); 482 if (!toggle || shell.getAttribute('data-hub-legend-collapse-bound')) return; 483 shell.setAttribute('data-hub-legend-collapse-bound', '1'); 484 485 var divider = shell.closest('.senate-map-divider'); 486 var rule = shell.querySelector('.forecast-hub-map-below-rule'); 487 488 function getSectionsBelowMap() { 489 var list = []; 490 if (!divider) return list; 491 var parent = divider.parentElement; 492 if (!parent) return list; 493 var sib = divider.nextElementSibling; 494 while (sib && parent.contains(sib)) { 495 if (sib.classList && sib.classList.contains('senate-right-divider-inset')) { 496 list.push(sib); 497 } 498 sib = sib.nextElementSibling; 499 } 500 return list; 501 } 502 503 /** Y of the 1px line above the key (rule bottom). */ 504 function ruleLineY() { 505 if (!rule) return 0; 506 return rule.getBoundingClientRect().bottom; 507 } 508 509 /** Pull sections up until the top of the first one meets the rule line. */ 510 function measureCollapseShift() { 511 var sections = getSectionsBelowMap(); 512 if (!sections.length || !rule) return 0; 513 var targetY = ruleLineY(); 514 var controlTop = sections[0].getBoundingClientRect().top; 515 var shift = controlTop - targetY; 516 return shift > 0 ? Math.ceil(shift) : 0; 517 } 518 519 function clearSectionShift() { 520 getSectionsBelowMap().forEach(function (sec) { 521 sec.classList.remove('forecast-hub-legend-collapse-shifted'); 522 sec.classList.remove('forecast-hub-legend-collapse-shifted--lead'); 523 sec.style.removeProperty('--forecast-hub-legend-collapse-shift'); 524 }); 525 } 526 527 function applyCollapseShift() { 528 var sections = getSectionsBelowMap(); 529 if (!sections.length) return; 530 var shiftPx = measureCollapseShift() + 'px'; 531 sections.forEach(function (sec, i) { 532 if (i === 0) { 533 sec.classList.add('forecast-hub-legend-collapse-shifted'); 534 sec.classList.add('forecast-hub-legend-collapse-shifted--lead'); 535 sec.style.setProperty('--forecast-hub-legend-collapse-shift', shiftPx); 536 } else { 537 sec.classList.remove('forecast-hub-legend-collapse-shifted'); 538 sec.classList.remove('forecast-hub-legend-collapse-shifted--lead'); 539 sec.style.removeProperty('--forecast-hub-legend-collapse-shift'); 540 } 541 }); 542 } 543 544 function setCollapsed(collapsed) { 545 shell.classList.toggle('forecast-hub-map-legend-shell--collapsed', collapsed); 546 if (divider) { 547 divider.classList.toggle('forecast-hub-map-divider--legend-collapsed', collapsed); 548 } 549 if (!collapsed) { 550 clearForecastHubCollapsedMapRule(divider); 551 clearSectionShift(); 552 } else { 553 syncForecastHubCollapsedMapRule(shell, rule, divider); 554 void shell.offsetHeight; 555 applyCollapseShift(); 556 } 557 toggle.setAttribute('aria-expanded', collapsed ? 'false' : 'true'); 558 toggle.setAttribute('aria-label', collapsed ? 'Show map legend and controls' : 'Hide map legend and controls'); 559 } 560 561 if (!shell._hubLegendResizeBound) { 562 shell._hubLegendResizeBound = true; 563 window.addEventListener('resize', function () { 564 if (!shell.classList.contains('forecast-hub-map-legend-shell--collapsed')) return; 565 syncForecastHubCollapsedMapRule(shell, rule, divider); 566 applyCollapseShift(); 567 if (typeof ET.syncForecastHubHubRules === 'function') ET.syncForecastHubHubRules(); 568 }); 569 } 570 571 toggle.addEventListener('click', function () { 572 setCollapsed(!shell.classList.contains('forecast-hub-map-legend-shell--collapsed')); 573 if (typeof ET.syncForecastHubHubRules === 'function') ET.syncForecastHubHubRules(); 574 }); 575 } 576 577 bindLegendCollapse(); 578 bindRacesAsideCollapse(); 579 initForecastHubMidtermCountdown(); 580 var urlMapSource = typeof ET.consumeHubMapSourceFromUrl === 'function' 581 ? ET.consumeHubMapSourceFromUrl() 582 : null; 583 if (urlMapSource) { 584 ui.mapSource = urlMapSource; 585 } else if (!ui.mapSource) { 586 ui.mapSource = typeof ET.forecastHubDefaultMapSource === 'function' 587 ? ET.forecastHubDefaultMapSource(svgId) 588 : 'polling'; 589 } 590 if (opts.kind === 'house' && ui.mapSource === 'polling') { 591 ui.mapSource = 'ratings'; 592 } 593 syncLegendKey(toolbar, ui.showFlips, ui.showMargins, ui.mapSource); 594 syncMapSourceToggle(opts.mapSourceRowId, ui.mapSource); 595 syncFlipsToggleRow(opts.showFlipsId, opts.flipsRowId, ui.showFlips); 596 syncFlipsToggleRow(opts.showMarginsId, opts.marginsRowId, ui.showMargins); 597 598 function applyAll() { 599 var u = ET.getForecastHubMapUi(svgId); 600 syncLegendKey(toolbar, u.showFlips, u.showMargins, u.mapSource); 601 syncMapSourceToggle(opts.mapSourceRowId, u.mapSource); 602 syncFlipsToggleRow(opts.showFlipsId, opts.flipsRowId, u.showFlips); 603 syncFlipsToggleRow(opts.showMarginsId, opts.marginsRowId, u.showMargins); 604 if (opts.kind === 'house') { 605 if (typeof window.PollsmaxEnhanceHouseHubApplyUi === 'function') { 606 window.PollsmaxEnhanceHouseHubApplyUi(u, { mapToggle: true }); 607 } else if (typeof ET.refreshHouseCdMap === 'function') { 608 ET.refreshHouseCdMap(svgId); 609 } else if (opts.barIds && typeof ET.refreshHouseHubSeatBar === 'function') { 610 ET.refreshHouseHubSeatBar(opts.barIds, svgId); 611 } 612 } else { 613 if (opts.barIds) { 614 if (typeof ET.refreshForecastHubSeatBar === 'function') { 615 ET.refreshForecastHubSeatBar(opts.barIds, opts.kind, svgId); 616 } else if (typeof ET.syncForecastHubSeatBarFlips === 'function') { 617 ET.syncForecastHubSeatBarFlips(opts.barIds, u.showFlips); 618 } 619 } 620 if (opts.kind === 'gov' && typeof ET.refreshForecastHubGovMap === 'function') { 621 ET.refreshForecastHubGovMap(svgId); 622 } else if (typeof ET.refreshForecastHubSenateMap === 'function') { 623 ET.refreshForecastHubSenateMap(svgId); 624 } 625 } 626 if (typeof ET.refreshForecastHubAsideRaces === 'function') { 627 ET.refreshForecastHubAsideRaces(svgId); 628 } 629 /* Chamber-hub enhance listens for this to re-paint prerendered maps/bars. */ 630 try { 631 document.dispatchEvent(new CustomEvent('pollsmax-forecast-hub-map-ui-changed', { 632 detail: { svgId: svgId, kind: opts.kind, ui: u } 633 })); 634 } catch (eEv) { /* ignore */ } 635 } 636 637 if (opts.mapSourceRowId) { 638 var mapRow = $(opts.mapSourceRowId);
639 if (mapRow && !mapRow.getAttribute('data-hub-map-source-bound')) { 640 mapRow.setAttribute('data-hub-map-source-bound', '1'); 641 mapRow.querySelectorAll('[data-map-source]').forEach(function (btn) { 642 btn.addEventListener('click', function () { 643 var src = btn.getAttribute('data-map-source'); 644 if (src !== 'polling' && src !== 'ratings') return; 645 if (opts.kind === 'house' && src === 'polling') return; 646 ET.getForecastHubMapUi(svgId).mapSource = src; 647 applyAll(); 648 }); 649 }); 650 if (typeof ET.initForecastHubSourceTips === 'function') { 651 ET.initForecastHubSourceTips(mapRow); 652 } 653 } 654 } 655 656 if (opts.showFlipsId) { 657 var flipsCb = $(opts.showFlipsId); 658 if (flipsCb && !flipsCb.getAttribute('data-hub-bound')) { 659 flipsCb.setAttribute('data-hub-bound', '1'); 660 flipsCb.addEventListener('change', function () { 661 ET.getForecastHubMapUi(svgId).showFlips = flipsCb.checked; 662 applyAll(); 663 }); 664 } 665 } 666 667 if (opts.showMarginsId) { 668 var margCb = $(opts.showMarginsId); 669 if (margCb && !margCb.getAttribute('data-hub-bound')) { 670 margCb.setAttribute('data-hub-bound', '1'); 671 margCb.addEventListener('change', function () { 672 ET.getForecastHubMapUi(svgId).showMargins = margCb.checked; 673 applyAll(); 674 }); 675 } 676 } 677 678 applyAll(); 679 if (typeof ET.initHubMobileRaces === 'function') { 680 ET.initHubMobileRaces(); 681 } 682 }; 683 684 /** Nov 3, 2026 12:00 AM Eastern â hub map header countdown. */ 685 var HUB_MIDTERM_COUNTDOWN_TARGET_MS = new Date('2026-11-03T00:00:00-05:00').getTime(); 686 var _hubMidtermCountdownTimer = null; 687 688 function padHubCountdown2(n) { 689 n = Math.max(0, Math.floor(n)); 690 return n < 10 ? '0' + n : String(n); 691 } 692 693 function hubMidtermCountdownParts(nowMs, targetMs) { 694 if (targetMs <= nowMs) { 695 return { months: 0, days: 0, hours: 0, minutes: 0, seconds: 0 }; 696 } 697 var now = new Date(nowMs); 698 var target = new Date(targetMs); 699 var months = 0; 700 var cursor = new Date(now.getTime()); 701 while (true) { 702 var next = new Date(cursor.getFullYear(), cursor.getMonth() + 1, cursor.getDate(), 703 cursor.getHours(), cursor.getMinutes(), cursor.getSeconds()); 704 if (next.getTime() > target.getTime()) break; 705 months++; 706 cursor = next; 707 } 708 var remain = target.getTime() - cursor.getTime(); 709 var days = Math.floor(remain / 86400000); 710 remain -= days * 86400000; 711 var hours = Math.floor(remain / 3600000); 712 remain -= hours * 3600000; 713 var minutes = Math.floor(remain / 60000); 714 remain -= minutes * 60000; 715 var seconds = Math.floor(remain / 1000); 716 return { months: months, days: days, hours: hours, minutes: minutes, seconds: seconds }; 717 } 718 719 function renderHubMidtermCountdown(root) { 720 if (!root) return; 721 var parts = hubMidtermCountdownParts(Date.now(), HUB_MIDTERM_COUNTDOWN_TARGET_MS); 722 ['months', 'days', 'hours', 'minutes', 'seconds'].forEach(function (key) { 723 var el = root.querySelector('[data-hub-cd="' + key + '"]'); 724 if (el) el.textContent = padHubCountdown2(parts[key]); 725 }); 726 } 727 728 function syncHubMidtermCountdownLayout() { 729 if (typeof ET.syncForecastHubHeroCountdownBoxSize === 'function') { 730 ET.syncForecastHubHeroCountdownBoxSize(); 731 } 732 if (typeof ET.syncForecastHubMapHeadSlotAlign === 'function') { 733 ET.syncForecastHubMapHeadSlotAlign(); 734 } else { 735 document.querySelectorAll('.forecast-hub-map-head-area').forEach(function (area) { 736 var eyebrow = area.querySelector('.forecast-hub-map-eyebrow'); 737 var title = area.querySelector('.forecast-hub-map-title'); 738 var slot = area.querySelector('.forecast-hub-map-head-slot'); 739 var cd = area.querySelector('[data-forecast-hub-midterm-countdown]'); 740 if (!title || !slot) return; 741 if (typeof ET.isMobileLayout === 'function' && ET.isMobileLayout()) { 742 slot.style.top = ''; 743 return; 744 } 745 var tr = title.getBoundingClientRect(); 746 var ar = area.getBoundingClientRect(); 747 var centerY = tr.top + tr.height / 2; 748 if (eyebrow) { 749 var er = eyebrow.getBoundingClientRect(); 750 centerY = (er.top + tr.bottom) / 2; 751 var size = Math.max(28, Math.round(tr.bottom - er.top)); 752 if (cd && !cd.classList.contains('forecast-hub-midterm-countdown--hidden')) { 753 cd.style.setProperty('--hub-cd-box-size', size + 'px'); 754 } 755 } 756 slot.style.top = (centerY - ar.top) + 'px'; 757 }); 758 } 759 if (typeof syncHouseHubForecastTitleFit === 'function') { 760 syncHouseHubForecastTitleFit(); 761 } 762 } 763 764 function syncHubHeroCountdownHoverAria(cd, open) { 765 if (!cd) return; 766 var hover = cd.querySelector('.forecast-hub-midterm-countdown__hover'); 767 if (hover) hover.setAttribute('aria-hidden', open ? 'false' : 'true'); 768 } 769 770 function closeHubHeroCountdownHovers(except) { 771 document.querySelectorAll('.forecast-hub-page-head--forecast-hero .forecast-hub-midterm-countdown--hero.forecast-hub-midterm-countdown--hover-open').forEach(function (cd) { 772 if (cd === except) return; 773 cd.classList.remove('forecast-hub-midterm-countdown--hover-open'); 774 syncHubHeroCountdownHoverAria(cd, false); 775 }); 776 } 777 778 function bindHubHeroCountdownTap() { 779 if (window._hubHeroCountdownTapBound) return; 780 window._hubHeroCountdownTapBound = true; 781 782 function isSenGovHubMobile() { 783 return typeof ET.isSenGovHubMobilePage === 'function' && ET.isSenGovHubMobilePage(); 784 } 785 786 document.addEventListener('click', function (e) { 787 if (!isSenGovHubMobile()) { 788 closeHubHeroCountdownHovers(null); 789 return; 790 } 791 792 var box = e.target.closest('.forecast-hub-page-head--forecast-hero .forecast-hub-midterm-countdown--hero .forecast-hub-midterm-countdown__box'); 793 if (box) { 794 var cd = box.closest('.forecast-hub-midterm-countdown--hero'); 795 if (!cd) return; 796 var open = !cd.classList.contains('forecast-hub-midterm-countdown--hover-open'); 797 closeHubHeroCountdownHovers(null); 798 if (open) { 799 cd.classList.add('forecast-hub-midterm-countdown--hover-open'); 800 syncHubHeroCountdownHoverAria(cd, true); 801 } else { 802 cd.classList.remove('forecast-hub-midterm-countdown--hover-open'); 803 syncHubHeroCountdownHoverAria(cd, false); 804 } 805 return; 806 } 807 808 if (!e.target.closest('.forecast-hub-page-head--forecast-hero .forecast-hub-midterm-countdown--hero')) { 809 closeHubHeroCountdownHovers(null); 810 } 811 }); 812 } 813 814 function initForecastHubMidtermCountdown() { 815 if (typeof ET.initForecastHubMidtermCountdown === 'function') { 816 ET.initForecastHubMidtermCountdown(); 817 syncHubMidtermCountdownLayout(); 818 bindHubHeroCountdownTap(); 819 return; 820 } 821 function tick() { 822 document.querySelectorAll('[data-forecast-hub-midterm-countdown]').forEach(renderHubMidtermCountdown); 823 } 824 tick(); 825 syncHubMidtermCountdownLayout(); 826 bindHubHeroCountdownTap(); 827 if (_hubMidtermCountdownTimer != null) { 828 clearInterval(_hubMidtermCountdownTimer); 829 _hubMidtermCountdownTimer = null; 830 } 831 if (typeof ET.onAlignedSecondTick === 'function') { 832 ET.onAlignedSecondTick(tick); 833 } else { 834 _hubMidtermCountdownTimer = setInterval(tick, 1000); 835 } 836 if (!window._hubMidtermCountdownResizeBound) { 837 window._hubMidtermCountdownResizeBound = true; 838 window.addEventListener('resize', function () { 839 syncHubMidtermCountdownLayout(); 840 if (typeof ET.isSenGovHubMobilePage === 'function' && !ET.isSe
840nGovHubMobilePage()) { 841 closeHubHeroCountdownHovers(null); 842 } 843 }); 844 window.addEventListener('load', syncHubMidtermCountdownLayout); 845 } 846 } 847 848 ET.syncHubMidtermCountdownLayout = syncHubMidtermCountdownLayout; 849 850 function bindRacesAsideCollapse() { 851 var collapseBtn = document.querySelector('.forecast-hub-aside-collapse-toggle--collapse'); 852 var expandBtn = document.querySelector('.forecast-hub-aside-collapse-toggle--expand'); 853 var expandRow = document.querySelector('.forecast-hub-aside-expand-row'); 854 if (!collapseBtn || document.body.getAttribute('data-hub-aside-collapse-bound')) return; 855 document.body.setAttribute('data-hub-aside-collapse-bound', '1'); 856 857 function syncAria(collapsed) { 858 collapseBtn.setAttribute('aria-expanded', collapsed ? 'false' : 'true'); 859 collapseBtn.setAttribute('aria-label', collapsed ? 'Show race list' : 'Hide race list'); 860 if (expandBtn) { 861 expandBtn.setAttribute('aria-expanded', collapsed ? 'true' : 'false'); 862 expandBtn.setAttribute('aria-label', collapsed ? 'Show race list' : 'Hide race list'); 863 } 864 if (expandRow) expandRow.setAttribute('aria-hidden', collapsed ? 'false' : 'true'); 865 } 866 867 function setCollapsed(collapsed, opts) { 868 opts = opts || {}; 869 if (opts.fromUser) { 870 if (collapsed) { 871 document.body.dataset.hubAsideUserCollapsed = '1'; 872 } else { 873 delete document.body.dataset.hubAsideUserCollapsed; 874 } 875 } 876 if (collapsed) captureAsideToggleAnchor(); 877 document.body.classList.toggle('forecast-hub-races-aside-collapsed', collapsed); 878 syncAria(collapsed); 879 if (typeof ET.syncForecastHubHubRules === 'function') ET.syncForecastHubHubRules(); 880 if (typeof ET.syncAllHubAsideVoteColVisibility === 'function') ET.syncAllHubAsideVoteColVisibility(); 881 if (typeof ET.scheduleForecastHubHeadSeatLabelAlign === 'function') { 882 ET.scheduleForecastHubHeadSeatLabelAlign(); 883 } 884 if (!collapsed) { 885 captureAsideToggleAnchor(); 886 if (typeof ET.clearHubSearchPollsLayoutLocks === 'function') ET.clearHubSearchPollsLayoutLocks(); 887 window.dispatchEvent(new Event('resize')); 888 } else if (typeof ET.refreshHubSearchPollsLayout === 'function') { 889 ET.refreshHubSearchPollsLayout(); 890 } 891 if (opts.fromUser && typeof ET.syncHubAsideWidthCollapseForPage === 'function') { 892 ET.syncHubAsideWidthCollapseForPage(); 893 } 894 } 895 896 ET._forecastHubSetAsideCollapsed = setCollapsed; 897 ET.finishForecastHubAsideInitialCollapse = function () { /* race list defaults to expanded */ }; 898 899 function toggle() { 900 setCollapsed(!document.body.classList.contains('forecast-hub-races-aside-collapsed'), { fromUser: true }); 901 } 902 903 collapseBtn.addEventListener('click', toggle); 904 if (expandBtn) expandBtn.addEventListener('click', toggle); 905 906 syncAria(document.body.classList.contains('forecast-hub-races-aside-collapsed')); 907 captureAsideToggleAnchor(); 908 if (typeof ET.syncForecastHubHubRules === 'function') ET.syncForecastHubHubRules(); 909 if (typeof ET.syncHubAsideWidthCollapseForPage === 'function') ET.syncHubAsideWidthCollapseForPage(); 910 if (typeof ET.scheduleAllHubAsideVoteColVisibilitySync === 'function') { 911 ET.scheduleAllHubAsideVoteColVisibilitySync(); 912 } else if (typeof ET.syncAllHubAsideVoteColVisibility === 'function') { 913 ET.syncAllHubAsideVoteColVisibility(); 914 } 915 916 if (!collapseBtn._hubAsideResizeBound) { 917 collapseBtn._hubAsideResizeBound = true; 918 window.addEventListener('resize', function () { 919 if (!window.matchMedia('(min-width: 1024px)').matches) { 920 if (document.body.classList.contains('forecast-hub-races-aside-collapsed')) { 921 setCollapsed(false); 922 } 923 return; 924 } 925 if (!document.body.classList.contains('forecast-hub-races-aside-collapsed')) { 926 captureAsideToggleAnchor(); 927 } 928 if (typeof ET.syncForecastHubHubRules === 'function') ET.syncForecastHubHubRules(); 929 if (typeof ET.syncAllHubAsideVoteColVisibility === 'function') ET.syncAllHubAsideVoteColVisibility(); 930 }); 931 } 932 } 933 934 function initForecastHubSourceTipsOnLoad() { 935 ET.initForecastHubSourceTips(document); 936 } 937 938 if (document.readyState === 'loading') { 939 document.addEventListener('DOMContentLoaded', initForecastHubMidtermCountdown); 940 document.addEventListener('DOMContentLoaded', initForecastHubSourceTipsOnLoad); 941 } else { 942 initForecastHubMidtermCountdown(); 943 initForecastHubSourceTipsOnLoad(); 944 } 945 946 function bootHubMobileRacesModule() { 947 if (typeof ET.scheduleHubMobileRacesInit === 'function') { 948 ET.scheduleHubMobileRacesInit(); 949 } else if (typeof ET.initHubMobileRaces === 'function') { 950 ET.initHubMobileRaces(); 951 } 952 } 953 954 (function loadHubMobileRacesModule() { 955 if (document.querySelector('script[data-hub-mobile-races-js], script[src*="forecast-hub-mobile-races.js"]')) { 956 return; 957 } 958 if (window.__hubMobileRacesScriptLoading) return; 959 window.__hubMobileRacesScriptLoading = true; 960 var src = hubAssetUrl('/assets/forecast-hub-mobile-races.js'); 961 var s = document.createElement('script'); 962 s.src = src; 963 s.setAttribute('data-hub-mobile-races-js', '1'); 964 s.onload = function () { 965 window.__hubMobileRacesScriptLoading = false;
966 bootHubMobileRacesModule(); 967 }; 968 s.onerror = function () { 969 window.__hubMobileRacesScriptLoading = false; 970 if (console && console.warn) console.warn('[Pollsmax] forecast-hub-mobile-races.js failed to load'); 971 }; 972 document.head.appendChild(s); 973 })(); 974})();
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.