bep40 commited on
Commit
13ecfa2
·
verified ·
1 Parent(s): f34b365

Delete static/wc_shorts_slide.js, static/wc_shorts_inject.js

Browse files
static/wc_shorts_inject.js DELETED
@@ -1,76 +0,0 @@
1
- /**
2
- * WC Shorts Inject — Chạy SAU app_v2.js
3
- * Tự tạo anchor DIV rồi render WC Shorts slide TRÊN World Cup section
4
- */
5
- (function(){
6
- 'use strict';
7
-
8
- function inject(){
9
- // Tìm WC section
10
- const wcSection = document.getElementById('wc2026-live-section');
11
- const homeEl = document.getElementById('view-home');
12
- if(!homeEl) return;
13
-
14
- // Đã inject?
15
- if(document.getElementById('wc-shorts-slide-section')) return;
16
-
17
- // Đảm bảo renderWCSSlideSection đã load
18
- if(typeof renderWCSSlideSection !== 'function'){
19
- // Chờ rồi thử lại
20
- setTimeout(inject, 500);
21
- return;
22
- }
23
-
24
- // Tạo container rồi render vào
25
- const div = document.createElement('div');
26
- div.id = 'wc-shorts-slide-container-tmp';
27
- div.style.display = 'none';
28
-
29
- // Insert TRƯỚC WC section, hoặc vào đầu home
30
- if(wcSection && wcSection.parentNode){
31
- wcSection.parentNode.insertBefore(div, wcSection);
32
- } else {
33
- homeEl.insertBefore(div, homeEl.firstChild);
34
- }
35
-
36
- // Render slide section vào div
37
- renderWCSSlideSection(div);
38
-
39
- // Bỏ wrapper thừa, di chuyển section ra đúng vị trí
40
- const section = div.querySelector('.wc-shorts-slide-section');
41
- if(section){
42
- section.id = 'wc-shorts-slide-section';
43
- if(wcSection && wcSection.parentNode){
44
- wcSection.parentNode.insertBefore(section, wcSection);
45
- }
46
- div.remove();
47
- }
48
- }
49
-
50
- // Chạy khi DOM sẵn sàng
51
- if(document.readyState === 'loading'){
52
- document.addEventListener('DOMContentLoaded', ()=> setTimeout(inject, 800));
53
- } else {
54
- setTimeout(inject, 800);
55
- }
56
-
57
- // Cũng chạy sau khi loadHome xong
58
- const origLoadHome = window.loadHome;
59
- if(origLoadHome){
60
- window.loadHome = function(){
61
- const result = origLoadHome.apply(this, arguments);
62
- setTimeout(inject, 200);
63
- setTimeout(inject, 800);
64
- setTimeout(inject, 2000);
65
- return result;
66
- };
67
- }
68
-
69
- // Fallback: retry nhiều lần
70
- let retries = 0;
71
- const retryIv = setInterval(()=>{
72
- retries++;
73
- inject();
74
- if(retries > 20) clearInterval(retryIv);
75
- }, 1000);
76
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/wc_shorts_slide.js DELETED
@@ -1,597 +0,0 @@
1
- /**
2
- * WC Shorts Slide — A standalone slide section ABOVE the World Cup section
3
- * TikTok-style vertical feed with autoplay, titles, interaction buttons
4
- * Each short = 1 fullscreen slide with scroll-snap
5
- */
6
- (function(){
7
- 'use strict';
8
-
9
- // ===== Inject CSS =====
10
- if(document.getElementById('wc-shorts-slide-css')) return;
11
- const style = document.createElement('style');
12
- style.id = 'wc-shorts-slide-css';
13
- style.textContent = `
14
- /* === WC SHORTS SLIDE SECTION (above WC section) === */
15
- .wc-shorts-slide-section {
16
- margin: 6px 4px;
17
- background: linear-gradient(135deg, #000 0%, #0a0a2e 50%, #0d1117 100%);
18
- border: 1px solid #1a3a5a;
19
- border-radius: 10px;
20
- overflow: hidden;
21
- }
22
- .wc-shorts-slide-header {
23
- display: flex;
24
- align-items: center;
25
- justify-content: space-between;
26
- padding: 10px 12px 6px;
27
- }
28
- .wc-shorts-slide-header h3 {
29
- font-size: 15px;
30
- color: #fff;
31
- font-weight: 800;
32
- margin: 0;
33
- display: flex;
34
- align-items: center;
35
- gap: 6px;
36
- }
37
- .wc-shorts-slide-header h3 .emoji { font-size: 18px; }
38
- .wc-shorts-slide-header .badge-live {
39
- font-size: 9px;
40
- color: #e74c3c;
41
- font-weight: 700;
42
- animation: wc-slide-pulse 1.5s infinite;
43
- }
44
- @keyframes wc-slide-pulse { 0%,100%{opacity:1} 50%{opacity:.4} }
45
- .wc-shorts-slide-header .view-all-btn {
46
- background: #0b6bcb;
47
- border: none;
48
- color: #fff;
49
- padding: 5px 12px;
50
- border-radius: 12px;
51
- font-size: 10px;
52
- font-weight: 700;
53
- cursor: pointer;
54
- white-space: nowrap;
55
- }
56
- .wc-shorts-slide-header .view-all-btn:active { opacity: .8; }
57
-
58
- /* Horizontal thumbnail slider */
59
- .wc-shorts-h-slider {
60
- display: flex;
61
- overflow-x: auto;
62
- gap: 8px;
63
- padding: 4px 10px 12px;
64
- scroll-snap-type: x mandatory;
65
- scrollbar-width: none;
66
- }
67
- .wc-shorts-h-slider::-webkit-scrollbar { display: none; }
68
- .wc-shorts-h-item {
69
- flex: 0 0 130px;
70
- scroll-snap-align: start;
71
- cursor: pointer;
72
- border-radius: 8px;
73
- overflow: hidden;
74
- background: #0a1520;
75
- border: 1px solid #1a3a5a;
76
- transition: border-color .2s, transform .2s;
77
- position: relative;
78
- }
79
- .wc-shorts-h-item:hover { border-color: #0b6bcb; transform: scale(1.03); }
80
- .wc-shorts-h-item:active { transform: scale(.97); }
81
- .wc-shorts-h-thumb {
82
- position: relative;
83
- width: 100%;
84
- aspect-ratio: 9/16;
85
- background: #1a2a3a;
86
- overflow: hidden;
87
- }
88
- .wc-shorts-h-thumb img {
89
- width: 100%; height: 100%; object-fit: cover;
90
- }
91
- .wc-shorts-h-thumb .h-play-icon {
92
- position: absolute; left: 50%; top: 50%;
93
- transform: translate(-50%, -50%);
94
- width: 36px; height: 36px;
95
- border-radius: 50%;
96
- background: rgba(0,0,0,.6);
97
- display: flex; align-items: center; justify-content: center;
98
- color: #fff; font-size: 14px;
99
- }
100
- .wc-shorts-h-thumb .h-duration {
101
- position: absolute; bottom: 4px; right: 4px;
102
- background: rgba(0,0,0,.7);
103
- font-size: 8px; color: #fff;
104
- padding: 1px 4px; border-radius: 3px;
105
- }
106
- .wc-shorts-h-thumb .h-vtv-badge {
107
- position: absolute; top: 4px; left: 4px;
108
- background: rgba(11,107,203,.85);
109
- font-size: 7px; color: #fff;
110
- padding: 1px 5px; border-radius: 3px;
111
- font-weight: 700;
112
- }
113
- .wc-shorts-h-info { padding: 5px 6px; }
114
- .wc-shorts-h-info .h-title {
115
- font-size: 9px; color: #ccc; line-height: 1.25;
116
- display: -webkit-box; -webkit-line-clamp: 2;
117
- -webkit-box-orient: vertical; overflow: hidden;
118
- }
119
- .wc-shorts-empty-slider {
120
- text-align: center; padding: 20px 10px; color: #666; font-size: 12px;
121
- width: 100%;
122
- }
123
- .wc-shorts-empty-slider .reload-btn {
124
- background: #0b6bcb; border: none; color: #fff;
125
- padding: 6px 16px; border-radius: 14px; font-size: 11px;
126
- font-weight: 700; cursor: pointer; margin-top: 10px;
127
- }
128
-
129
- /* === FULL-SCREEN TIKTOK OVERLAY (opened from slide) === */
130
- .wc-st-overlay {
131
- position: fixed; inset: 0; background: #000;
132
- z-index: 99999; display: none; flex-direction: column;
133
- }
134
- .wc-st-overlay.active { display: flex; }
135
- .wc-st-top-bar {
136
- position: absolute; top: 0; left: 0; right: 0;
137
- z-index: 10; padding: 10px 12px;
138
- display: flex; align-items: center; justify-content: space-between;
139
- background: linear-gradient(rgba(0,0,0,.7), transparent);
140
- }
141
- .wc-st-top-bar .back-btn {
142
- background: rgba(255,255,255,.15);
143
- border: none; color: #fff;
144
- width: 36px; height: 36px; border-radius: 50%;
145
- font-size: 18px; cursor: pointer;
146
- display: flex; align-items: center; justify-content: center;
147
- }
148
- .wc-st-top-bar .st-counter {
149
- color: #fff; font-size: 12px; font-weight: 600;
150
- background: rgba(0,0,0,.4); padding: 3px 10px;
151
- border-radius: 10px; backdrop-filter: blur(4px);
152
- }
153
-
154
- .wc-st-feed {
155
- flex: 1; overflow-y: scroll;
156
- scroll-snap-type: y mandatory;
157
- scrollbar-width: none;
158
- }
159
- .wc-st-feed::-webkit-scrollbar { display: none; }
160
-
161
- .wc-st-slide {
162
- height: 100vh; width: 100%;
163
- scroll-snap-align: start;
164
- position: relative;
165
- background: #000;
166
- display: flex; align-items: center; justify-content: center;
167
- overflow: hidden;
168
- }
169
- .wc-st-slide iframe {
170
- width: 100%; height: 100%; border: none; background: #000;
171
- }
172
-
173
- /* Title & info overlay */
174
- .wc-st-info {
175
- position: absolute; bottom: 0; left: 0; right: 65px;
176
- padding: 12px 14px 18px;
177
- background: linear-gradient(transparent 0%, rgba(0,0,0,.65) 35%, rgba(0,0,0,.9) 100%);
178
- z-index: 3; pointer-events: none;
179
- }
180
- .wc-st-info .st-channel {
181
- display: inline-block; font-size: 9px; font-weight: 700;
182
- padding: 2px 8px; border-radius: 10px; color: #fff;
183
- background: #0b6bcb; margin-bottom: 8px;
184
- }
185
- .wc-st-info .st-title {
186
- font-size: 14px; color: #fff; font-weight: 700;
187
- line-height: 1.35; text-shadow: 0 1px 4px rgba(0,0,0,.7);
188
- display: -webkit-box; -webkit-line-clamp: 3;
189
- -webkit-box-orient: vertical; overflow: hidden;
190
- }
191
- .wc-st-info .st-link {
192
- display: inline-block; margin-top: 8px;
193
- font-size: 11px; color: #8ab4d8; text-decoration: underline;
194
- pointer-events: auto; cursor: pointer;
195
- }
196
-
197
- /* Right-side action buttons */
198
- .wc-st-actions {
199
- position: absolute; right: 8px; bottom: 90px;
200
- display: flex; flex-direction: column; align-items: center;
201
- gap: 16px; z-index: 5;
202
- }
203
- .wc-st-action {
204
- display: flex; flex-direction: column; align-items: center; gap: 2px;
205
- background: none; border: none; color: #fff; cursor: pointer;
206
- font-size: 10px; text-shadow: 0 1px 3px rgba(0,0,0,.5);
207
- }
208
- .wc-st-action .a-icon {
209
- width: 46px; height: 46px; border-radius: 50%;
210
- background: rgba(255,255,255,.15);
211
- display: flex; align-items: center; justify-content: center;
212
- font-size: 20px; backdrop-filter: blur(4px);
213
- transition: background .2s, transform .15s;
214
- }
215
- .wc-st-action:active .a-icon { transform: scale(.88); background: rgba(255,255,255,.3); }
216
- .wc-st-action .a-count { font-size: 10px; color: #eee; font-weight: 600; }
217
-
218
- /* Loading state */
219
- .wc-st-loading {
220
- position: absolute; inset: 0; display: flex;
221
- flex-direction: column; align-items: center; justify-content: center;
222
- gap: 12px; color: #888; font-size: 13px;
223
- }
224
- .wc-st-loading .spinner {
225
- width: 28px; height: 28px;
226
- border: 3px solid #333; border-top-color: #0b6bcb;
227
- border-radius: 50%; animation: wcst-spin .8s linear infinite;
228
- }
229
- @keyframes wcst-spin { to { transform: rotate(360deg); } }
230
-
231
- /* No shorts message */
232
- .wc-st-nosho {
233
- position: absolute; inset: 0; display: flex;
234
- flex-direction: column; align-items: center; justify-content: center;
235
- gap: 10px; color: #666; font-size: 14px; text-align: center; padding: 20px;
236
- }
237
- .wc-st-nosho .icon { font-size: 48px; }
238
- .wc-st-nosho .retry-btn {
239
- background: #0b6bcb; border: none; color: #fff;
240
- padding: 10px 28px; border-radius: 20px;
241
- font-size: 13px; font-weight: 700; cursor: pointer;
242
- }
243
- `;
244
- document.head.appendChild(style);
245
-
246
- // ===== State =====
247
- let _wcSCached = null; // cached shorts data
248
- let _wcSOverlayTimer = null; // auto-advance timer
249
- let _wcSManualScroll = false;
250
-
251
- // ===== Fetch shorts =====
252
- async function fetchShorts(){
253
- const endpoints = [
254
- '/api/shorts/wc?count=50',
255
- '/api/shorts/vtvnamo?count=50',
256
- '/api/shorts?channel=vtvnambo',
257
- ];
258
- for(const ep of endpoints){
259
- try{
260
- const r = await fetch(ep, {signal: AbortSignal.timeout(12000)});
261
- const data = await r.json();
262
- if(Array.isArray(data) && data.length > 0){
263
- // Deduplicate
264
- const seen = new Set(), deduped = [];
265
- for(const s of data){
266
- if(!seen.has(s.id)){ seen.add(s.id); deduped.push(s); }
267
- }
268
- if(deduped.length > 0) return deduped;
269
- }
270
- }catch(e){ console.warn('WC Shorts fetch failed for', ep, e); }
271
- }
272
- return [];
273
- }
274
-
275
- // ===== Render the HORIZONTAL SLIDE (placed above WC section) =====
276
- window.renderWCSSlideSection = async function(container){
277
- const section = document.createElement('div');
278
- section.className = 'wc-shorts-slide-section';
279
- section.id = 'wc-shorts-slide-section';
280
-
281
- // Header
282
- section.innerHTML = `
283
- <div class="wc-shorts-slide-header">
284
- <h3><span class="emoji">📱</span> Shorts World Cup <span class="badge-live">● LIVE</span></h3>
285
- <button class="view-all-btn" id="wc-st-viewall">Xem tất cả →</button>
286
- </div>
287
- <div class="wc-shorts-h-slider" id="wc-st-hslider">
288
- <div style="width:100%;text-align:center;padding:20px;color:#666;font-size:12px">
289
- <div class="spinner" style="width:20px;height:20px;border:2px solid #333;border-top-color:#0b6bcb;border-radius:50%;animation:wcst-spin .8s linear infinite;margin:0 auto 8px"></div>
290
- Đang tải shorts...
291
- </div>
292
- </div>
293
- `;
294
- container.appendChild(section);
295
-
296
- const slider = section.querySelector('#wc-st-hslider');
297
- const viewAllBtn = section.querySelector('#wc-st-viewall');
298
-
299
- // Fetch data
300
- let shorts = await fetchShorts();
301
- _wcSCached = shorts;
302
-
303
- if(!shorts || !shorts.length){
304
- slider.innerHTML = `<div class="wc-shorts-empty-slider">
305
- <div>📱</div><div style="margin-top:6px;color:#888">Đang cập nhật shorts...</div>
306
- <button class="reload-btn" onclick="renderWCSSlideSection(document.getElementById('wc-shorts-slide-section').parentNode)">Thử lại</button>
307
- </div>`;
308
- viewAllBtn.onclick = () => openWCSF(shorts || [], 0);
309
- return;
310
- }
311
-
312
- // Render horizontal slider items
313
- let h = '';
314
- shorts.forEach((s, i)=>{
315
- const thumbImg = s.img || `https://i.ytimg.com/vi/${s.id}/hqdefault.jpg`;
316
- h += `<div class="wc-shorts-h-item" data-idx="${i}" onclick="openWCSF(_wcSCached, ${i})">`;
317
- h += `<div class="wc-shorts-h-thumb">`;
318
- h += `<img src="${escHtml(thumbImg)}" loading="lazy" onerror="this.style.display='none'">`;
319
- h += `<span class="h-vtv-badge">VTV Nam Bộ</span>`;
320
- h += `<div class="h-play-icon">▶</div>`;
321
- h += `</div>`;
322
- h += `<div class="wc-shorts-h-info"><div class="h-title">${escHtml(s.title)}</div></div>`;
323
- h += `</div>`;
324
- });
325
- slider.innerHTML = h;
326
-
327
- viewAllBtn.onclick = () => openWCSF(shorts, 0);
328
- };
329
-
330
- // ===== FULL-SCREEN TIKTOK OVERLAY =====
331
- window.openWCSF = function(shorts, startIdx){
332
- if(!shorts || !shorts.length){
333
- // Try to fetch fresh
334
- openWCSFLoading(startIdx);
335
- return;
336
- }
337
- buildWCSF(shorts, startIdx);
338
- };
339
-
340
- function openWCSFLoading(startIdx){
341
- const overlay = getOrCreateWCSFOverlay();
342
- overlay.classList.add('active');
343
- overlay.innerHTML = `<div class="wc-st-loading" style="position:absolute;inset:0;background:#000;z-index:100">
344
- <div class="spinner"></div>Đang tải shorts...
345
- </div>`;
346
- fetchShorts().then(shorts => {
347
- _wcSCached = shorts;
348
- if(shorts && shorts.length){
349
- buildWCSF(shorts, startIdx || 0);
350
- } else {
351
- overlay.innerHTML = `<div class="wc-st-nosho">
352
- <div class="icon">📱</div><div>Không tải được shorts</div>
353
- <button class="retry-btn" onclick="openWCSF(_wcSCached, ${startIdx||0})">Thử lại</button>
354
- <button class="retry-btn" style="background:#333;margin-top:4px" onclick="closeWCSF()">Đóng</button>
355
- </div>`;
356
- }
357
- }).catch(() => {
358
- overlay.innerHTML = `<div class="wc-st-nosho">
359
- <div class="icon">📱</div><div>Lỗi kết nối</div>
360
- <button class="retry-btn" onclick="openWCSF(_wcSCached, ${startIdx||0})">Thử lại</button>
361
- </div>`;
362
- });
363
- }
364
-
365
- function getOrCreateWCSFOverlay(){
366
- let overlay = document.getElementById('wc-st-overlay');
367
- if(!overlay){
368
- overlay = document.createElement('div');
369
- overlay.id = 'wc-st-overlay';
370
- overlay.className = 'wc-st-overlay';
371
- document.body.appendChild(overlay);
372
- }
373
- return overlay;
374
- }
375
-
376
- window.closeWCSF = function(){
377
- const overlay = document.getElementById('wc-st-overlay');
378
- if(!overlay) return;
379
- overlay.classList.remove('active');
380
- overlay.innerHTML = '';
381
- if(_wcSOverlayTimer){ clearInterval(_wcSOverlayTimer); _wcSOverlayTimer = null; }
382
- // Stop any playing iframes
383
- overlay.querySelectorAll('iframe').forEach(f => { f.src = ''; });
384
- };
385
-
386
- function buildWCSF(shorts, startIdx){
387
- const overlay = getOrCreateWCSFOverlay();
388
- overlay.classList.add('active');
389
-
390
- // Rotate so startIdx is first
391
- const ordered = startIdx > 0
392
- ? [...shorts.slice(startIdx), ...shorts.slice(0, startIdx)]
393
- : [...shorts];
394
- const total = ordered.length;
395
-
396
- // Build HTML
397
- let h = '';
398
- // Top bar
399
- h += `<div class="wc-st-top-bar">
400
- <button class="back-btn" onclick="closeWCSF()">←</button>
401
- <span class="st-counter" id="wc-st-counter">1/${total}</span>
402
- </div>`;
403
-
404
- // Feed
405
- h += `<div class="wc-st-feed" id="wc-st-feed">`;
406
-
407
- ordered.forEach((s, i)=>{
408
- const id = s.id || '';
409
- const embedSrc = `https://www.youtube.com/embed/${id}?autoplay=0&rel=0&playsinline=1&mute=1&loop=1&playlist=${id}&enablejsapi=1&origin=${encodeURIComponent(location.origin)}`;
410
-
411
- h += `<div class="wc-st-slide" data-vid="${escHtml('wcst-'+id)}" data-idx="${i}" data-ytid="${escHtml(id)}">`;
412
- // iframe — lazy loaded, autoplay handled by scroll observer
413
- h += `<iframe data-yt-src="${embedSrc}" allowfullscreen allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture;fullscreen"></iframe>`;
414
-
415
- // Info overlay (title)
416
- h += `<div class="wc-st-info">`;
417
- h += `<span class="st-channel">VTV Nam Bộ</span>`;
418
- h += `<div class="st-title">${escHtml(s.title)}</div>`;
419
- h += `<span class="st-link" onclick="window.open('https://youtube.com/shorts/${escHtml(id)}','_blank')">Mở trên YouTube →</span>`;
420
- h += `</div>`;
421
-
422
- // Right action buttons
423
- h += `<div class="wc-st-actions">`;
424
- h += `<button class="wc-st-action" title="Xem" onclick="event.stopPropagation();wcSTTrack('${escHtml(id)}','view',${i})"><div class="a-icon">👁</div><span class="a-count" id="wcst-vc-${i}">0</span></button>`;
425
- h += `<button class="wc-st-action" title="Thích" onclick="event.stopPropagation();wcSTTrack('${escHtml(id)}','like',${i})"><div class="a-icon">❤️</div><span class="a-count" id="wcst-lc-${i}">0</span></button>`;
426
- h += `<button class="wc-st-action" title="Chia sẻ" onclick="event.stopPropagation();wcSTShare('${escHtml(id)}')"><div class="a-icon">📤</div></button>`;
427
- h += `<button class="wc-st-action" title="YouTube" onclick="event.stopPropagation();window.open('https://youtube.com/shorts/${escHtml(id)}','_blank')"><div class="a-icon">🔗</div></button>`;
428
- h += `</div>`;
429
-
430
- h += `</div>`;
431
- });
432
-
433
- h += `</div>`; // end feed
434
- overlay.innerHTML = h;
435
-
436
- // Init autoplay on scroll
437
- initWCSFAutoplay(ordered);
438
-
439
- // Load stats
440
- loadWCSFStats(ordered);
441
- }
442
-
443
- // ===== Autoplay on scroll (intersection observer) =====
444
- function initWCSFAutoplay(shorts){
445
- const feed = document.getElementById('wc-st-feed');
446
- if(!feed) return;
447
- const slides = feed.querySelectorAll('.wc-st-slide');
448
- let curIdx = -1;
449
- _wcSManualScroll = false;
450
- let manualTimer = null;
451
-
452
- function activate(idx){
453
- if(idx === curIdx || idx < 0 || idx >= slides.length) return;
454
- slides.forEach((sl, j)=>{
455
- const fr = sl.querySelector('iframe');
456
- if(j === idx){
457
- // Load iframe if needed
458
- if(fr && !fr.src && fr.dataset.ytSrc){ fr.src = fr.dataset.ytSrc; }
459
- // Play (with mute to satisfy autoplay policy)
460
- if(fr && fr.src){
461
- setTimeout(()=>{
462
- try{
463
- fr.contentWindow.postMessage(JSON.stringify({event:'command',func:'mute',args:''}),'*');
464
- fr.contentWindow.postMessage(JSON.stringify({event:'command',func:'playVideo',args:''}),'*');
465
- }catch(e){}
466
- }, 400);
467
- }
468
- } else {
469
- // Pause others
470
- if(fr && fr.src){
471
- try{ fr.contentWindow.postMessage(JSON.stringify({event:'command',func:'pauseVideo',args:''}),'*'); }catch(e){}
472
- }
473
- }
474
- });
475
- curIdx = idx;
476
- // Update counter
477
- const counter = document.getElementById('wc-st-counter');
478
- if(counter) counter.textContent = `${idx+1}/${shorts.length}`;
479
- }
480
-
481
- // Use IntersectionObserver for precise snap detection
482
- const io = new IntersectionObserver((entries)=>{
483
- if(_wcSManualScroll) return;
484
- entries.forEach(entry=>{
485
- if(entry.isIntersecting && entry.intersectionRatio > 0.6){
486
- const idx = parseInt(entry.target.dataset.idx);
487
- if(!isNaN(idx)) activate(idx);
488
- }
489
- });
490
- }, {threshold: [0.6], root: feed});
491
-
492
- slides.forEach(sl => io.observe(sl));
493
-
494
- // Scroll event for manual detection (fallback)
495
- let scrollTimer;
496
- feed.addEventListener('scroll', ()=>{
497
- _wcSManualScroll = true;
498
- clearTimeout(manualTimer);
499
- manualTimer = setTimeout(()=>{ _wcSManualScroll = false; }, 3000);
500
- clearTimeout(scrollTimer);
501
- scrollTimer = setTimeout(()=>{
502
- const rect = feed.getBoundingClientRect();
503
- const ctr = rect.top + rect.height / 2;
504
- let best = -1, bestD = 1e9;
505
- slides.forEach((sl,i)=>{
506
- const r = sl.getBoundingClientRect();
507
- const d = Math.abs(r.top + r.height/2 - ctr);
508
- if(d < bestD){ bestD = d; best = i; }
509
- });
510
- if(best >= 0) activate(best);
511
- }, 100);
512
- }, {passive: true});
513
-
514
- // Auto-advance every 12 seconds
515
- if(_wcSOverlayTimer) clearInterval(_wcSOverlayTimer);
516
- _wcSOverlayTimer = setInterval(()=>{
517
- if(_wcSManualScroll) return;
518
- if(!document.getElementById('wc-st-feed')){ clearInterval(_wcSOverlayTimer); _wcSOverlayTimer = null; return; }
519
- const next = curIdx + 1;
520
- if(next < slides.length){
521
- slides[next].scrollIntoView({behavior:'smooth', block:'start'});
522
- } else {
523
- slides[0].scrollIntoView({behavior:'smooth', block:'start'});
524
- }
525
- }, 12000);
526
-
527
- // Activate first after short delay
528
- setTimeout(()=>{
529
- if(slides.length > 0){ slides[0].scrollIntoView({behavior:'start'}); activate(0); }
530
- }, 300);
531
- }
532
-
533
- // ===== Interaction tracking =====
534
- window.wcSTTrack = async function(videoId, type, idx){
535
- try{
536
- const r = await fetch('/api/short-action', {
537
- method:'POST', headers:{'Content-Type':'application/json'},
538
- body: JSON.stringify({id:videoId, action:type})
539
- });
540
- const j = await r.json();
541
- if(j && j.stats){
542
- if(idx !== undefined){
543
- const vc = document.getElementById(`wcst-vc-${idx}`);
544
- const lc = document.getElementById(`wcst-lc-${idx}`);
545
- if(type==='view' && vc) vc.textContent = fmtNum(j.stats.views);
546
- if(type==='like' && lc) lc.textContent = fmtNum(j.stats.likes);
547
- }
548
- }
549
- }catch(e){ console.warn('Track error:', e); }
550
- };
551
-
552
- window.wcSTShare = function(videoId){
553
- const url = 'https://youtube.com/shorts/' + videoId;
554
- if(navigator.share){
555
- navigator.share({title:'Short World Cup', url}).catch(()=>{});
556
- } else {
557
- navigator.clipboard.writeText(url).then(()=>alert('Đã sao chép link!')).catch(()=>{});
558
- }
559
- };
560
-
561
- async function loadWCSFStats(shorts){
562
- const ids = shorts.map(s=>s.id).filter(Boolean);
563
- if(!ids.length) return;
564
- // Batch in groups of 20
565
- for(let i=0;i<ids.length;i+=20){
566
- const batch = ids.slice(i,i+20).join(',');
567
- try{
568
- const r = await fetch('/api/short-stats?ids='+batch);
569
- const j = await r.json();
570
- if(j.stats){
571
- shorts.forEach((s,idx)=>{
572
- const st = j.stats[s.id];
573
- if(st){
574
- const vc = document.getElementById(`wcst-vc-${idx}`);
575
- const lc = document.getElementById(`wcst-lc-${idx}`);
576
- if(vc) vc.textContent = fmtNum(st.views);
577
- if(lc) lc.textContent = fmtNum(st.likes);
578
- }
579
- });
580
- }
581
- }catch(e){}
582
- }
583
- }
584
-
585
- function fmtNum(n){
586
- if(!n) return '0';
587
- if(n>=1e6) return (n/1e6).toFixed(1)+'M';
588
- if(n>=1e3) return (n/1e3).toFixed(1)+'K';
589
- return String(n);
590
- }
591
-
592
- // ===== Helpers =====
593
- function escHtml(s){
594
- return String(s||'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));
595
- }
596
-
597
- })();