HEX
Server: LiteSpeed
System: Linux emerald.serverdnsnetwork.com 5.14.0-611.5.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Nov 11 08:09:09 EST 2025 x86_64
User: mahdetej (1135)
PHP: 8.1.34
Disabled: show_source, system, shell_exec, passthru, exec, popen, proc_open
Upload Files
File: /home/mahdetej/public_html/wp-content/plugins/Melli-shild/assets/js/performance.js
/**
 * MelliShield Performance Analyzer
 *
 * @package Melli_Shield
 * @since 1.0.0
 */
(function ($) {
    'use strict';

    var Perf = {
        resources: [],
        checked: 0,
        total: 0,
        running: false,

        init: function () {
            if ($('#melli-shield-perf-start').length === 0 &&
                $('#melli-shield-perf-client').length === 0 &&
                $('#melli-shield-frontend-results').length === 0) {
                return;
            }
            this.bind();

            // Auto-load frontend data if spinner present
            if ($('#melli-shield-frontend-results .spinner').length > 0) {
                this.loadFrontendData();
            }
        },

        bind: function () {
            var self = this;
            $('#melli-shield-perf-start').on('click', function (e) { e.preventDefault(); self.startServer(); });
            $('#melli-shield-perf-client').on('click', function (e) { e.preventDefault(); self.collectClient(); });
        },

        // ========================================
        // Server-Side Analysis
        // ========================================

        startServer: function () {
            if (this.running) return;
            this.running = true;
            this.resources = [];
            this.checked = 0;
            this.total = 0;

            var url = $('#melli-shield-perf-url').val() || '';
            this.status('Extracting resources...', 'active');
            this.disableButtons(true);
            $('#melli-shield-perf-results').html('<p class="msh-text-muted"><span class="msh-spinner"></span> Extracting...</p>');

            var self = this;
            $.ajax({
                url: melli_shield_ajax.ajax_url, type: 'POST', dataType: 'json',
                data: { action: 'melli_shield_perf_extract', page_url: url, _wpnonce: melli_shield_ajax.nonce },
                success: function (r) {
                    if (r.success && r.data.resources) {
                        self.resources = r.data.resources;
                        self.total = self.resources.length;
                        if (self.total === 0) {
                            $('#melli-shield-perf-results').html('<div class="msh-empty"><p>منبعی یافت نشد.</p></div>');
                            self.done(); return;
                        }
                        self.renderTable(r.data);
                        self.status('Checking URLs...', 'active');
                        self.checkNext();
                    } else {
                        self.done();
                        MelliShield.showNotice(r.data && r.data.message ? r.data.message : 'Error', 'error');
                    }
                },
                error: function (xhr, st) { self.done(); MelliShield.showNotice('AJAX error: ' + st, 'error'); }
            });
        },

        checkNext: function () {
            if (this.checked >= this.total) { this.serverComplete(); return; }
            var res = this.resources[this.checked];
            var $row = $('#perf-row-' + this.checked);
            $row.find('.p-status').html('<span class="msh-status-pending">⏳</span>');
            this.progress(Math.round((this.checked / this.total) * 100));

            var self = this;
            $.ajax({
                url: melli_shield_ajax.ajax_url, type: 'POST', dataType: 'json',
                data: { action: 'melli_shield_perf_check_url', url: res.url, _wpnonce: melli_shield_ajax.nonce },
                success: function (r) {
                    if (r.success) self.renderRow(self.checked, r.data);
                    else $row.find('.p-status').html('<span class="msh-status-error">❌</span>');
                    self.checked++;
                    setTimeout(function () { self.checkNext(); }, 50);
                },
                error: function () {
                    $row.find('.p-status').html('<span class="msh-status-error">❌</span>');
                    self.checked++;
                    setTimeout(function () { self.checkNext(); }, 50);
                }
            });
        },

        renderTable: function (data) {
            var h = '<div class="msh-card"><div id="perf-progress" class="msh-progress msh-progress--labeled"><div id="perf-bar" class="msh-progress__bar" style="width:0%">0%</div></div>';
            h += '<p id="perf-text" class="msh-text-xs msh-text-muted msh-mt-sm"></p></div>';
            h += '<p class="msh-card__hint"><strong>' + data.total + '</strong> منبع | HTML: ' + MelliShield.formatBytes(data.html_size) + '</p>';
            h += '<div class="msh-table-wrap"><table class="msh-table"><thead><tr><th>#</th><th>URL</th><th>نوع</th><th>دامنه</th><th>زمان</th><th>وضعیت</th><th>حجم</th><th>کش</th><th>رتبه</th></tr></thead><tbody>';
            for (var i = 0; i < data.resources.length; i++) {
                var r = data.resources[i], dom = this.domain(r.url);
                h += '<tr id="perf-row-' + i + '"><td>' + (i + 1) + '</td>';
                h += '<td><code style="font-size:11px;word-break:break-all;">' + MelliShield.truncate(r.url, 80) + '</code></td>';
                h += '<td><span class="melli-shield-badge melli-shield-badge-' + MelliShield.escapeHtml(r.type) + '">' + MelliShield.escapeHtml(r.type) + '</span></td>';
                h += '<td><code style="font-size:11px;">' + MelliShield.escapeHtml(dom) + '</code></td>';
                h += '<td class="p-time">—</td><td class="p-status"><span class="msh-status-pending">⏳</span></td>';
                h += '<td class="p-size">—</td><td class="p-cache">—</td><td class="p-rating">—</td></tr>';
            }
            h += '</tbody></table></div>';
            $('#melli-shield-perf-results').html(h);
        },

        renderRow: function (i, d) {
            var $r = $('#perf-row-' + i);
            if (!$r.length) return;
            var tc = this.timingClass(d.response_time, 500, 1000);
            $r.find('.p-time').html('<span class="' + tc + '">' + d.response_time + 'ms</span>');
            if (d.status === 'error') {
                $r.find('.p-status').html('<span class="msh-status-error" title="' + MelliShield.escapeHtml(d.error) + '">❌ ' + (d.is_blocked ? 'Blocked' : 'Error') + '</span>');
            } else {
                var sc = (d.status_code >= 200 && d.status_code < 400) ? 'msh-text-success' : 'msh-text-danger';
                $r.find('.p-status').html('<span class="' + sc + '">' + d.status_code + '</span>');
            }
            $r.find('.p-size').text(d.content_length > 0 ? MelliShield.formatBytes(d.content_length) : '—');
            if (d.cache_control) {
                var cached = d.cache_control.indexOf('max-age') !== -1 || d.cache_control.indexOf('public') !== -1;
                $r.find('.p-cache').html(cached ? '<span class="msh-text-success">✓</span>' : '<span class="msh-text-danger">✗</span>');
            }
            var rm = { fast: '🟢 Fast', medium: '🟡 Medium', slow: '🔴 Slow' };
            var rc = { fast: 'msh-text-success', medium: 'msh-text-warn', slow: 'msh-text-danger' };
            $r.find('.p-rating').html('<span class="' + (rc[d.rating] || 'msh-text-muted') + '">' + (rm[d.rating] || '—') + '</span>');
            $r.removeClass('msh-row-danger msh-row-warn msh-row-external');
            if (d.is_blocked || d.status === 'error') $r.addClass('msh-row-danger');
            else if (d.is_external && d.response_time > 1000) $r.addClass('msh-row-warn');
            else if (d.is_external) $r.addClass('msh-row-external');
        },

        serverComplete: function () {
            this.progress(100); this.done();
            var slow = 0, fast = 0, med = 0, err = 0;
            $('#melli-shield-perf-results table tbody tr').each(function () {
                var t = parseInt($(this).find('.p-time span').text(), 10);
                if (!isNaN(t)) { if (t > 1000) slow++; else if (t > 500) med++; else fast++; }
                var st = $(this).find('.p-status').text();
                if (st.indexOf('Blocked') !== -1 || st.indexOf('Error') !== -1) err++;
            });
            var sh = '<div class="msh-stats" style="margin:15px 0;">';
            sh += this.card('کل', this.total) + this.card('سریع', fast, 'success');
            sh += this.card('متوسط', med, 'warn') + this.card('کند', slow, 'danger');
            sh += this.card('خطا', err, err > 0 ? 'danger' : 'success') + '</div>';
            if (slow > 0 || err > 0) {
                sh += '<div class="msh-notice msh-notice--warning" style="margin-bottom:15px;"><p><strong>پیشنهاد:</strong></p><ul style="margin:6px 20px 0 0;line-height:1.8;">';
                if (slow > 0) sh += '<li>' + slow + ' منبع کند شناسایی شد.</li>';
                if (err > 0) sh += '<li>' + err + ' منبع مسدود یا خطادار.</li>';
                sh += '</ul></div>';
            } else {
                sh += '<div class="msh-notice msh-notice--success" style="margin-bottom:15px;"><p><strong>همه منابع سریع هستند.</strong></p></div>';
            }
            $('#perf-progress').after(sh);
            MelliShield.showNotice('Complete: ' + fast + ' fast, ' + med + ' medium, ' + slow + ' slow, ' + err + ' errors.', (slow > 0 || err > 0) ? 'warning' : 'success');
        },

        // ========================================
        // Admin Page Client-Side
        // ========================================

        collectClient: function () {
            if (!window.performance || !window.performance.getEntriesByType) {
                MelliShield.showNotice('Resource Timing API not supported.', 'error'); return;
            }
            var entries = performance.getEntriesByType('resource'), data = [];
            for (var i = 0; i < entries.length; i++) {
                var e = entries[i];
                data.push({
                    url: e.name, type: e.initiatorType || 'other', duration: Math.round(e.duration),
                    dns: Math.round(e.domainLookupEnd - e.domainLookupStart),
                    connect: Math.round(e.connectEnd - e.connectStart),
                    ttfb: Math.round(e.responseStart - e.requestStart),
                    download: Math.round(e.responseEnd - e.responseStart),
                    size: e.transferSize || 0, cached: e.transferSize === 0 && e.decodedBodySize > 0
                });
            }
            data.sort(function (a, b) { return b.duration - a.duration; });
            if (data.length === 0) {
                $('#melli-shield-perf-client-results').html('<div class="msh-empty"><p>داده‌ای در دسترس نیست.</p></div>'); return;
            }
            var $c = $('#melli-shield-perf-client-results');
            $c.html('');
            this.renderClientInto(data, $c);
            $.ajax({
                url: melli_shield_ajax.ajax_url, type: 'POST',
                data: { action: 'melli_shield_perf_save_client', entries: JSON.stringify(data), _wpnonce: melli_shield_ajax.nonce },
                success: function (r) { if (r.success) MelliShield.showNotice('Data saved (' + data.length + ' resources).', 'success'); }
            });
        },

        // ========================================
        // Frontend Data Loader
        // ========================================

        loadFrontendData: function () {
            var self = this;
            $.ajax({
                url: melli_shield_ajax.ajax_url, type: 'POST', dataType: 'json',
                data: { action: 'melli_shield_perf_get_frontend', _wpnonce: melli_shield_ajax.nonce },
                success: function (r) {
                    if (r.success && r.data.entries) {
                        var meta = r.data.meta || {}, entries = r.data.entries;
                        var info = '<div class="msh-card" style="margin-bottom:18px;">';
                        info += '<div style="display:flex;gap:20px;flex-wrap:wrap;font-size:13px;color:var(--msh-muted);">';
                        if (meta.url) info += '<span><strong>صفحه:</strong> <code>' + MelliShield.escapeHtml(meta.url) + '</code></span>';
                        if (meta.title) info += '<span><strong>عنوان:</strong> ' + MelliShield.escapeHtml(meta.title) + '</span>';
                        if (r.data.collected) info += '<span><strong>جمع‌آوری:</strong> ' + MelliShield.escapeHtml(r.data.collected) + '</span>';
                        info += '<span><strong>منابع:</strong> ' + entries.length + '</span></div>';
                        if (meta.page_timing && meta.page_timing.load_event) {
                            var pt = meta.page_timing;
                            info += '<div class="msh-stats" style="margin-top:14px;">';
                            info += self.card('DNS', pt.dns + 'ms', pt.dns > 100 ? 'danger' : 'success');
                            info += self.card('TTFB', pt.ttfb + 'ms', pt.ttfb > 500 ? 'danger' : 'success');
                            info += self.card('DOM', pt.dom_interactive + 'ms', pt.dom_interactive > 3000 ? 'danger' : 'warn');
                            info += self.card('بارگذاری', pt.load_event + 'ms', pt.load_event > 5000 ? 'danger' : 'success');
                            info += '</div>';
                        }
                        info += '</div>';
                        var $c = $('#melli-shield-frontend-results');
                        $c.html(info);
                        self.renderClientInto(entries, $c);
                    } else {
                        var msg = (r.data && r.data.message) ? r.data.message : 'No data found.';
                        $('#melli-shield-frontend-results').html(
                            '<div class="msh-empty"><p>' + MelliShield.escapeHtml(msg) + '</p>' +
                            '<p style="margin-top:8px;font-size:13px;color:var(--msh-muted);">به یک صفحه عمومی بروید و از نوار ادمین: <strong>ملی شیلد → تحلیل این صفحه</strong></p></div>');
                    }
                },
                error: function () {
                    $('#melli-shield-frontend-results').html('<div class="msh-notice msh-notice--error"><p>خطا در بارگذاری داده.</p></div>');
                }
            });
        },

        // ========================================
        // Shared Client Renderer
        // ========================================

        renderClientInto: function (data, $container) {
            var site = window.location.hostname;
            var totalSize = 0, slowCount = 0, cachedCount = 0, extCount = 0;

            for (var s = 0; s < data.length; s++) {
                totalSize += data[s].size || 0;
                if (data[s].duration > 1000) slowCount++;
                if (data[s].cached) cachedCount++;
                try { if (new URL(data[s].url).hostname !== site) extCount++; } catch (e) { }
            }

            var h = '';

            // Summary cards
            h += '<div class="msh-stats" style="margin-bottom:18px;">';
            h += this.card('منابع', data.length);
            h += this.card('حجم کل', MelliShield.formatBytes(totalSize), 'primary');
            h += this.card('کند (>۱ث)', slowCount, slowCount > 0 ? 'danger' : 'success');
            h += this.card('کش‌شده', cachedCount, 'primary');
            h += this.card('خارجی', extCount, extCount > 0 ? 'warn' : 'success');
            h += '</div>';

            if (slowCount > 0 || extCount > 5) {
                h += '<div class="msh-notice msh-notice--warning" style="margin-bottom:15px;">';
                h += '<p><strong>پیشنهاد:</strong></p><ul style="margin:6px 20px 0 0;line-height:1.8;">';
                if (slowCount > 0) h += '<li>' + slowCount + ' منبع بیش از ۱ ثانیه لود می‌شود.</li>';
                if (extCount > 5) h += '<li>' + extCount + ' منبع خارجی — حالت ملی/آفلاین را در نظر بگیرید.</li>';
                if (cachedCount < data.length * 0.3) h += '<li>نرخ کش پایین است؛ کش مرورگر را فعال کنید.</li>';
                h += '</ul></div>';
            } else {
                h += '<div class="msh-notice msh-notice--success" style="margin-bottom:15px;">';
                h += '<p><strong>عملکرد خوب است.</strong></p></div>';
            }

            h += '<h3 class="msh-perf-section-title">جزئیات منابع (بر اساس مدت)</h3>';
            h += '<div class="msh-table-wrap"><table class="msh-table"><thead><tr>';
            h += '<th>#</th><th>URL</th><th>Type</th><th>DNS</th><th>Connect</th><th>TTFB</th><th>Download</th><th>Total</th><th>Size</th><th>Cached</th>';
            h += '</tr></thead><tbody>';

            var max = Math.min(data.length, 100);
            for (var i = 0; i < max; i++) {
                var e = data[i];
                var tc = this.timingClass(e.duration, 500, 1000);
                var rowClass = e.duration > 1000 ? 'msh-row-danger' : (e.duration > 500 ? 'msh-row-warn' : '');
                var ext = false;
                try { ext = new URL(e.url).hostname !== site; } catch (ex) { }

                h += '<tr class="' + rowClass + '">';
                h += '<td>' + (i + 1) + '</td>';
                h += '<td><code class="msh-text-xs" style="word-break:break-all;">' + MelliShield.truncate(e.url, 70) + '</code>';
                if (ext) h += ' <span class="msh-text-warn msh-text-xs">⬆ ext</span>';
                h += '</td>';
                h += '<td class="msh-text-xs">' + MelliShield.escapeHtml(e.type) + '</td>';
                h += '<td>' + this.colorMs(e.dns, 50) + '</td>';
                h += '<td>' + this.colorMs(e.connect, 100) + '</td>';
                h += '<td>' + this.colorMs(e.ttfb, 200) + '</td>';
                h += '<td>' + this.colorMs(e.download, 300) + '</td>';
                h += '<td><span class="' + tc + '">' + e.duration + 'ms</span></td>';
                h += '<td>' + (e.size > 0 ? MelliShield.formatBytes(e.size) : '—') + '</td>';
                h += '<td>' + (e.cached ? '<span class="msh-text-primary">✓</span>' : '—') + '</td>';
                h += '</tr>';
            }

            if (data.length > 100) {
                h += '<tr><td colspan="10" class="msh-text-muted" style="text-align:center;">Showing top 100 of ' + data.length + '</td></tr>';
            }

            h += '</tbody></table></div>';

            // Waterfall chart
            h += this.waterfall(data.slice(0, 20));

            // External domains summary
            var domains = {};
            for (var d = 0; d < data.length; d++) {
                try {
                    var dom = new URL(data[d].url).hostname;
                    if (dom !== site) {
                        if (!domains[dom]) domains[dom] = { count: 0, totalTime: 0, totalSize: 0 };
                        domains[dom].count++;
                        domains[dom].totalTime += data[d].duration;
                        domains[dom].totalSize += data[d].size || 0;
                    }
                } catch (ex) { }
            }

            var domArr = [];
            for (var dk in domains) {
                if (domains.hasOwnProperty(dk)) {
                    domArr.push({ domain: dk, count: domains[dk].count, time: domains[dk].totalTime, size: domains[dk].totalSize });
                }
            }
            domArr.sort(function (a, b) { return b.time - a.time; });

            if (domArr.length > 0) {
                h += '<h3 class="msh-perf-section-title">خلاصه دامنه‌های خارجی</h3>';
                h += '<div class="msh-table-wrap"><table class="msh-table"><thead><tr>';
                h += '<th>دامنه</th><th>درخواست</th><th>زمان کل</th><th>حجم</th><th>میانگین</th><th>وضعیت</th>';
                h += '</tr></thead><tbody>';

                for (var di = 0; di < domArr.length; di++) {
                    var dd = domArr[di];
                    var avg = Math.round(dd.time / dd.count);
                    var avgClass = this.timingClass(avg, 200, 500);

                    h += '<tr>';
                    h += '<td><code>' + MelliShield.escapeHtml(dd.domain) + '</code></td>';
                    h += '<td>' + dd.count + '</td>';
                    h += '<td>' + dd.time + 'ms</td>';
                    h += '<td>' + MelliShield.formatBytes(dd.size) + '</td>';
                    h += '<td><span class="' + avgClass + '">' + avg + 'ms</span></td>';
                    h += '<td>';
                    if (avg > 500) h += '<span class="msh-text-danger">🔴 Slow — consider blocking</span>';
                    else if (avg > 200) h += '<span class="msh-text-warn">🟡 Medium</span>';
                    else h += '<span class="msh-text-success">🟢 Fast</span>';
                    h += '</td></tr>';
                }

                h += '</tbody></table></div>';
            }

            $container.append(h);
        },

        // ========================================
        // Waterfall Chart
        // ========================================

        waterfall: function (data) {
            if (!data || data.length === 0) return '';

            var maxD = data[0].duration || 1;
            var h = '<h3 class="msh-perf-section-title">📊 Waterfall — Top 20 Slowest</h3>';
            h += '<div class="melli-shield-waterfall">';

            for (var i = 0; i < data.length; i++) {
                var e = data[i];
                var tw = Math.max(2, Math.round((e.duration / maxD) * 100));
                var dw = Math.max(0, Math.round((e.dns / maxD) * 100));
                var cw = Math.max(0, Math.round((e.connect / maxD) * 100));
                var bw = Math.max(0, Math.round((e.ttfb / maxD) * 100));
                var lw = Math.max(1, tw - dw - cw - bw);
                if (lw < 0) lw = 1;

                var tc = this.timingClass(e.duration, 500, 1000);

                h += '<div class="melli-shield-waterfall-row">';
                h += '<div class="melli-shield-waterfall-label" title="' + MelliShield.escapeHtml(e.url) + '"><code class="msh-text-xs">' + MelliShield.escapeHtml(MelliShield.truncate(e.url, 40)) + '</code></div>';
                h += '<div class="melli-shield-waterfall-bar">';
                if (dw > 0) h += '<div class="msh-waterfall-seg-dns" style="width:' + dw + '%;" title="DNS: ' + e.dns + 'ms"></div>';
                if (cw > 0) h += '<div class="msh-waterfall-seg-connect" style="width:' + cw + '%;" title="Connect: ' + e.connect + 'ms"></div>';
                if (bw > 0) h += '<div class="msh-waterfall-seg-ttfb" style="width:' + bw + '%;" title="TTFB: ' + e.ttfb + 'ms"></div>';
                if (lw > 0) h += '<div class="msh-waterfall-seg-download" style="width:' + lw + '%;" title="Download: ' + e.download + 'ms"></div>';
                h += '</div>';
                h += '<div class="melli-shield-waterfall-time ' + tc + '">' + e.duration + 'ms</div>';
                h += '</div>';
            }

            // Scale
            h += '<div style="display:flex;align-items:center;margin-top:8px;font-size:10px;color:#999;">';
            h += '<div style="width:250px;"></div>';
            h += '<div style="flex:1;display:flex;justify-content:space-between;">';
            h += '<span>0ms</span><span>' + Math.round(maxD * 0.5) + 'ms</span><span>' + maxD + 'ms</span>';
            h += '</div><div style="width:65px;"></div></div>';

            // Legend
            h += '<div style="display:flex;gap:15px;margin-top:12px;padding-top:10px;border-top:1px solid #e0e0e0;font-size:11px;color:#666;">';
            h += '<span><span style="display:inline-block;width:12px;height:12px;background:#9b59b6;border-radius:2px;vertical-align:middle;margin-right:3px;"></span> DNS</span>';
            h += '<span><span style="display:inline-block;width:12px;height:12px;background:#e67e22;border-radius:2px;vertical-align:middle;margin-right:3px;"></span> Connect</span>';
            h += '<span><span style="display:inline-block;width:12px;height:12px;background:#3498db;border-radius:2px;vertical-align:middle;margin-right:3px;"></span> TTFB</span>';
            h += '<span><span style="display:inline-block;width:12px;height:12px;background:#2ecc71;border-radius:2px;vertical-align:middle;margin-right:3px;"></span> Download</span>';
            h += '</div></div>';

            return h;
        },

        // ========================================
        // UI Helpers
        // ========================================

        card: function (label, value, tone) {
            tone = tone || '';
            var toneClass = tone ? ' msh-stat-card--' + tone : '';
            return '<div class="msh-stat-card ms-stat' + toneClass + '" style="min-width:110px;">' +
                '<div class="msh-stat-card__label ms-stat__label">' + MelliShield.escapeHtml(String(label)) + '</div>' +
                '<div class="msh-stat-card__value ms-stat__num">' + MelliShield.escapeHtml(String(value)) + '</div></div>';
        },

        timingClass: function (ms, warnAt, dangerAt) {
            if (ms <= 0) return 'msh-text-muted';
            if (ms > dangerAt) return 'msh-text-danger';
            if (ms > warnAt) return 'msh-text-warn';
            return 'msh-text-success';
        },

        colorMs: function (ms, threshold) {
            return '<span class="' + this.timingClass(ms, threshold / 2, threshold) + '">' + ms + 'ms</span>';
        },

        progress: function (pct) {
            pct = Math.min(100, Math.max(0, pct));
            $('#perf-bar').css('width', pct + '%').text(pct + '%');
            $('#perf-text').text('در حال بررسی ' + this.checked + ' / ' + this.total + '...');
        },

        status: function (text, state) {
            var $el = $('#melli-shield-perf-status');
            $el.text(text).removeClass('is-active is-success is-warn is-error msh-text-muted');
            if (state) {
                $el.addClass('msh-status-text is-' + state);
            } else {
                $el.addClass('msh-status-text');
            }
        },

        disableButtons: function (disabled) {
            $('#melli-shield-perf-start, #melli-shield-perf-client').prop('disabled', disabled);
        },

        done: function () {
            this.running = false;
            this.disableButtons(false);
            this.status('✓ Complete', 'success');
        },

        domain: function (url) {
            try { var a = document.createElement('a'); a.href = url; return a.hostname; }
            catch (e) { return ''; }
        }
    };

    $(document).ready(function () {
        Perf.init();
    });

})(jQuery);