Spaces:
Running
Running
Complete WC Shorts TikTok-style slideshow with autoplay, title overlay, and smooth scroll
Browse files- static/wc2026_v2.js +187 -162
static/wc2026_v2.js
CHANGED
|
@@ -3,46 +3,10 @@ let _wc2026Data=null;let _wcRefreshInterval=null;
|
|
| 3 |
const WC_TEAMS={'Mexico':'🇲🇽 Mexico','Colombia':'🇨🇴 Colombia','USA':'🇺🇸 Mỹ','United States':'🇺🇸 Mỹ','Brazil':'🇧🇷 Brazil','Argentina':'🇦🇷 Argentina','Germany':'🇩🇪 Đức','France':'🇫🇷 Pháp','Spain':'🇪🇸 Tây Ban Nha','England':'🏴 Anh','Portugal':'🇵🇹 Bồ Đào Nha','Netherlands':'🇳🇱 Hà Lan','Italy':'🇮🇹 Ý','Belgium':'🇧🇪 Bỉ','Croatia':'🇭🇷 Croatia','Uruguay':'🇺🇾 Uruguay','Japan':'🇯🇵 Nhật Bản','South Korea':'🇰🇷 Hàn Quốc','Korea Republic':'🇰🇷 Hàn Quốc','Australia':'🇦🇺 Úc','Saudi Arabia':'🇸🇦 Ả Rập Xê Út','Iran':'🇮🇷 Iran','Qatar':'🇶🇦 Qatar','Canada':'🇨🇦 Canada','Morocco':'🇲🇦 Morocco','Senegal':'🇸🇳 Senegal','Ecuador':'🇪🇨 Ecuador','Serbia':'🇷🇸 Serbia','Switzerland':'🇨🇭 Thụy Sĩ','Denmark':'🇩🇰 Đan Mạch','Poland':'🇵🇱 Ba Lan','Turkey':'🇹🇷 Thổ Nhĩ Kỳ','Türkiye':'🇹🇷 Thổ Nhĩ Kỳ','Ukraine':'🇺🇦 Ukraine','Egypt':'🇪🇬 Ai Cập','Nigeria':'🇳🇬 Nigeria','Cameroon':'🇨🇲 Cameroon','Ghana':'🇬🇭 Ghana','Tunisia':'🇹🇳 Tunisia','Algeria':'🇩🇿 Algeria','South Africa':'🇿🇦 Nam Phi','Indonesia':'🇮🇩 Indonesia','Vietnam':'🇻🇳 Việt Nam','China PR':'🇨🇳 Trung Quốc','New Zealand':'🇳🇿 New Zealand','Panama':'🇵🇦 Panama','Costa Rica':'🇨🇷 Costa Rica','Jamaica':'🇯🇲 Jamaica','Honduras':'🇭🇳 Honduras','Paraguay':'🇵🇾 Paraguay','Bolivia':'🇧🇴 Bolivia','Chile':'🇨🇱 Chile','Peru':'🇵🇪 Peru','Venezuela':'🇻🇪 Venezuela','Norway':'🇳🇴 Na Uy','Sweden':'🇸🇪 Thụy Điển','Austria':'🇦🇹 Áo','Scotland':'🏴 Scotland','Wales':'🏴 Xứ Wales','Ireland':'🇮🇪 Ireland','Romania':'🇷🇴 Romania','Hungary':'🇭🇺 Hungary','Greece':'🇬🇷 Hy Lạp','Uzbekistan':'🇺🇿 Uzbekistan','Iraq':'🇮🇶 Iraq','TBD':'🏳️ TBD'};
|
| 4 |
function _t(n){return WC_TEAMS[n]||WC_TEAMS[(n||'').trim()]||'🏴 '+n;}
|
| 5 |
|
| 6 |
-
const WC_CSS=`<style>
|
| 7 |
-
.wc-bxh{max-height:460px;overflow-y:auto;padding:2px;display:block}
|
| 8 |
-
.wc-bxh,.wc-bxh *{box-sizing:border-box!important;float:none!important;max-width:100%!important}
|
| 9 |
-
.wc-bxh script,.wc-bxh style,.wc-bxh ins,.wc-bxh iframe,.wc-bxh .info,.wc-bxh .btns,.wc-bxh .form{display:none!important}
|
| 10 |
-
.wc-bxh .title-table,.wc-bxh h6{display:block!important;background:#0b1a2a!important;color:#f0c040!important;font-size:11px!important;font-weight:800!important;padding:6px 8px!important;margin:8px 0 0!important;border-radius:5px 5px 0 0!important;border-bottom:1px solid #1a3a5a!important}
|
| 11 |
-
.wc-bxh .bxh-head{display:flex!important;align-items:center!important;padding:4px 6px!important;background:#0a1320!important;color:#6a9fca!important;font-size:9px!important;font-weight:700!important;border-bottom:1px solid #1a3a5a!important}
|
| 12 |
-
.wc-bxh .bxh-head .h-team{flex:1!important;text-align:left!important}
|
| 13 |
-
.wc-bxh .bxh-head .h-stat{flex:0 0 26px!important;text-align:center!important}
|
| 14 |
-
.wc-bxh .bxh-head .h-pts{flex:0 0 30px!important;text-align:center!important;color:#f0c040!important}
|
| 15 |
-
.wc-bxh .item{display:flex!important;align-items:center!important;padding:5px 6px!important;border-bottom:1px solid #10202f!important;background:transparent!important}
|
| 16 |
-
.wc-bxh .item:nth-child(even){background:#0a1320!important}
|
| 17 |
-
.wc-bxh .item .team{flex:1!important;display:flex!important;align-items:center!important;gap:5px!important;min-width:0!important;text-align:left!important}
|
| 18 |
-
.wc-bxh .item .team>span{flex:0 0 16px!important;color:#6a9fca!important;font-size:10px!important;text-align:center!important}
|
| 19 |
-
.wc-bxh .item .team a{flex:1!important;display:flex!important;align-items:center!important;gap:5px!important;min-width:0!important;color:#eee!important;text-decoration:none!important}
|
| 20 |
-
.wc-bxh .item .team a p{font-size:11px!important;color:#eee!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;margin:0!important}
|
| 21 |
-
.wc-bxh .item .copy{display:flex!important;align-items:center!important;flex:0 0 auto!important}
|
| 22 |
-
.wc-bxh .item .copy>p{flex:0 0 26px!important;text-align:center!important;font-size:10px!important;color:#ccc!important;margin:0!important}
|
| 23 |
-
.wc-bxh .item .copy>p:last-of-type,.wc-bxh .item .copy>p:nth-last-child(2){flex:0 0 30px!important}
|
| 24 |
-
.wc-bxh .item .copy>p strong{color:#f0c040!important;font-weight:800!important}
|
| 25 |
-
.wc-bxh img{width:18px!important;height:18px!important;object-fit:contain!important;vertical-align:middle!important;display:inline-block!important;flex:0 0 18px!important}
|
| 26 |
-
.wc-bxh strong{color:#f0c040!important}
|
| 27 |
-
.wc-bxh a{color:#8ab4d8!important;text-decoration:none!important}
|
| 28 |
-
.wc-bxh table{width:100%!important;border-collapse:collapse!important;font-size:11px!important;display:table!important}
|
| 29 |
-
.wc-bxh th{background:#0b1a2a!important;color:#6a9fca!important;padding:5px 3px!important;font-size:9px!important;text-align:center!important}
|
| 30 |
-
.wc-bxh td{padding:4px 3px!important;border-bottom:1px solid #0d1a2a!important;font-size:10px!important;color:#ddd!important}
|
| 31 |
-
</style>`;
|
| 32 |
-
|
| 33 |
function switchWCTab(tab){
|
| 34 |
document.querySelectorAll('.wc-tab').forEach(t=>t.classList.remove('active'));
|
| 35 |
-
const tabMap = {
|
| 36 |
-
|
| 37 |
-
'fixtures': 'Lịch',
|
| 38 |
-
'standings': 'BXH',
|
| 39 |
-
'highlights': 'Highlight',
|
| 40 |
-
'shorts': 'Shorts',
|
| 41 |
-
'stats': 'Thống'
|
| 42 |
-
};
|
| 43 |
-
document.querySelectorAll('.wc-tab').forEach(t=>{
|
| 44 |
-
if(tabMap[tab] && t.textContent.includes(tabMap[tab])) t.classList.add('active');
|
| 45 |
-
});
|
| 46 |
const el=document.getElementById('wc-content');if(!el)return;
|
| 47 |
if(tab==='news')renderWCNews(el);
|
| 48 |
else if(tab==='fixtures')renderWCFixtures(el);
|
|
@@ -90,34 +54,18 @@ function renderWCStandings(el){
|
|
| 90 |
if(!_wc2026Data){el.innerHTML='<div class="loading">Đang tải...</div>';return;}
|
| 91 |
const d=_wc2026Data.standings||{};
|
| 92 |
if(d.html&&d.html.length>100){
|
| 93 |
-
el.innerHTML=
|
| 94 |
-
const box=el.querySelector('.wc-bxh');
|
| 95 |
-
if(box){
|
| 96 |
-
box.querySelectorAll('.title-table, h6').forEach(t=>{
|
| 97 |
-
if(t.nextElementSibling && t.nextElementSibling.classList.contains('bxh-head'))return;
|
| 98 |
-
const head=document.createElement('div');
|
| 99 |
-
head.className='bxh-head';
|
| 100 |
-
head.innerHTML='<span class="h-team">Đội</span><span class="h-stat">Tr</span><span class="h-stat">T</span><span class="h-stat">H</span><span class="h-stat">B</span><span class="h-stat">BT</span><span class="h-stat">BB</span><span class="h-pts">HS</span><span class="h-pts">Đ</span>';
|
| 101 |
-
t.insertAdjacentElement('afterend',head);
|
| 102 |
-
});
|
| 103 |
-
}
|
| 104 |
el.querySelectorAll('a').forEach(a=>a.addEventListener('click',e=>e.preventDefault()));
|
| 105 |
return;
|
| 106 |
}
|
| 107 |
-
|
| 108 |
-
const gm=matches.filter(m=>m.group&&m.group.toLowerCase().includes('group'));
|
| 109 |
-
if(!gm.length){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</small></div>';return;}
|
| 110 |
-
const groups={};gm.forEach(m=>{const g=m.group;if(!groups[g])groups[g]={};[m.home,m.away].forEach(t=>{if(!groups[g][t])groups[g][t]={p:0,w:0,d:0,l:0,gf:0,ga:0,pts:0};});if(m.home_score!=null&&m.away_score!=null){const hs=+m.home_score,as=+m.away_score;if(!isNaN(hs)&&!isNaN(as)){groups[g][m.home].p++;groups[g][m.away].p++;groups[g][m.home].gf+=hs;groups[g][m.home].ga+=as;groups[g][m.away].gf+=as;groups[g][m.away].ga+=hs;if(hs>as){groups[g][m.home].w++;groups[g][m.home].pts+=3;groups[g][m.away].l++;}else if(hs<as){groups[g][m.away].w++;groups[g][m.away].pts+=3;groups[g][m.home].l++;}else{groups[g][m.home].d++;groups[g][m.home].pts++;groups[g][m.away].d++;groups[g][m.away].pts++;}}}});
|
| 111 |
-
let h='<div style="max-height:450px;overflow-y:auto;padding:2px"><div style="font-size:10px;color:#6a9fca;text-align:center;padding:3px">🏆 BXH World Cup 2026 · 🔴 Livescore</div>';
|
| 112 |
-
Object.keys(groups).sort().forEach(g=>{const teams=Object.entries(groups[g]).map(([n,s])=>({name:n,...s,gd:s.gf-s.ga})).sort((a,b)=>b.pts-a.pts||b.gd-a.gd);h+=`<div style="font-size:11px;font-weight:800;color:#8ab4d8;padding:6px 4px 3px;margin-top:4px;border-bottom:1px solid #1a3a5a">${esc(g)}</div><table style="width:100%;border-collapse:collapse;font-size:10px;margin:2px 0"><tr style="background:#0b1a2a"><th style="text-align:left;padding:4px;color:#6a9fca">Đội</th><th style="color:#6a9fca;padding:3px">Tr</th><th style="color:#6a9fca;padding:3px">T</th><th style="color:#6a9fca;padding:3px">H</th><th style="color:#6a9fca;padding:3px">B</th><th style="color:#6a9fca;padding:3px">HS</th><th style="color:#f0c040;padding:3px;font-weight:800">Đ</th></tr>`;teams.forEach((t,i)=>{h+=`<tr style="background:${i<2?'#0a1a10':'transparent'}"><td style="padding:3px;color:#eee;white-space:nowrap">${_t(t.name)}</td><td style="text-align:center;padding:3px;color:#999">${t.p}</td><td style="text-align:center;color:#4ade80">${t.w}</td><td style="text-align:center;color:#fbbf24">${t.d}</td><td style="text-align:center;color:#f87171">${t.l}</td><td style="text-align:center;color:${t.gd>=0?'#4ade80':'#f87171'}">${t.gd>0?'+':''}${t.gd}</td><td style="text-align:center;color:#f0c040;font-weight:800">${t.pts}</td></tr>`;});h+='</table>';});
|
| 113 |
-
h+='</div>';el.innerHTML=h;
|
| 114 |
}
|
| 115 |
|
| 116 |
// === STATS ===
|
| 117 |
function renderWCStats(el){
|
| 118 |
if(!_wc2026Data){el.innerHTML='<div class="loading">Đang tải...</div>';return;}
|
| 119 |
const d=_wc2026Data.stats||{};
|
| 120 |
-
if(d.html&&d.html.length>100){el.innerHTML=
|
| 121 |
else 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>';
|
| 122 |
}
|
| 123 |
|
|
@@ -130,60 +78,87 @@ async function renderWCHighlights(el){
|
|
| 130 |
const seen = new Map();
|
| 131 |
wcVids.forEach((v,i)=>{if(!seen.has(v.link)) seen.set(v.link, {...v, _li: seen.size});});
|
| 132 |
wcVids = Array.from(seen.values());
|
| 133 |
-
if(!wcVids.length){
|
| 134 |
-
el.innerHTML='<div class="loading">Chưa có highlight World Cup<br><small style="color:#6a9fca">Highlight sẽ xuất hiện khi giải diễn ra</small></div>';
|
| 135 |
-
return;
|
| 136 |
-
}
|
| 137 |
let h='<div style="display:grid;grid-template-columns:repeat(2,1fr);gap:6px;max-height:450px;overflow-y:auto;padding:4px">';
|
| 138 |
wcVids.forEach((v,i)=>{
|
| 139 |
h+=`<div style="cursor:pointer;border-radius:6px;overflow:hidden;background:#0a1520" onclick="openHighlightFeed('world-cup',${v._li},'${esc(v.link)}')">`;
|
| 140 |
h+=`<div style="position:relative;aspect-ratio:16/9;background:#1a2a3a">${v.img?`<img src="${esc(v.img)}" loading="lazy" style="width:100%;height:100%;object-fit:cover" onerror="this.style.display='none'">`:''}<div class="card-play">▶</div></div>`;
|
| 141 |
-
h+=`<div style="padding:4px 6px"><span style="font-size:8px;color:#f0c040">🌍 World Cup</span><div style="font-size:10px;color:#ccc;line-height:1.2;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden">${esc(v.title)}</div></div>`;
|
| 142 |
-
h+='</div>';
|
| 143 |
});
|
| 144 |
h+='</div>';el.innerHTML=h;
|
| 145 |
-
}catch(e){
|
| 146 |
-
el.innerHTML='<div class="loading">Lỗi tải highlight: '+esc(e.message)+'</div>';
|
| 147 |
-
}
|
| 148 |
}
|
| 149 |
|
| 150 |
-
// ===
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
async function renderWCShorts(el){
|
| 152 |
-
el.innerHTML='<div class="loading" style="display:flex;flex-direction:column;align-items:center;gap:8px;padding:30px"><div
|
| 153 |
|
| 154 |
-
// Fetch VTV Nam Bộ shorts from dedicated endpoint
|
| 155 |
let wcShorts = [];
|
|
|
|
|
|
|
| 156 |
try {
|
| 157 |
-
const r = await fetch('/api/shorts/vtvnamo?count=
|
| 158 |
wcShorts = await r.json() || [];
|
| 159 |
-
} catch(e) {
|
| 160 |
-
console.warn('VTV shorts fetch failed:', e);
|
| 161 |
-
}
|
| 162 |
|
| 163 |
-
// Also
|
| 164 |
if(typeof _shortsData !== 'undefined' && _shortsData.length){
|
| 165 |
const existingIds = new Set(wcShorts.map(s=>s.id));
|
| 166 |
-
_shortsData.forEach(s=>{
|
| 167 |
-
if(s.channel === 'vtvnambo' && !existingIds.has(s.id)){
|
| 168 |
-
wcShorts.push(s);
|
| 169 |
-
}
|
| 170 |
-
});
|
| 171 |
}
|
| 172 |
|
| 173 |
-
// Also
|
| 174 |
if(typeof _wcShortsData !== 'undefined' && _wcShortsData.length){
|
| 175 |
const existingIds = new Set(wcShorts.map(s=>s.id));
|
| 176 |
_wcShortsData.forEach(s=>{if(!existingIds.has(s.id)) wcShorts.push(s);});
|
| 177 |
}
|
| 178 |
|
| 179 |
if(!wcShorts.length){
|
| 180 |
-
el.innerHTML=`<div style="
|
| 181 |
-
<div style="font-size:32px;margin-bottom:8px">📱</div>
|
| 182 |
-
<div style="color:#ccc;font-size:14px;font-weight:700;margin-bottom:6px">Shorts World Cup</div>
|
| 183 |
-
<div style="margin-bottom:12px">VTV Nam Bộ & khoảnh khắc World Cup</div>
|
| 184 |
-
<div style="font-size:11px;color:#6a9fca;margin-bottom:16px">Đang cập nhật shorts từ YouTube...</div>
|
| 185 |
-
<button onclick="openWCShortsFeed(0)" style="background:#0b6bcb;border:none;color:#fff;padding:8px 20px;border-radius:16px;font-size:12px;font-weight:700;cursor:pointer">📱 Xem Shorts</button>
|
| 186 |
-
</div>`;
|
| 187 |
return;
|
| 188 |
}
|
| 189 |
|
|
@@ -191,73 +166,60 @@ async function renderWCShorts(el){
|
|
| 191 |
const seen = new Set();
|
| 192 |
wcShorts = wcShorts.filter(s=>{if(seen.has(s.id)) return false; seen.add(s.id); return true;});
|
| 193 |
|
| 194 |
-
//
|
|
|
|
|
|
|
|
|
|
| 195 |
let h = `<div style="padding:4px">
|
| 196 |
<div style="display:flex;justify-content:space-between;align-items:center;padding:4px 6px 8px">
|
| 197 |
<span style="font-size:13px;font-weight:800;color:#f0c040">📱 Shorts World Cup</span>
|
| 198 |
-
<span style="font-size:10px;color:#8ab4d8;cursor:pointer" onclick="openWCShortsFeed(0)">Xem tất cả →</span>
|
| 199 |
</div>
|
| 200 |
-
<div
|
| 201 |
-
|
| 202 |
-
wcShorts.slice(0,
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
h += `<
|
| 206 |
-
h += `<div
|
| 207 |
-
h +=
|
| 208 |
-
h += `<div style="position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:30px;height:30px;border-radius:50%;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;color:#fff;font-size:12px">▶</div>`;
|
| 209 |
-
h += `</div>`;
|
| 210 |
-
h += `<div style="padding:4px 5px"><span style="font-size:8px;font-weight:700;padding:1px 4px;border-radius:3px;color:#fff;background:${badgeBg}">${badge}</span><div style="font-size:9px;color:#ccc;line-height:1.2;margin-top:2px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden">${esc(s.title)}</div></div>`;
|
| 211 |
h += `</div>`;
|
| 212 |
});
|
| 213 |
|
| 214 |
-
|
| 215 |
-
h += `<div style="flex:0 0 100px;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#0b2e4a,#1a3a5a);border-radius:8px;cursor:pointer;border:1px dashed #2a5a8a;color:#8ab4d8;font-size:11px;font-weight:700;text-align:center;padding:8px;flex-shrink:0" onclick="openWCShortsFeed(0)">📱 Xem tất cả<br><small style="font-size:9px;color:#6a9fca">${wcShorts.length} shorts</small></div>`;
|
| 216 |
-
|
| 217 |
h += `</div></div>`;
|
| 218 |
el.innerHTML = h;
|
| 219 |
}
|
| 220 |
|
| 221 |
-
// ===
|
| 222 |
async function openWCShortsFeed(startIdx){
|
| 223 |
showView('view-tiktok');
|
| 224 |
const el = document.getElementById('view-tiktok');
|
| 225 |
-
el.innerHTML = '<div class="loading">Đang tải shorts...</div>';
|
| 226 |
|
| 227 |
-
|
| 228 |
-
let wcShorts = [];
|
| 229 |
|
| 230 |
-
//
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
console.warn('VTV endpoint failed:', e);
|
| 237 |
}
|
| 238 |
|
| 239 |
-
//
|
| 240 |
-
if(typeof _shortsData !== 'undefined' && _shortsData.length){
|
| 241 |
-
_shortsData.
|
| 242 |
-
if(s.channel === 'vtvnambo') wcShorts.push(s);
|
| 243 |
-
});
|
| 244 |
-
}
|
| 245 |
-
// 3. From _wcShortsData
|
| 246 |
-
if(typeof _wcShortsData !== 'undefined' && _wcShortsData.length){
|
| 247 |
-
const existingIds = new Set(wcShorts.map(s=>s.id));
|
| 248 |
-
_wcShortsData.forEach(s=>{if(!existingIds.has(s.id)) wcShorts.push(s);});
|
| 249 |
}
|
| 250 |
-
|
|
|
|
| 251 |
if(!wcShorts.length){
|
| 252 |
try{
|
| 253 |
const r = await fetch('/api/shorts');
|
| 254 |
-
const
|
| 255 |
-
wcShorts =
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
}catch(e){
|
| 259 |
-
wcShorts = [];
|
| 260 |
-
}
|
| 261 |
}
|
| 262 |
|
| 263 |
// Deduplicate
|
|
@@ -265,42 +227,64 @@ async function openWCShortsFeed(startIdx){
|
|
| 265 |
wcShorts = wcShorts.filter(s=>{if(seen.has(s.id)) return false; seen.add(s.id); return true;});
|
| 266 |
|
| 267 |
if(!wcShorts.length){
|
| 268 |
-
el.innerHTML = '<div class="
|
| 269 |
return;
|
| 270 |
}
|
| 271 |
|
| 272 |
-
|
| 273 |
-
if(typeof _wcShortsData !== 'undefined') _wcShortsData = wcShorts;
|
| 274 |
|
| 275 |
// Rotate so startIdx is first
|
| 276 |
const ordered = startIdx > 0 ? [...wcShorts.slice(startIdx), ...wcShorts.slice(0,startIdx)] : [...wcShorts];
|
| 277 |
|
|
|
|
| 278 |
let h = `<button class="back-btn" onclick="switchCat('home')">← World Cup Shorts</button>`;
|
| 279 |
-
h += '<div class="
|
| 280 |
|
| 281 |
ordered.forEach((s,i)=>{
|
| 282 |
const id = s.id || '';
|
| 283 |
-
// YouTube embed
|
| 284 |
-
const src = `https://www.youtube.com/embed/${id}?autoplay=0&rel=0&playsinline=1&mute=
|
| 285 |
-
const
|
| 286 |
-
const isVTV = s.channel === 'vtvnambo';
|
| 287 |
-
const badge = isVTV ? 'VTV Nam Bộ' : '⚽ WC';
|
| 288 |
-
const badgeClass = isVTV ? 'badge-wc' : 'badge-fpt';
|
| 289 |
const videoId = 'wcshort-' + id;
|
| 290 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
});
|
| 292 |
|
| 293 |
h += '</div></div>';
|
| 294 |
el.innerHTML = h;
|
| 295 |
|
| 296 |
-
// Init
|
| 297 |
-
|
|
|
|
|
|
|
|
|
|
| 298 |
}
|
| 299 |
|
| 300 |
-
|
|
|
|
| 301 |
const feed = document.getElementById('wc-shorts-feed');
|
| 302 |
if(!feed) return;
|
| 303 |
-
const slides = feed.querySelectorAll('.
|
| 304 |
let cur = -1;
|
| 305 |
let isManualScroll = false;
|
| 306 |
let manualTimer = null;
|
|
@@ -310,21 +294,21 @@ function initWCShortsFeed(){
|
|
| 310 |
slides.forEach((sl,idx)=>{
|
| 311 |
const fr = sl.querySelector('iframe');
|
| 312 |
if(idx === i){
|
|
|
|
| 313 |
if(fr && !fr.src && fr.dataset.ytSrc){ fr.src = fr.dataset.ytSrc; }
|
| 314 |
-
//
|
| 315 |
if(fr && fr.src){
|
| 316 |
-
|
| 317 |
-
fr.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}',
|
| 318 |
-
}
|
| 319 |
}
|
|
|
|
| 320 |
const vid = sl.dataset.vid;
|
| 321 |
-
if(vid && !sl._viewed){ sl._viewed = true;
|
| 322 |
} else {
|
| 323 |
-
// Pause
|
| 324 |
if(fr && fr.src){
|
| 325 |
-
try {
|
| 326 |
-
fr.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
|
| 327 |
-
} catch(e){}
|
| 328 |
}
|
| 329 |
}
|
| 330 |
});
|
|
@@ -342,14 +326,15 @@ function initWCShortsFeed(){
|
|
| 342 |
const ctr = rect.top + rect.height / 2;
|
| 343 |
let best = -1, bestD = 1e9;
|
| 344 |
slides.forEach((sl,i)=>{
|
| 345 |
-
const
|
|
|
|
| 346 |
if(d < bestD){ bestD = d; best = i; }
|
| 347 |
});
|
| 348 |
if(best >= 0) activate(best);
|
| 349 |
-
},
|
| 350 |
});
|
| 351 |
|
| 352 |
-
// Auto-advance every
|
| 353 |
const autoTimer = setInterval(()=>{
|
| 354 |
if(isManualScroll || !document.getElementById('wc-shorts-feed')) return;
|
| 355 |
const next = cur + 1;
|
|
@@ -358,15 +343,55 @@ function initWCShortsFeed(){
|
|
| 358 |
} else {
|
| 359 |
slides[0].scrollIntoView({behavior:'smooth', block:'start'});
|
| 360 |
}
|
| 361 |
-
},
|
| 362 |
|
| 363 |
feed._autoTimer = autoTimer;
|
| 364 |
|
| 365 |
-
|
|
|
|
| 366 |
if(slides.length > 0){ slides[0].scrollIntoView({behavior:'start'}); activate(0); }
|
| 367 |
-
},
|
| 368 |
}
|
| 369 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 370 |
// === LIVE AUTO-REFRESH (90s) ===
|
| 371 |
function startWCLiveRefresh(){
|
| 372 |
if(_wcRefreshInterval)clearInterval(_wcRefreshInterval);
|
|
|
|
| 3 |
const WC_TEAMS={'Mexico':'🇲🇽 Mexico','Colombia':'🇨🇴 Colombia','USA':'🇺🇸 Mỹ','United States':'🇺🇸 Mỹ','Brazil':'🇧🇷 Brazil','Argentina':'🇦🇷 Argentina','Germany':'🇩🇪 Đức','France':'🇫🇷 Pháp','Spain':'🇪🇸 Tây Ban Nha','England':'🏴 Anh','Portugal':'🇵🇹 Bồ Đào Nha','Netherlands':'🇳🇱 Hà Lan','Italy':'🇮🇹 Ý','Belgium':'🇧🇪 Bỉ','Croatia':'🇭🇷 Croatia','Uruguay':'🇺🇾 Uruguay','Japan':'🇯🇵 Nhật Bản','South Korea':'🇰🇷 Hàn Quốc','Korea Republic':'🇰🇷 Hàn Quốc','Australia':'🇦🇺 Úc','Saudi Arabia':'🇸🇦 Ả Rập Xê Út','Iran':'🇮🇷 Iran','Qatar':'🇶🇦 Qatar','Canada':'🇨🇦 Canada','Morocco':'🇲🇦 Morocco','Senegal':'🇸🇳 Senegal','Ecuador':'🇪🇨 Ecuador','Serbia':'🇷🇸 Serbia','Switzerland':'🇨🇭 Thụy Sĩ','Denmark':'🇩🇰 Đan Mạch','Poland':'🇵🇱 Ba Lan','Turkey':'🇹🇷 Thổ Nhĩ Kỳ','Türkiye':'🇹🇷 Thổ Nhĩ Kỳ','Ukraine':'🇺🇦 Ukraine','Egypt':'🇪🇬 Ai Cập','Nigeria':'🇳🇬 Nigeria','Cameroon':'🇨🇲 Cameroon','Ghana':'🇬🇭 Ghana','Tunisia':'🇹🇳 Tunisia','Algeria':'🇩🇿 Algeria','South Africa':'🇿🇦 Nam Phi','Indonesia':'🇮🇩 Indonesia','Vietnam':'🇻🇳 Việt Nam','China PR':'🇨🇳 Trung Quốc','New Zealand':'🇳🇿 New Zealand','Panama':'🇵🇦 Panama','Costa Rica':'🇨🇷 Costa Rica','Jamaica':'🇯🇲 Jamaica','Honduras':'🇭🇳 Honduras','Paraguay':'🇵🇾 Paraguay','Bolivia':'🇧🇴 Bolivia','Chile':'🇨🇱 Chile','Peru':'🇵🇪 Peru','Venezuela':'🇻🇪 Venezuela','Norway':'🇳🇴 Na Uy','Sweden':'🇸🇪 Thụy Điển','Austria':'🇦🇹 Áo','Scotland':'🏴 Scotland','Wales':'🏴 Xứ Wales','Ireland':'🇮🇪 Ireland','Romania':'🇷🇴 Romania','Hungary':'🇭🇺 Hungary','Greece':'🇬🇷 Hy Lạp','Uzbekistan':'🇺🇿 Uzbekistan','Iraq':'🇮🇶 Iraq','TBD':'🏳️ TBD'};
|
| 4 |
function _t(n){return WC_TEAMS[n]||WC_TEAMS[(n||'').trim()]||'🏴 '+n;}
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
function switchWCTab(tab){
|
| 7 |
document.querySelectorAll('.wc-tab').forEach(t=>t.classList.remove('active'));
|
| 8 |
+
const tabMap = {'news':'Tin','fixtures':'Lịch','standings':'BXH','highlights':'Highlight','shorts':'Shorts','stats':'Thống'};
|
| 9 |
+
document.querySelectorAll('.wc-tab').forEach(t=>{if(tabMap[tab]&&t.textContent.includes(tabMap[tab]))t.classList.add('active');});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
const el=document.getElementById('wc-content');if(!el)return;
|
| 11 |
if(tab==='news')renderWCNews(el);
|
| 12 |
else if(tab==='fixtures')renderWCFixtures(el);
|
|
|
|
| 54 |
if(!_wc2026Data){el.innerHTML='<div class="loading">Đang tải...</div>';return;}
|
| 55 |
const d=_wc2026Data.standings||{};
|
| 56 |
if(d.html&&d.html.length>100){
|
| 57 |
+
el.innerHTML='<div class="wc-bxh">'+d.html+'</div>';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
el.querySelectorAll('a').forEach(a=>a.addEventListener('click',e=>e.preventDefault()));
|
| 59 |
return;
|
| 60 |
}
|
| 61 |
+
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</small></div>';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
}
|
| 63 |
|
| 64 |
// === STATS ===
|
| 65 |
function renderWCStats(el){
|
| 66 |
if(!_wc2026Data){el.innerHTML='<div class="loading">Đang tải...</div>';return;}
|
| 67 |
const d=_wc2026Data.stats||{};
|
| 68 |
+
if(d.html&&d.html.length>100){el.innerHTML='<div class="wc-bxh">'+d.html+'</div>';el.querySelectorAll('a').forEach(a=>a.addEventListener('click',e=>e.preventDefault()));}
|
| 69 |
else 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>';
|
| 70 |
}
|
| 71 |
|
|
|
|
| 78 |
const seen = new Map();
|
| 79 |
wcVids.forEach((v,i)=>{if(!seen.has(v.link)) seen.set(v.link, {...v, _li: seen.size});});
|
| 80 |
wcVids = Array.from(seen.values());
|
| 81 |
+
if(!wcVids.length){el.innerHTML='<div class="loading">Chưa có highlight World Cup</div>';return;}
|
|
|
|
|
|
|
|
|
|
| 82 |
let h='<div style="display:grid;grid-template-columns:repeat(2,1fr);gap:6px;max-height:450px;overflow-y:auto;padding:4px">';
|
| 83 |
wcVids.forEach((v,i)=>{
|
| 84 |
h+=`<div style="cursor:pointer;border-radius:6px;overflow:hidden;background:#0a1520" onclick="openHighlightFeed('world-cup',${v._li},'${esc(v.link)}')">`;
|
| 85 |
h+=`<div style="position:relative;aspect-ratio:16/9;background:#1a2a3a">${v.img?`<img src="${esc(v.img)}" loading="lazy" style="width:100%;height:100%;object-fit:cover" onerror="this.style.display='none'">`:''}<div class="card-play">▶</div></div>`;
|
| 86 |
+
h+=`<div style="padding:4px 6px"><span style="font-size:8px;color:#f0c040">🌍 World Cup</span><div style="font-size:10px;color:#ccc;line-height:1.2;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden">${esc(v.title)}</div></div></div>`;
|
|
|
|
| 87 |
});
|
| 88 |
h+='</div>';el.innerHTML=h;
|
| 89 |
+
}catch(e){el.innerHTML='<div class="loading">Lỗi tải highlight</div>';}
|
|
|
|
|
|
|
| 90 |
}
|
| 91 |
|
| 92 |
+
// ============================================================
|
| 93 |
+
// === WC SHORTS — TikTok-style vertical slideshow ===
|
| 94 |
+
// ============================================================
|
| 95 |
+
|
| 96 |
+
// Inject WC Shorts CSS
|
| 97 |
+
(function(){
|
| 98 |
+
if(document.getElementById('wc-shorts-css')) return;
|
| 99 |
+
const style = document.createElement('style');
|
| 100 |
+
style.id = 'wc-shorts-css';
|
| 101 |
+
style.textContent = `
|
| 102 |
+
.wc-shorts-container{width:100%;height:72vh;max-height:620px;min-height:380px;background:#000;border-radius:8px;overflow:hidden;position:relative}
|
| 103 |
+
.wc-shorts-feed{height:100%;overflow-y:scroll;scroll-snap-type:y mandatory;scrollbar-width:none;-ms-overflow-style:none}
|
| 104 |
+
.wc-shorts-feed::-webkit-scrollbar{display:none}
|
| 105 |
+
.wc-short-slide{height:72vh;max-height:620px;min-height:380px;scroll-snap-align:start;position:relative;background:#000;display:flex;align-items:center;justify-content:center;overflow:hidden}
|
| 106 |
+
.wc-short-slide iframe{width:100%;height:100%;border:none;background:#000}
|
| 107 |
+
.wc-short-overlay{position:absolute;bottom:0;left:0;right:0;padding:12px 14px 16px;background:linear-gradient(transparent 0%,rgba(0,0,0,.72) 40%,rgba(0,0,0,.92) 100%);z-index:3;pointer-events:none}
|
| 108 |
+
.wc-short-badge{display:inline-block;font-size:9px;font-weight:700;padding:2px 8px;border-radius:10px;color:#fff;background:#0b6bcb;margin-bottom:6px}
|
| 109 |
+
.wc-short-title{font-size:13px;color:#fff;font-weight:700;line-height:1.35;text-shadow:0 1px 3px rgba(0,0,0,.6);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
|
| 110 |
+
.wc-short-counter{position:absolute;top:10px;right:10px;background:rgba(0,0,0,.55);font-size:10px;padding:3px 9px;border-radius:10px;color:#fff;z-index:4;font-weight:600;backdrop-filter:blur(4px)}
|
| 111 |
+
.wc-short-actions{position:absolute;right:10px;bottom:80px;display:flex;flex-direction:column;align-items:center;gap:16px;z-index:5}
|
| 112 |
+
.wc-short-action-btn{display:flex;flex-direction:column;align-items:center;gap:2px;background:none;border:0;color:#fff;cursor:pointer;font-size:10px;text-shadow:0 1px 3px rgba(0,0,0,.5)}
|
| 113 |
+
.wc-short-action-btn .icon{width:44px;height:44px;border-radius:50%;background:rgba(255,255,255,.15);display:flex;align-items:center;justify-content:center;font-size:20px;backdrop-filter:blur(4px);transition:background .2s}
|
| 114 |
+
.wc-short-action-btn:hover .icon{background:rgba(255,255,255,.28)}
|
| 115 |
+
.wc-short-action-btn .count{font-size:10px;color:#eee;font-weight:600}
|
| 116 |
+
.wc-shorts-empty{text-align:center;padding:40px 20px;color:#888;font-size:13px}
|
| 117 |
+
.wc-shorts-empty .icon{font-size:40px;margin-bottom:10px}
|
| 118 |
+
.wc-shorts-empty button{background:#0b6bcb;border:none;color:#fff;padding:10px 24px;border-radius:20px;font-size:13px;font-weight:700;cursor:pointer;margin-top:16px}
|
| 119 |
+
/* Horizontal slider on WC tab */
|
| 120 |
+
.wc-shorts-slider{display:flex;overflow-x:auto;gap:8px;padding:4px 4px 8px;scrollbar-width:none}
|
| 121 |
+
.wc-shorts-slider::-webkit-scrollbar{display:none}
|
| 122 |
+
.wc-shorts-slider-item{flex:0 0 120px;cursor:pointer;border-radius:8px;overflow:hidden;background:#0a1520;border:1px solid #1a3a5a;transition:border-color .2s,transform .2s}
|
| 123 |
+
.wc-shorts-slider-item:hover{border-color:#0b6bcb;transform:scale(1.03)}
|
| 124 |
+
.wc-shorts-slider-thumb{position:relative;width:100%;aspect-ratio:9/16;background:#1a2a3a;overflow:hidden}
|
| 125 |
+
.wc-shorts-slider-thumb img{width:100%;height:100%;object-fit:cover}
|
| 126 |
+
.wc-shorts-slider-thumb .play-overlay{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:32px;height:32px;border-radius:50%;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;color:#fff;font-size:13px}
|
| 127 |
+
.wc-shorts-slider-info{padding:4px 5px}
|
| 128 |
+
.wc-shorts-slider-info .badge{font-size:8px;font-weight:700;padding:1px 5px;border-radius:3px;color:#fff;background:#0b6bcb;display:inline-block;margin-bottom:2px}
|
| 129 |
+
.wc-shorts-slider-info .title{font-size:9px;color:#ccc;line-height:1.2;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
|
| 130 |
+
.wc-shorts-view-all{flex:0 0 100px;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#0b2e4a,#1a3a5a);border-radius:8px;cursor:pointer;border:1px dashed #2a5a8a;color:#8ab4d8;font-size:11px;font-weight:700;text-align:center;padding:8px;flex-shrink:0;transition:border-color .2s}
|
| 131 |
+
.wc-shorts-view-all:hover{border-color:#0b6bcb;color:#fff}
|
| 132 |
+
`;
|
| 133 |
+
document.head.appendChild(style);
|
| 134 |
+
})();
|
| 135 |
+
|
| 136 |
+
// === WC SHORTS TAB (horizontal slider in WC section) ===
|
| 137 |
async function renderWCShorts(el){
|
| 138 |
+
el.innerHTML='<div class="loading" style="display:flex;flex-direction:column;align-items:center;gap:8px;padding:30px"><div style="width:24px;height:24px;border:2px solid #333;border-top-color:#00ccff;border-radius:50%;animation:vtvspin .8s linear infinite"></div>Đang tải shorts...</div>';
|
| 139 |
|
|
|
|
| 140 |
let wcShorts = [];
|
| 141 |
+
|
| 142 |
+
// Fetch from dedicated VTV Nam Bộ endpoint
|
| 143 |
try {
|
| 144 |
+
const r = await fetch('/api/shorts/vtvnamo?count=50', {signal: AbortSignal.timeout(15000)});
|
| 145 |
wcShorts = await r.json() || [];
|
| 146 |
+
} catch(e) { console.warn('VTV shorts fetch failed:', e); }
|
|
|
|
|
|
|
| 147 |
|
| 148 |
+
// Also from _shortsData
|
| 149 |
if(typeof _shortsData !== 'undefined' && _shortsData.length){
|
| 150 |
const existingIds = new Set(wcShorts.map(s=>s.id));
|
| 151 |
+
_shortsData.forEach(s=>{if(s.channel==='vtvnambo'&&!existingIds.has(s.id)){wcShorts.push(s);}});
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
}
|
| 153 |
|
| 154 |
+
// Also from _wcShortsData
|
| 155 |
if(typeof _wcShortsData !== 'undefined' && _wcShortsData.length){
|
| 156 |
const existingIds = new Set(wcShorts.map(s=>s.id));
|
| 157 |
_wcShortsData.forEach(s=>{if(!existingIds.has(s.id)) wcShorts.push(s);});
|
| 158 |
}
|
| 159 |
|
| 160 |
if(!wcShorts.length){
|
| 161 |
+
el.innerHTML=`<div class="wc-shorts-empty"><div class="icon">📱</div><div style="color:#ccc;font-size:14px;font-weight:700;margin-bottom:6px">Shorts World Cup</div><div>VTV Nam Bộ & khoảnh khắc World Cup</div><div style="font-size:11px;color:#6a9fca;margin-top:4px">Đang cập nhật shorts từ YouTube...</div><button onclick="openWCShortsFeed(0)">📱 Xem Shorts</button></div>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
return;
|
| 163 |
}
|
| 164 |
|
|
|
|
| 166 |
const seen = new Set();
|
| 167 |
wcShorts = wcShorts.filter(s=>{if(seen.has(s.id)) return false; seen.add(s.id); return true;});
|
| 168 |
|
| 169 |
+
// Store globally
|
| 170 |
+
_wcShortsData = wcShorts;
|
| 171 |
+
|
| 172 |
+
// Render horizontal slider
|
| 173 |
let h = `<div style="padding:4px">
|
| 174 |
<div style="display:flex;justify-content:space-between;align-items:center;padding:4px 6px 8px">
|
| 175 |
<span style="font-size:13px;font-weight:800;color:#f0c040">📱 Shorts World Cup</span>
|
| 176 |
+
<span style="font-size:10px;color:#8ab4d8;cursor:pointer;font-weight:600" onclick="openWCShortsFeed(0)">Xem tất cả →</span>
|
| 177 |
</div>
|
| 178 |
+
<div class="wc-shorts-slider">`;
|
| 179 |
+
|
| 180 |
+
wcShorts.slice(0,15).forEach((s,i)=>{
|
| 181 |
+
h += `<div class="wc-shorts-slider-item" onclick="openWCShortsFeed(${i})">`;
|
| 182 |
+
h += `<div class="wc-shorts-slider-thumb">`;
|
| 183 |
+
h += s.img ? `<img src="${esc(s.img)}" loading="lazy" onerror="this.style.display='none'">` : '';
|
| 184 |
+
h += `<div class="play-overlay">▶</div></div>`;
|
| 185 |
+
h += `<div class="wc-shorts-slider-info"><span class="badge">VTV Nam Bộ</span><div class="title">${esc(s.title)}</div></div>`;
|
|
|
|
|
|
|
|
|
|
| 186 |
h += `</div>`;
|
| 187 |
});
|
| 188 |
|
| 189 |
+
h += `<div class="wc-shorts-view-all" onclick="openWCShortsFeed(0)">📱 Xem tất cả<br><small style="font-size:9px;color:#6a9fca">${wcShorts.length} shorts</small></div>`;
|
|
|
|
|
|
|
| 190 |
h += `</div></div>`;
|
| 191 |
el.innerHTML = h;
|
| 192 |
}
|
| 193 |
|
| 194 |
+
// === FULL-SCREEN TIKTOK FEED ===
|
| 195 |
async function openWCShortsFeed(startIdx){
|
| 196 |
showView('view-tiktok');
|
| 197 |
const el = document.getElementById('view-tiktok');
|
| 198 |
+
el.innerHTML = '<div class="loading" style="display:flex;flex-direction:column;align-items:center;gap:8px"><div style="width:24px;height:24px;border:2px solid #333;border-top-color:#00ccff;border-radius:50%;animation:vtvspin .8s linear infinite"></div>Đang tải shorts...</div>';
|
| 199 |
|
| 200 |
+
let wcShorts = _wcShortsData || [];
|
|
|
|
| 201 |
|
| 202 |
+
// If no cached data, fetch fresh
|
| 203 |
+
if(!wcShorts.length){
|
| 204 |
+
try {
|
| 205 |
+
const r = await fetch('/api/shorts/vtvnamo?count=50', {signal: AbortSignal.timeout(15000)});
|
| 206 |
+
wcShorts = await r.json() || [];
|
| 207 |
+
} catch(e) { console.warn('VTV fetch failed:', e); }
|
|
|
|
| 208 |
}
|
| 209 |
|
| 210 |
+
// Fallback to _shortsData
|
| 211 |
+
if(!wcShorts.length && typeof _shortsData !== 'undefined' && _shortsData.length){
|
| 212 |
+
wcShorts = _shortsData.filter(s => s.channel === 'vtvnambo');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
}
|
| 214 |
+
|
| 215 |
+
// Fallback to general shorts
|
| 216 |
if(!wcShorts.length){
|
| 217 |
try{
|
| 218 |
const r = await fetch('/api/shorts');
|
| 219 |
+
const all = await r.json() || [];
|
| 220 |
+
wcShorts = all.filter(s => s.channel === 'vtvnambo');
|
| 221 |
+
if(!wcShorts.length) wcShorts = all;
|
| 222 |
+
}catch(e){ wcShorts = []; }
|
|
|
|
|
|
|
|
|
|
| 223 |
}
|
| 224 |
|
| 225 |
// Deduplicate
|
|
|
|
| 227 |
wcShorts = wcShorts.filter(s=>{if(seen.has(s.id)) return false; seen.add(s.id); return true;});
|
| 228 |
|
| 229 |
if(!wcShorts.length){
|
| 230 |
+
el.innerHTML = '<div class="wc-shorts-empty"><div class="icon">📱</div><div>Chưa có shorts</div></div>';
|
| 231 |
return;
|
| 232 |
}
|
| 233 |
|
| 234 |
+
_wcShortsData = wcShorts;
|
|
|
|
| 235 |
|
| 236 |
// Rotate so startIdx is first
|
| 237 |
const ordered = startIdx > 0 ? [...wcShorts.slice(startIdx), ...wcShorts.slice(0,startIdx)] : [...wcShorts];
|
| 238 |
|
| 239 |
+
// Build TikTok-style feed
|
| 240 |
let h = `<button class="back-btn" onclick="switchCat('home')">← World Cup Shorts</button>`;
|
| 241 |
+
h += '<div class="wc-shorts-container"><div class="wc-shorts-feed" id="wc-shorts-feed">';
|
| 242 |
|
| 243 |
ordered.forEach((s,i)=>{
|
| 244 |
const id = s.id || '';
|
| 245 |
+
// YouTube embed — autoplay=0 initially, will be set to 1 when active
|
| 246 |
+
const src = `https://www.youtube.com/embed/${id}?autoplay=0&rel=0&playsinline=1&mute=0&loop=1&playlist=${id}&enablejsapi=1&origin=${encodeURIComponent(location.origin)}`;
|
| 247 |
+
const shareUrl = 'https://youtube.com/shorts/' + id;
|
|
|
|
|
|
|
|
|
|
| 248 |
const videoId = 'wcshort-' + id;
|
| 249 |
+
|
| 250 |
+
h += `<div class="wc-short-slide" data-vid="${esc(videoId)}" data-idx="${i}">`;
|
| 251 |
+
h += `<iframe data-yt-src="${src}" allowfullscreen allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture;fullscreen"></iframe>`;
|
| 252 |
+
|
| 253 |
+
// Title overlay
|
| 254 |
+
h += `<div class="wc-short-overlay">`;
|
| 255 |
+
h += `<span class="wc-short-badge">VTV Nam Bộ</span>`;
|
| 256 |
+
h += `<div class="wc-short-title">${esc(s.title)}</div>`;
|
| 257 |
+
h += `</div>`;
|
| 258 |
+
|
| 259 |
+
// Counter
|
| 260 |
+
h += `<span class="wc-short-counter">${i+1}/${ordered.length}</span>`;
|
| 261 |
+
|
| 262 |
+
// Action buttons (right side)
|
| 263 |
+
h += `<div class="wc-short-actions">`;
|
| 264 |
+
h += `<button class="wc-short-action-btn" onclick="event.stopPropagation();doWCShortInteract('${esc(id)}','view',this)" title="Xem"><div class="icon">👁</div><span class="count" id="wc-vc-${i}">0</span></button>`;
|
| 265 |
+
h += `<button class="wc-short-action-btn" onclick="event.stopPropagation();doWCShortInteract('${esc(id)}','like',this)" title="Thích"><div class="icon">❤️</div><span class="count" id="wc-lc-${i}">0</span></button>`;
|
| 266 |
+
h += `<button class="wc-short-action-btn" onclick="event.stopPropagation();doWCShortInteract('${esc(id)}','share',this)" title="Chia sẻ"><div class="icon">📤</div></button>`;
|
| 267 |
+
h += `<button class="wc-short-action-btn" onclick="event.stopPropagation();window.open('${shareUrl}','_blank')" title="Mở trên YouTube"><div class="icon">🔗</div></button>`;
|
| 268 |
+
h += `</div>`;
|
| 269 |
+
|
| 270 |
+
h += `</div>`;
|
| 271 |
});
|
| 272 |
|
| 273 |
h += '</div></div>';
|
| 274 |
el.innerHTML = h;
|
| 275 |
|
| 276 |
+
// Init autoplay on scroll
|
| 277 |
+
initWCShortsAutoplay();
|
| 278 |
+
|
| 279 |
+
// Load interaction counts
|
| 280 |
+
loadWCShortCounters(ordered);
|
| 281 |
}
|
| 282 |
|
| 283 |
+
// === Autoplay logic ===
|
| 284 |
+
function initWCShortsAutoplay(){
|
| 285 |
const feed = document.getElementById('wc-shorts-feed');
|
| 286 |
if(!feed) return;
|
| 287 |
+
const slides = feed.querySelectorAll('.wc-short-slide');
|
| 288 |
let cur = -1;
|
| 289 |
let isManualScroll = false;
|
| 290 |
let manualTimer = null;
|
|
|
|
| 294 |
slides.forEach((sl,idx)=>{
|
| 295 |
const fr = sl.querySelector('iframe');
|
| 296 |
if(idx === i){
|
| 297 |
+
// Load iframe if not loaded
|
| 298 |
if(fr && !fr.src && fr.dataset.ytSrc){ fr.src = fr.dataset.ytSrc; }
|
| 299 |
+
// Autoplay via postMessage
|
| 300 |
if(fr && fr.src){
|
| 301 |
+
setTimeout(()=>{
|
| 302 |
+
try { fr.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}','*'); } catch(e){}
|
| 303 |
+
}, 300);
|
| 304 |
}
|
| 305 |
+
// Track view
|
| 306 |
const vid = sl.dataset.vid;
|
| 307 |
+
if(vid && !sl._viewed){ sl._viewed = true; doWCShortInteract(vid.replace('wcshort-',''),'',null); }
|
| 308 |
} else {
|
| 309 |
+
// Pause
|
| 310 |
if(fr && fr.src){
|
| 311 |
+
try { fr.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}','*'); } catch(e){}
|
|
|
|
|
|
|
| 312 |
}
|
| 313 |
}
|
| 314 |
});
|
|
|
|
| 326 |
const ctr = rect.top + rect.height / 2;
|
| 327 |
let best = -1, bestD = 1e9;
|
| 328 |
slides.forEach((sl,i)=>{
|
| 329 |
+
const r = sl.getBoundingClientRect();
|
| 330 |
+
const d = Math.abs(r.top + r.height/2 - ctr);
|
| 331 |
if(d < bestD){ bestD = d; best = i; }
|
| 332 |
});
|
| 333 |
if(best >= 0) activate(best);
|
| 334 |
+
}, 120);
|
| 335 |
});
|
| 336 |
|
| 337 |
+
// Auto-advance every 10 seconds
|
| 338 |
const autoTimer = setInterval(()=>{
|
| 339 |
if(isManualScroll || !document.getElementById('wc-shorts-feed')) return;
|
| 340 |
const next = cur + 1;
|
|
|
|
| 343 |
} else {
|
| 344 |
slides[0].scrollIntoView({behavior:'smooth', block:'start'});
|
| 345 |
}
|
| 346 |
+
}, 10000);
|
| 347 |
|
| 348 |
feed._autoTimer = autoTimer;
|
| 349 |
|
| 350 |
+
// Activate first slide
|
| 351 |
+
setTimeout(()=>{
|
| 352 |
if(slides.length > 0){ slides[0].scrollIntoView({behavior:'start'}); activate(0); }
|
| 353 |
+
}, 500);
|
| 354 |
}
|
| 355 |
|
| 356 |
+
// === Interaction helpers ===
|
| 357 |
+
async function doWCShortInteract(videoId, type, btn){
|
| 358 |
+
if(type){
|
| 359 |
+
try{
|
| 360 |
+
const r = await fetch('/api/short-action',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:videoId,action:type})});
|
| 361 |
+
const j = await r.json();
|
| 362 |
+
if(j && j.stats){
|
| 363 |
+
// Update counts in the active slide
|
| 364 |
+
const activeSlide = document.querySelector('.wc-short-slide');
|
| 365 |
+
if(activeSlide){
|
| 366 |
+
const idx = activeSlide.dataset.idx;
|
| 367 |
+
if(type==='view'){ const el=document.getElementById('wc-vc-'+idx); if(el) el.textContent=fmtNum(j.stats.views); }
|
| 368 |
+
if(type==='like'){ const el=document.getElementById('wc-lc-'+idx); if(el) el.textContent=fmtNum(j.stats.likes); }
|
| 369 |
+
}
|
| 370 |
+
}
|
| 371 |
+
}catch(e){}
|
| 372 |
+
} else {
|
| 373 |
+
// Just a view count increment
|
| 374 |
+
try{ await fetch('/api/short-action',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:videoId,action:'view'})}); }catch(e){}
|
| 375 |
+
}
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
async function loadWCShortCounters(shorts){
|
| 379 |
+
for(let i=0;i<shorts.length;i++){
|
| 380 |
+
const id = shorts[i].id;
|
| 381 |
+
if(!id) continue;
|
| 382 |
+
try{
|
| 383 |
+
const r = await fetch('/api/short-stats?ids='+id);
|
| 384 |
+
const j = await r.json();
|
| 385 |
+
if(j.stats && j.stats[id]){
|
| 386 |
+
const vc=document.getElementById('wc-vc-'+i); if(vc) vc.textContent=fmtNum(j.stats[id].views);
|
| 387 |
+
const lc=document.getElementById('wc-lc-'+i); if(lc) lc.textContent=fmtNum(j.stats[id].likes);
|
| 388 |
+
}
|
| 389 |
+
}catch(e){}
|
| 390 |
+
}
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
function fmtNum(n){if(!n)return'0';if(n>=1e6)return(n/1e6).toFixed(1)+'M';if(n>=1e3)return(n/1e3).toFixed(1)+'K';return String(n);}
|
| 394 |
+
|
| 395 |
// === LIVE AUTO-REFRESH (90s) ===
|
| 396 |
function startWCLiveRefresh(){
|
| 397 |
if(_wcRefreshInterval)clearInterval(_wcRefreshInterval);
|