Spaces:
Running
Running
FIX: BXH self-rendered from fixtures JSON (groups), news always fetches og:image, livescore confirmed"
Browse files- static/wc2026_v2.js +154 -183
static/wc2026_v2.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
| 1 |
// === WORLD CUP 2026 - Full render ===
|
| 2 |
-
// Fixtures: sorted by date,
|
| 3 |
-
// BXH:
|
| 4 |
-
//
|
| 5 |
-
// News: images from og:image fallback
|
| 6 |
|
| 7 |
let _wc2026Data = null;
|
| 8 |
let _wcRefreshInterval = null;
|
| 9 |
|
| 10 |
-
// Country name translations + flag emojis
|
| 11 |
const WC_TEAMS = {
|
| 12 |
'Mexico':'🇲🇽 Mexico','Colombia':'🇨🇴 Colombia','USA':'🇺🇸 Mỹ','United States':'🇺🇸 Mỹ',
|
| 13 |
'Brazil':'🇧🇷 Brazil','Argentina':'🇦🇷 Argentina','Germany':'🇩🇪 Đức','France':'🇫🇷 Pháp',
|
|
@@ -23,208 +21,181 @@ const WC_TEAMS = {
|
|
| 23 |
'Serbia':'🇷🇸 Serbia','Switzerland':'🇨🇭 Thụy Sĩ','Denmark':'🇩🇰 Đan Mạch',
|
| 24 |
'Poland':'🇵🇱 Ba Lan','Wales':'🏴 Xứ Wales','Sweden':'🇸🇪 Thụy Điển',
|
| 25 |
'Norway':'🇳🇴 Na Uy','Austria':'🇦🇹 Áo','Czech Republic':'🇨🇿 Séc','Czechia':'🇨🇿 Séc',
|
| 26 |
-
'Scotland':'🏴 Scotland','Turkey':'🇹🇷 Thổ Nhĩ Kỳ','
|
| 27 |
-
'Egypt':'🇪🇬 Ai Cập','Algeria':'🇩🇿 Algeria',
|
| 28 |
-
'
|
|
|
|
| 29 |
'Indonesia':'🇮🇩 Indonesia','Vietnam':'🇻🇳 Việt Nam','Thailand':'🇹🇭 Thái Lan',
|
| 30 |
-
'China':'🇨🇳 Trung Quốc','
|
| 31 |
-
'
|
| 32 |
-
'
|
| 33 |
-
'
|
| 34 |
-
'
|
| 35 |
-
'
|
| 36 |
-
'Palestine':'🇵🇸 Palestine','Bahrain':'🇧🇭 Bahrain','Oman':'🇴🇲 Oman',
|
| 37 |
-
'Kuwait':'🇰🇼 Kuwait','UAE':'🇦🇪 UAE','North Korea':'🇰🇵 Triều Tiên',
|
| 38 |
'Cuba':'🇨🇺 Cuba','El Salvador':'🇸🇻 El Salvador','Guatemala':'🇬🇹 Guatemala',
|
| 39 |
'Trinidad and Tobago':'🇹🇹 Trinidad & Tobago','Haiti':'🇭🇹 Haiti',
|
| 40 |
-
'Suriname':'🇸🇷 Suriname','Guyana':'🇬🇾 Guyana',
|
| 41 |
'Mali':'🇲🇱 Mali','Burkina Faso':'🇧🇫 Burkina Faso','Mozambique':'🇲🇿 Mozambique',
|
| 42 |
-
'Tanzania':'🇹🇿 Tanzania','
|
| 43 |
-
'
|
| 44 |
-
'
|
| 45 |
-
'Benin':'🇧🇯 Benin','Equatorial Guinea':'🇬🇶 Guinea Xích Đạo',
|
| 46 |
-
'Botswana':'🇧🇼 Botswana','Comoros':'🇰🇲 Comoros',
|
| 47 |
-
'TBD':'🏳️ Chưa xác định','To Be Determined':'🏳️ Chưa xác định',
|
| 48 |
};
|
| 49 |
-
function
|
| 50 |
-
if (!name) return '🏳️ Chưa xác định';
|
| 51 |
-
return WC_TEAMS[name] || WC_TEAMS[name.trim()] || '🏴 ' + name;
|
| 52 |
-
}
|
| 53 |
|
| 54 |
-
function switchWCTab(tab)
|
| 55 |
-
document.querySelectorAll('.wc-tab').forEach(t
|
| 56 |
-
document.querySelectorAll('.wc-tab').forEach(t
|
| 57 |
-
if
|
| 58 |
-
(tab === 'fixtures' && t.textContent.includes('Lịch')) ||
|
| 59 |
-
(tab === 'standings' && t.textContent.includes('BXH')) ||
|
| 60 |
-
(tab === 'stats' && t.textContent.includes('Thống')))
|
| 61 |
-
t.classList.add('active');
|
| 62 |
});
|
| 63 |
-
const el
|
| 64 |
-
if
|
| 65 |
-
if
|
| 66 |
-
|
| 67 |
-
else if
|
| 68 |
-
else if
|
| 69 |
-
else if (tab === 'stats') renderWCStats(el);
|
| 70 |
}
|
| 71 |
|
| 72 |
-
// === NEWS (
|
| 73 |
-
function renderWCNews(el)
|
| 74 |
-
const news
|
| 75 |
-
const
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
combined.slice(0, 20).forEach((a, i) => {
|
| 85 |
-
const badge = a._type === 'road' ? '<span class="badge badge-wc">Đường tới WC</span>' : '<span class="badge badge-fpt">Tin WC</span>';
|
| 86 |
-
h += `<div class="wc-news-item" onclick="readArticle('${esc(a.link)}')">`;
|
| 87 |
-
// Image: show if available, otherwise lazy-load from article og:image
|
| 88 |
-
const imgId = 'wcimg-' + i;
|
| 89 |
-
if (a.img) {
|
| 90 |
-
h += `<div class="wc-news-img"><img src="${esc(a.img)}" onerror="this.parentElement.innerHTML=''"></div>`;
|
| 91 |
-
} else {
|
| 92 |
-
h += `<div class="wc-news-img" id="${imgId}"></div>`;
|
| 93 |
-
}
|
| 94 |
-
h += `<div class="wc-news-text">${badge}<div class="wc-news-title">${esc(a.title)}</div><div class="wc-news-via">${esc(a.source || '')}</div></div></div>`;
|
| 95 |
-
});
|
| 96 |
-
h += '</div>';
|
| 97 |
-
el.innerHTML = h;
|
| 98 |
-
// Lazy load missing images from og:image
|
| 99 |
-
combined.slice(0, 20).forEach((a, i) => {
|
| 100 |
-
if (a.img) return;
|
| 101 |
-
const imgEl = document.getElementById('wcimg-' + i);
|
| 102 |
-
if (!imgEl) return;
|
| 103 |
-
fetch('/api/article?url=' + encodeURIComponent(a.link)).then(r => r.json()).then(d => {
|
| 104 |
-
if (d && (d.og_image || d.img)) {
|
| 105 |
-
imgEl.innerHTML = `<img src="${esc(d.og_image || d.img)}" onerror="this.parentElement.innerHTML=''">`;
|
| 106 |
-
}
|
| 107 |
-
}).catch(() => {});
|
| 108 |
});
|
|
|
|
|
|
|
|
|
|
| 109 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
|
| 111 |
-
// === FIXTURES (sorted by date,
|
| 112 |
-
function renderWCFixtures(el)
|
| 113 |
-
const d
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
h
|
| 140 |
-
// Time + Group + Venue
|
| 141 |
-
h += `<div style="display:flex;justify-content:space-between;font-size:9px;color:#6a9fca;margin-bottom:2px">`;
|
| 142 |
-
h += `<span>🕐 ${esc(time)}${m.group ? ' · ' + esc(m.group) : ''}</span>`;
|
| 143 |
-
if (m.location) h += `<span style="max-width:50%;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">🏟 ${esc(m.location)}</span>`;
|
| 144 |
-
h += '</div>';
|
| 145 |
-
// Teams + Score
|
| 146 |
-
h += '<div style="display:flex;align-items:center;gap:3px">';
|
| 147 |
-
h += `<span style="flex:1;text-align:right;font-size:11px;color:#eee">${_teamVN(m.home)}</span>`;
|
| 148 |
-
if (isLive) {
|
| 149 |
-
h += `<span style="min-width:46px;text-align:center;font-size:12px;font-weight:900;color:#e74c3c;background:#1a0808;padding:2px 5px;border-radius:4px;animation:wc-pulse 1.5s infinite">${esc(m.score)}</span>`;
|
| 150 |
-
} else if (isDone) {
|
| 151 |
-
h += `<span style="min-width:46px;text-align:center;font-size:12px;font-weight:800;color:#4ade80">${esc(m.score)}</span>`;
|
| 152 |
-
} else {
|
| 153 |
-
h += `<span style="min-width:46px;text-align:center;font-size:10px;color:#555">vs</span>`;
|
| 154 |
-
}
|
| 155 |
-
h += `<span style="flex:1;text-align:left;font-size:11px;color:#eee">${_teamVN(m.away)}</span>`;
|
| 156 |
-
h += '</div>';
|
| 157 |
-
if (isLive) h += '<div style="text-align:center;font-size:8px;color:#e74c3c;font-weight:700;margin-top:1px">● LIVE</div>';
|
| 158 |
-
h += '</div>';
|
| 159 |
});
|
| 160 |
}
|
| 161 |
-
h
|
| 162 |
-
el.innerHTML = h;
|
| 163 |
}
|
| 164 |
|
| 165 |
-
// === BXH (rendered
|
| 166 |
-
function renderWCStandings(el)
|
| 167 |
-
const d
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
.
|
| 174 |
-
.wc-bxh-
|
| 175 |
-
.
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
.
|
| 179 |
-
|
| 180 |
-
.wc-bxh-wrap strong,.wc-bxh-wrap b{color:#f0c040!important}
|
| 181 |
-
.wc-bxh-wrap h3,.wc-bxh-wrap h4,.wc-bxh-wrap .title-content strong{color:#8ab4d8!important;font-size:12px}
|
| 182 |
-
.wc-bxh-wrap .title-content{display:flex;align-items:center;gap:6px;background:#0b1a2a!important;border-radius:4px;margin:8px 0 2px;padding:6px 8px}
|
| 183 |
-
.wc-bxh-wrap a{color:#8ab4d8!important;text-decoration:none!important}
|
| 184 |
-
.wc-bxh-wrap .info,.wc-bxh-wrap .btns{display:none!important}
|
| 185 |
-
.wc-bxh-wrap ul{list-style:none;padding:0;margin:0}
|
| 186 |
-
.wc-bxh-wrap li{border-bottom:1px solid #0d1a2a}
|
| 187 |
-
</style>`;
|
| 188 |
-
el.innerHTML = css + '<div class="wc-bxh-wrap">' + d.html + '</div>';
|
| 189 |
-
el.querySelectorAll('a').forEach(a => a.addEventListener('click', e => e.preventDefault()));
|
| 190 |
-
} else {
|
| 191 |
-
el.innerHTML = '<div class="loading">BXH World Cup 2026 sẽ cập nhật khi giải khởi tranh<br><small style="color:#6a9fca">11/06/2026 · Livescore tự động từ bongda.com.vn</small></div>';
|
| 192 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
}
|
| 194 |
|
| 195 |
// === STATS ===
|
| 196 |
-
function renderWCStats(el)
|
| 197 |
-
const d
|
| 198 |
-
if
|
| 199 |
-
el.innerHTML
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
el.innerHTML = '<div class="loading">Thống kê sẽ cập nhật khi giải bắt đầu<br><small style="color:#6a9fca">Vua phá lưới · Kiến tạo · Thẻ phạt</small></div>';
|
| 203 |
}
|
| 204 |
}
|
| 205 |
|
| 206 |
-
// === LIVE AUTO-REFRESH (90s) ===
|
| 207 |
-
function startWCLiveRefresh()
|
| 208 |
-
if
|
| 209 |
-
_wcRefreshInterval
|
| 210 |
-
if
|
| 211 |
-
try
|
| 212 |
-
const data
|
| 213 |
-
if
|
| 214 |
-
_wc2026Data
|
| 215 |
-
const wcEl
|
| 216 |
-
if
|
| 217 |
-
const
|
| 218 |
-
if
|
| 219 |
-
const
|
| 220 |
-
if
|
| 221 |
-
else if
|
| 222 |
-
else if
|
| 223 |
-
else if
|
| 224 |
}
|
| 225 |
}
|
| 226 |
}
|
| 227 |
-
}
|
| 228 |
-
},
|
| 229 |
}
|
| 230 |
-
setTimeout(startWCLiveRefresh,
|
|
|
|
| 1 |
// === WORLD CUP 2026 - Full render ===
|
| 2 |
+
// Fixtures: JSON sorted by date, VN names + flags, livescore auto-refresh
|
| 3 |
+
// BXH: Self-rendered from fixtures data (grouped by bang)
|
| 4 |
+
// News: Always fetch og:image for thumbnails
|
|
|
|
| 5 |
|
| 6 |
let _wc2026Data = null;
|
| 7 |
let _wcRefreshInterval = null;
|
| 8 |
|
|
|
|
| 9 |
const WC_TEAMS = {
|
| 10 |
'Mexico':'🇲🇽 Mexico','Colombia':'🇨🇴 Colombia','USA':'🇺🇸 Mỹ','United States':'🇺🇸 Mỹ',
|
| 11 |
'Brazil':'🇧🇷 Brazil','Argentina':'🇦🇷 Argentina','Germany':'🇩🇪 Đức','France':'🇫🇷 Pháp',
|
|
|
|
| 21 |
'Serbia':'🇷🇸 Serbia','Switzerland':'🇨🇭 Thụy Sĩ','Denmark':'🇩🇰 Đan Mạch',
|
| 22 |
'Poland':'🇵🇱 Ba Lan','Wales':'🏴 Xứ Wales','Sweden':'🇸🇪 Thụy Điển',
|
| 23 |
'Norway':'🇳🇴 Na Uy','Austria':'🇦🇹 Áo','Czech Republic':'🇨🇿 Séc','Czechia':'🇨🇿 Séc',
|
| 24 |
+
'Scotland':'🏴 Scotland','Turkey':'🇹🇷 Thổ Nhĩ Kỳ','Türkiye':'🇹🇷 Thổ Nhĩ Kỳ',
|
| 25 |
+
'Ukraine':'🇺🇦 Ukraine','Egypt':'🇪🇬 Ai Cập','Algeria':'🇩🇿 Algeria',
|
| 26 |
+
'South Africa':'🇿🇦 Nam Phi','DR Congo':'🇨🇩 DR Congo',
|
| 27 |
+
'Ivory Coast':'🇨🇮 Bờ Biển Ngà',"Côte d'Ivoire":'🇨🇮 Bờ Biển Ngà',
|
| 28 |
'Indonesia':'🇮🇩 Indonesia','Vietnam':'🇻🇳 Việt Nam','Thailand':'🇹🇭 Thái Lan',
|
| 29 |
+
'China PR':'🇨🇳 Trung Quốc','New Zealand':'🇳🇿 New Zealand','Slovenia':'🇸🇮 Slovenia',
|
| 30 |
+
'Albania':'🇦🇱 Albania','Ireland':'🇮🇪 Ireland','Republic of Ireland':'🇮🇪 Ireland',
|
| 31 |
+
'Romania':'🇷🇴 Romania','Hungary':'🇭🇺 Hungary','Iceland':'🇮🇸 Iceland',
|
| 32 |
+
'Finland':'🇫🇮 Phần Lan','Greece':'🇬🇷 Hy Lạp','Uzbekistan':'🇺🇿 Uzbekistan',
|
| 33 |
+
'Iraq':'🇮🇶 Iraq','Jordan':'🇯🇴 Jordan','Palestine':'🇵🇸 Palestine',
|
| 34 |
+
'Bahrain':'🇧🇭 Bahrain','Oman':'🇴🇲 Oman','UAE':'🇦🇪 UAE',
|
|
|
|
|
|
|
| 35 |
'Cuba':'🇨🇺 Cuba','El Salvador':'🇸🇻 El Salvador','Guatemala':'🇬🇹 Guatemala',
|
| 36 |
'Trinidad and Tobago':'🇹🇹 Trinidad & Tobago','Haiti':'🇭🇹 Haiti',
|
|
|
|
| 37 |
'Mali':'🇲🇱 Mali','Burkina Faso':'🇧🇫 Burkina Faso','Mozambique':'🇲🇿 Mozambique',
|
| 38 |
+
'Tanzania':'🇹🇿 Tanzania','Zambia':'🇿🇲 Zambia','Kenya':'🇰🇪 Kenya',
|
| 39 |
+
'Angola':'🇦🇴 Angola','Guinea':'🇬🇳 Guinea','Benin':'🇧🇯 Benin',
|
| 40 |
+
'TBD':'🏳️ TBD','To Be Determined':'🏳️ TBD',
|
|
|
|
|
|
|
|
|
|
| 41 |
};
|
| 42 |
+
function _t(name){return WC_TEAMS[name]||WC_TEAMS[(name||'').trim()]||'🏴 '+name;}
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
+
function switchWCTab(tab){
|
| 45 |
+
document.querySelectorAll('.wc-tab').forEach(t=>t.classList.remove('active'));
|
| 46 |
+
document.querySelectorAll('.wc-tab').forEach(t=>{
|
| 47 |
+
if((tab==='news'&&t.textContent.includes('Tin'))||(tab==='fixtures'&&t.textContent.includes('Lịch'))||(tab==='standings'&&t.textContent.includes('BXH'))||(tab==='stats'&&t.textContent.includes('Thống')))t.classList.add('active');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
});
|
| 49 |
+
const el=document.getElementById('wc-content');if(!el)return;
|
| 50 |
+
if(!_wc2026Data){el.innerHTML='<div class="loading">Đang tải...</div>';return;}
|
| 51 |
+
if(tab==='news')renderWCNews(el);
|
| 52 |
+
else if(tab==='fixtures')renderWCFixtures(el);
|
| 53 |
+
else if(tab==='standings')renderWCStandings(el);
|
| 54 |
+
else if(tab==='stats')renderWCStats(el);
|
|
|
|
| 55 |
}
|
| 56 |
|
| 57 |
+
// === NEWS (always load og:image for missing thumbnails) ===
|
| 58 |
+
function renderWCNews(el){
|
| 59 |
+
const news=_wc2026Data.news||[];const road=_wc2026Data.road||[];
|
| 60 |
+
const combined=[];let ni=0,ri=0;
|
| 61 |
+
while(ni<news.length||ri<road.length){if(ni<news.length)combined.push({...news[ni++],_type:'news'});if(ri<road.length)combined.push({...road[ri++],_type:'road'});}
|
| 62 |
+
if(!combined.length){el.innerHTML='<div class="loading">Đang cập nhật...</div>';return;}
|
| 63 |
+
let h='<div class="wc-news-grid">';
|
| 64 |
+
combined.slice(0,20).forEach((a,i)=>{
|
| 65 |
+
const badge=a._type==='road'?'<span class="badge badge-wc">Đường tới WC</span>':'<span class="badge badge-fpt">Tin WC</span>';
|
| 66 |
+
h+=`<div class="wc-news-item" onclick="readArticle('${esc(a.link)}')">`;
|
| 67 |
+
h+=`<div class="wc-news-img" id="wcimg-${i}">${a.img?`<img src="${esc(a.img)}" onerror="loadWCImg(${i},'${esc(a.link)}')">`:''}</div>`;
|
| 68 |
+
h+=`<div class="wc-news-text">${badge}<div class="wc-news-title">${esc(a.title)}</div><div class="wc-news-via">${esc(a.source||'')}</div></div></div>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
});
|
| 70 |
+
h+='</div>';el.innerHTML=h;
|
| 71 |
+
// Load images for items without img
|
| 72 |
+
combined.slice(0,20).forEach((a,i)=>{if(!a.img)loadWCImg(i,a.link);});
|
| 73 |
}
|
| 74 |
+
window.loadWCImg=function(i,url){
|
| 75 |
+
const el=document.getElementById('wcimg-'+i);if(!el)return;
|
| 76 |
+
fetch('/api/article?url='+encodeURIComponent(url)).then(r=>r.json()).then(d=>{
|
| 77 |
+
const img=d?.og_image||d?.img||'';
|
| 78 |
+
if(img)el.innerHTML=`<img src="${esc(img)}" onerror="this.style.display='none'">`;
|
| 79 |
+
}).catch(()=>{});
|
| 80 |
+
};
|
| 81 |
|
| 82 |
+
// === FIXTURES (sorted by date, VN names + flags, livescore) ===
|
| 83 |
+
function renderWCFixtures(el){
|
| 84 |
+
const d=_wc2026Data.fixtures||{};const matches=d.matches||[];
|
| 85 |
+
if(!matches.length){el.innerHTML='<div class="loading">Đang tải lịch thi đấu...</div>';return;}
|
| 86 |
+
const sorted=[...matches].sort((a,b)=>(a.date_utc||'').localeCompare(b.date_utc||''));
|
| 87 |
+
const byDate={};
|
| 88 |
+
sorted.forEach(m=>{const day=m.date_vn?m.date_vn.split(' ').slice(1).join(' '):'TBD';if(!byDate[day])byDate[day]=[];byDate[day].push(m);});
|
| 89 |
+
let h='<div style="max-height:450px;overflow-y:auto;padding:2px">';
|
| 90 |
+
h+=`<div style="font-size:10px;color:#6a9fca;text-align:center;padding:3px 0">🏆 World Cup 2026 · ${d.total||104} trận · Giờ VN · 🔴 Livescore tự động</div>`;
|
| 91 |
+
for(const[day,dm]of Object.entries(byDate)){
|
| 92 |
+
h+=`<div style="font-size:11px;font-weight:800;color:#f0c040;padding:6px 4px 2px;margin-top:3px;border-bottom:1px solid #1a3a5a">📅 ${esc(day)}</div>`;
|
| 93 |
+
dm.forEach(m=>{
|
| 94 |
+
const live=m.status==='live',done=m.status==='finished';
|
| 95 |
+
const bc=live?'#e74c3c':done?'#2d8659':'#1a3a5a';
|
| 96 |
+
const time=m.date_vn?m.date_vn.split(' ')[0]:'';
|
| 97 |
+
h+=`<div style="padding:4px;margin:2px 0;border-left:3px solid ${bc};border-radius:3px;background:#0a1520">`;
|
| 98 |
+
h+=`<div style="display:flex;justify-content:space-between;font-size:8px;color:#6a9fca">`;
|
| 99 |
+
h+=`<span>🕐 ${esc(time)}${m.group?' · '+esc(m.group):''}</span>`;
|
| 100 |
+
if(m.location)h+=`<span style="max-width:45%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">🏟 ${esc(m.location)}</span>`;
|
| 101 |
+
h+='</div>';
|
| 102 |
+
h+='<div style="display:flex;align-items:center;gap:2px;margin-top:2px">';
|
| 103 |
+
h+=`<span style="flex:1;text-align:right;font-size:11px;color:#eee">${_t(m.home)}</span>`;
|
| 104 |
+
if(live)h+=`<span style="min-width:44px;text-align:center;font-size:12px;font-weight:900;color:#e74c3c;background:#1a0808;padding:1px 4px;border-radius:3px">${esc(m.score)}</span>`;
|
| 105 |
+
else if(done)h+=`<span style="min-width:44px;text-align:center;font-size:11px;font-weight:800;color:#4ade80">${esc(m.score)}</span>`;
|
| 106 |
+
else h+=`<span style="min-width:44px;text-align:center;font-size:10px;color:#555">vs</span>`;
|
| 107 |
+
h+=`<span style="flex:1;text-align:left;font-size:11px;color:#eee">${_t(m.away)}</span>`;
|
| 108 |
+
h+='</div>';
|
| 109 |
+
if(live)h+='<div style="text-align:center;font-size:8px;color:#e74c3c;font-weight:700">● LIVE</div>';
|
| 110 |
+
h+='</div>';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
});
|
| 112 |
}
|
| 113 |
+
h+='</div>';el.innerHTML=h;
|
|
|
|
| 114 |
}
|
| 115 |
|
| 116 |
+
// === BXH (self-rendered from fixtures JSON — grouped by bảng, calculate points) ===
|
| 117 |
+
function renderWCStandings(el){
|
| 118 |
+
const d=_wc2026Data.fixtures||{};const matches=d.matches||[];
|
| 119 |
+
// Extract group stage matches only
|
| 120 |
+
const groupMatches=matches.filter(m=>m.group&&m.group.toLowerCase().includes('group'));
|
| 121 |
+
if(!groupMatches.length){
|
| 122 |
+
// Fallback: try bongda.com.vn HTML
|
| 123 |
+
const bxh=_wc2026Data.standings||{};
|
| 124 |
+
if(bxh.html&&bxh.html.length>100){
|
| 125 |
+
el.innerHTML=`<style>.wc-bxh{max-height:450px;overflow-y:auto}.wc-bxh,.wc-bxh *{color:#ddd!important;background:transparent!important;border-color:#1a3a5a!important}.wc-bxh table{width:100%;border-collapse:collapse;font-size:11px}.wc-bxh th{background:#0b1a2a!important;color:#6a9fca!important;padding:5px 3px;font-size:9px}.wc-bxh td{padding:4px 3px;border-bottom:1px solid #0d1a2a!important}.wc-bxh img{width:16px;height:16px;vertical-align:middle;margin-right:3px}.wc-bxh strong{color:#f0c040!important}.wc-bxh .title-content{background:#0b1a2a!important;border-radius:4px;margin:6px 0 2px;padding:5px 8px}.wc-bxh .info,.wc-bxh .btns{display:none!important}</style><div class="wc-bxh">${bxh.html}</div>`;
|
| 126 |
+
el.querySelectorAll('a').forEach(a=>a.addEventListener('click',e=>e.preventDefault()));
|
| 127 |
+
return;
|
| 128 |
+
}
|
| 129 |
+
el.innerHTML='<div class="loading">BXH cập nhật khi giải bắt đầu 11/06/2026<br><small style="color:#6a9fca">🔴 Livescore tự động từ bongda.com.vn</small></div>';
|
| 130 |
+
return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
}
|
| 132 |
+
// Calculate standings per group
|
| 133 |
+
const groups={};
|
| 134 |
+
groupMatches.forEach(m=>{
|
| 135 |
+
const g=m.group;if(!groups[g])groups[g]={};
|
| 136 |
+
[m.home,m.away].forEach(team=>{if(!groups[g][team])groups[g][team]={p:0,w:0,d:0,l:0,gf:0,ga:0,pts:0};});
|
| 137 |
+
if(m.home_score!==null&&m.away_score!==null){
|
| 138 |
+
const hs=parseInt(m.home_score),as=parseInt(m.away_score);
|
| 139 |
+
if(!isNaN(hs)&&!isNaN(as)){
|
| 140 |
+
groups[g][m.home].p++;groups[g][m.away].p++;
|
| 141 |
+
groups[g][m.home].gf+=hs;groups[g][m.home].ga+=as;
|
| 142 |
+
groups[g][m.away].gf+=as;groups[g][m.away].ga+=hs;
|
| 143 |
+
if(hs>as){groups[g][m.home].w++;groups[g][m.home].pts+=3;groups[g][m.away].l++;}
|
| 144 |
+
else if(hs<as){groups[g][m.away].w++;groups[g][m.away].pts+=3;groups[g][m.home].l++;}
|
| 145 |
+
else{groups[g][m.home].d++;groups[g][m.home].pts+=1;groups[g][m.away].d++;groups[g][m.away].pts+=1;}
|
| 146 |
+
}
|
| 147 |
+
}
|
| 148 |
+
});
|
| 149 |
+
let h='<div style="max-height:450px;overflow-y:auto;padding:2px">';
|
| 150 |
+
h+='<div style="font-size:10px;color:#6a9fca;text-align:center;padding:3px 0">🏆 BXH World Cup 2026 · 🔴 Livescore tự động</div>';
|
| 151 |
+
const sortedGroups=Object.keys(groups).sort();
|
| 152 |
+
sortedGroups.forEach(g=>{
|
| 153 |
+
const teams=Object.entries(groups[g]).map(([name,s])=>({name,...s,gd:s.gf-s.ga})).sort((a,b)=>b.pts-a.pts||b.gd-a.gd||b.gf-a.gf);
|
| 154 |
+
h+=`<div style="font-size:11px;font-weight:800;color:#8ab4d8;padding:6px 4px 3px;margin-top:6px;border-bottom:1px solid #1a3a5a">${esc(g)}</div>`;
|
| 155 |
+
h+='<table style="width:100%;border-collapse:collapse;font-size:10px;margin:2px 0">';
|
| 156 |
+
h+='<tr style="background:#0b1a2a"><th style="text-align:left;padding:4px;color:#6a9fca">Đội</th><th style="color:#6a9fca;padding:4px">Tr</th><th style="color:#6a9fca;padding:4px">T</th><th style="color:#6a9fca;padding:4px">H</th><th style="color:#6a9fca;padding:4px">B</th><th style="color:#6a9fca;padding:4px">BT</th><th style="color:#6a9fca;padding:4px">BB</th><th style="color:#6a9fca;padding:4px">HS</th><th style="color:#f0c040;padding:4px;font-weight:800">Đ</th></tr>';
|
| 157 |
+
teams.forEach((t,i)=>{
|
| 158 |
+
const bg=i<2?'#0a1a10':'transparent';// Top 2 qualify
|
| 159 |
+
const border=i===1?'border-bottom:1px solid #2d8659':''
|
| 160 |
+
h+=`<tr style="background:${bg};${border}"><td style="padding:4px;color:#eee;white-space:nowrap">${_t(t.name)}</td><td style="text-align:center;padding:4px;color:#999">${t.p}</td><td style="text-align:center;padding:4px;color:#4ade80">${t.w}</td><td style="text-align:center;padding:4px;color:#fbbf24">${t.d}</td><td style="text-align:center;padding:4px;color:#f87171">${t.l}</td><td style="text-align:center;padding:4px;color:#ccc">${t.gf}</td><td style="text-align:center;padding:4px;color:#ccc">${t.ga}</td><td style="text-align:center;padding:4px;color:${t.gd>=0?'#4ade80':'#f87171'}">${t.gd>0?'+':''}${t.gd}</td><td style="text-align:center;padding:4px;color:#f0c040;font-weight:800;font-size:12px">${t.pts}</td></tr>`;
|
| 161 |
+
});
|
| 162 |
+
h+='</table>';
|
| 163 |
+
});
|
| 164 |
+
h+='</div>';el.innerHTML=h;
|
| 165 |
}
|
| 166 |
|
| 167 |
// === STATS ===
|
| 168 |
+
function renderWCStats(el){
|
| 169 |
+
const d=_wc2026Data.stats||{};
|
| 170 |
+
if(d.html&&d.html.length>100){
|
| 171 |
+
el.innerHTML=`<style>.wc-st{max-height:450px;overflow-y:auto}.wc-st,.wc-st *{color:#ddd!important;background:transparent!important}.wc-st table{width:100%;border-collapse:collapse;font-size:11px}.wc-st th{background:#0b1a2a!important;color:#6a9fca!important;padding:5px}.wc-st td{padding:4px;border-bottom:1px solid #0d1a2a!important}.wc-st img{width:16px;height:16px;vertical-align:middle}.wc-st strong{color:#f0c040!important}</style><div class="wc-st">${d.html}</div>`;
|
| 172 |
+
}else{
|
| 173 |
+
el.innerHTML='<div class="loading">Thống kê cập nhật khi giải bắt đầu<br><small style="color:#6a9fca">Vua phá lưới · Kiến tạo · Thẻ phạt</small></div>';
|
|
|
|
| 174 |
}
|
| 175 |
}
|
| 176 |
|
| 177 |
+
// === LIVE AUTO-REFRESH (90s) — cả fixtures LẪN BXH đều live ===
|
| 178 |
+
function startWCLiveRefresh(){
|
| 179 |
+
if(_wcRefreshInterval)clearInterval(_wcRefreshInterval);
|
| 180 |
+
_wcRefreshInterval=setInterval(async()=>{
|
| 181 |
+
if(!document.getElementById('view-home')?.classList.contains('active'))return;
|
| 182 |
+
try{
|
| 183 |
+
const data=await fetch('/api/wc2026').then(r=>r.json()).catch(()=>null);
|
| 184 |
+
if(data){
|
| 185 |
+
_wc2026Data=data;
|
| 186 |
+
const wcEl=document.getElementById('wc-content');
|
| 187 |
+
if(wcEl){
|
| 188 |
+
const tab=document.querySelector('.wc-tab.active');
|
| 189 |
+
if(tab){
|
| 190 |
+
const t=tab.textContent;
|
| 191 |
+
if(t.includes('Tin'))renderWCNews(wcEl);
|
| 192 |
+
else if(t.includes('Lịch'))renderWCFixtures(wcEl);
|
| 193 |
+
else if(t.includes('BXH'))renderWCStandings(wcEl);
|
| 194 |
+
else if(t.includes('Thống'))renderWCStats(wcEl);
|
| 195 |
}
|
| 196 |
}
|
| 197 |
}
|
| 198 |
+
}catch(e){}
|
| 199 |
+
},90000);
|
| 200 |
}
|
| 201 |
+
setTimeout(startWCLiveRefresh,5000);
|