bep40 commited on
Commit
b4dce59
·
verified ·
1 Parent(s): 1da6727

FIX: BXH self-rendered from fixtures JSON (groups), news always fetches og:image, livescore confirmed"

Browse files
Files changed (1) hide show
  1. 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, Vietnamese names + country flags
3
- // BXH: beautiful group tables
4
- // All with livescore (auto-refresh 90s)
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ỳ','Ukraine':'🇺🇦 Ukraine',
27
- 'Egypt':'🇪🇬 Ai Cập','Algeria':'🇩🇿 Algeria','South Africa':'🇿🇦 Nam Phi',
28
- 'DR Congo':'🇨🇩 DR Congo','Ivory Coast':'🇨🇮 Bờ Biển Ngà',"Côte d'Ivoire":'🇨🇮 Bờ Biển Ngà',
 
29
  'Indonesia':'🇮🇩 Indonesia','Vietnam':'🇻🇳 Việt Nam','Thailand':'🇹🇭 Thái Lan',
30
- 'China':'🇨🇳 Trung Quốc','China PR':'🇨🇳 Trung Quốc',
31
- 'New Zealand':'🇳🇿 New Zealand','Slovenia':'🇸🇮 Slovenia','Albania':'🇦🇱 Albania',
32
- 'Ireland':'🇮🇪 Ireland','Republic of Ireland':'🇮🇪 Ireland',
33
- 'Russia':'🇷🇺 Nga','Romania':'🇷🇴 Romania','Hungary':'🇭🇺 Hungary',
34
- 'Iceland':'🇮🇸 Iceland','Finland':'🇫🇮 Phần Lan','Greece':'🇬🇷 Hy Lạp',
35
- 'Uzbekistan':'🇺🇿 Uzbekistan','Iraq':'🇮🇶 Iraq','Jordan':'🇯🇴 Jordan',
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','Congo':'🇨🇬 Congo','Zambia':'🇿🇲 Zambia',
43
- 'Zimbabwe':'🇿🇼 Zimbabwe','Uganda':'🇺🇬 Uganda','Kenya':'🇰🇪 Kenya',
44
- 'Angola':'🇦🇴 Angola','Guinea':'🇬🇳 Guinea','Gabon':'🇬🇦 Gabon',
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 _teamVN(name) {
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 => t.classList.remove('active'));
56
- document.querySelectorAll('.wc-tab').forEach(t => {
57
- if ((tab === 'news' && t.textContent.includes('Tin')) ||
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 = document.getElementById('wc-content');
64
- if (!el) return;
65
- if (!_wc2026Data) { el.innerHTML = '<div class="loading">Đang tải...</div>'; return; }
66
- if (tab === 'news') renderWCNews(el);
67
- else if (tab === 'fixtures') renderWCFixtures(el);
68
- else if (tab === 'standings') renderWCStandings(el);
69
- else if (tab === 'stats') renderWCStats(el);
70
  }
71
 
72
- // === NEWS (with images) ===
73
- function renderWCNews(el) {
74
- const news = _wc2026Data.news || [];
75
- const road = _wc2026Data.road || [];
76
- const combined = [];
77
- let ni = 0, ri = 0;
78
- while (ni < news.length || ri < road.length) {
79
- if (ni < news.length) combined.push({...news[ni++], _type: 'news'});
80
- if (ri < road.length) combined.push({...road[ri++], _type: 'road'});
81
- }
82
- if (!combined.length) { el.innerHTML = '<div class="loading">Đang cập nhật...</div>'; return; }
83
- let h = '<div class="wc-news-grid">';
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, Vietnamese names + flags, livescore) ===
112
- function renderWCFixtures(el) {
113
- const d = _wc2026Data.fixtures || {};
114
- const matches = d.matches || [];
115
- if (!matches.length) { el.innerHTML = '<div class="loading">Đang tải lịch thi đấu...</div>'; return; }
116
-
117
- // Sort by date
118
- const sorted = [...matches].sort((a, b) => (a.date_utc || '').localeCompare(b.date_utc || ''));
119
-
120
- // Group by date (day)
121
- const byDate = {};
122
- sorted.forEach(m => {
123
- const day = m.date_vn ? m.date_vn.split(' ').slice(1).join(' ') : 'Chưa xác định';
124
- if (!byDate[day]) byDate[day] = [];
125
- byDate[day].push(m);
126
- });
127
-
128
- let h = '<div style="max-height:450px;overflow-y:auto;padding:2px">';
129
- h += `<div style="font-size:10px;color:#6a9fca;text-align:center;padding:4px 0">🏆 World Cup 2026 — ${d.total || 104} trận · Giờ Việt Nam · Livescore tự động</div>`;
130
-
131
- for (const [day, dayMatches] of Object.entries(byDate)) {
132
- h += `<div style="font-size:11px;font-weight:800;color:#f0c040;padding:8px 4px 3px;margin-top:4px;border-bottom:1px solid #1a3a5a">📅 ${esc(day)}</div>`;
133
- dayMatches.forEach(m => {
134
- const isLive = m.status === 'live';
135
- const isDone = m.status === 'finished';
136
- const border = isLive ? '#e74c3c' : isDone ? '#2d8659' : '#1a3a5a';
137
- const time = m.date_vn ? m.date_vn.split(' ')[0] : '';
138
-
139
- h += `<div style="padding:5px 4px;margin:2px 0;border-left:3px solid ${border};border-radius:3px;background:#0a1520">`;
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 += '</div>';
162
- el.innerHTML = h;
163
  }
164
 
165
- // === BXH (rendered as beautiful group tables with livescore from bongda API) ===
166
- function renderWCStandings(el) {
167
- const d = _wc2026Data.standings || {};
168
- if (d.html && d.html.length > 100) {
169
- // Inject with dark theme CSS override
170
- const css = `<style>
171
- .wc-bxh-wrap{max-height:450px;overflow-y:auto;padding:4px}
172
- .wc-bxh-wrap,.wc-bxh-wrap *{color:#ddd!important;background:transparent!important;border-color:#1a3a5a!important}
173
- .wc-bxh-wrap table{width:100%;border-collapse:collapse;font-size:11px;margin:6px 0}
174
- .wc-bxh-wrap th{background:#0b1a2a!important;color:#6a9fca!important;padding:5px 3px;font-size:9px;text-align:center;font-weight:700}
175
- .wc-bxh-wrap th:first-child,.wc-bxh-wrap td:first-child{text-align:left;padding-left:6px}
176
- .wc-bxh-wrap td{padding:4px 3px;text-align:center;border-bottom:1px solid #0d1a2a!important;font-size:11px}
177
- .wc-bxh-wrap tr:nth-child(even) td{background:#060d14!important}
178
- .wc-bxh-wrap tr:hover td{background:#0d1a2a!important}
179
- .wc-bxh-wrap img{width:16px;height:16px;vertical-align:middle;margin-right:3px}
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 = _wc2026Data.stats || {};
198
- if (d.html && d.html.length > 100) {
199
- el.innerHTML = `<style>.wc-stats-wrap{max-height:450px;overflow-y:auto;padding:4px}.wc-stats-wrap,.wc-stats-wrap *{color:#ddd!important;background:transparent!important}.wc-stats-wrap table{width:100%;border-collapse:collapse;font-size:11px}.wc-stats-wrap th{background:#0b1a2a!important;color:#6a9fca!important;padding:5px 3px}.wc-stats-wrap td{padding:4px 3px;border-bottom:1px solid #0d1a2a!important}.wc-stats-wrap img{width:16px;height:16px;vertical-align:middle;margin-right:3px}.wc-stats-wrap strong{color:#f0c040!important}</style><div class="wc-stats-wrap">${d.html}</div>`;
200
- el.querySelectorAll('a').forEach(a => a.addEventListener('click', e => e.preventDefault()));
201
- } else {
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 (_wcRefreshInterval) clearInterval(_wcRefreshInterval);
209
- _wcRefreshInterval = setInterval(async () => {
210
- if (!document.getElementById('view-home')?.classList.contains('active')) return;
211
- try {
212
- const data = await fetch('/api/wc2026').then(r => r.json()).catch(() => null);
213
- if (data) {
214
- _wc2026Data = data;
215
- const wcEl = document.getElementById('wc-content');
216
- if (wcEl) {
217
- const activeTab = document.querySelector('.wc-tab.active');
218
- if (activeTab) {
219
- const txt = activeTab.textContent;
220
- if (txt.includes('Tin')) renderWCNews(wcEl);
221
- else if (txt.includes('Lịch')) renderWCFixtures(wcEl);
222
- else if (txt.includes('BXH')) renderWCStandings(wcEl);
223
- else if (txt.includes('Thống')) renderWCStats(wcEl);
224
  }
225
  }
226
  }
227
- } catch(e) {}
228
- }, 90000);
229
  }
230
- setTimeout(startWCLiveRefresh, 5000);
 
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);