bep40 commited on
Commit
88a17a4
·
verified ·
1 Parent(s): 5d66909

Restore static/wc_shorts_slide.js to 0cc342c

Browse files
Files changed (1) hide show
  1. static/wc_shorts_slide.js +597 -0
static/wc_shorts_slide.js ADDED
@@ -0,0 +1,597 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ })();