PageSourceSearch

https://sanykala.com/wp-content/themes/bakala/inc/integrations/ele…/assets/js/story.js?ver=1781603154

js sanykala.com collected 2026-10-07 01:04:20 UTC 29,740 bytes, 679 lines download raw bytes

1/**
2 * Bakala Story Widget — story.js
3 * نسخه هماهنگ شده با بهینه‌سازها - رفع مشکل لود تصاویر و ویدیو
4 */
5(function ($) {
6    'use strict';
7
8    const ajax    = (typeof bk_story_ajax !== 'undefined') ? bk_story_ajax.ajax_url : '/wp-admin/admin-ajax.php';
9    const nonce   = (typeof bk_story_ajax !== 'undefined') ? bk_story_ajax.nonce   : '';
10    const loggedIn = (typeof bk_story_ajax !== 'undefined') && !!bk_story_ajax.is_logged_in;
11
12    // ───────────────────────────────────────────────────────────────
13    // VIDEO
14    // ───────────────────────────────────────────────────────────────
15    const videos = {};
16
17    function fmtTime(s) {
18        const m = Math.floor(s / 60), sec = Math.floor(s % 60);
19        return m + ':' + (sec < 10 ? '0' : '') + sec;
20    }
21
22    function initVideo(videoId) {
23        if (typeof videoId !== 'string') return;
24        const vid = document.getElementById(videoId);
25        if (!vid) return;
26
27        // ⭐ لود src ویدیو از data attribute
28        var source = vid.querySelector('source[data-video-get]');
29        if (source && !source.src) {
30            source.src = source.getAttribute('data-video-get');
31            vid.load();
32        }
33
34        if (videos[videoId]) cleanupVideo(videoId, false);
35
36        const ctrl  = document.querySelector('.video-custom-controls[data-video-id="' + videoId + '"]');
37        if (!ctrl) return;
38
39        const fill    = ctrl.querySelector('.video-progress-fill');
40        const progBar = ctrl.querySelector('.video-progress-bar');
41        const curT    = ctrl.querySelector('.video-current-time');
42        const durT    = ctrl.querySelector('.video-duration');
43
44        function onMeta() { if (durT && !isNaN(vid.duration)) durT.textContent = fmtTime(vid.duration); }
45        function onProgClick(e) {
46            if (!vid.duration) return;
47            const r = progBar.getBoundingClientRect();
48            vid.currentTime = ((e.clientX - r.left) / r.width) * vid.duration;
49        }
50
51        vid.addEventListener('loadedmetadata', onMeta);
52        if (progBar) progBar.addEventListener('click', onProgClick);
53// دکمه play/pause - native binding
54var playBtn = document.querySelector('.video-play-btn[data-video-id="' + videoId + '"]');
55if (playBtn && !playBtn._bound) {
56    playBtn._bound = true;
57    playBtn.addEventListener('click', function(e) {
58        e.stopPropagation();
59        e.preventDefault();
60        if (vid.paused) {
61            vid.play().catch(function(){});
62            $(playBtn).find('.vp-play').hide();
63            $(playBtn).find('.vp-pause').show();
64            if (currentStory) currentStory.resumeProgress();
65        } else {
66            vid.pause();
67            $(playBtn).find('.vp-play').show();
68            $(playBtn).find('.vp-pause').hide();
69            if (currentStory) currentStory.pauseProgress();
70        }
71    }, true); // ⭐ true = capture phase - قبل از همه اجرا میشه
72}
73
74// دکمه mute - native binding
75var muteBtn = document.querySelector('.video-mute-btn[data-video-id="' + videoId + '"]');
76if (muteBtn && !muteBtn._bound) {
77    muteBtn._bound = true;
78    muteBtn.addEventListener('click', function(e) {
79        e.stopPropagation();
80        e.preventDefault();
81        vid.muted = !vid.muted;
82        if (vid.muted) {
83            $(muteBtn).find('.vp-sound').hide();
84            $(muteBtn).find('.vp-muted').show();
85        } else {
86            $(muteBtn).find('.vp-sound').show();
87            $(muteBtn).find('.vp-muted').hide();
88        }
89    }, true);
90}
91        videos[videoId] = { vid, fill, curT, interval: null, onMeta, onProgClick };
92
93        const container = vid.closest('.story-video-container') || vid.closest('.story-slide-item');
94        if (container) {
95            let holdTimer = null;
96            let heldPause = false;
97            container._holdStart = function(e) {
98                holdTimer = setTimeout(function() {
99                    if (!vid.paused) { vid.pause(); heldPause = true; if (currentStory) currentStory.pauseProgress(); }
100                }, 180);
101            };
102            container._holdEnd = function(e) {
103                clearTimeout(holdTimer);
104                if (heldPause && vid.paused) { vid.play().catch(function(){}); heldPause = false; if (currentStory) currentStory.resumeProgress(); }
105            };
106            container.addEventListener('touchstart', container._holdStart, { passive: true });
107            container.addEventListener('touchend',   container._holdEnd);
108            container.addEventListener('touchcancel',container._holdEnd);
109        }
110
111        vid.play().then(function() { startVidProgress(videoId); }).catch(function() {});
112    }
113
114    function startVidProgress(videoId) {
115        const c = videos[videoId]; if (!c) return;
116        if (c.interval) clearInterval(c.interval);
117        c.interval = setInterval(function() {
118            if (!c.vid) return;
119            if (c.curT && c.vid.duration) c.curT.textContent = fmtTime(c.vid.currentTime);
120            if (c.fill && c.vid.duration)  c.fill.style.width = (c.vid.currentTime / c.vid.duration * 100) + '%';
121            if (c.vid.ended) { stopVidProgress(videoId); if (currentStory) currentStory.goNext(); }
122        }, 150);
123    }
124
125    function stopVidProgress(videoId) {
126        const c = videos[videoId]; if (!c || !c.interval) return;
127        clearInterval(c.interval); c.interval = null;
128    }
129
130    function cleanupVideo(videoId, pause) {
131        const c = videos[videoId]; if (!c) return;
132        stopVidProgress(videoId);
133        if (c.vid) { c.vid.removeEventListener('loadedmetadata', c.onMeta); }
134        const ctrl = document.querySelector('.video-custom-control
134s[data-video-id="' + videoId + '"]');
135        if (ctrl && c.onProgClick) { const pb = ctrl.querySelector('.video-progress-bar'); if (pb) pb.removeEventListener('click', c.onProgClick); }
136        const container = c.vid ? (c.vid.closest('.story-video-container') || c.vid.closest('.story-slide-item')) : null;
137        if (container) {
138            if (container._holdStart) container.removeEventListener('touchstart', container._holdStart);
139            if (container._holdEnd)   { container.removeEventListener('touchend',    container._holdEnd); container.removeEventListener('touchcancel', container._holdEnd); }
140        }
141        if (pause !== false && c.vid) c.vid.pause();
142        delete videos[videoId];
143    }
144
145    // ───────────────────────────────────────────────────────────────
146    // STORY SLIDE CLASS
147    // ───────────────────────────────────────────────────────────────
148    function StorySlide(storyId) {
149        this.id       = storyId;
150        this.$wrapper = $('[data-slide-ref="' + storyId + '"]');
151        this.$slide   = $('[data-slide="' + storyId + '"]');
152        this.items    = this.$slide.find('.story-slide-items > .story-slide-item').toArray();
153        this.thumbEls = this.$slide.find('.story-slide-thumbs .story-slide-thumb-item').toArray();
154        this.current  = 0;
155        this.total    = this.items.length;
156        this.rafId    = null;
157        this.startTime = null;
158        this.duration  = 5000;
159        this._pausedAt = null;
160    }
161
162    StorySlide.prototype = {
163
164        _setThumbPct: function(idx, pct) {
165            if (this.thumbEls[idx]) this.thumbEls[idx].style.setProperty('--progress', pct + '%');
166        },
167        _resetThumbs: function() {
168            var self = this;
169            this.thumbEls.forEach(function(el, i) {
170                el.classList.remove('active', 'passed');
171                el.style.setProperty('--progress', i < self.current ? '100%' : '0%');
172                if (i < self.current) el.classList.add('passed');
173            });
174            if (this.thumbEls[this.current]) {
175                this.thumbEls[this.current].classList.add('active');
176                this.thumbEls[this.current].style.setProperty('--progress', '0%');
177            }
178        },
179        startProgress: function() {
180            var self = this;
181            if (this.rafId) { cancelAnimationFrame(this.rafId); this.rafId = null; }
182            this._resetThumbs();
183            this.startTime = Date.now();
184            function tick() {
185                var elapsed = Date.now() - self.startTime;
186                var pct = Math.min(elapsed / self.duration * 100, 100);
187                self._setThumbPct(self.current, pct);
188                if (pct < 100) { self.rafId = requestAnimationFrame(tick); }
189                else {
190                    self.rafId = null;
191                    if (self.thumbEls[self.current]) {
192                        self.thumbEls[self.current].classList.remove('active');
193                        self.thumbEls[self.current].classList.add('passed');
194                        self.thumbEls[self.current].style.setProperty('--progress', '100%');
195                    }
196                    setTimeout(function() { self.goNext(); }, 50);
197                }
198            }
199            this.rafId = requestAnimationFrame(tick);
200        },
201        pauseProgress: function() {
202            if (this.rafId) { cancelAnimationFrame(this.rafId); this.rafId = null; }
203            this._pausedAt = Date.now() - (this.startTime || Date.now());
204        },
205        resumeProgress: function() {
206            var self = this;
207            var elapsed = this._pausedAt || 0;
208            this.startTime = Date.now() - elapsed;
209            this._pausedAt = null;
210            function tick() {
211                var e = Date.now() - self.startTime;
212                var pct = Math.min(e / self.duration * 100, 100);
213                self._setThumbPct(self.current, pct);
214                if (pct < 100) { self.rafId = requestAnimationFrame(tick); }
215                else { self.rafId = null; setTimeout(function() { self.goNext(); }, 50); }
216            }
217            this.rafId = requestAnimationFrame(tick);
218        },
219        stopProgress: function() {
220            if (this.rafId) { cancelAnimationFrame(this.rafId); this.rafId = null; }
221        },
222
223        activate: function(index) {
224            if (index < 0 || index >= this.total) return;
225            var self = this;
226
227            this.stopProgress();
228            var prevVidId = this._vidId(this.current);
229            if (prevVidId) cleanupVideo(prevVidId);
230
231            this.current = index;
232
233            $(this.items).removeClass('active');
234            $(this.items[index]).addClass('active');
235
236            var item = this.items[index];
237
238            // ⭐ لود تصویر lazy
239            var img = item ? item.querySelector('img[data-image-get]') : null;
240            if (img) {
241                var realSrc = img.getAttribute('data-image-get');
242                if (realSrc && (img.src.indexOf('data:') === 0 || img.src === '')) {
243                    var p = new Image();
244                    p.onload = function() { img.src = p.src; };
245                    p.src = realSrc;
246                }
247            }
248
249            var vid = item ? item.querySelector('video') : null;
250
251            if (vid) {
252                var vidId = vid.id;
253                this.duration = 15000;
254
255                function startVid() {
256                    if (vid.duration > 0 && !isNaN(vid.duration)) self.duration = vid.duration * 1000;
257                    self.startProgress();
258                    initVideo(vidId);
259                }
260
261                if (vid.readyState >= 1) {
262                    startVid();
263                } else {
264                    vid.addEventListener('loadedmetadata', function onMeta() {
265                        vid.removeEventListener('loadedmetadata', onMeta);
266                        if (vid.duration > 0 && !isNaN(vid.duration)) self.duration = vid.duration * 1000;
267                        self._resetThumbs();
268                        self.startTime = Date.now();
269                        startVidProgress(vidId);
270                        if (!self.rafId) {
271                            function syncTick() {
272                                if (!vid.duration || isNaN(vid.duration)) return;
273                                var pct = (vid.currentTime / vid.duration) * 100;
274                                self._setThumbPct(self.current, pct);
275                                if (!vid.ended && !vid.paused) self.rafId = requestAnimationFrame(syncTick);
276                                if (vid.ended) setTimeout(function() { self.goNext(); }, 50);
277                            }
278                            self.rafId = requestAnimationFrame(syncTick);
279                        }
280                    });
281                    vid.load();
282                    initVideo(vidId);
283                }
284            } else {
285                this.duration = 5000;
286                this.startProgress();
287
288                if (item && !item._holdBound && 'ontouchstart' in window) {
289                    item._holdBound = true;
290                    var holdT = null, held = false;
291                    item.addEventListener('touchstart', function(e) {
292                        if ($(e.target).closest('.story-slide-next, .story-slide-prev, button, a').length) return;
293                        holdT = setTimeout(function() { held = true; if (currentStory) currentStory.pauseProgress(); }, 500);
294                    }, { passive: true });
295                    item.addEventListener('touchend', function(e) {
296                        clearTimeout(holdT);
297                        if (held) { held = false; if (currentStory) currentStory.resumeProgress(); }
298                    });
299                    item.addEventListener('touchcancel', function() { clearTimeout(holdT); held = false; });
300                }
301            }
302        },
303
304        _vidId: function(index) {
305            var item = this.items[index];
306            if (!item) return null;
307            var v = item.querySelector('video');
308            return v ? v.id : null;
309        },
310
311        next: function() {
312            if (this.current < this.total - 1) { this.activate(this.current + 1); return true; }
313            return false;
314        },
315        prev: function() {
316            if (this.current > 0) { this.activate(this.current - 1); return true; }
317            return false;
318        },
319        goNext: function() {
320            if (!this.next()) {
321                var nxt = this.$wrapper.attr('data-next-story');
322                if (nxt) {
323                    window.bkStoryOpen(nxt);
324                } else {
325                    closeStory(this.$wrapper);
326                }
327            }
328        },
329        destroy: function() {
330            this.stopProgress();
331            var vidId = this._vidId(this.current);
332            if (vidId) cleanupVideo(vidId);
333        }
334    };
335
336    // ───────────────────────────────────────────────────────────────
337    // OPEN / CLOSE
338    // ───────────────────────────────────────────────────────────────
339    var currentStory = null;
340
341window.bkStoryOpen = function(storyId, direction) {
342    if (currentStory) { currentStory.destroy(); currentStory = null; }
343    var $w = $('[data-slide-ref="' + storyId + '"]');
344    if (!$w.length) return;
345    
346    var $prevVisible = $('.story-wrapper:visible');
347    
348    if (direction && $prevVisible.length && $prevVisible[0] !== $w[0]) {
349        var $oldSlide = $prevVisible.find('.story-slide').first();
350        var $newSlide = $w.find('.story-slide').first();
351        
352        var w = $oldSlide.outerWidth() || 400;
353        var halfW = w / 2;
354        
355        var oldRotate, newRotateStart;
356        if (direction === 'next') {
357            oldRotate = 90;
358            newRotateStart = -90;
359        } else {
360            oldRotate = -90;
361            newRotateStart = 90;
362        }
363        
364        // wrapper سه‌بعدی
365        $prevVisible.css({ perspective: '1400px', 'perspective-origin': '50% 50%' });
366        $w.css({ perspective: '1400px', 'perspective-origin': '50% 50%' });
367        
368        // ── setup اولیه ──
369        $oldSlide.css({
370            transition: 'none',
371            'transform-style': 'preserve-3d',
372            'box-shadow': '0 30px 80px rgba(0,0,0,.5)',
373            transform: 'translateZ(0px) rotateY(0deg) scale(1)'
374        });
375        
376        $w.show();
377        $newSlide.css({
378            transition: 'none',
379            'transform-style': 'preserve-3d',
380            'box-shadow': '0 30px 80px rgba(0,0,0,.5)',
381            transform: 'translateZ(-400px) rotateY(' + newRotateStart + 'deg) scale(0.6)',
382            opacity: 0.3,
383            filter: 'brightness(0.4) blur(3px)'
384        });
385        
386        // ── مرحله ۱: قدیمی عقب میره + کوچیک میشه + شروع چرخش ──
387        requestAnimationFrame(function() {
388            var easing = 'cubic-bezier(.65,.05,.36,1)';
389            
390            $oldSlide.css({
391                transition: 'all 400ms ' + easing,
392                transform: 'translateZ(-400px) rotateY(' + (oldRotate * 0.5) + 'deg) 
392scale(0.6)',
393                opacity: 0.5,
394                filter: 'brightness(0.5) blur(2px)'
395            });
396            
397            // ── مرحله ۲: قدیمی کامل میچرخه و میره، جدید میاد جلو ──
398            setTimeout(function() {
399                $oldSlide.css({
400                    transition: 'all 400ms ' + easing,
401                    transform: 'translateZ(-400px) rotateY(' + oldRotate + 'deg) scale(0.6)',
402                    opacity: 0,
403                    filter: 'brightness(0.3) blur(4px)'
404                });
405                
406                $newSlide.css({
407                    transition: 'all 500ms ' + easing,
408                    transform: 'translateZ(-400px) rotateY(0deg) scale(0.6)',
409                    opacity: 0.7,
410                    filter: 'brightness(0.7) blur(1px)'
411                });
412                
413                // ── مرحله ۳: جدید میاد جلو و بزرگ میشه ──
414                setTimeout(function() {
415                    $newSlide.css({
416                        transition: 'all 450ms ' + easing,
417                        transform: 'translateZ(0px) rotateY(0deg) scale(1)',
418                        opacity: 1,
419                        filter: 'brightness(1) blur(0)'
420                    });
421                }, 250);
422            }, 300);
423        });
424        
425        // ── پاک‌سازی ──
426        setTimeout(function() {
427            $prevVisible.hide().css({ perspective: '', 'perspective-origin': '' });
428            $w.css({ perspective: '', 'perspective-origin': '' });
429            $oldSlide.removeAttr('style').show();
430            $newSlide.removeAttr('style').show();
431        }, 1200);
432    } else {
433        $prevVisible.hide();
434        $w.fadeIn(150);
435    }
436    
437    $w.find('.story-comments').hide();
438    currentStory = new StorySlide(storyId);
439    currentStory.activate(0);
440    markSeen(storyId);
441};
442    function closeStory($w) {
443        if (currentStory) { currentStory.destroy(); currentStory = null; }
444        $w.fadeOut(150);
445    }
446
447    // ───────────────────────────────────────────────────────────────
448    // SEEN
449    // ───────────────────────────────────────────────────────────────
450    function markSeen(id) {
451        try {
452            var seen = JSON.parse(localStorage.getItem('bk_seen') || '[]');
453            if (seen.indexOf(id) < 0) { seen.push(id); localStorage.setItem('bk_seen', JSON.stringify(seen)); }
454            $('[data-story-slide="' + id + '"] .story-image-wrapper').addClass('bk-seen');
455        } catch(e) {}
456    }
457    $(document).ready(function() {
458        try {
459            var seen = JSON.parse(localStorage.getItem('bk_seen') || '[]');
460            seen.forEach(function(id) { $('[data-story-slide="' + id + '"] .story-image-wrapper').addClass('bk-seen'); });
461        } catch(e) {}
462    });
463
464    // ───────────────────────────────────────────────────────────────
465    // EVENTS
466    // ───────────────────────────────────────────────────────────────
467
468    window.goStoryNext = function() {
469    if (!currentStory) return;
470    if (!currentStory.next()) {
471        var n = currentStory.$wrapper.data('next-story');
472        if (n) {
473            window.bkStoryOpen(n, 'next');
474        } else {
475            closeStory(currentStory.$wrapper);
476        }
477    }
478}
479
480window.goStoryPrev = function() {
481    if (!currentStory) return;
482    if (!currentStory.prev()) {
483        var p = currentStory.$wrapper.data('prev-story');
484        if (p) {
485            window.bkStoryOpen(p, 'prev');
486        }
487    }
488}
489
490
491    $(document).on('click', '.story-item[data-story-slide]', function() {
492        window.bkStoryOpen($(this).data('story-slide'));
493    });
494
495    $(document).on('click', '.story-wrapper-close-story', function() {
496        closeStory($(this).closest('.story-wrapper'));
497    });
498
499    $(document).on('click', '.story-wrapper-close-related', function(e) {
500        e.stopPropagation();
501        $(this).closest('.story-related-products').fadeOut(150);
502    });
503
504    $(document).on('click', '.story-wrapper', function(e) {
505        if (!$(e.target).closest('.story-slide, .story-switch-btn, .story-wrapper-close-story').length) {
506            closeStory($(this));
507        }
508    });
509
510    $(document).on('click', '.story-switch-btn', function(e) {
511        e.stopPropagation();
512        var t = $(this).attr('data-target');
513        if (t) window.bkStoryOpen(t);
514    });
515// دکمه play/pause ویدیو (دسکتاپ)
516$(document).on('click', '.video-play-btn', function(e) {
517    e.stopPropagation();
518    var videoId = $(this).data('video-id');
519    var vid = document.getElementById(videoId);
520    if (!vid) return;
521    var $btn = $(this);
522    if (vid.paused) {
523        vid.play().catch(function(){});
524        $btn.find('.vp-play').hide();
525        $btn.find('.vp-pause').show();
526        if (currentStory) currentStory.resumeProgress();
527    } else {
528        vid.pause();
529        $btn.find('.vp-play').show();
530        $btn.find('.vp-pause').hide();
531        if (currentStory) currentStory.pauseProgress();
532    }
533});
534
535// دکمه mute/unmute ویدیو
536$(document).on('click', '.video-mute-btn', function(e) {
537    e.stopPropagation();
538    var videoId = $(this).data('video-id');
539    var vid = document.getElementById(videoId);
540    if (!vid) return;
541    var $btn = $(this);
542    vid.muted = !vid.muted;
543    if (vid.muted) {
544        $btn.find('.vp-sound').hide();
545        $btn.find('.vp-muted').show();
546    } else {
547        $btn.find('.vp-sound').show();
548        $btn.find('.vp-muted').hide();
549    }
550});
551  $(document).on('click', '.story-slide-link', function(e) {
552    e.stopPropagation();
553    var href = $(this).attr('href');
554    var target = $(this).attr('target') || '_self';
555    if (href) window.open(href, target);
556});
557    $(document).on('click', '.story-slide-next', function(e) {
558        e.stopPropagation();
559        goStoryNext();
560    });
561
562    $(document).on('click', '.story-slide-prev', function(e) {
563        e.stopPropagation();
564        goStoryPrev();
565    });
566
567    $(document).on('keydown', function(e) {
568        if (!currentStory) return;
569        if (e.key === 'Escape') {
570            closeStory(currentStory.$wrapper);
571        } else if (e.key === 'ArrowRight') {
572            goStoryNext();
573        } else if (e.key === 'ArrowLeft') {
574            goStoryPrev();
575        }
576    });
577
578    var touchX0 = 0;
579    $(document).on('touchstart', '.story-slide', function(e) {
580        touchX0 = e.originalEvent.touches[0].clientX;
581    });
582
583    $(document).on('touchend', '.story-slide', function(e) {
584        if (!currentStory) return;
585        var touchX1 = e.originalEvent.changedTouches[0].clientX;
586        var dx = touchX1 - touchX0;
587        if (Math.abs(dx) < 50) return;
588        if (dx < 0) goStoryPrev();
589        else goStoryNext();
590    });
591
592    $(document).on('contextmenu', '.story-slide img, .story-slide video', function(e) {
593        e.preventDefault(); return false;
594    });
595
596    $(document).on('dragstart', '.story-slide img, .story-slide video', function(e) {
597        e.preventDefault(); return false;
598    });
599
600    // ───────────────────────────────────────────────────────────────
601    // LIKE
602    // ───────────────────────────────────────────────────────────────
603    $(document).on('click', '.like-button', function(e) {
604        e.stopPropagation();
605        var $btn = $(this), id = $btn.data('story-id');
606        if (!loggedIn) {
607            var liked = $btn.hasClass('liked');
608            $btn.toggleClass('liked', !liked).find('svg').attr('fill', !liked ? 'red' : 'none');
609            $btn.find('.like-count').text(parseInt($btn.find('.like-count').text() || 0) + (!liked ? 1 : -1));
610            return;
611        }
612        if ($btn.hasClass('processing')) return;
613        $btn.addClass('processing');
614        $.post(ajax, { action: 'bk_story_like', nonce: nonce, story_id: id }, function(res) {
615            $btn.removeClass('processing');
616            if (!res.success) return;
617            $btn.toggleClass('liked', res.data.liked).find('svg').attr('fill', res.data.liked ? 'red' : 'none');
618            $btn.find('.like-count').text(res.data.count);
619        });
620    });
621
622    // ───────────────────────────────────────────────────────────────
623    // COMMENT
624    // ───────────────────────────────────────────────────────────────
625    $(document).on('click', '.comment-button', function(e) {
626        e.stopPropagation();
627        var id = $(this).data('story-id');
628        var $panel = $('[data-slide="' + id + '"]').find('.story-comments');
629        if (!$panel.length) return;
630        if ($panel.is(':visible')) {
631            $panel.hide();
632            if (currentStory) currentStory.resumeProgress();
633            var vid = $('[data-slide="' + id + '"]').find('video')[0];
634            if (vid) vid.play().catch(function(){});
635            return;
636        }
637        $panel.show();
638        if (currentStory) currentStory.pauseProgress();
639        var vid = $('[data-slide="' + id + '"]').find('video')[0];
640        if (vid) vid.pause();
641        var $list = $panel.find('.story-comments-list');
642        $list.html('<div class="loading-comments">در حال بارگذاری...</div>');
643        $.get(ajax, { action: 'bk_story_get_comments', nonce: nonce, story_id: id }, function(res) {
644            $list.html(res.success && res.data.html ? res.data.html : '<div class="no-comments">هنوز نظری نیست.</div>');
645        }).fail(function() { $list.html('<div class="error-message">خطا.</div>'); });
646    });
647
648    $(document).on('click', '.close-story-comments', function(e) {
649        e.stopPropagation();
650        var $panel = $(this).closest('.story-comments');
651        $panel.hide();
652        if (currentStory) currentStory.resumeProgress();
653        var $slide = $panel.closest('.story-slide');
654        var vid = $slide.find('video')[0];
655        if (vid) vid.play().catch(function(){});
656    });
657
658    $(document).on('submit', '.story-comment-form', function(e) {
659        e.preventDefault();
660        var $form = $(this), id = $form.data('story-id'), text = $form.find('textarea').val().trim();
661        if (!text) return;
662        var $btn = $form.find('[type=submit]').prop('disabled', true);
663        $.post(ajax, { action: 'bk_story_add_comment', nonce: nonce, story_id: id, comment_text: text, bk_nonce: $form.find('#bk_nonce').val() }, function(res) {
664            $btn.prop('disabled', false);
665            if (!res.success) return;
666            $form.find('textarea').val('');
667            var $list = $form.closest('.story-comments').find('.story-comments-list');
668            if (res.data.pending) {
669                $list.append('<div style="text-align:center;padding:10px;color:rgba(255,255,255,.7);font-size:12px;">✓ نظر شما پس از تایید مدیر نمایش داده می‌شود.</div>');
670            } else if (res.data.html) {
671                $list.find('.no-comments').remove();
672                $list.append(res.data.html);
673                $list.scrollTop($list[0].scrollHeight);
674                $('.comment-button[data-story-id="' + id + '"] .comment-count').text(function(i, v) { return (parseInt(v) || 0) + 1; });
675            }
676        }).fail(function() { $btn.prop('disabled', false); });
677    });
678
679})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.