bep40 commited on
Commit
5488128
·
verified ·
1 Parent(s): 2a0e039

Upload static/match_detail.js

Browse files
Files changed (1) hide show
  1. static/match_detail.js +91 -463
static/match_detail.js CHANGED
@@ -1,107 +1,54 @@
1
- // === VNEWS — Match Detail UI (v5) ===
2
- // Tabs: Thống (H2H stats, prediction, recent results) + Diễn biến (goals, cards, subs with player names)
3
  (function(){
4
  if(window._mdLoadedV5) return;
5
  window._mdLoadedV5 = true;
6
- if(window._mdLoaded) window._mdLoaded = true; // prevent v4 from loading too
7
 
8
  if(!document.getElementById('mdv5-css')){
9
  const s=document.createElement('style');
10
  s.id='mdv5-css';
11
  s.textContent=`
12
- /* === BASE === */
13
  .mdv5-sec{margin-bottom:14px}
14
- .mdv5-sec-title{font-size:12px;font-weight:800;color:#5cb87a;margin-bottom:6px;padding-bottom:4px;border-bottom:1px solid #2a2a2a;display:flex;align-items:center;gap:6px}
15
-
16
- /* === MATCH HEADER === */
17
  .mdv5-header{background:linear-gradient(135deg,#1a2a1f,#0d1117);border:1px solid #2d8659;border-radius:10px;padding:12px;text-align:center;margin-bottom:12px}
18
- .mdv5-league{font-size:9px;color:#5cb87a;font-weight:700;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px}
19
  .mdv5-teams{display:flex;align-items:center;justify-content:center;gap:12px}
20
  .mdv5-team{display:flex;flex-direction:column;align-items:center;gap:4px;flex:1}
21
  .mdv5-team img{width:44px;height:44px;object-fit:contain;background:#111;border-radius:50%;padding:4px;border:2px solid #333}
22
- .mdv5-team span{font-size:13px;color:#fff;text-align:center;font-weight:700}
23
- .mdv5-vs{min-width:70px;text-align:center;display:flex;flex-direction:column;align-items:center;gap:2px}
24
- .mdv5-vs .mdv5-score{font-size:26px;font-weight:900;color:#f0c040;line-height:1}
25
- .mdv5-vs .mdv5-status{font-size:9px;font-weight:700;padding:2px 10px;border-radius:4px;letter-spacing:.5px;margin-top:2px}
26
  .mdv5-vs .mdv5-status.finished{background:#2d8659;color:#a8ffb8}
27
- .mdv5-vs .mdv5-status.live{background:#c0392b;color:#fff;animation:mdv5pulse 1.5s ease-in-out infinite}
28
- .mdv5-vs .mdv5-status.halftime{background:#b7950b;color:#fff}
29
- .mdv5-info{display:flex;gap:12px;justify-content:center;margin-top:10px;flex-wrap:wrap}
30
- .mdv5-info-item{font-size:10px;color:#aaa;display:flex;align-items:center;gap:3px}
31
- .mdv5-info-item b{color:#ddd;font-size:11px}
32
-
33
- /* === TABS === */
34
  .mdv5-tabs{display:flex;gap:4px;margin-bottom:10px}
35
- .mdv5-tab-btn{flex:1;padding:9px 4px;border:none;border-radius:6px;font-size:11px;font-weight:700;cursor:pointer;transition:all .2s}
36
  .mdv5-tab-btn.active{background:#2d8659;color:#fff}
37
  .mdv5-tab-btn:not(.active){background:#1a1a1a;color:#888}
38
- .mdv5-tab-btn:not(.active):hover{background:#2a2a2a;color:#bbb}
39
  .mdv5-tab-content{display:none}
40
  .mdv5-tab-content.active{display:block}
41
-
42
- /* === DIỄN BIẾN === */
43
- .mdv5-timeline{position:relative}
44
- .mdv5-period{font-size:11px;font-weight:800;color:#5cb87a;padding:6px 0 4px 0;border-bottom:1px solid #2a2a2a;margin-bottom:2px;display:flex;align-items:center;gap:6px}
45
- .mdv5-period::before{content:"";display:inline-block;width:3px;height:12px;background:#5cb87a;border-radius:2px}
46
- .mdv5-ev{display:flex;align-items:flex-start;gap:8px;padding:7px 0;border-bottom:1px solid #1a1a1a;position:relative}
47
- .mdv5-ev-time{font-size:11px;font-weight:800;color:#f0c040;min-width:40px;flex-shrink:0;padding-top:1px;text-align:center}
48
- .mdv5-ev-icon{width:20px;height:20px;flex-shrink:0;display:flex;align-items:center;justify-content:center;margin-top:-1px}
49
- .mdv5-ev-icon svg{width:16px;height:16px}
50
- .mdv5-ev-body{flex:1;min-width:0}
51
- .mdv5-ev-team{display:inline-block;font-size:8px;font-weight:800;padding:1px 5px;border-radius:3px;margin-right:4px;vertical-align:middle}
52
- .mdv5-ev-team.home{background:#1a3a2a;color:#5cb87a;border:1px solid #2d5a3a}
53
- .mdv5-ev-team.away{background:#3a2a1a;color:#e85d04;border:1px solid #5a3a2a}
54
- .mdv5-ev-title{font-size:12px;color:#fff;font-weight:700;vertical-align:middle}
55
- .mdv5-ev-detail{font-size:11px;color:#999;margin-top:3px;line-height:1.5}
56
- .mdv5-ev-player{color:#ddd;font-weight:600}
57
- .mdv5-ev-out{color:#e85d04;font-weight:600}
58
- .mdv5-ev-in{color:#5cb87a;font-weight:600}
59
- .mdv5-ev-arrow{color:#555;margin:0 5px;font-size:12px}
60
- .mdv5-ev-assist{color:#888;font-size:10px;font-style:italic}
61
-
62
- /* === THỐNG KÊ === */
63
  .mdv5-stat-row{display:flex;align-items:center;gap:8px;padding:5px 0;font-size:11px;border-bottom:1px solid #1a1a1a}
64
- .mdv5-stat-home{width:36px;text-align:right;color:#5cb87a;font-weight:700;flex-shrink:0}
65
  .mdv5-stat-label{flex:1;text-align:center;color:#999;font-size:10px}
66
- .mdv5-stat-away{width:36px;text-align:left;color:#e85d04;font-weight:700;flex-shrink:0}
67
-
68
- /* === H2H STANDINGS === */
69
- .mdv5-h2h-row{display:flex;align-items:center;gap:6px;padding:6px 8px;background:#111;border-radius:6px;margin-bottom:3px;font-size:11px}
70
- .mdv5-h2h-pos{width:20px;text-align:center;color:#888;font-size:10px;flex-shrink:0}
71
- .mdv5-h2h-logo{width:20px;height:20px;object-fit:contain;flex-shrink:0}
72
- .mdv5-h2h-name{flex:1;color:#ddd;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}
73
- .mdv5-h2h-stats{display:flex;gap:6px;font-size:10px;color:#999}
74
- .mdv5-h2h-stat{min-width:18px;text-align:center}
75
-
76
- /* === PREDICTION === */
77
  .mdv5-pred{background:linear-gradient(135deg,#1a2a1f,#0d1117);border:1px solid #2d8659;border-radius:10px;padding:10px;margin-bottom:12px}
78
- .mdv5-pred-title{font-size:9px;color:#5cb87a;font-weight:700;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px;text-align:center}
79
  .mdv5-pred-teams{display:flex;align-items:center;justify-content:center;gap:8px}
80
- .mdv5-pred-team{display:flex;flex-direction:column;align-items:center;gap:3px}
81
- .mdv5-pred-team img{width:28px;height:28px;object-fit:contain}
82
- .mdv5-pred-team span{font-size:10px;color:#ddd;font-weight:600}
83
  .mdv5-pred-vs{font-size:14px;font-weight:800;color:#f0c040}
84
- .mdv5-pred-bar{display:flex;height:6px;border-radius:3px;overflow:hidden;margin-top:8px;background:#222}
85
- .mdv5-pred-bar-h{background:#5cb87a;height:100%}
86
- .mdv5-pred-bar-d{background:#666;height:100%}
87
- .mdv5-pred-bar-a{background:#e85d04;height:100%}
88
- .mdv5-pred-labels{display:flex;justify-content:space-between;margin-top:4px;font-size:9px;color:#999}
89
-
90
- /* === RECENT RESULTS === */
91
  .mdv5-recent-row{display:flex;align-items:center;gap:8px;padding:6px 8px;background:#111;border-radius:6px;margin-bottom:3px;font-size:11px}
92
- .mdv5-recent-date{font-size:9px;color:#888;min-width:55px;flex-shrink:0}
93
- .mdv5-recent-league{font-size:8px;color:#666;flex-shrink:0;max-width:60px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
94
- .mdv5-recent-teams{flex:1;display:flex;align-items:center;gap:4px;min-width:0}
95
- .mdv5-recent-home{text-align:right;flex:1;color:#ddd;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
96
- .mdv5-recent-away{text-align:left;flex:1;color:#ddd;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
97
- .mdv5-recent-score{font-size:11px;font-weight:800;color:#f0c040;min-width:36px;text-align:center;flex-shrink:0}
98
-
99
- /* === COMMON === */
100
  .mdv5-no-data{text-align:center;color:#666;padding:16px 8px;font-size:12px}
101
- .mdv5-err{color:#e85d04;font-size:12px;padding:8px;background:#2a1a1a;border-radius:6px;margin-bottom:6px;line-height:1.5}
102
- .mdv5-warn{color:#f0c040;font-size:12px;padding:8px;background:#2a2a1a;border-radius:6px;margin-bottom:6px;line-height:1.5}
103
  .mdv5-source{font-size:9px;color:#5cb87a;text-align:center;padding:3px;background:#1a2a1f;border-radius:4px;margin-bottom:8px}
104
- .mdv5-open-ext{display:inline-block;margin-top:10px;padding:8px 16px;background:#2d8659;color:#fff;border-radius:8px;text-decoration:none;font-size:12px;font-weight:600}
105
  @keyframes mdv5spin{to{transform:rotate(360deg)}}
106
  @keyframes mdv5pulse{0%,100%{opacity:1}50%{opacity:.5}}
107
  `;
@@ -109,447 +56,128 @@
109
  }
110
 
111
  function esc(s){const d=document.createElement('div');d.textContent=s||'';return d.innerHTML;}
112
- function sanitizeHtml(html){
113
- if(!html)return'';
114
- let h=html.replace(/style="[^"]*"/gi,'');
115
- h=h.replace(/<script[^>]*>.*?<\/script>/gi,'');
116
- h=h.replace(/<iframe[^>]*>.*?<\/iframe>/gi,'');
117
- h=h.replace(/<style[^>]*>.*?<\/style>/gi,'');
118
- h=h.replace(/on\w+="[^"]*"/gi,'');
119
- return h;
120
- }
121
-
122
- // === CLIENT-SIDE PARSER ===
123
- function parseBongdaHtml(html,eventId){
124
- try{
125
- const parser=new DOMParser();
126
- const doc=parser.parseFromString(html,'text/html');
127
- const result={event_id:eventId,found:false,sections:[]};
128
- function cl(s){return(s||'').replace(/\s+/g,' ').trim();}
129
-
130
- // 1. TEAMS + SCORE
131
- const teamsEl=doc.querySelector('.teams');
132
- if(teamsEl){
133
- const info={};
134
- const homeEl=teamsEl.querySelector('.team.home')||teamsEl.querySelector('[class*="home"]');
135
- if(homeEl){
136
- const ne=homeEl.querySelector('p:not(.logo)')||homeEl.querySelector('p');
137
- if(ne)info.home_team=cl(ne.textContent);
138
- const lo=homeEl.querySelector('img');
139
- if(lo)info.home_logo=lo.getAttribute('src')||'';
140
- }
141
- const awayEl=teamsEl.querySelector('.team.away')||teamsEl.querySelector('[class*="away"]');
142
- if(awayEl){
143
- const ne=awayEl.querySelector('p:not(.logo)')||awayEl.querySelector('p');
144
- if(ne)info.away_team=cl(ne.textContent);
145
- const lo=awayEl.querySelector('img');
146
- if(lo)info.away_logo=lo.getAttribute('src')||'';
147
- }
148
- const scoreEl=teamsEl.querySelector('.score');
149
- if(scoreEl){
150
- const parts=Array.from(scoreEl.querySelectorAll('p')).map(p=>cl(p.textContent));
151
- if(parts.length>=2)info.score=parts[0]+' - '+parts[1];
152
- const lb=scoreEl.querySelector('.label');
153
- if(lb)info.status_label=cl(lb.textContent);
154
- }
155
- if(info.home_team&&info.away_team){
156
- result.info=info;result.found=true;result.sections.push('info');
157
- }
158
- }
159
-
160
- // 2. MATCH INFO
161
- const mi=doc.querySelector('.match-info');
162
- if(mi){const te=mi.querySelector('.times')||mi.querySelector('li');if(te){result.info=result.info||{};result.info.datetime=cl(te.textContent);}}
163
-
164
- // 3. EVENTS (.events > .period > .event)
165
- const eventsData=[];
166
- const eventsDiv=doc.querySelector('.events');
167
- if(eventsDiv){
168
- let currentPeriod='';
169
- for(const child of eventsDiv.children){
170
- if(!child.tagName)continue;
171
- const cls=child.className||('');
172
- if(cls.includes('period')){
173
- const h2=child.querySelector('h2');
174
- if(h2)currentPeriod=cl(h2.textContent);
175
- for(const ev of child.children){
176
- if(!ev.tagName)continue;
177
- const evCls=ev.className||('');
178
- if(!evCls.includes('event'))continue;
179
- const team=evCls.includes('home')?'home':'away';
180
- const evData={team:team,period:currentPeriod,type:'unknown',time:'',players:'',player_in:'',player_out:'',scorer:'',assist:'',player:''};
181
-
182
- // Event type
183
- const typeEl=ev.querySelector('.event-type');
184
- if(typeEl){
185
- if(typeEl.querySelector('[class*="redcard"]')){evData.type='redcard';evData.card_type='red';}
186
- else if(typeEl.querySelector('[class*="yellowcard"]')){evData.type='yellowcard';evData.card_type='yellow';}
187
- else if(typeEl.querySelector('[class*="goal"]')){evData.type='goal';}
188
- else if(typeEl.querySelector('[class*="substitution"]')){evData.type='substitution';}
189
- else{
190
- const svgRects=typeEl.querySelectorAll('svg rect');
191
- for(const rect of svgRects){if(rect.getAttribute('fill')==='#E20007'){evData.type='redcard';evData.card_type='red';break;}}
192
- if(evData.type==='unknown'){
193
- const svgCircles=typeEl.querySelectorAll('svg circle');
194
- for(const c of svgCircles){if(c.getAttribute('fill')==='white'&&c.getAttribute('r')==='8'){evData.type='goal';break;}}
195
- }
196
- if(evData.type==='unknown'&&ev.querySelector('.players.subst'))evData.type='substitution';
197
- }
198
- }
199
- const playersEl=ev.querySelector('.players');
200
- if(playersEl&&evData.type==='unknown'){
201
- const pcls=playersEl.className||('');
202
- if(pcls.includes('goal'))evData.type='goal';
203
- else if(pcls.includes('card'))evData.type='redcard';
204
- else if(pcls.includes('subst'))evData.type='substitution';
205
- }
206
-
207
- // Time
208
- if(playersEl){
209
- const timeEl=playersEl.querySelector('.event-time');
210
- if(timeEl)evData.time=cl(timeEl.textContent);
211
- evData.players=cl(playersEl.textContent);
212
-
213
- const texts=[];
214
- playersEl.querySelectorAll('div').forEach(d=>{const t=cl(d.textContent);if(t&&t!==evData.time)texts.push(t);});
215
- playersEl.querySelectorAll('p').forEach(p=>{const t=cl(p.textContent);if(t&&!texts.includes(t))texts.push(t);});
216
-
217
- if(evData.type==='substitution'){
218
- if(texts.length>=2){evData.player_out=texts[0];evData.player_in=texts[1];}
219
- else if(texts.length===1){evData.player_in=texts[0];}
220
- }else if(evData.type==='goal'){
221
- if(texts.length>=1)evData.scorer=texts[0];
222
- if(texts.length>=2)evData.assist=texts[1];
223
- }else if(evData.type==='redcard'||evData.type==='yellowcard'){
224
- if(texts.length>=1)evData.player=texts.join(' ');
225
- }
226
- }
227
- eventsData.push(evData);
228
- }
229
- }
230
- }
231
- }
232
- if(eventsData.length>0){result.events=eventsData;result.sections.push('events');}
233
-
234
- // 4. PREDICTION
235
- const predCard=doc.querySelector('.prediction-card');
236
- if(predCard){
237
- const predData={};
238
- const teamInfo=predCard.querySelector('.team-info');
239
- if(teamInfo){
240
- const teams=teamInfo.querySelectorAll('.team');
241
- if(teams.length>=2){
242
- predData.home_name=cl(teams[0].querySelector('.team-name')?.textContent||'');
243
- predData.away_name=cl(teams[1].querySelector('.team-name')?.textContent||'');
244
- const divider=teamInfo.querySelector('.divider');
245
- if(divider)predData.result=cl(divider.textContent);
246
- }
247
- }
248
- const voteCount=predCard.querySelector('.vote-count');
249
- if(voteCount)predData.vote_count=cl(voteCount.textContent);
250
- result.prediction=predData;
251
- }
252
-
253
- // 5. RECENT MATCHES
254
- const recentMatches=[];
255
- const matchesList=doc.querySelector('.matches-list');
256
- if(matchesList){
257
- matchesList.querySelectorAll('.match-detail,.match-item,li').forEach(function(item){
258
- const dateEl=item.querySelector('.date,.time,.match-time');
259
- const leagueEl=item.querySelector('.league');
260
- const homeEl=item.querySelector('.home,.team-home');
261
- const awayEl=item.querySelector('.away,.team-away');
262
- const scoreEl=item.querySelector('.score,.result');
263
- if(homeEl||awayEl){
264
- recentMatches.push({date:cl(dateEl?.textContent||''),league:cl(leagueEl?.textContent||''),home:cl(homeEl?.textContent||''),away:cl(awayEl?.textContent||''),score:cl(scoreEl?.textContent||'vs')});
265
- }
266
- });
267
- }
268
- if(recentMatches.length>0){result.recent_matches=recentMatches;result.sections.push('recent');}
269
-
270
- // 6. H2H STANDINGS
271
- const h2hData=[];
272
- const h2hEl=doc.querySelector('.h2h-standings');
273
- if(h2hEl){
274
- h2hEl.querySelectorAll('tr').forEach(function(row){
275
- const cells=row.querySelectorAll('td');
276
- if(cells.length>=4){
277
- const logo=row.querySelector('img');
278
- const nameEl=row.querySelector('.team-name,p.link,.name');
279
- h2hData.push({pos:cl(cells[0]?.textContent||''),logo:logo?.getAttribute('src')||'',name:cl(nameEl?.textContent||''),played:cl(cells[1]?.textContent||''),wins:cl(cells[2]?.textContent||''),draws:cl(cells[3]?.textContent||''),losses:cl(cells[4]?.textContent||''),gf:cl(cells[5]?.textContent||''),ga:cl(cells[6]?.textContent||''),points:cl(cells[8]?.textContent||'')});
280
- }
281
- });
282
- }
283
- if(h2hData.length>0){result.h2h_standings=h2hData;result.sections.push('h2h_standings');}
284
-
285
- return result;
286
- }catch(e){
287
- return{event_id:eventId,found:false,sections:[],error:e.message};
288
- }
289
- }
290
 
291
- // === FETCH HTML ===
292
- async function fetchBongdaHTML(eventId,matchUrl){
293
- const urls=[];
294
- if(matchUrl){const clean=matchUrl.replace(/\/(preview|centre)\/.*/,'');urls.push(clean+'/centre/',clean+'/preview/');}
295
- urls.push('https://bongda.com.vn/tran-dau/'+eventId+'/centre/','https://bongda.com.vn/tran-dau/'+eventId+'/preview/');
296
- for(const url of urls){
297
- try{
298
- const r=await fetch(url,{signal:AbortSignal.timeout(12000),headers:{'Accept':'text/html'}});
299
- if(r.ok){const html=await r.text();if(html.length>1000&&html.includes('.teams'))return{html,url,method:'direct'};}
300
- }catch(e){}
301
- }
302
- for(const url of urls){
303
- for(const proxy of['https://api.allorigins.win/raw?url=','https://corsproxy.io/?']){
304
- try{
305
- const r=await fetch(proxy+encodeURIComponent(url),{signal:AbortSignal.timeout(15000)});
306
- if(r.ok){const html=await r.text();if(html.length>1000&&html.includes('.teams'))return{html,url,method:'proxy'};}
307
- }catch(e){}
308
- }
309
- }
310
- return null;
311
- }
312
-
313
- // === MAIN LOAD FUNCTION ===
314
  window.loadMatchTab=async function(tab){
315
- if(tab!=='detail'){if(_origLoadMatchTab)_origLoadMatchTab.call(this,tab);return;}
316
- document.querySelectorAll('.mo-tab').forEach(function(t){t.classList.remove('active');});
317
- document.querySelectorAll('.mo-tab').forEach(function(t){const txt=t.textContent||'';if(txt.includes('Chi tiết')||txt.includes('📋')||txt.includes('Thống kê')||txt.includes('Diễn biến'))t.classList.add('active');});
318
  const el=document.getElementById('mo-body');
319
  if(!el)return;
320
- el.innerHTML='<div class="mdv5-no-data"><div style="display:inline-block;width:20px;height:20px;border:2px solid #333;border-top-color:#5cb87a;border-radius:50%;animation:mdv5spin .8s linear infinite;vertical-align:middle;margin-right:8px"></div>Đang tải chi tiết trận đấu...</div>';
321
  const eventId=window._currentEventId;
322
- const matchUrl=window._currentMatchUrl;
323
- if(!eventId){el.innerHTML='<div class="mdv5-err">⚠️ Không tìm thấy mã trận đấu.</div>';return;}
324
-
325
- let data=null;
326
 
327
- // Strategy 1: Server /live endpoint
328
  try{
329
- let apiUrl='/api/match/'+eventId+'/live';
330
- if(matchUrl)apiUrl+='?page_url='+encodeURIComponent(matchUrl);
331
- const r=await fetch(apiUrl,{signal:AbortSignal.timeout(15000)});
332
- if(r.ok){data=await r.json();if(!data.found||!data.info||!data.info.home_team)data=null;}
333
- }catch(e){}
334
-
335
- // Strategy 2: Server /detail endpoint
336
- if(!data){
337
- try{
338
- let apiUrl='/api/match/'+eventId+'/detail';
339
- if(matchUrl)apiUrl+='?url='+encodeURIComponent(matchUrl);
340
- const r=await fetch(apiUrl,{signal:AbortSignal.timeout(15000)});
341
- if(r.ok){data=await r.json();if(!data.found||!data.info||!data.info.home_team)data=null;}
342
- }catch(e){}
343
- }
344
-
345
- // Strategy 3: Client-side scraping
346
- if(!data||!data.found){
347
- const fetched=await fetchBongdaHTML(eventId,matchUrl);
348
- if(fetched){
349
- data=parseBongdaHtml(fetched.html,eventId);
350
- if(data.found){el.innerHTML='<div class="mdv5-source">📡 Dữ liệu tải trực tiếp từ bongda.com.vn'+(fetched.method!=='direct'?' (qua proxy)':'')+'</div>';}
351
  }
352
- }
353
-
354
- if(data&&data.found&&data.info&&data.info.home_team){
355
- renderMatchDetail(el,data);
356
- }else{
357
- const bongdaLink=matchUrl||'https://bongda.com.vn/tran-dau/'+eventId+'/centre/';
358
- el.innerHTML='<div class="mdv5-warn">⚠️ Không thể tải dữ liệu chi tiết.<br><small>Event ID: '+esc(String(eventId))+'</small><br><a class="mdv5-open-ext" href="'+esc(bongdaLink)+'" target="_blank" rel="noopener">🔗 Xem trên bongda.com.vn</a></div>';
359
  }
360
  };
361
 
362
- // === RENDER ===
363
- function renderMatchDetail(el,data){
364
  const info=data.info||{};
365
  let h='';
366
 
367
- // HEADER: Teams + Score
368
- h+='<div class="mdv5-header">';
369
- if(info.league)h+='<div class="mdv5-league">'+esc(info.league)+'</div>';
370
- h+='<div class="mdv5-teams">';
371
  h+='<div class="mdv5-team">';
372
- if(info.home_logo)h+='<img src="'+esc(info.home_logo)+'" alt="'+esc(info.home_team)+'" onerror="this.style.display=\'none\'">';
373
- h+='<span>'+esc(info.home_team)+'</span></div>';
374
  h+='<div class="mdv5-vs">';
375
- if(info.score)h+='<span class="mdv5-score">'+esc(info.score)+'</span>';
376
- else h+='<span style="font-size:14px;color:#888">VS</span>';
377
  if(info.status_label){
378
  const sl=info.status_label;let cls='finished';
379
  if(sl==='LIVE'||sl==='H1'||sl==='H2')cls='live';
380
  if(sl==='HT'||sl==='Nghỉ')cls='halftime';
381
- h+='<span class="mdv5-status '+cls+'">'+esc(sl)+'</span>';
382
  }
383
- h+='</div>';
384
- h+='<div class="mdv5-team">';
385
- if(info.away_logo)h+='<img src="'+esc(info.away_logo)+'" alt="'+esc(info.away_team)+'" onerror="this.style.display=\'none\'">';
386
- h+='<span>'+esc(info.away_team)+'</span></div>';
387
- h+='</div>';
388
- const infoItems=[];
389
- if(info.datetime)infoItems.push('📅 <b>'+esc(info.datetime)+'</b>');
390
- if(info.stadium)infoItems.push('🏟 <b>'+esc(info.stadium)+'</b>');
391
- if(info.referee)infoItems.push('👨‍⚖️ <b>'+esc(info.referee)+'</b>');
392
- if(infoItems.length>0){h+='<div class="mdv5-info">';infoItems.forEach(i=>{h+='<span class="mdv5-info-item">'+i+'</span>';});h+='</div>';}
393
- h+='</div>';
394
 
395
- // DETERMINE TAB VISIBILITY
396
  const hasEvents=data.events&&data.events.length>0;
397
  const hasStats=(data.h2h_stats_parsed&&Object.keys(data.h2h_stats_parsed).length>0)||data.prediction||data.recent_matches;
398
- const showBoth=hasEvents&&hasStats;
399
-
400
- // TABS
401
- if(showBoth){
402
  h+='<div class="mdv5-tabs">';
403
- h+='<button class="mdv5-tab-btn active" onclick="window._mdv5ShowTab(\'stats\',this)">📊 Thống kê</button>';
404
- h+='<button class="mdv5-tab-btn" onclick="window._mdv5ShowTab(\'events\',this)">📢 Diễn biến</button>';
405
  h+='</div>';
406
  }
407
 
408
- // === TAB: THỐNG KÊ ===
409
  if(hasStats){
410
- h+='<div id="mdv5-tab-stats" class="mdv5-tab-content'+(showBoth?'':' active')+'">';
411
-
412
- // H2H Stats from API
413
- if(data.h2h_stats_parsed&&Object.keys(data.h2h_stats_parsed).length>0){
414
- const parsed=data.h2h_stats_parsed;
415
- const nz=Object.values(parsed).some(s=>s.home!=="0"||s.away!=="0");
416
- if(nz){
417
- h+='<div class="mdv5-sec"><div class="mdv5-sec-title">📊 Tỷ lệ đối đầu</div>';
418
- for(const label in parsed){
419
- const sv=parsed[label];
420
- h+='<div class="mdv5-stat-row"><span class="mdv5-stat-home">'+esc(sv.home)+'</span><span class="mdv5-stat-label">'+esc(label)+'</span><span class="mdv5-stat-away">'+esc(sv.away)+'</span></div>';
421
- }
422
- h+='</div>';
423
  }
424
- }
425
-
426
- // H2H Standings
427
- if(data.h2h_standings&&data.h2h_standings.length>0){
428
- h+='<div class="mdv5-sec"><div class="mdv5-sec-title">🏆 Bảng xếp hạng đối đầu</div>';
429
- data.h2h_standings.forEach(function(row){
430
- h+='<div class="mdv5-h2h-row">';
431
- h+='<span class="mdv5-h2h-pos">'+esc(row.pos)+'</span>';
432
- if(row.logo)h+='<img class="mdv5-h2h-logo" src="'+esc(row.logo)+'" alt="">';
433
- h+='<span class="mdv5-h2h-name">'+esc(row.name)+'</span>';
434
- h+='<div class="mdv5-h2h-stats">';
435
- if(row.played)h+='<span class="mdv5-h2h-stat">'+esc(row.played)+'</span>';
436
- if(row.wins)h+='<span class="mdv5-h2h-stat">'+esc(row.wins)+'</span>';
437
- if(row.draws)h+='<span class="mdv5-h2h-stat">'+esc(row.draws)+'</span>';
438
- if(row.losses)h+='<span class="mdv5-h2h-stat">'+esc(row.losses)+'</span>';
439
- if(row.points)h+='<span class="mdv5-h2h-stat"><b>'+esc(row.points)+'</b></span>';
440
- h+='</div></div>';
441
- });
442
  h+='</div>';
443
  }
444
 
445
- // Prediction
446
  if(data.prediction){
447
- const p=data.prediction;
448
- h+='<div class="mdv5-pred">';
449
- h+='<div class="mdv5-pred-title">🎯 Dự đoán</div>';
450
- h+='<div class="mdv5-pred-teams">';
451
- h+='<div class="mdv5-pred-team"><span>'+esc(p.home_name||'')+'</span></div>';
452
- h+='<div class="mdv5-pred-vs">'+esc(p.result||'VS')+'</div>';
453
- h+='<div class="mdv5-pred-team"><span>'+esc(p.away_name||'')+'</span></div>';
454
- h+='</div>';
455
- if(p.vote_count)h+='<div style="text-align:center;font-size:9px;color:#888;margin-top:6px">Tổng bình chọn: '+esc(p.vote_count)+' lượt</div>';
456
- h+='</div>';
457
  }
458
 
459
- // Recent matches
460
- if(data.recent_matches&&data.recent_matches.length>0){
461
  h+='<div class="mdv5-sec"><div class="mdv5-sec-title">📋 Kết quả gần nhất</div>';
462
- data.recent_matches.slice(0,8).forEach(function(m){
463
- h+='<div class="mdv5-recent-row">';
464
- h+='<span class="mdv5-recent-date">'+esc(m.date||'')+'</span>';
465
- if(m.league)h+='<span class="mdv5-recent-league">'+esc(m.league)+'</span>';
466
- h+='<div class="mdv5-recent-teams">';
467
- h+='<span class="mdv5-recent-home">'+esc(m.home||'')+'</span>';
468
- h+='<span class="mdv5-recent-score">'+esc(m.score||'')+'</span>';
469
- h+='<span class="mdv5-recent-away">'+esc(m.away||'')+'</span>';
470
- h+='</div></div>';
471
  });
472
  h+='</div>';
473
  }
474
-
475
  h+='</div>';
476
  }
477
 
478
- // === TAB: DIỄN BIẾN ===
479
  if(hasEvents){
480
- h+='<div id="mdv5-tab-events" class="mdv5-tab-content'+(showBoth&&!hasStats?' active':'')+'">';
481
- h+='<div class="mdv5-sec"><div class="mdv5-sec-title">📢 Diễn biến chi tiết</div>';
482
- h+='<div class="mdv5-timeline">';
483
  let lastPeriod='';
484
- data.events.forEach(function(ev){
485
  if(ev.period&&ev.period!==lastPeriod){
486
- h+='<div class="mdv5-period">'+esc(ev.period)+'</div>';
487
  lastPeriod=ev.period;
488
  }
489
  const isHome=ev.team==='home';
490
- const teamBadge=isHome?'<span class="mdv5-ev-team home">HOME</span>':'<span class="mdv5-ev-team away">AWAY</span>';
491
- let iconHtml='';
492
- let titleHtml='';
493
- let detailHtml='';
494
-
495
- if(ev.type==='goal'){
496
- iconHtml='<span class="mdv5-ev-icon">⚽</span>';
497
- titleHtml='<span class="mdv5-ev-title">BÀN THẮNG</span>';
498
- if(ev.scorer)detailHtml='<span class="mdv5-ev-player">'+esc(ev.scorer)+'</span>';
499
- if(ev.assist)detailHtml+=' <span class="mdv5-ev-assist">('+esc(ev.assist)+')</span>';
500
- }else if(ev.type==='redcard'){
501
- iconHtml='<span class="mdv5-ev-icon"><svg viewBox="0 0 14 20" width="16" height="16"><rect fill="#E20007" height="20" rx="2" width="14"></rect></svg></span>';
502
- titleHtml='<span class="mdv5-ev-title">THỺ ĐỎ</span>';
503
- if(ev.player)detailHtml='<span class="mdv5-ev-player">'+esc(ev.player)+'</span>';
504
- }else if(ev.type==='yellowcard'){
505
- iconHtml='<span class="mdv5-ev-icon"><svg viewBox="0 0 14 20" width="16" height="16"><rect fill="#F0C040" height="20" rx="2" width="14"></rect></svg></span>';
506
- titleHtml='<span class="mdv5-ev-title">THỺ VÀNG</span>';
507
- if(ev.player)detailHtml='<span class="mdv5-ev-player">'+esc(ev.player)+'</span>';
508
- }else if(ev.type==='substitution'){
509
- iconHtml='<span class="mdv5-ev-icon">↔️</span>';
510
- titleHtml='<span class="mdv5-ev-title">THAY ĐỔI</span>';
511
- if(ev.player_out&&ev.player_in){
512
- detailHtml='<span class="mdv5-ev-out">'+esc(ev.player_out)+'</span>';
513
- detailHtml+='<span class="mdv5-ev-arrow">↓↑</span>';
514
- detailHtml+='<span class="mdv5-ev-in">'+esc(ev.player_in)+'</span>';
515
- }else if(ev.players){
516
- detailHtml='<span class="mdv5-ev-player">'+esc(ev.players)+'</span>';
517
- }
518
- }else{
519
- iconHtml='<span class="mdv5-ev-icon">•</span>';
520
- if(ev.players)titleHtml='<span class="mdv5-ev-title">'+esc(ev.players)+'</span>';
521
- }
522
-
523
- h+='<div class="mdv5-ev">';
524
- h+='<span class="mdv5-ev-time">'+esc(ev.time||'')+'</span>';
525
- h+=iconHtml;
526
- h+='<div class="mdv5-ev-body">';
527
- h+=teamBadge+' '+titleHtml;
528
- if(detailHtml)h+='<div class="mdv5-ev-detail">'+detailHtml+'</div>';
529
  h+='</div></div>';
530
  });
531
  h+='</div></div>';
532
- h+='</div>';
533
- }
534
-
535
- // Raw HTML fallbacks
536
- if(data.stats_html&&data.stats_html.length>10){
537
- h+='<div class="mdv5-sec"><div class="mdv5-sec-title">🌟 Thông số bổ sung</div><div class="md-raw-html">'+sanitizeHtml(data.stats_html)+'</div></div>';
538
  }
539
 
540
  el.innerHTML=h;
541
 
542
- // Tab switching
543
- window._mdv5ShowTab=function(tab,btn){
544
  document.querySelectorAll('.mdv5-tab-btn').forEach(b=>{b.classList.remove('active');b.style.background='#1a1a1a';b.style.color='#888';});
545
  if(btn){btn.classList.add('active');btn.style.background='#2d8659';btn.style.color='#fff';}
546
- const statsEl=document.getElementById('mdv5-tab-stats');
547
- const eventsEl=document.getElementById('mdv5-tab-events');
548
- if(statsEl)statsEl.style.display=tab==='stats'?'block':'none';
549
- if(eventsEl)eventsEl.style.display=tab==='events'?'block':'none';
550
  };
551
-
552
- el.querySelectorAll('a').forEach(function(a){a.addEventListener('click',function(e){e.preventDefault();});});
553
  }
554
 
555
  function injectDetailTab(){
@@ -558,11 +186,11 @@
558
  const d=document.createElement('span');
559
  d.className='mo-tab mo-tab-detail';
560
  d.textContent='📋 Chi tiết';
561
- d.onclick=function(){window.loadMatchTab('detail');};
562
  tabs.insertBefore(d,tabs.firstChild);
563
  }
564
 
565
- document.addEventListener('click',function(e){
566
  const md=e.target.closest('.match-detail');
567
  if(!md)return;
568
  const link=md.querySelector('.status a, .teams a[href*="tran-dau"], a[href*="tran-dau"]');
@@ -576,6 +204,6 @@
576
  }
577
  },true);
578
 
579
- if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',function(){setTimeout(injectDetailTab,1000);});
580
  else setTimeout(injectDetailTab,1000);
581
- })();
 
1
+ // === VNEWS — Match Detail UI (v5-fast) ===
2
+ // Uses /api/proxy/bongda for fast server-side scraping
3
  (function(){
4
  if(window._mdLoadedV5) return;
5
  window._mdLoadedV5 = true;
 
6
 
7
  if(!document.getElementById('mdv5-css')){
8
  const s=document.createElement('style');
9
  s.id='mdv5-css';
10
  s.textContent=`
 
11
  .mdv5-sec{margin-bottom:14px}
12
+ .mdv5-sec-title{font-size:12px;font-weight:800;color:#5cb87a;margin-bottom:6px;padding-bottom:4px;border-bottom:1px solid #2a2a2a}
 
 
13
  .mdv5-header{background:linear-gradient(135deg,#1a2a1f,#0d1117);border:1px solid #2d8659;border-radius:10px;padding:12px;text-align:center;margin-bottom:12px}
 
14
  .mdv5-teams{display:flex;align-items:center;justify-content:center;gap:12px}
15
  .mdv5-team{display:flex;flex-direction:column;align-items:center;gap:4px;flex:1}
16
  .mdv5-team img{width:44px;height:44px;object-fit:contain;background:#111;border-radius:50%;padding:4px;border:2px solid #333}
17
+ .mdv5-vs .mdv5-score{font-size:26px;font-weight:900;color:#f0c040}
18
+ .mdv5-vs .mdv5-status{font-size:9px;font-weight:700;padding:2px 10px;border-radius:4px}
 
 
19
  .mdv5-vs .mdv5-status.finished{background:#2d8659;color:#a8ffb8}
20
+ .mdv5-vs .mdv5-status.live{background:#c0392b;color:#fff;animation:mdv5pulse 1.5s infinite}
 
 
 
 
 
 
21
  .mdv5-tabs{display:flex;gap:4px;margin-bottom:10px}
22
+ .mdv5-tab-btn{flex:1;padding:9px;border:none;border-radius:6px;font-size:11px;font-weight:700;cursor:pointer}
23
  .mdv5-tab-btn.active{background:#2d8659;color:#fff}
24
  .mdv5-tab-btn:not(.active){background:#1a1a1a;color:#888}
 
25
  .mdv5-tab-content{display:none}
26
  .mdv5-tab-content.active{display:block}
27
+ .mdv5-timeline{}
28
+ .mdv5-period{font-size:11px;font-weight:800;color:#5cb87a;padding:6px 0;border-bottom:1px solid #2a2a2a;margin-bottom:2px}
29
+ .mdv5-ev{display:flex;align-items:flex-start;gap:8px;padding:7px 0;border-bottom:1px solid #1a1a1a}
30
+ .mdv5-ev-time{font-size:11px;font-weight:800;color:#f0c040;min-width:40px;text-align:center}
31
+ .mdv5-ev-icon{width:20px;height:20px;display:flex;align-items:center;justify-content:center}
32
+ .mdv5-ev-body{flex:1}
33
+ .mdv5-ev-team{font-size:8px;font-weight:800;padding:1px 5px;border-radius:3px;margin-right:4px}
34
+ .mdv5-ev-team.home{background:#1a3a2a;color:#5cb87a}
35
+ .mdv5-ev-team.away{background:#3a2a1a;color:#e85d04}
36
+ .mdv5-ev-title{font-size:12px;color:#fff;font-weight:700}
37
+ .mdv5-ev-detail{font-size:11px;color:#999;margin-top:3px}
 
 
 
 
 
 
 
 
 
 
 
38
  .mdv5-stat-row{display:flex;align-items:center;gap:8px;padding:5px 0;font-size:11px;border-bottom:1px solid #1a1a1a}
39
+ .mdv5-stat-home{width:36px;text-align:right;color:#5cb87a;font-weight:700}
40
  .mdv5-stat-label{flex:1;text-align:center;color:#999;font-size:10px}
41
+ .mdv5-stat-away{width:36px;text-align:left;color:#e85d04;font-weight:700}
 
 
 
 
 
 
 
 
 
 
42
  .mdv5-pred{background:linear-gradient(135deg,#1a2a1f,#0d1117);border:1px solid #2d8659;border-radius:10px;padding:10px;margin-bottom:12px}
43
+ .mdv5-pred-title{font-size:9px;color:#5cb87a;font-weight:700;text-align:center;margin-bottom:8px}
44
  .mdv5-pred-teams{display:flex;align-items:center;justify-content:center;gap:8px}
 
 
 
45
  .mdv5-pred-vs{font-size:14px;font-weight:800;color:#f0c040}
 
 
 
 
 
 
 
46
  .mdv5-recent-row{display:flex;align-items:center;gap:8px;padding:6px 8px;background:#111;border-radius:6px;margin-bottom:3px;font-size:11px}
47
+ .mdv5-recent-date{font-size:9px;color:#888;min-width:55px}
48
+ .mdv5-recent-score{font-size:11px;font-weight:800;color:#f0c040;min-width:36px;text-align:center}
 
 
 
 
 
 
49
  .mdv5-no-data{text-align:center;color:#666;padding:16px 8px;font-size:12px}
50
+ .mdv5-warn{color:#f0c040;font-size:12px;padding:8px;background:#2a2a1a;border-radius:6px}
 
51
  .mdv5-source{font-size:9px;color:#5cb87a;text-align:center;padding:3px;background:#1a2a1f;border-radius:4px;margin-bottom:8px}
 
52
  @keyframes mdv5spin{to{transform:rotate(360deg)}}
53
  @keyframes mdv5pulse{0%,100%{opacity:1}50%{opacity:.5}}
54
  `;
 
56
  }
57
 
58
  function esc(s){const d=document.createElement('div');d.textContent=s||'';return d.innerHTML;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  window.loadMatchTab=async function(tab){
61
+ if(tab!=='detail'){if(window._origLoadMatchTab)window._origLoadMatchTab.call(this,tab);return;}
62
+ document.querySelectorAll('.mo-tab').forEach(t=>t.classList.remove('active'));
63
+ document.querySelectorAll('.mo-tab').forEach(t=>{const txt=t.textContent||'';if(txt.includes('Chi tiết')||txt.includes('📋')||txt.includes('Thống kê')||txt.includes('Diễn biến'))t.classList.add('active');});
64
  const el=document.getElementById('mo-body');
65
  if(!el)return;
66
+ el.innerHTML='<div class="mdv5-no-data"><div style="display:inline-block;width:20px;height:20px;border:2px solid #333;border-top-color:#5cb87a;border-radius:50%;animation:mdv5spin .8s linear infinite"></div><div style="margin-top:8px">Đang tải...</div></div>';
67
  const eventId=window._currentEventId;
68
+ if(!eventId){el.innerHTML='<div class="mdv5-warn">⚠️ Không tìm thấy mã trận đấu.</div>';return;}
 
 
 
69
 
 
70
  try{
71
+ const r = await fetch('/api/proxy/bongda?event_id=' + eventId, {signal: AbortSignal.timeout(20000)});
72
+ const data = await r.json();
73
+ if(data.found && data.info && data.info.home_team){
74
+ el.innerHTML = '<div class="mdv5-source">📡 Dữ liệu tải nhanh</div>';
75
+ renderMatchDetail(el, data);
76
+ }else{
77
+ el.innerHTML = '<div class="mdv5-warn">⚠️ Không thể tải dữ liệu.<br>Event ID: ' + esc(String(eventId)) + '<br><a href="https://bongda.com.vn/tran-dau/' + eventId + '/centre/" target="_blank" class="mdv5-open-ext">Xem trên bongda.com.vn</a></div>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  }
79
+ }catch(e){
80
+ el.innerHTML = '<div class="mdv5-warn">⚠️ Lỗi tải dữ liệu: ' + esc(e.message) + '</div>';
 
 
 
 
 
81
  }
82
  };
83
 
84
+ function renderMatchDetail(el, data){
 
85
  const info=data.info||{};
86
  let h='';
87
 
88
+ // Header
89
+ h+='<div class="mdv5-header"><div class="mdv5-teams">';
 
 
90
  h+='<div class="mdv5-team">';
91
+ if(info.home_logo)h+='<img src="' + esc(info.home_logo) + '" alt="' + esc(info.home_team) + '">';
92
+ h+='<span>' + esc(info.home_team) + '</span></div>';
93
  h+='<div class="mdv5-vs">';
94
+ if(info.score)h+='<span class="mdv5-score">' + esc(info.score) + '</span>';
 
95
  if(info.status_label){
96
  const sl=info.status_label;let cls='finished';
97
  if(sl==='LIVE'||sl==='H1'||sl==='H2')cls='live';
98
  if(sl==='HT'||sl==='Nghỉ')cls='halftime';
99
+ h+='<span class="mdv5-status ' + cls + '">' + esc(sl) + '</span>';
100
  }
101
+ h+='</div><div class="mdv5-team">';
102
+ if(info.away_logo)h+='<img src="' + esc(info.away_logo) + '" alt="' + esc(info.away_team) + '">';
103
+ h+='<span>' + esc(info.away_team) + '</span></div></div></div>';
 
 
 
 
 
 
 
 
104
 
105
+ // Tabs
106
  const hasEvents=data.events&&data.events.length>0;
107
  const hasStats=(data.h2h_stats_parsed&&Object.keys(data.h2h_stats_parsed).length>0)||data.prediction||data.recent_matches;
108
+
109
+ if(hasStats||hasEvents){
 
 
110
  h+='<div class="mdv5-tabs">';
111
+ if(hasStats)h+='<button class="mdv5-tab-btn active" onclick="window._mdv5ShowTab(\'stats\',this)">📊 Thống kê</button>';
112
+ if(hasEvents)h+='<button class="mdv5-tab-btn" onclick="window._mdv5ShowTab(\'events\',this)">📢 Diễn biến</button>';
113
  h+='</div>';
114
  }
115
 
116
+ // Stats tab
117
  if(hasStats){
118
+ h+='<div id="mdv5-tab-stats" class="mdv5-tab-content" style="display:' + (hasEvents?'none':'block') + '">';
119
+
120
+ if(data.h2h_stats_parsed){
121
+ h+='<div class="mdv5-sec"><div class="mdv5-sec-title">📊 Tỷ lệ đối đầu</div>';
122
+ for(const label in data.h2h_stats_parsed){
123
+ const sv=data.h2h_stats_parsed[label];
124
+ h+='<div class="mdv5-stat-row"><span class="mdv5-stat-home">' + esc(sv.home) + '</span><span class="mdv5-stat-label">' + esc(label) + '</span><span class="mdv5-stat-away">' + esc(sv.away) + '</span></div>';
 
 
 
 
 
 
125
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  h+='</div>';
127
  }
128
 
 
129
  if(data.prediction){
130
+ h+='<div class="mdv5-pred"><div class="mdv5-pred-title">🎯 Dự đoán</div><div class="mdv5-pred-teams">';
131
+ h+='<span>' + esc(data.prediction.home_name || '') + '</span><span class="mdv5-pred-vs">' + esc(data.prediction.result || 'VS') + '</span><span>' + esc(data.prediction.away_name || '') + '</span></div></div>';
 
 
 
 
 
 
 
 
132
  }
133
 
134
+ if(data.recent_matches){
 
135
  h+='<div class="mdv5-sec"><div class="mdv5-sec-title">📋 Kết quả gần nhất</div>';
136
+ data.recent_matches.slice(0,6).forEach(m=>{
137
+ h+='<div class="mdv5-recent-row"><span class="mdv5-recent-date">' + esc(m.date) + '</span><span>' + esc(m.home) + '</span><span class="mdv5-recent-score">' + esc(m.score) + '</span><span>' + esc(m.away) + '</span></div>';
 
 
 
 
 
 
 
138
  });
139
  h+='</div>';
140
  }
 
141
  h+='</div>';
142
  }
143
 
144
+ // Events tab
145
  if(hasEvents){
146
+ h+='<div id="mdv5-tab-events" class="mdv5-tab-content" style="display:' + (hasStats?'none':'block') + '">';
147
+ h+='<div class="mdv5-sec"><div class="mdv5-sec-title">📢 Diễn biến trận đấu</div><div class="mdv5-timeline">';
 
148
  let lastPeriod='';
149
+ data.events.forEach(ev=>{
150
  if(ev.period&&ev.period!==lastPeriod){
151
+ h+='<div class="mdv5-period">' + esc(ev.period) + '</div>';
152
  lastPeriod=ev.period;
153
  }
154
  const isHome=ev.team==='home';
155
+ const badge=isHome?'<span class="mdv5-ev-team home">HOME</span>':'<span class="mdv5-ev-team away">AWAY</span>';
156
+ let icon='', title='', detail='';
157
+
158
+ if(ev.type==='goal'){icon='⚽';title='BÀN THẮNG';detail=esc(ev.scorer||ev.players);}
159
+ else if(ev.type==='redcard'){icon='🟥';title='THỺ ĐỎ';detail=esc(ev.player||ev.players);}
160
+ else if(ev.type==='yellowcard'){icon='🟨';title='THỺ VÀNG';detail=esc(ev.player||ev.players);}
161
+ else if(ev.type==='substitution'){icon='↔️';title='THAY ĐỔI';detail=esc(ev.players);}
162
+ else{icon='•';detail=esc(ev.players);}
163
+
164
+ h+='<div class="mdv5-ev"><span class="mdv5-ev-time">' + esc(ev.time||'') + '</span><span class="mdv5-ev-icon">' + icon + '</span><div class="mdv5-ev-body">' + badge + ' <span class="mdv5-ev-title">' + title + '</span>';
165
+ if(detail)h+='<div class="mdv5-ev-detail">' + detail + '</div>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  h+='</div></div>';
167
  });
168
  h+='</div></div>';
 
 
 
 
 
 
169
  }
170
 
171
  el.innerHTML=h;
172
 
173
+ window._mdv5ShowTab=(tab,btn)=>{
 
174
  document.querySelectorAll('.mdv5-tab-btn').forEach(b=>{b.classList.remove('active');b.style.background='#1a1a1a';b.style.color='#888';});
175
  if(btn){btn.classList.add('active');btn.style.background='#2d8659';btn.style.color='#fff';}
176
+ const stats=document.getElementById('mdv5-tab-stats');
177
+ const events=document.getElementById('mdv5-tab-events');
178
+ if(stats)stats.style.display=tab==='stats'?'block':'none';
179
+ if(events)events.style.display=tab==='events'?'block':'none';
180
  };
 
 
181
  }
182
 
183
  function injectDetailTab(){
 
186
  const d=document.createElement('span');
187
  d.className='mo-tab mo-tab-detail';
188
  d.textContent='📋 Chi tiết';
189
+ d.onclick=()=>window.loadMatchTab('detail');
190
  tabs.insertBefore(d,tabs.firstChild);
191
  }
192
 
193
+ document.addEventListener('click',e=>{
194
  const md=e.target.closest('.match-detail');
195
  if(!md)return;
196
  const link=md.querySelector('.status a, .teams a[href*="tran-dau"], a[href*="tran-dau"]');
 
204
  }
205
  },true);
206
 
207
+ if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',()=>setTimeout(injectDetailTab,1000));
208
  else setTimeout(injectDetailTab,1000);
209
+ })();