1(function(window, document, _undefined) { 2 "use strict"; 3 4// Helper to render bells correctly 5window.bfGetBellSvg = window.bfGetBellSvg || function(isActive) { 6 const iconClass = isActive ? 'fa--xf fas fa-bell' : 'fa--xf far fa-bell'; 7 return '<i class="' + iconClass + ' bf-ls-icon" data-xf-init="icon" aria-hidden="true"></i>'; 8}; 9 10 if (!window.bfLiveScoreInitialized) { 11 window.bfLiveScoreInitialized = true; 12 13 document.addEventListener('click', function(e) { 14 var followBtn = e.target.closest('.bf-ls-follow-btn'); 15 if (followBtn) { 16 e.preventDefault(); 17 e.stopPropagation(); 18 19 if (followBtn.classList.contains('disabled') || followBtn.hasAttribute('disabled')) { 20 return; 21 } 22 var container = followBtn.closest('.bf-ls-item-container'); 23 if (container && parseInt(container.getAttribute('data-status')) >= 3) { 24 return; 25 } 26 27 if (!XF.config.userId || parseInt(XF.config.userId, 10) === 0) { 28 var loginUrl = XF.canonicalizeUrl('login'); 29 if (XF.loadOverlay) { XF.loadOverlay(loginUrl); } 30 else if (XF.Overlay) { new XF.Overlay(loginUrl).show(); } 31 else { window.location = loginUrl; } 32 return; 33 } 34 35 var matchId = followBtn.getAttribute('data-match-id'); 36 var wasActive = followBtn.classList.contains('active'); 37 var allButtons = document.querySelectorAll('.bf-ls-follow-btn[data-match-id="' + matchId + '"]'); 38 var newState = !wasActive; 39 var newLabel = newState ? 'Unfollow this match' : 'Follow this match'; 40 var newAria = newState ? 'true' : 'false'; 41 42 allButtons.forEach(function(btn) { 43 if (newState) { btn.classList.add('active'); } else { btn.classList.remove('active'); } 44 btn.setAttribute('aria-pressed', newAria); 45 btn.setAttribute('aria-label', newLabel); 46 btn.setAttribute('title', newLabel); 47 48 if (window.bfGetBellSvg) { 49 btn.innerHTML = window.bfGetBellSvg(newState); 50 XF.activate(btn); 51 } 52 }); 53 54 if (XF.config.userId && parseInt(XF.config.userId, 10) > 0) { 55 var storedFollows = []; 56 try { storedFollows = JSON.parse(localStorage.getItem('bfLiveScore_follows')) || []; } catch(e) {} 57 var idx = storedFollows.indexOf(matchId); 58 if (newState) { if (idx === -1) storedFollows.push(matchId); } else { if (idx > -1) storedFollows.splice(idx, 1); } 59 localStorage.setItem('bfLiveScore_follows', JSON.stringify(storedFollows)); 60 } 61 62 XF.ajax('POST', XF.canonicalizeUrl('score/follow'), { match_id: matchId }, function(data) {}, { 63 error: function() { 64 allButtons.forEach(function(btn) { 65 if (wasActive) { 66 btn.classList.add('active'); 67 btn.setAttribute('aria-pressed', 'true'); 68 } else { 69 btn.classList.remove('active'); 70 btn.setAttribute('aria-pressed', 'false'); 71 } 72 }); 73 XF.alert('Action failed.'); 74 } 75 }); 76 return; 77 } 78 79 if (e.target.closest('.bf-ls-modal')) return; 80 81 var flagTrigger = e.target.closest('.bf-ls-flag-trigger'); 82 if (flagTrigger) { 83 var country = flagTrigger.getAttribute('data-country'); 84 var code = flagTrigger.getAttribute('data-country-code'); 85 if (country && window.bfLiveScoreAppInstance) { 86 window.bfLiveScoreAppInstance.activateCountryFilter(country, code); 87 } 88 return; 89 } 90 91 var scoreBox = e.target.closest('.bf-ls-portal-score, .bf-ls-widget-score'); 92 if (!scoreBox) return; 93 94 var itemContainer = scoreBox.closest('.bf-ls-item-container'); 95 if (!itemContainer) return; 96 97 var details = null; 98 try { details = itemContainer.querySelector(':scope > .bf-ls-details-hidden'); } 99 catch(err) { for (var i = 0; i < itemContainer.children.length; i++) { if (itemContainer.children[i].classList.contains('bf-ls-details-hidden')) { details = itemContainer.children[i]; break; } } } 100
101 if (!details) { 102 var headerRow = itemContainer.querySelector('.bf-ls-header-row'); 103 var timeline = itemContainer.querySelector('.bf-ls-timeline'); 104 if (headerRow && timeline) { 105 var tempDiv = document.createElement('div'); 106 tempDiv.appendChild(headerRow.cloneNode(true)); 107 tempDiv.appendChild(timeline.cloneNode(true)); 108 details = tempDiv; 109 } 110 } 111 112 if (!details || !details.innerHTML.trim()) return; 113 var overlay = document.getElementById('bf-ls-global-overlay'); 114 if (overlay) { 115 var body = overlay.querySelector('.bf-ls-modal-body'); 116 if (body) { 117 e.preventDefault(); 118 119 // 1. MAKE VISIBLE FIRST (Critical for XF 2.3 Icons) 120 overlay.classList.add('active'); 121 document.body.style.overflow = 'hidden'; 122 123 // 2. SET CONTENT 124 body.innerHTML = details.innerHTML; 125 126 var lazyImages = body.querySelectorAll('img[data-src]'); 127 lazyImages.forEach(function(img) { 128 img.src = img.getAttribute('data-src'); 129 img.removeAttribute('data-src'); 130 }); 131 132 // 3. ACTIVATE ICONS (Now that they are visible) 133 XF.activate(body); 134 135 window.bfLsLastFocus = document.activeElement; 136 137 var closeBtn = overlay.querySelector('.bf-ls-modal-close'); 138 if (closeBtn) closeBtn.focus(); 139 } 140 } 141 }); 142 } 143 144 XF.BFLiveScoreApp = XF.Element.newHandler({ 145 options: { url: null, mode: 'widget', filter: '' }, 146 activeCountryFilter: null, 147 currentMode: 'static', 148 currentOffset: 0, 149 timeOffset: 0, // <--- 1. NEW PROPERTY 150 151 // <--- 2. NEW HELPER FUNCTION 152 getCorrectedDate: function() { 153 // Returns a Date object adjusted to match Server Time 154 return new Date(Date.now() + this.timeOffset); 155 }, 156 // ----------------------- 157 158 escapeHtml: function(text) { 159 if (!text) return ''; 160 return String(text).replace(/[&<>"']/g, function(m) { 161 return {'&': '&', '<': '<', '>': '>', '"': '"', "'": '''}[m]; 162 }); 163 }, 164 165 init: function() { 166 window.bfLiveScoreAppInstance = this; 167 if (!this.options.url) return; 168 169 // <--- 3. ADD THIS BLOCK HERE 170 var serverTs = parseInt(this.target.getAttribute('data-server-time'), 10); 171 if (serverTs) { 172 // Calculate difference: Server Time (ms) - Client System Time (ms) 173 this.timeOffset = (serverTs * 1000) - Date.now(); 174 console.log("BF LiveScore: Time Sync Offset:", this.timeOffset, "ms"); 175 } 176 // --------------------------- 177 178 this.convertTimes(); 179 var self = this; 180 181 // --- GLOBAL CLICK HANDLER FOR HEADERS (Fixes Today Tab) --- 182 this.target.addEventListener('click', function(e) { 183 // Check if we clicked a Header 184 var header = e.target.closest('.bf-ls-portal-header[data-group-toggle="true"]'); 185 if (header) { 186 // Ignore if clicking the flag/filter icon 187 if (e.target.closest('.bf-ls-flag-trigger')) return; 188 189 header.classList.toggle('collapsed'); 190 var next = header.nextElementSibling; 191 if (next && next.classList.contains('bf-ls-group-container')) { 192 next.classList.toggle('collapsed'); 193 } 194 } 195 }); 196 // --------------------------------------------------------- 197 198 document.addEventListener('bf-livescore-update', function(e) { 199 if (e.detail) { 200 self.applyPatch(e.detail); 201 } 202 }); 203 204 if (this.options.mode === 'portal' || this.options.mode === 'full_widget') { 205 this.setupTabs();
206 this.setupOddsToggle(); 207 this.setupSoundToggle(); 208 this.setupFilterChip(); 209 210 var activeTab = this.target.querySelector('.bf-ls-tab.active'); 211 var tabType = activeTab ? activeTab.getAttribute('data-tab') : 'all'; 212 213 if (tabType !== 'live') { 214 var wrapper = this.target.querySelector('.bf-ls-date-scroll-wrapper'); 215 if (wrapper) { 216 wrapper.style.display = 'flex'; 217 wrapper.classList.add('active'); 218 this.generateDateButtons(); 219 } 220 } 221 222 if (XF.config.userId && parseInt(XF.config.userId) > 0) { 223 var savedCountry = localStorage.getItem('bfLiveScore_filter_country'); 224 var savedCode = localStorage.getItem('bfLiveScore_filter_code'); 225 if (savedCountry) { 226 this.activateCountryFilter(savedCountry, savedCode); 227 } 228 } 229 } 230 // --- DYNAMIC POLLING LOOP START --- 231 var poll = function() { 232 var now = self.getCorrectedDate(); 233 var totalMins = (now.getUTCHours() * 60) + now.getUTCMinutes(); 234 235 var delay = 8000; // FAST (Default) - Changed to 8s 236 237 if (totalMins >= 180 && totalMins < 660) { 238 delay = 25000; // SLOWEST (03:00 - 11:00 UTC) 239 } else if (totalMins >= 1 && totalMins < 180) { 240 delay = 11000; // MEDIUM (00:01 - 03:00 UTC) 241 } 242 243 setTimeout(function() { 244 self.refresh(false); 245 poll(); 246 }, delay); 247 }; 248 poll(); // Kickstart the loop 249 // --- DYNAMIC POLLING LOOP END --- 250 251 document.addEventListener('visibilitychange', function() { 252 if (document.visibilityState === 'visible') { 253 console.log('Tab active: Forcing LiveScore update...'); 254 // FIX: Pass 'true' as second arg to suppress sound on wake-up 255 self.refresh(true, true); 256 } 257 }); 258 259 // PASTE YOUR NEW BLOCK HERE: 260var hasContent = this.target.querySelector('.bf-ls-item-container'); // Check for matches 261 262if (this.options.mode === 'widget' || this.options.mode === 'full_widget') { 263 if (hasContent) { 264 console.log("BF LiveScore: SSR Content found. Skipping initial fetch."); 265 } else { 266 this.refresh(true); 267 } 268} 269 270 // --- Init Filter Check (To hide tomorrow's matches) --- 271 272 if (this.options.mode === 'portal' && this.currentOffset === 0) { 273 274 this.filterRows(tabType); 275 276 } 277 278 }, 279 280 convertTimes: function() { 281 this.target.querySelectorAll('.bf-ls-local-time').forEach(function(el) { 282 var ts = parseInt(el.getAttribute('data-timestamp')); 283 if (ts) { 284 var d = new Date(ts * 1000); 285 var hh = String(d.getHours()).padStart(2,'0'); 286 var mn = String(d.getMinutes()).padStart(2,'0'); 287 el.textContent = hh + ':' + mn; 288 el.classList.remove('bf-ls-local-time'); 289 } 290 }); 291 }, 292 293 setupOddsToggle: function() { 294 var self = this; 295 var checkbox = this.target.querySelector('.bf-ls-odds-checkbox'); 296 if (!checkbox || checkbox.dataset.init) return; 297 checkbox.dataset.init = '1'; 298 var stored = 'false'; 299 try { stored = localStorage.getItem('bfLiveScore_showOdds'); } catch(e) {} 300 checkbox.checked = (stored === 'true'); 301 if (checkbox.checked) this.target.classList.add('bf-show-odds'); 302 checkbox.addEventListener('change', function() { 303 if (this.checked) { 304 self.target.classList.add('bf-show-odds'); 305 try { localStorage.setItem('bfLiveScore_showOdds', 'true'); } catch(e) {} 306 } else { 307 self.target.classList.remove('bf-show-odds'); 308 try { localStorage.setItem('bfLiveScore_showOdds', 'false'); } catch(e) {} 309 } 310 }); 311 }, 312 313 setupSoundToggle: function() { 314 var self = this; 315 var toggle = this.target.querySelector('.bf-ls-sound-toggle, .bf-ls-sound-btn'); 316 if (!toggle) return; 317 318 var isSoundOn = false; 319 try { isSoundOn = (localStorage.getItem('bfLiveScore_muted') === 'false'); } catch(e) {} 320 321 var svgs = { 322 on: '<i class="fa--xf fas fa-volume-up" data-xf-init="icon" aria-hidden="true"></i>', 323 off: '<i class="fa--xf fas fa-volume-mute" data-xf-init="icon" aria-hidden="true"></i>' 324 }; 325 326 var updateIcon = function(active) { 327 if (active) { 328 toggle.classList.add('active'); 329 toggle.innerHTML = svgs.on; 330 } else { 331 toggle.classList.remove('active'); 332 toggle.innerHTML = svgs.off; 333 } 334 XF.activate(toggle); 335 }; 336 337 updateIcon(isSoundOn); 338 339 toggle.addEventListener('click', function() { 340 var active = this.classList.contains('active'); 341 if (active) { 342 updateIcon(false); 343 try { localStorage.setItem('bfLiveScore_muted', 'true'); } catch(e) {} 344 } else { 345 updateIcon(true); 346 self.playSound(); 347 try { localStorage.setItem('bfLiveScore_muted', 'false'); } catch(e) {} 348 } 349 }); 350 }, 351 352 playSound: function() { 353 if (localStorage.getItem('bfLiveScore_muted') !== 'false') return; 354 var audio = this.target.querySelector('.bf-ls-goal-audio'); 355 if (audio) { 356 audio.pause(); 357 audio.currentTime = 0; 358 audio.volume = 0.5; 359 var p = audio.play(); 360 if (p !== undefined) { p.catch(function(e) { console.log("Audio blocked"); }); } 361 } 362 }, 363 364 setupTabs: function() { 365 var self = this; 366 var tabs = this.target.querySelectorAll('.bf-ls-tab'); 367
368 tabs.forEach(function(tab) { 369 tab.addEventListener('click', function(e) { 370 var type = this.getAttribute('data-tab'); 371 tabs.forEach(t => { t.classList.remove('active'); t.setAttribute('aria-selected', 'false'); }); 372 this.classList.add('active'); 373 this.setAttribute('aria-selected', 'true'); 374 375 var dateWrapper = self.target.querySelector('.bf-ls-date-scroll-wrapper'); 376 377 var staticContent = self.target.querySelector('.bf-ls-content'); 378 var dynamicContent = self.target.querySelector('.bf-ls-schedule-content'); 379 380 if (type === 'live') { 381 if (dateWrapper) dateWrapper.style.display = 'none'; 382 if (staticContent) staticContent.style.display = 'block'; 383 if (dynamicContent) dynamicContent.style.display = 'none'; 384 385 self.currentOffset = 0; 386 var dateBtns = self.target.querySelectorAll('.bf-ls-date-btn'); 387 dateBtns.forEach(b => b.classList.remove('active')); 388 var todayBtn = self.target.querySelector('.bf-ls-date-btn[data-offset="0"]'); 389 if (todayBtn) todayBtn.classList.add('active'); 390 391 self.currentMode = 'static'; 392 self.filterRows('live'); 393 } 394 else { 395 if (dateWrapper) { 396 dateWrapper.style.display = 'flex'; 397 dateWrapper.classList.add('active'); 398 self.generateDateButtons(); 399 } 400 401 if (self.currentOffset === 0) { 402 if (staticContent) staticContent.style.display = 'block'; 403 if (dynamicContent) dynamicContent.style.display = 'none'; 404 self.currentMode = 'static'; 405 self.filterRows(type); 406 } else { 407 if (staticContent) staticContent.style.display = 'none'; 408 if (dynamicContent) dynamicContent.style.display = 'block'; 409 self.currentMode = 'schedule'; 410 411 var activeBtn = dateWrapper.querySelector('.bf-ls-date-btn.active'); 412 if (activeBtn) { 413 self.loadScheduleDate(activeBtn.getAttribute('data-date')); 414 } 415 } 416 } 417 }); 418 }); 419 }, 420 421 generateDateButtons: function() { 422 var wrapper = this.target.querySelector('.bf-ls-date-scroll-wrapper'); 423 if (!wrapper) return; 424 wrapper.innerHTML = ''; 425 426 var start = -2; 427 var end = 4; 428 var today = this.getCorrectedDate(); 429 430 for (var i = start; i <= end; i++) { 431 var d = new Date(today); 432 d.setDate(today.getDate() + i); 433 434 var yyyy = d.getFullYear(); 435 var mm = String(d.getMonth() + 1).padStart(2, '0'); 436 var dd = String(d.getDate()).padStart(2, '0'); 437 var dateStr = yyyy + '-' + mm + '-' + dd; 438 439 var label = ''; 440 if (i === 0) { 441 label = 'Today'; 442 } else { 443 var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; 444 label = monthNames[d.getMonth()] + ' ' + d.getDate(); 445 } 446 447 var btn = document.createElement('div'); 448 btn.className = 'bf-ls-date-btn'; 449 450 if (i === this.currentOffset) { 451 btn.classList.add('active'); 452 } else if (this.currentOffset < start || this.currentOffset > end) { 453 if (i === 0) { 454 btn.classList.add('active'); 455 this.currentOffset = 0; 456 } 457 } 458 459 btn.textContent = label; 460 btn.setAttribute('data-date', dateStr); 461 btn.setAttribute('data-offset', i); 462 463 var self = this; 464 btn.addEventListener('click', function() { 465 wrapper.querySelectorAll('.bf-ls-date-btn').forEach(b => b.classList.remove('active')); 466 this.classList.add('active'); 467 468 var offset = parseInt(this.getAttribute('data-offset')); 469 self.currentOffset = offset; 470 471 if (offset === 0) { 472 self.currentMode = 'static'; 473 var staticContent = self.target.querySelector('.bf-ls-content'); 474 var dynamicContent = self.target.querySelector('.bf-ls-schedule-content'); 475 if (staticContent) staticContent.style.display = 'block'; 476 if (dynamicContent) dynamicContent.style.display = 'none'; 477 478 var activeTab = self.target.querySelector('.bf-ls-tab.active'); 479 if (activeTab) self.filterRows(activeTab.getAttribute('data-tab')); 480 } else { 481 self.currentMode = 'schedule'; 482 var staticContent = self.target.querySelector('.bf-ls-content'); 483 var dynamicContent = self.target.querySelector('.bf-ls-schedule-content'); 484 if (staticContent) staticContent.style.display = 'none'; 485 if (dynamicContent) dynamicContent.style.display = 'block'; 486 487 self.loadScheduleDate(this.getAttribute('data-date')); 488 } 489 }); 490 491 wrapper.appendChild(btn); 492 } 493 }, 494 495 loadScheduleDate: function(dateStr) { 496 var self = this; 497 var container = this.target.querySelector('.bf-ls-schedule-content'); 498 if (!container) return; 499 500 container.innerHTML = '<div style="padding:20px; text-align:center; color:#999;">Loading ' + dateStr + '...</div>'; 501 502 if (typeof window.bfLoadScheduleForDate === 'function') { 503 window.bfLoadScheduleForDate(dateStr).then(function(fixtures) { 504 self.renderScheduleMatches(fixtures); 505 }).catch(function() { 506 container.innerHTML = '<div style="padding:20px; text-align:center; color:#c0392b;">Failed to load schedule.</div>'; 507 }); 508 } else { 509 container.innerHTML = '<div style="padding:20px; text-align:center; color:#999;">Schedule loader not ready.</div>'; 510 } 511 }, 512 513 renderScheduleMatches: function(fixtures) { 514 var container = this.target.querySelector('.bf-ls-schedule-content'); 515 if (!container) return; 516 if (!fixtures || fixtures.length === 0) { 517 container.innerHTML = '<div style="padding:30px; text-align:center; color:#999;">No matches found for this date.</div>'; 518 return; 519 } 520 521 // 1. Sort matches (Priority > Country > League > Status > Time) 522 fixtures.sort((a, b) => { 523 const rawPA = a.league_priority != null ? parseInt(a.league_priority) : 3; 524 const rawPB = b.league_priority != null ? parseInt(b.league_priority) : 3; 525 526 // NEUTRALIZE TIER 4 (Closed) -> TREAT AS TIER 3 (Open) 527 // This keeps them mixed alphabetically 528 const pA = (rawPA === 4) ? 3 : rawPA; 529 const pB = (rawPB === 4) ? 3 : rawPB; 530 531 if (pA !== pB) return pA - pB; 532 533 // Group by Country/League first 534 const cA = (a.country || "").toUpperCase(); 535 const cB = (b.country || "").toUpperCase(); 536 if (cA !== cB) return cA.localeCompare(cB); 537 538 const lA = (a.league || "").toUpperCase(); 539 const lB = (b.league || "").toUpperCase(); 540 if (lA !== lB) return lA.localeCompare(lB); 541 542 // Then Status (Live > Upcoming > Finished) 543 const sA = parseInt(a.status_weight || 2); 544 const sB = parseInt(b.status_weight || 2);
545 if (sA !== sB) return sA - sB; 546 547 return (a.timestamp || 0) - (b.timestamp || 0); 548 }); 549 550 var activeTab = this.target.querySelector('.bf-ls-tab.active'); 551 var type = activeTab ? activeTab.getAttribute('data-tab') : 'all'; 552 553 var isLiveTab = (type === 'live'); 554 555 var html = ''; 556 var visibleCount = 0; 557 558 var self = this; 559 var currentGroupHtml = ''; 560 var groupHeaderHtml = ''; 561 var groupPriority = 3; 562 var lastHeaderKey = ''; 563 564 var closeGroup = function() { 565 if (!groupHeaderHtml) return; 566 567 // CHANGED: Collapse Priority 4 (Your List) and 5 (Bottom) 568 var isClosedTier = (groupPriority >= 4); 569 570 var startCollapsed = (isClosedTier && !isLiveTab) ? ' collapsed' : ''; 571 572 html += '<div class="bf-ls-portal-header' + startCollapsed + '" data-group-toggle="true">'; 573 html += groupHeaderHtml; 574 // --- FIX: ADDED data-xf-init="icon" SO XF LOADS THE SVG --- 575 html += '<i class="fa--xf fas fa-chevron-up bf-ls-header-arrow" data-xf-init="icon" aria-hidden="true"></i>'; 576 html += '</div>'; 577 578 html += '<div class="bf-ls-group-container' + startCollapsed + '">'; 579 html += currentGroupHtml; 580 html += '</div>'; 581 582 currentGroupHtml = ''; 583 groupHeaderHtml = ''; 584 }; 585 586 var storedFollows = []; 587 if (XF.config.userId && parseInt(XF.config.userId, 10) > 0) { 588 try { storedFollows = JSON.parse(localStorage.getItem('bfLiveScore_follows')) || []; } catch(e) {} 589 } 590 var followedSet = new Set(storedFollows.map(String)); 591 592 fixtures.forEach(function(m) { 593 var statusWeight = parseInt(m.status_weight || 2); 594 var country = m.country || ''; 595 596 var show = false; 597 if (type === 'all') show = true; 598 else if (type === 'live') show = (statusWeight === 1); 599 else if (type === 'upcoming') show = (statusWeight === 2); 600 else if (type === 'finished') show = (statusWeight === 3 || statusWeight === 4); 601 else if (type === 'today' || type === 'yesterday') show = true; 602 603 // 1. Apply BBCode Filter (Global Filter) 604 if (show && self.options.filter) { 605 var filterVal = self.options.filter.toLowerCase(); 606 var mCountry = (m.country || '').toLowerCase(); 607 var mLeague = (m.league || '').toLowerCase(); 608 // Match PHP logic: Contains in Country OR League 609 if (mCountry.indexOf(filterVal) === -1 && mLeague.indexOf(filterVal) === -1) { 610 show = false; 611 } 612 } 613 614 // 2. Apply UI Flag Filter (User Clicked Flag) 615 616 if (show && self.activeCountryFilter) { 617 if (country !== self.activeCountryFilter) show = false; 618 } 619 620 if (!show) return; 621 visibleCount++; 622 623 var leagueName = m.league || 'Unknown League'; 624 var cCode = m.country_code || (country ? country.substring(0,3).toUpperCase() : ''); 625 var isFinished = (statusWeight === 3 || statusWeight === 4); 626 627 var currentHeaderKey = country + '|' + leagueName; 628 629 if (currentHeaderKey !== lastHeaderKey) { 630 closeGroup(); 631 632 var headerInner = ''; 633 if (cCode) { 634 var lowerCode = cCode.toLowerCase(); 635 headerInner += '<span class="bf-ls-flag-trigger" data-country="' + self.escapeHtml(country) + '" data-country-code="' + self.escapeHtml(cCode) + '" title="Filter by ' + self.escapeHtml(country) + '">'; 636 headerInner += '<span class="fi fi-' + lowerCode + ' bf-icon-flag" style="vertical-align:middle; margin-right:4px; border-radius:2px;"></span></span>'; 637 } 638 headerInner += '<span>' + (country ? self.escapeHtml(country) + ': ' : '') + self.escapeHtml(leagueName) + '</span>'; 639 640 groupHeaderHtml = headerInner; 641 groupPriority = m.league_priority != null ? parseInt(m.league_priority) : 3; 642 lastHeaderKey = currentHeaderKey; 643 } 644 645 // --- BUILD MATCH ROW --- 646 var rowHtml = '<div class="bf-ls-item-container" data-match-id="' + (m.id) + '" data-status="' + statusWeight + '" data-country="' + country + '">'; 647 648 var isFollowed = followedSet.has(String(m.id)); 649 var followClass = isFollowed ? 'active' : ''; 650 var ariaPressed = isFollowed ? 'true' : 'false'; 651 var bellSvg = window.bfGetBellSvg(isFollowed);
652 var btnTitle = isFinished ? 'Match Finished' : (isFollowed ? 'Unfollow this match' : 'Follow this match'); 653 var extraClass = isFinished ? ' is-finished' : ''; 654 if (isFinished) extraClass += ' disabled'; 655 656 var timeDisplay = ''; 657 if (statusWeight === 2 && m.timestamp) { 658 var d = new Date(m.timestamp * 1000); 659 var hh = String(d.getHours()).padStart(2,'0'); 660 var mn = String(d.getMinutes()).padStart(2,'0'); 661 timeDisplay = hh + ':' + mn; 662 } 663 else if (m.time_display) { 664 var isLive = (parseInt(m.status_weight) === 1); 665 timeDisplay = (isLive ? '<span class="bf-ls-live-dot"></span> ' : '') + m.time_display; 666 } 667 else if (m.timestamp) { 668 var d = new Date(m.timestamp * 1000); 669 var hh = String(d.getHours()).padStart(2,'0'); 670 var mn = String(d.getMinutes()).padStart(2,'0'); 671 timeDisplay = hh + ':' + mn; 672 } 673 if (!m.time_display && ['PST', 'CANC', 'ABD', 'SUSP', 'INT', 'WO', 'AWD'].includes(m.status)) { 674 timeDisplay = m.status; 675 } 676 677 var scoreDisplay = m.score || '-'; 678 var hasOdds = (m.odds && (m.odds['1'] || m.odds['X'] || m.odds['2'])); 679 var cardClass = 'bf-ls-match-card' + (hasOdds ? ' has-odds' : ''); 680 681 rowHtml += '<div class="' + cardClass + '">'; 682 rowHtml += '<div class="bf-ls-card-top">'; 683 rowHtml += '<button type="button" class="bf-ls-follow-btn ' + followClass + extraClass + '" '; 684 rowHtml += 'data-match-id="' + (m.id) + '" aria-pressed="' + ariaPressed + '" '; 685 if (isFinished) { rowHtml += 'disabled '; } 686 rowHtml += 'aria-label="' + btnTitle + '" title="' + btnTitle + '">' + bellSvg + '</button>'; 687 688 rowHtml += '<div class="bf-ls-portal-time">' + timeDisplay + '</div>'; 689 690 rowHtml += '<div class="bf-ls-portal-teams">'; 691 rowHtml += '<span class="bf-ls-portal-team home" title="' + self.escapeHtml(m.home) + '">' + self.escapeHtml(m.home) + '</span>'; 692 rowHtml += '<span class="bf-ls-portal-score">' + scoreDisplay + '</span>'; 693 rowHtml += '<span class="bf-ls-portal-team away" title="' + self.escapeHtml(m.away) + '">' + self.escapeHtml(m.away) + '</span>'; 694 rowHtml += '</div>'; 695 696 if (parseInt(m.status_weight) === 1) { 697 rowHtml += '<a href="' + (m.affiliate_link||'#') + '" target="_blank" class="bf-ls-bet-btn">BET</a>'; 698 } 699 700 rowHtml += '</div>'; 701 702 if (!isFinished && hasOdds) { 703 rowHtml += '<div class="bf-ls-odds-row"><div class="bf-ls-odds-inner">'; 704 var renderOdd = function(label) { 705 var o = (m.odds && m.odds[label]) ? m.odds[label] : null; 706 if (o && o.val) { 707 var moveClass = o.move ? (' move-' + o.move) : ''; 708 return '<a href="' + (o.link || '#') + '" target="_blank" class="bf-ls-odd-btn' + moveClass + '" title="Best odds from ' + (o.bookie || '') + '">' + 709 '<span class="bf-ls-odd-label">' + label + '</span>' + 710 '<span class="bf-ls-odd-val">' + o.val + 711 (o.pct ? ' <span class="bf-ls-odd-change">' + o.pct + '</span>' : '') + 712 '</span></a>'; 713 } else { return '<div class="bf-ls-odd-btn disabled"></div>'; } 714 }; 715 rowHtml += renderOdd('1') + renderOdd('X') + renderOdd('2'); 716 rowHtml += '</div></div>'; 717 } 718 rowHtml += '</div>'; 719 720 rowHtml += '<div class="bf-ls-details-hidden" style="display:none">'; 721 rowHtml += '<div class="bf-ls-header-row"><div class="bf-ls-header-team home"><span>' + m.home + '</span>'; 722 if (m.home_logo) rowHtml += '<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-src="' + m.home_logo + '" class="bf-ls-header-logo" width="40" height="40" decoding="async" />'; 723 rowHtml += '</div><div class="bf-ls-header-score"><span class="bf-ls-time-val">' + timeDisplay + '</span><span class="bf-ls-score-val">' + scoreDisplay + '</span>'; 724 if (m.ht_score) rowHtml += '<span class="bf-ls-time-val" style="font-size:12px; margin-top:5px; opacity:0.6">(HT ' + m.ht_score + ')</span>'; 725 rowHtml += '</div><div class="bf-ls-header-team away">'; 726 if (m.away_logo) rowHtml += '<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-src="' + m.away_logo + '" class="bf-ls-header-logo" width="40" height="40" decoding="async" />'; 727 rowHtml += '<span>' + m.away + '</span></div></div>'; 728
729 // --- TIMELINE EVENTS REWRITE --- 730 rowHtml += '<div class="bf-ls-timeline">'; 731 if (m.events && m.events.length > 0) { 732 m.events.forEach(function(ev) { 733 var isHome = (parseInt(ev.team_id) === parseInt(m.home_id)); 734 735 // Parse detail for special markers 736 var detail = ev.detail || ''; 737 var isPenalty = (detail.indexOf('Penalty') !== -1 && ev.type === 'goal'); 738 var isOwnGoal = (detail.indexOf('Own') !== -1 && ev.type === 'goal'); 739 var isMissedPenalty = (detail.indexOf('Missed') !== -1); 740 var isRedCard = (detail.indexOf('Red') !== -1); 741 742 // Build Icon HTML 743 var iconHtml = ''; 744 if (ev.type === 'goal') { 745 if (isMissedPenalty) { 746 iconHtml = '<i class="fa--xf fas fa-times-circle bf-icon bf-icon-miss" title="Missed Penalty" data-xf-init="icon"></i>'; 747 } else { 748 iconHtml = '<i class="fa--xf fas fa-futbol bf-icon" style="color:var(--xf-textColor)" data-xf-init="icon"></i>'; 749 } 750 } else if (ev.type === 'card') { 751 var colorClass = isRedCard ? 'bf-icon-red' : 'bf-icon-yellow'; 752 iconHtml = '<i class="fa--xf fas fa-square bf-icon ' + colorClass + '" data-xf-init="icon"></i>'; 753 } else if (ev.type === 'var') { 754 iconHtml = '<i class="fa--xf fas fa-video bf-icon bf-icon-var" data-xf-init="icon"></i>'; 755 } 756 757 // Start Row 758 rowHtml += '<div class="bf-ls-timeline-row">'; 759 760 // HOME COLUMN 761 rowHtml += '<div class="bf-ls-t-home">'; 762 if (isHome) { 763 if (ev.type === 'subst') { 764 // Substitution Layout (In Top / Out Bottom) 765 rowHtml += '<div class="bf-sub-container">'; 766 rowHtml += '<div class="bf-sub-row bf-sub-in">' + ev.player + ' <i class="fa--xf fas fa-arrow-up" style="color:#2ecc71; margin-left:6px;" data-xf-init="icon"></i></div>'; 767 if (ev.assist) { 768 rowHtml += '<div class="bf-sub-row bf-sub-out">' + ev.assist + ' <i class="fa--xf fas fa-arrow-down" style="color:#e74c3c; margin-left:6px;" data-xf-init="icon"></i></div>'; 769 } 770 rowHtml += '</div>'; 771 } else { 772 // Standard Event 773 rowHtml += '<span class="bf-event-text">' + ev.player; 774 if (isPenalty) rowHtml += ' <span class="bf-event-detail">(P)</span>'; 775 if (isOwnGoal) rowHtml += ' <span class="bf-event-detail">(OG)</span>'; 776 rowHtml += '</span> ' + iconHtml; 777 } 778 } 779 rowHtml += '</div>'; 780 781 // TIME COLUMN 782 rowHtml += '<div class="bf-ls-t-time">' + (ev.time||'') + '</div>'; 783 784 // AWAY COLUMN 785 rowHtml += '<div class="bf-ls-t-away">'; 786 if (!isHome) { 787 if (ev.type === 'subst') { 788 // Substitution Layout 789 rowHtml += '<div class="bf-sub-container">'; 790 rowHtml += '<div class="bf-sub-row bf-sub-in"><i class="fa--xf fas fa-arrow-up" style="color:#2ecc71; margin-right:6px;" data-xf-init="icon"></i> ' + ev.player + '</div>'; 791 if (ev.assist) { 792 rowHtml += '<div class="bf-sub-row bf-sub-out">
792<i class="fa--xf fas fa-arrow-down" style="color:#e74c3c; margin-right:6px;" data-xf-init="icon"></i> ' + ev.assist + '</div>'; 793 } 794 rowHtml += '</div>'; 795 } else { 796 // Standard Event 797 rowHtml += iconHtml + ' <span class="bf-event-text">' + ev.player; 798 if (isPenalty) rowHtml += ' <span class="bf-event-detail">(P)</span>'; 799 if (isOwnGoal) rowHtml += ' <span class="bf-event-detail">(OG)</span>'; 800 rowHtml += '</span>'; 801 } 802 } 803 rowHtml += '</div>'; // End Away 804 805 rowHtml += '</div>'; // End Row 806 }); 807 } else { 808 rowHtml += '<div style="padding:20px; text-align:center; opacity:0.6">No events recorded.</div>'; 809 } 810 rowHtml += '</div></div>'; 811 rowHtml += '</div>'; 812 currentGroupHtml += rowHtml; 813 }); 814 815 closeGroup(); 816 817 if (visibleCount === 0) { 818 container.innerHTML = '<div style="padding:30px; text-align:center; color:#999;">No matches found for this selection.</div>'; 819 } else { 820 container.innerHTML = html; 821 822 container.querySelectorAll('img[data-src]').forEach(function(img) { 823 img.src = img.getAttribute('data-src'); 824 img.removeAttribute('data-src'); 825 }); 826 827 // This triggers XF to scan the new HTML for 'data-xf-init="icon"' and render the SVGs 828 XF.activate(container); 829 830 if (self.activeCountryFilter) { 831 self.filterRows(type); 832 } 833 } 834 }, 835 836 setupFilterChip: function() { 837 var self = this; 838 var chip = this.target.querySelector('.bf-ls-filter-chip'); 839 840 // 1. Reset the chip element to clear any previous listeners (prevents duplicates) 841 if (chip) { 842 var newChip = chip.cloneNode(true); 843 chip.parentNode.replaceChild(newChip, chip); 844 chip = newChip; 845 } 846 847 if (!chip) return; 848 849 // 2. Select the close button (newly created inside the chip) 850 var closeBtn = chip.querySelector('.bf-filter-close'); 851 852 // 3. Define the Close Action 853 var closeAction = function(e) { 854 // Stop the click from bubbling up 855 e.preventDefault(); 856 e.stopPropagation(); 857 858 // Clear the filter logic 859 self.activeCountryFilter = null; 860 if (XF.config.userId && parseInt(XF.config.userId, 10) > 0) { 861 localStorage.removeItem('bfLiveScore_filter_country'); 862 localStorage.removeItem('bfLiveScore_filter_code'); 863 } 864 865 // Hide chip 866 chip.style.display = 'none'; 867 868 // Refresh rows to show all matches again 869 var activeTab = self.target.querySelector('.bf-ls-tab.active'); 870 var tabType = activeTab ? activeTab.getAttribute('data-tab') : 'all'; 871 self.filterRows(tabType); 872 }; 873 874 // 4. Mobile Check (Width <= 560px) 875 if (window.innerWidth <= 560) { 876 // MOBILE: Click ANYWHERE on the chip 877 chip.addEventListener('click', closeAction); 878 } else { 879 // DESKTOP: Click only the 'X' button 880 if (closeBtn) { 881 closeBtn.addEventListener('click', closeAction); 882 } 883 } 884 }, 885 886 activateCountryFilter: function(country, code) { 887 this.activeCountryFilter = country; 888 if (XF.config.userId && parseInt(XF.config.userId, 10) > 0) { 889 localStorage.setItem('bfLiveScore_filter_country', country); 890 localStorage.setItem('bfLiveScore_filter_code', code || country.substring(0,3).toUpperCase()); 891 } 892 893 var chip = this.target.querySelector('.bf-ls-filter-chip'); 894 if (chip) { 895 chip.innerHTML = '<span class="bf-filter-label-full">' + country + '</span>' + 896 '<span class="bf-filter-label-short">' + (code || country.substring(0,3).toUpperCase()) + '</span>' + 897 '<span class="bf-filter-close" title="Clear Filter">×</span>'; 898 chip.style.display = 'inline-flex'; 899 this.setupFilterChip(); 900 } 901 902 var activeTab = this.target.querySelector('.bf-ls-tab.active'); 903 var tabType = activeTab ? activeTab.getAttribute('data-tab') : 'all'; 904 this.filterRows(tabType); 905 }, 906 907 filterRows: function(type) { 908 var containerSelector = (this.currentMode === 'schedule') ? '.bf-ls-schedule-content' : '.bf-ls-content'; 909 var container = this.target.querySelector(containerSelector); 910 if (!container) return; 911 912 var self = this; 913 var totalVisible = 0; 914 915 // --- NEW: Calculate Local Today String (YYYY-MM-DD) --- 916
917 var now = this.getCorrectedDate(); 918 919 var localTodayStr = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0'); 920 921 // ------------------------------------------------------- 922 923 // Iterate over GROUPS, not just matches 924 var groups = container.querySelectorAll('.bf-ls-group-container'); 925 926 groups.forEach(function(group) { 927 var groupMatches = group.querySelectorAll('.bf-ls-item-container'); 928 var visibleInGroup = 0; 929 930 groupMatches.forEach(function(el) { 931 var status = parseInt(el.getAttribute('data-status') || 0); 932 var rowCountry = el.getAttribute('data-country'); 933 var ts = parseInt(el.getAttribute('data-timestamp') || 0); 934 var show = false; 935 936 if (type === 'all') show = true; 937 else if (type === 'live') show = (status === 1); 938 else if (type === 'upcoming') show = (status === 2); 939 else if (type === 'finished') show = (status === 3 || status === 4); 940 941 // ================================================================= 942 // === THIS IS THE MISSING BLOCK THAT HIDES "TOMORROW" MATCHES === 943 // ================================================================= 944 if (self.currentMode === 'static' && self.currentOffset === 0 && ts > 0) { 945 // If it's NOT Live (Status 1), strictly enforce the date 946 if (status !== 1) { 947 var d = new Date(ts * 1000); 948 var matchDateStr = d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); 949 950 // If the match date is NOT strictly today, hide it. 951 if (matchDateStr !== localTodayStr) { 952 show = false; 953 } 954 } 955 } 956 // ================================================================= 957 958 if (show && self.activeCountryFilter) { 959 if (rowCountry !== self.activeCountryFilter) show = false; 960 } 961 962 el.style.display = show ? '' : 'none'; 963 if (show) visibleInGroup++; 964 }); 965 966 var header = group.previousElementSibling; // The header is immediately before the group 967 968 if (visibleInGroup > 0) { 969 totalVisible += visibleInGroup; 970 group.style.display = ''; // Make sure group div is visible 971 if (header) header.style.display = ''; 972 973 974 } else { 975 // Hide the entire group if no matches match filter 976 group.style.display = 'none'; 977 if (header) header.style.display = 'none'; 978 } 979 }); 980 981 var noDataMsg = this.target.querySelector('.bf-ls-no-data'); 982 if (noDataMsg) { 983 noDataMsg.style.display = (totalVisible === 0) ? 'block' : 'none'; 984 } 985 }, 986 987 refresh: function(force, suppressSound) { 988 if (!document.body.contains(this.target)) return; 989 if (this.currentMode === 'schedule' || this.currentOffset !== 0) return; 990 991 // REMOVED: We want to poll the server to trigger the API fetcher 992 // if (!force && window.bfCentrifugeClient && window.bfCentrifugeClient.state === 'connected') { 993 // console.log("BF LiveScore: Socket active, skipping poll."); 994 // return; 995 // } 996 997 var self = this; 998 999 var oldScores = {}; 1000 this.target.querySelectorAll('.bf-ls-item-container').forEach(function(el) { 1001 var mid = el.getAttribute('data-match-id'); 1002 var score = el.getAttribute('data-home-score'); 1003 if (mid && score) { 1004 oldScores[mid] = score; 1005 } 1006 }); 1007 1008 var now = Date.now(); 1009 if (force || !this.lastFullRefresh || (now - this.lastFullRefresh > 120000)) { 1010 this.lastFullRefresh = now; 1011 this.fetchDynamic(oldScores, suppressSound); 1012 } else { 1013 XF.ajax('GET', XF.canonicalizeUrl('score/patch'), {}, function(data) { 1014 if (data.updates && data.updates.length > 0) { 1015 self.applyPatch(data.updates); 1016 } 1017 }, { skipDefaultError: true, global: false }); 1018 } 1019 }, 1020 1021 getScoreSum: function(str) { 1022 1023 if (!str || str.indexOf('-') === -1) return -1; 1024 1025 var parts = str.split('-'); 1026 1027 if (parts.length !== 2) return -1; 1028 1029 var h = parseInt(parts[0], 10); 1030 1031 var a = parseInt(parts[1], 10); 1032 1033 if (isNaN(h) || isNaN(a)) return -1; 1034 1035 return h + a; 1036 1037 }, 1038 1039 applyPatch: function(updates) { 1040 var self = this; 1041 var missingMatches = 0; 1042
1043 updates.forEach(function(m) { 1044 var row = self.target.querySelector('.bf-ls-item-container[data-match-id="' + m.id + '"]'); 1045 if (row) { 1046 // 1. Update Score 1047 var scoreEl = row.querySelector('.bf-ls-portal-score, .bf-ls-widget-score'); 1048 if (scoreEl && scoreEl.textContent !== m.score) { 1049 var oldScoreText = scoreEl.textContent; 1050 1051 var oldSum = self.getScoreSum(oldScoreText); 1052 1053 var newSum = self.getScoreSum(m.score); 1054 scoreEl.textContent = m.score; 1055 row.setAttribute('data-home-score', m.score); 1056 1057 // Only trigger if Total Goals INCREASED (Prevents VAR reversals & Kickoff sounds) 1058 1059 if (oldSum > -1 && newSum > oldSum && m.score.indexOf('-') > -1) { 1060 self.triggerGoalAnimation(row, scoreEl); 1061 } 1062 } 1063 1064 // 2. Update Time (SAFEGUARD ADDED) 1065 // Only update time if the match is NOT 'Upcoming' (Status 2). 1066 // This prevents "15:00" being overwritten by "VS" during odds updates. 1067 if (parseInt(m.status) !== 2) { 1068 var timeEl = row.querySelector('.bf-ls-portal-time, .bf-ls-widget-time'); 1069 if (timeEl) { 1070 var isReallyLive = (m.status == 1 && m.time !== 'FT' && m.time !== 'AET' && m.time !== 'PEN'); 1071 timeEl.innerHTML = (isReallyLive ? '<span class="bf-ls-live-dot"></span>' : '') + m.time; 1072 } 1073 } 1074 } else { 1075 missingMatches++; 1076 } 1077 }); 1078 1079 1080 if (missingMatches > 0) { 1081 console.log("BF LiveScore: Received updates for " + missingMatches + " missing matches. Forcing HTML fetch."); 1082 self.refresh(true); 1083 } 1084 }, 1085 1086 fetchDynamic: function(oldScores, suppressSound) { 1087 var self = this; 1088 XF.ajax('GET', this.options.url, { filter: this.options.filter, mode: this.options.mode }, function(data) { 1089 if (data.html && data.html.content) { 1090 self.applyContent(data.html.content, oldScores, suppressSound); 1091 } 1092 }); 1093 }, 1094 1095 applyContent: function(htmlContent, oldScores, suppressSound) { 1096 var self = this; 1097 if (self.currentMode === 'schedule' || self.currentOffset !== 0) return; 1098 1099 var contentBox = self.target.querySelector('.bf-ls-content'); 1100 if (contentBox) { 1101 // --- STATE MEMORY FIX: Capture EXACT State (True/False) --- 1102 // Previously, we only captured "Open" headers. 1103 // Now, we capture true (collapsed) or false (open) for EVERY header. 1104 var headerStates = {}; 1105 contentBox.querySelectorAll('.bf-ls-portal-header').forEach(function(h) { 1106 var key = h.innerText.trim(); 1107 headerStates[key] = h.classList.contains('collapsed'); 1108 }); 1109 // ----------------------------- 1110 1111 contentBox.innerHTML = htmlContent; 1112 1113 // --- STATE RESTORE FIX: Force previous state --- 1114 contentBox.querySelectorAll('.bf-ls-portal-header').forEach(function(h) { 1115 var key = h.innerText.trim(); 1116 1117 // Only override if we saw this header before 1118 if (headerStates.hasOwnProperty(key)) { 1119 var shouldBeCollapsed = headerStates[key]; 1120 var next = h.nextElementSibling; 1121 1122 if (shouldBeCollapsed) { 1123 h.classList.add('collapsed'); 1124 if (next && next.classList.contains('bf-ls-group-container')) { 1125 next.classList.add('collapsed'); 1126 } 1127 } else { 1128 h.classList.remove('collapsed'); 1129 if (next && next.classList.contains('bf-ls-group-container')) { 1130 next.classList.remove('collapsed'); 1131 } 1132 } 1133 } 1134 }); 1135 // ------------------------------- 1136 1137 try { self.convertTimes(); } catch(e) {} 1138 1139 try {
1140 var storedFollows = []; 1141 if (XF.config.userId && parseInt(XF.config.userId, 10) > 0) { 1142 try { storedFollows = JSON.parse(localStorage.getItem('bfLiveScore_follows')) || []; } catch(e) {} 1143 } 1144 var followedSet = new Set(storedFollows.map(String)); 1145 1146 var goalDetected = false; 1147 1148 contentBox.querySelectorAll('.bf-ls-item-container').forEach(function(container) { 1149 var mid = container.getAttribute('data-match-id'); 1150 var btn = container.querySelector('.bf-ls-follow-btn'); 1151 1152 if (btn && followedSet.has(mid)) { 1153 btn.classList.add('active'); 1154 btn.setAttribute('aria-pressed', 'true'); 1155 if (window.bfGetBellSvg) { 1156 btn.innerHTML = window.bfGetBellSvg(true); 1157 XF.activate(btn); 1158 } 1159 } 1160 1161 if (oldScores && oldScores[mid]) { 1162 var newScore = container.getAttribute('data-home-score'); 1163 1164 if (newScore && newScore !== oldScores[mid]) { 1165 // Mathematical Check for Goal Increase 1166 1167 var oldSum = self.getScoreSum(oldScores[mid]); 1168 1169 var newSum = self.getScoreSum(newScore); 1170 1171 1172 1173 if (oldSum > -1 && newSum > oldSum && newScore.indexOf('-') > -1) { 1174 self.triggerGoalAnimation(container, container.querySelector('.bf-ls-portal-score, .bf-ls-widget-score')); 1175 // --- FIX APPLIED HERE --- 1176 // Only allow sound if we are NOT suppressing it (e.g. on tab wake-up) 1177 if (!suppressSound) { 1178 goalDetected = true; 1179 } 1180 // ------------------------ 1181 } 1182 } 1183 } 1184 else { 1185 if (window['bfGoalTimer_' + mid]) { 1186 var card = container.querySelector('.bf-ls-match-card'); 1187 if (card) card.classList.add('bf-recent-goal'); 1188 } 1189 } 1190 }); 1191 1192 if (goalDetected) self.playSound(); 1193 1194 } catch(e) { console.error(e); } 1195 1196 try { 1197 var activeTab = self.target.querySelector('.bf-ls-tab.active'); 1198 if (activeTab) { 1199 self.filterRows(activeTab.getAttribute('data-tab')); 1200 } else { 1201 self.filterRows('all'); 1202 } 1203 } catch(e) {} 1204 } 1205 }, 1206 1207 triggerGoalAnimation: function(row, scoreEl) { 1208 var self = this; 1209 var card = row.querySelector('.bf-ls-match-card'); 1210 if (!card) return; 1211 1212 card.classList.remove('bf-goal-anim'); 1213 void card.offsetWidth; 1214 card.classList.add('bf-goal-anim'); 1215 1216 var originalScore = scoreEl.textContent; 1217 scoreEl.textContent = "GOAL!"; 1218 scoreEl.classList.add('bf-goal-text-anim'); 1219 1220 setTimeout(function() { 1221 scoreEl.textContent = originalScore; 1222 scoreEl.classList.remove('bf-goal-text-anim'); 1223 }, 3000); 1224 1225 var mid = row.getAttribute('data-match-id'); 1226 if (window['bfGoalTimer_' + mid]) clearTimeout(window['bfGoalTimer_' + mid]); 1227 1228 setTimeout(function() { 1229 var currentCard = self.target.querySelector('.bf-ls-item-container[data-match-id="' + mid + '"] .bf-ls-match-card'); 1230 if (currentCard) currentCard.classList.add('bf-recent-goal'); 1231 }, 3000); 1232 1233 window['bfGoalTimer_' + mid] = setTimeout(function() { 1234 var currentCard = self.target.querySelector('.bf-ls-item-container[data-match-id="' + mid + '"] .bf-ls-match-card'); 1235 if (currentCard) currentCard.classList.remove('bf-recent-goal'); 1236 delete window['bfGoalTimer_' + mid]; 1237 }, 63000); 1238 1239 this.playSound(); 1240 } 1241 });
1242 1243 XF.Element.register('bf-livescore-app', 'XF.BFLiveScoreApp'); 1244})(window, document); 1245 1246(function(){ 1247 try { 1248 var css = ".bf-ls-date-scroll-wrapper .bf-ls-date-btn.active, .bf-ls-date-scroll-wrapper .bf-ls-date-btn.active:hover { background: hsla(var(--xf-paletteColor5)) !important; border-color: hsla(var(--xf-paletteColor5)) !important; color: hsla(var(--xf-paletteColor1)) !important; box-shadow: 0 2px 4px rgba(0,0,0,0.3) !important; font-weight:700 !important; } " + 1249 ".bf-filter-label-short { display: none; } " + 1250 "@media (max-width: 800px) { .bf-filter-label-full { display: none; } .bf-filter-label-short { display: inline; } }"; 1251 var id = 'bf-ls-date-btn-runtime-style'; 1252 if (!document.getElementById(id)) { 1253 var s = document.createElement('style'); 1254 s.id = id; 1255 s.textContent = css; 1256 document.head.appendChild(s); 1257 } 1258 } catch (e) {} 1259})();
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.