SEMrush Rankings Tracker

Track SEMrush rankings with compact AMOLED theme (Optimized & Fixed)

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         SEMrush Rankings Tracker 
// @namespace    http://tampermonkey.net/
// @version      7.8
// @description  Track SEMrush rankings with compact AMOLED theme (Optimized & Fixed)
// @author       quantavil
// @license      MIT
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_addStyle
// @connect      semrush.com
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    // ============ CONFIGURATION ============
    const CONFIG = {
        CACHE_DAYS: 15,
        CACHE_KEY: 'semrush_cache_v4',
        SHORTCUT_KEY: 'KeyS',
        SHORTCUT_ALT: true,
        SHORTCUT_CTRL: false,
        SHORTCUT_SHIFT: false
    };
    // =======================================

    // Compact AMOLED Dark Theme
    GM_addStyle(`
        #sr-widget {
            position: fixed;
            top: 10px;
            right: 10px;
            background: rgba(0, 0, 0, 0.95);
            backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 255, 255, 0.15);
            border-radius: 14px;
            width: 340px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.9);
            z-index: 999999;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            color: #e8e8e8;
            display: none;
        }

        @media (prefers-reduced-motion: reduce) {
            #sr-widget {
                backdrop-filter: none;
                background: rgba(0, 0, 0, 0.98);
            }
        }

        #sr-widget.visible {
            display: block;
        }

        .sr-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 10px 12px;
            background: rgba(255, 255, 255, 0.04);
            border-radius: 14px 14px 0 0;
            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
            cursor: move;
            user-select: none;
        }

        .sr-title {
            font-weight: 700;
            font-size: 13px;
            color: #a0b0ff;
            letter-spacing: 0.3px;
        }

        .sr-controls {
            display: flex;
            gap: 6px;
        }

        .sr-btn {
            background: rgba(255, 255, 255, 0.06);
            border: 1px solid rgba(255, 255, 255, 0.12);
            padding: 4px 8px;
            border-radius: 6px;
            cursor: pointer;
            font-size: 13px;
            color: #e8e8e8;
            transition: all 0.2s ease;
            line-height: 1;
        }

        .sr-btn:hover {
            background: rgba(255, 255, 255, 0.12);
            transform: scale(1.05);
        }

        .sr-tabs {
            display: flex;
            background: rgba(0, 0, 0, 0.4);
            padding: 3px;
            margin: 6px 8px;
            border-radius: 10px;
            gap: 3px;
        }

        .sr-tab {
            flex: 1;
            padding: 6px 4px;
            text-align: center;
            cursor: pointer;
            border: none;
            background: transparent;
            color: rgba(255, 255, 255, 0.5);
            font-size: 11px;
            font-weight: 600;
            border-radius: 7px;
            transition: all 0.2s ease;
        }

        .sr-tab.active {
            background: rgba(160, 176, 255, 0.2);
            color: #a0b0ff;
        }

        .sr-content {
            padding: 0 10px 10px;
            overflow: hidden;
        }

        .sr-tab-content {
            display: none;
        }

        .sr-tab-content.active {
            display: block;
        }

        .sr-domain {
            font-size: 15px;
            font-weight: 700;
            margin-bottom: 8px;
            color: #ffffff;
            word-break: break-word;
            text-align: center;
        }

        .sr-carousel {
            position: relative;
            min-height: 260px;
        }

        .sr-carousel-nav {
            display: flex;
            gap: 6px;
            margin-bottom: 8px;
            justify-content: center;
        }

        .sr-carousel-btn {
            padding: 5px 10px;
            background: rgba(255, 255, 255, 0.06);
            border: 1px solid rgba(255, 255, 255, 0.12);
            border-radius: 7px;
            color: rgba(255, 255, 255, 0.6);
            font-size: 10px;
            cursor: pointer;
            transition: all 0.2s ease;
            font-weight: 700;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .sr-carousel-btn.active {
            background: rgba(160, 176, 255, 0.25);
            color: #a0b0ff;
            border-color: rgba(160, 176, 255, 0.4);
        }

        .sr-carousel-slide {
            display: none;
        }

        .sr-carousel-slide.active {
            display: block;
            animation: fadeIn 0.3s ease;
        }

        @keyframes fadeIn {
            from { opacity: 0; }
            to { opacity: 1; }
        }

        .sr-stat {
            display: flex;
            justify-content: space-between;
            padding: 6px 10px;
            margin: 4px 0;
            background: rgba(255, 255, 255, 0.04);
            border: 1px solid rgba(255, 255, 255, 0.08);
            border-radius: 8px;
            transition: background 0.2s ease;
        }

        .sr-stat:hover {
            background: rgba(255, 255, 255, 0.07);
        }

        .sr-stat-label {
            color: rgba(255, 255, 255, 0.75);
            font-size: 13px;
            font-weight: 700;
        }

        .sr-stat-value {
            color: #ffffff;
            font-weight: 500;
            font-size: 12px;
        }

        .sr-loading, .sr-error, .sr-no-data {
            text-align: center;
            padding: 40px 20px;
            color: rgba(255, 255, 255, 0.5);
            font-size: 12px;
            font-weight: 600;
        }

        .sr-error {
            color: #ff6b6b;
        }

        .sr-chart-container {
            padding: 8px 12px;
            background: rgba(255, 255, 255, 0.06);
            border: 1px solid rgba(255, 255, 255, 0.12);
            border-radius: 10px;
            margin-bottom: 0;
        }

        .sr-chart-title {
            font-size: 10px;
            color: rgba(255, 255, 255, 0.7);
            margin-bottom: 10px;
            text-align: center;
            font-weight: 700;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .sr-table-wrapper {
            max-height: 320px;
            overflow-y: auto;
            margin-bottom: 8px;
        }

        .sr-table-wrapper::-webkit-scrollbar {
            width: 8px;
        }

        .sr-table-wrapper::-webkit-scrollbar-track {
            background: rgba(255, 255, 255, 0.05);
            border-radius: 4px;
        }

        .sr-table-wrapper::-webkit-scrollbar-thumb {
            background: rgba(160, 176, 255, 0.3);
            border-radius: 4px;
        }

        .sr-table-wrapper::-webkit-scrollbar-thumb:hover {
            background: rgba(160, 176, 255, 0.5);
        }

        .sr-table {
            width: 100%;
            border-collapse: separate;
            border-spacing: 0 3px;
            font-size: 11px;
        }

        .sr-table th {
            background: rgba(255, 255, 255, 0.06);
            padding: 7px 5px;
            text-align: left;
            font-weight: 700;
            color: rgba(255, 255, 255, 0.8);
            font-size: 10px;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .sr-table td {
            padding: 7px 5px;
            background: rgba(255, 255, 255, 0.03);
            color: #e8e8e8;
            font-weight: 600;
        }

        .sr-table tr:hover td {
            background: rgba(255, 255, 255, 0.06);
        }

        .sr-link {
            color: #a0b0ff;
            text-decoration: none;
            font-weight: 600;
        }

        .sr-link:hover {
            text-decoration: underline;
        }

        .sr-cache-info {
            font-size: 9px;
            color: rgba(255, 255, 255, 0.4);
            text-align: center;
            margin-top: 4px;
            margin-bottom: 0;
            font-weight: 600;
        }

        .sr-clear-cache {
            width: 100%;
            padding: 8px;
            background: rgba(160, 176, 255, 0.2);
            color: #a0b0ff;
            border: 1px solid rgba(160, 176, 255, 0.3);
            border-radius: 8px;
            cursor: pointer;
            font-size: 11px;
            font-weight: 700;
            margin-top: 8px;
            transition: all 0.2s ease;
        }

        .sr-clear-cache:hover {
            background: rgba(160, 176, 255, 0.3);
        }

        /* Chartist.js Styles */
        #sr-widget canvas {
            max-height: 180px;
        }

        #sr-widget .ct-chart {
            max-height: 180px;
        }

        #sr-widget .ct-label {
            color: rgba(255, 255, 255, 0.7);
            font-size: 10px;
            font-weight: 600;
            fill: rgba(255, 255, 255, 0.7);
        }

        #sr-widget .ct-grid {
            stroke: rgba(255, 255, 255, 0.1);
            stroke-width: 1px;
        }

        #sr-widget .ct-series-a .ct-bar,
        #sr-widget .ct-series-a .ct-line,
        #sr-widget .ct-series-a .ct-point {
            stroke: rgba(180, 190, 255, 0.95);
        }

        #sr-widget .ct-bar {
            stroke-width: 20px;
        }

        #sr-widget .ct-series-a .ct-slice-donut {
            stroke: rgba(180, 190, 255, 0.95);
        }

        /* Chartist Tooltip Styles */
        .chartist-tooltip {
            position: absolute;
            display: inline-block;
            opacity: 0;
            min-width: 60px;
            padding: 8px 12px;
            background: rgba(0, 0, 0, 0.95);
            color: #a0b0ff;
            font-weight: 700;
            font-size: 12px;
            text-align: center;
            pointer-events: none;
            z-index: 1000000;
            transition: opacity 0.2s ease;
            border: 1px solid rgba(160, 176, 255, 0.4);
            border-radius: 6px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
        }

        .chartist-tooltip:before {
            content: "";
            position: absolute;
            top: 100%;
            left: 50%;
            width: 0;
            height: 0;
            margin-left: -5px;
            border: 5px solid transparent;
            border-top-color: rgba(0, 0, 0, 0.95);
        }

        .chartist-tooltip.tooltip-show {
            opacity: 1;
        }

        .ct-bar {
            cursor: pointer;
        }
    `);

    // Chartist.js Dynamic Loader
    const ChartsLoader = {
        loaded: false,
        loading: null,

        load() {
            if (this.loaded) return Promise.resolve();
            if (this.loading) return this.loading;

            this.loading = new Promise((resolve, reject) => {
                // Load Chartist CSS
                const css = document.createElement('link');
                css.rel = 'stylesheet';
                css.href = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/chartist.min.css';
                document.head.appendChild(css);

                // Load Chartist JS
                const script = document.createElement('script');
                script.src = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/chartist.min.js';
                script.onload = () => {
                    // Load Tooltip Plugin
                    const tooltipScript = document.createElement('script');
                    tooltipScript.src = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/chartist-plugin-tooltip.min.js';
                    tooltipScript.onload = () => {
                        this.loaded = true;
                        this.loading = null;
                        resolve();
                    };
                    tooltipScript.onerror = () => {
                        // Fallback: continue without tooltips
                        this.loaded = true;
                        this.loading = null;
                        resolve();
                    };
                    document.head.appendChild(tooltipScript);
                };
                script.onerror = () => {
                    this.loading = null;
                    reject(new Error('Failed to load Chartist.js'));
                };
                document.head.appendChild(script);
            });

            return this.loading;
        }
    };

    // Utility Functions
    const Utils = {
        getRootDomain(url) {
            try {
                const hostname = new URL(url).hostname;
                const parts = hostname.split('.');
                const cleanParts = parts[0] === 'www' ? parts.slice(1) : parts;

                if (cleanParts.length > 2) {
                    const tld = cleanParts.slice(-2).join('.');
                    if (['co.uk', 'com.au', 'co.in', 'co.jp'].includes(tld)) {
                        return cleanParts.slice(-3).join('.');
                    }
                    return cleanParts.slice(-2).join('.');
                }
                return cleanParts.join('.');
            } catch (e) {
                return null;
            }
        },

        formatNumber(num) {
            if (num == null) return 'N/A';
            if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B';
            if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M';
            if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K';
            return num.toLocaleString();
        },

        parseNumber(str) {
            if (!str) return null;
            str = str.replace(/,/g, '').trim();
            const multipliers = { B: 1e9, M: 1e6, K: 1e3 };
            for (let [suffix, mult] of Object.entries(multipliers)) {
                if (str.includes(suffix)) {
                    return parseFloat(str) * mult;
                }
            }
            return parseFloat(str);
        },

        parseDuration(timeStr) {
            if (!timeStr) return null;
            const parts = timeStr.split(':');
            if (parts.length !== 2) return null;
            return parseInt(parts[0]) * 60 + parseInt(parts[1]);
        },

        formatDuration(seconds) {
            if (seconds == null) return 'N/A';
            const mins = Math.floor(seconds / 60);
            const secs = Math.floor(seconds % 60);
            return `${mins}:${secs.toString().padStart(2, '0')}`;
        },

        parsePercentage(str) {
            if (!str) return null;
            return parseFloat(str.replace('%', ''));
        },

        getDaysAgo(timestamp) {
            return Math.floor((Date.now() - timestamp) / 86400000);
        }
    };

    // Cache Manager using GM API
    const Cache = {
        get() {
            try {
                const data = GM_getValue(CONFIG.CACHE_KEY, '{}');
                return JSON.parse(data);
            } catch (e) {
                console.error('Cache GET error:', e);
                return {};
            }
        },

        set(cache) {
            try {
                GM_setValue(CONFIG.CACHE_KEY, JSON.stringify(cache));
            } catch (e) {
                console.error('Cache SET error:', e);
            }
        },

        addDomain(domain, data) {
            const cache = this.get();
            cache[domain] = data;
            this.set(cache);
        },

        isValid(entry) {
            if (!entry?.timestamp) return false;
            return Utils.getDaysAgo(entry.timestamp) < CONFIG.CACHE_DAYS;
        },

        clear() {
            GM_deleteValue(CONFIG.CACHE_KEY);
        }
    };

    // SEMrush Scraper
    const SEMrush = {
        fetch(domain, callback) {
            const url = `https://www.semrush.com/website/${domain}/overview/`;

            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                onload: (response) => {
                    if (response.status === 403 || response.status === 429) {
                        callback(new Error('SEMrush blocked the request. Try opening semrush.com in a tab first to authenticate, then refresh.'), null);
                        return;
                    }

                    if (response.status !== 200) {
                        callback(new Error(`Failed to fetch (HTTP ${response.status})`), null);
                        return;
                    }

                    try {
                        const doc = new DOMParser().parseFromString(response.responseText, 'text/html');
                        const data = this.parse(doc, domain);
                        callback(null, data);
                    } catch (e) {
                        callback(e, null);
                    }
                },
                onerror: (error) => callback(new Error('Network error occurred'), null)
            });
        },

        parse(doc, domain) {
            const data = {
                domain,
                globalRank: null,
                countryRank: null,
                country: null,
                categoryRank: null,
                category: null,
                visits: null,
                avgDuration: null,
                trafficTrend: [],
                trafficByCountry: [],
                timestamp: Date.now()
            };

            // Extract ranks
            doc.querySelectorAll('[data-test="RankStats"]').forEach(stat => {
                const label = stat.querySelector('.textStyle_small')?.textContent || '';
                const value = stat.querySelector('.textStyle_h5')?.textContent || '';
                const categoryElement = stat.querySelector('.fw_600.white-space_wrap');
                const countryElement = stat.querySelector('.fw_600.white-space_wrap');

                if (label.includes('Global Rank')) {
                    data.globalRank = parseInt(value.replace(/,/g, ''));
                } else if (label.includes('Country Rank')) {
                    data.countryRank = parseInt(value.replace(/,/g, ''));
                    if (countryElement) {
                        data.country = countryElement.textContent.trim();
                    }
                } else if (label.includes('Category Rank')) {
                    data.categoryRank = parseInt(value.replace(/,/g, ''));
                    if (categoryElement) {
                        data.category = categoryElement.textContent.trim();
                    }
                }
            });

            // Extract metrics
            doc.querySelectorAll('[data-test="MetricsCard"]').forEach(card => {
                const label = card.querySelector('.textStyle_small')?.textContent || '';
                const value = card.querySelector('.textStyle_h5')?.textContent || '';

                if (label.includes('Visits') && !label.includes('Duration')) {
                    data.visits = Utils.parseNumber(value);
                } else if (label.includes('Avg. Visit Duration')) {
                    data.avgDuration = Utils.parseDuration(value);
                }
            });

            // Extract traffic trend
            const trendSection = doc.querySelector('.d_flex.flex-d_column.flex-g_1');
            if (trendSection) {
                const trend = [];

                // Look for the parent container that has the bars
                const barsContainer = trendSection.querySelector('.d_flex.flex-d_column.mt_8');

                if (barsContainer) {
                    // Get all direct children that are flex containers with bars
                    const bars = barsContainer.querySelectorAll(':scope > .d_flex');

                    bars.forEach(bar => {
                        // Find month and value within the bar's label section
                        const labelSection = bar.querySelector('.d_flex.flex-d_column.jc_center');

                        if (labelSection) {
                            const monthEl = labelSection.querySelector('.textStyle_small:not(.fw_600)');
                            const valueEl = labelSection.querySelector('.textStyle_small.fw_600');

                            if (monthEl && valueEl) {
                                const month = monthEl.textContent.trim();
                                const visits = Utils.parseNumber(valueEl.textContent);

                                if (month && visits) {
                                    trend.push({ month, visits });
                                }
                            }
                        }
                    });
                }

                // Reverse to show oldest to newest (Jun -> Jul -> Aug)
                if (trend.length > 0) {
                    data.trafficTrend = trend.reverse();
                }
            }

            // Extract traffic by country
            const countryTable = doc.querySelector('[data-test="TrafficByCountryTable"]');
            if (countryTable) {
                const rows = countryTable.querySelectorAll('tbody tr');
                const countries = [];

                rows.forEach((row, index) => {
                    if (index < 5) {
                        const countryName = row.querySelector('.fw_600')?.textContent.trim();
                        const percentCell = row.querySelectorAll('td')[1];
                        const percent = percentCell ? Utils.parsePercentage(percentCell.textContent) : null;

                        if (countryName && percent != null) {
                            countries.push({
                                country: countryName,
                                percentage: parseFloat(percent.toFixed(2))
                            });
                        }
                    }
                });

                // Calculate "Other" with proper rounding
                if (countries.length > 0) {
                    const totalShown = countries.reduce((sum, c) => sum + c.percentage, 0);
                    const remaining = Math.max(0, +(100 - totalShown).toFixed(2));

                    // Only add "Other" if it's significant
                    if (remaining >= 0.5) {
                        countries.push({
                            country: 'Other',
                            percentage: remaining
                        });
                    }
                }

                data.trafficByCountry = countries;
            }

            return data;
        }
    };

    // UI Components
    const UI = {
        widget: null,
        chartInstances: {},
        isDragging: false,
        currentX: 0,
        currentY: 0,
        initialX: 0,
        initialY: 0,

        create() {
            this.widget = document.createElement('div');
            this.widget.id = 'sr-widget';
            this.widget.innerHTML = `
                <div class="sr-header" id="sr-drag-handle">
                    <div class="sr-title">SEMrush Tracker</div>
                    <div class="sr-controls">
                        <button class="sr-btn" id="sr-refresh" title="Refresh data" aria-label="Refresh data">⟳</button>
                        <button class="sr-btn" id="sr-close" title="Toggle widget (${this.getShortcutText()})" aria-label="Toggle widget visibility">✕</button>
                    </div>
                </div>
                <div class="sr-tabs">
                    <button class="sr-tab active" data-tab="current">Current</button>
                    <button class="sr-tab" data-tab="all">All Sites</button>
                </div>
                <div class="sr-content">
                    <div class="sr-tab-content active" id="tab-current">
                        <div class="sr-loading">Loading...</div>
                    </div>
                    <div class="sr-tab-content" id="tab-all">
                        <div class="sr-loading">Loading...</div>
                    </div>
                </div>
            `;

            document.body.appendChild(this.widget);
            this.bindEvents();
            this.makeDraggable();
        },

        getShortcutText() {
            let keys = [];
            if (CONFIG.SHORTCUT_ALT) keys.push('Alt');
            if (CONFIG.SHORTCUT_CTRL) keys.push('Ctrl');
            if (CONFIG.SHORTCUT_SHIFT) keys.push('Shift');
            keys.push(CONFIG.SHORTCUT_KEY.replace('Key', ''));
            return keys.join('+');
        },

        makeDraggable() {
            const header = document.getElementById('sr-drag-handle');

            header.addEventListener('mousedown', (e) => {
                if (e.target.tagName === 'BUTTON') return;
                this.isDragging = true;
                const rect = this.widget.getBoundingClientRect();
                this.initialX = e.clientX - rect.left;
                this.initialY = e.clientY - rect.top;
            });

            document.addEventListener('mousemove', (e) => {
                if (!this.isDragging) return;
                e.preventDefault();

                let newX = e.clientX - this.initialX;
                let newY = e.clientY - this.initialY;

                // Keep within viewport
                newX = Math.max(0, Math.min(newX, window.innerWidth - this.widget.offsetWidth));
                newY = Math.max(0, Math.min(newY, window.innerHeight - this.widget.offsetHeight));

                this.widget.style.left = newX + 'px';
                this.widget.style.top = newY + 'px';
                this.widget.style.right = 'auto';
            });

            document.addEventListener('mouseup', () => {
                this.isDragging = false;
            });
        },

        bindEvents() {
            this.widget.querySelectorAll('.sr-tab').forEach(tab => {
                tab.addEventListener('click', () => this.switchTab(tab.dataset.tab));
            });

            document.getElementById('sr-close').addEventListener('click', () => {
                this.toggle();
            });

            document.getElementById('sr-refresh').addEventListener('click', () => {
                const domain = Utils.getRootDomain(window.location.href);
                if (domain) {
                    const cache = Cache.get();
                    delete cache[domain];
                    Cache.set(cache);
                    App.loadCurrent(domain);
                }
            });
        },

        switchTab(tabName) {
            this.widget.querySelectorAll('.sr-tab').forEach(t => t.classList.remove('active'));
            this.widget.querySelectorAll('.sr-tab-content').forEach(c => c.classList.remove('active'));

            this.widget.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
            document.getElementById(`tab-${tabName}`).classList.add('active');

            if (tabName === 'all') App.loadAll();
        },

        toggle() {
            this.widget.classList.toggle('visible');
        },

        show() {
            this.widget.classList.add('visible');
        },

        destroyChart(id) {
            const container = document.getElementById(`sr-${id}-chart`);
            if (container) {
                container.innerHTML = '';
            }
            if (this.chartInstances[id]) {
                if (this.chartInstances[id].detach) {
                    this.chartInstances[id].detach();
                }
                delete this.chartInstances[id];
            }
        },

        renderCurrent(data) {
            const container = document.getElementById('tab-current');

            if (!data) {
                container.innerHTML = '<div class="sr-error">No data available</div>';
                return;
            }

            const hasCountryData = data.trafficByCountry?.length > 0;
            const hasTrendData = data.trafficTrend?.length > 0;

            let html = `
                <div class="sr-domain">${data.domain}</div>
                <div class="sr-carousel">
                    <div class="sr-carousel-nav">
                        <button class="sr-carousel-btn active" data-slide="stats">📊 Stats</button>
                        ${hasTrendData ? '<button class="sr-carousel-btn" data-slide="trend">📈 Trend</button>' : ''}
                        ${hasCountryData ? '<button class="sr-carousel-btn" data-slide="country">🌍 Country</button>' : ''}
                    </div>

                    <!-- Main Stats Slide -->
                    <div class="sr-carousel-slide active" data-slide="stats">
                        <div class="sr-stat">
                            <span class="sr-stat-label">Global Rank</span>
                            <span class="sr-stat-value">${data.globalRank ? '#' + Utils.formatNumber(data.globalRank) : 'N/A'}</span>
                        </div>
                        <div class="sr-stat">
                            <span class="sr-stat-label">Country Rank</span>
                            <span class="sr-stat-value">${data.countryRank ? '#' + Utils.formatNumber(data.countryRank) : 'N/A'} ${data.country ? '(' + data.country + ')' : ''}</span>
                        </div>
                        <div class="sr-stat">
                            <span class="sr-stat-label">Category Rank</span>
                            <span class="sr-stat-value">${data.categoryRank ? '#' + Utils.formatNumber(data.categoryRank) : 'N/A'} ${data.category ? '(' + data.category + ')' : ''}</span>
                        </div>
                        <div class="sr-stat">
                            <span class="sr-stat-label">Monthly Visits</span>
                            <span class="sr-stat-value">${Utils.formatNumber(data.visits)}</span>
                        </div>
                        <div class="sr-stat">
                            <span class="sr-stat-label">Avg Duration</span>
                            <span class="sr-stat-value">${Utils.formatDuration(data.avgDuration)}</span>
                        </div>
                    </div>

                    <!-- Trend Slide -->
                    ${hasTrendData ? `
                        <div class="sr-carousel-slide" data-slide="trend">
                            <div class="sr-chart-container">
                                <div class="sr-chart-title">Monthly Traffic Trend</div>
                                <div class="ct-chart ct-perfect-fourth" id="sr-trend-chart"></div>
                            </div>
                        </div>
                    ` : ''}

                    <!-- Country Slide -->
                    ${hasCountryData ? `
                        <div class="sr-carousel-slide" data-slide="country">
                            <div class="sr-chart-container">
                                <div class="sr-chart-title">Traffic by Country</div>
                                <div class="ct-chart ct-perfect-fourth" id="sr-country-chart"></div>
                            </div>
                        </div>
                    ` : ''}
                </div>
                <div class="sr-cache-info">Cached ${Utils.getDaysAgo(data.timestamp)}d ago</div>
            `;

            container.innerHTML = html;

            // Bind carousel navigation
            container.querySelectorAll('.sr-carousel-btn').forEach(btn => {
                btn.addEventListener('click', () => {
                    container.querySelectorAll('.sr-carousel-btn').forEach(b => b.classList.remove('active'));
                    container.querySelectorAll('.sr-carousel-slide').forEach(s => s.classList.remove('active'));

                    btn.classList.add('active');
                    const slide = container.querySelector(`.sr-carousel-slide[data-slide="${btn.dataset.slide}"]`);
                    slide.classList.add('active');

                    // Load charts on-demand when switching to chart slides
                    if (btn.dataset.slide === 'trend' && hasTrendData) {
                        ChartsLoader.load().then(() => this.renderTrendChart(data.trafficTrend));
                    } else if (btn.dataset.slide === 'country' && hasCountryData) {
                        ChartsLoader.load().then(() => this.renderCountryChart(data.trafficByCountry));
                    }
                });
            });
        },

        renderCountryChart(countries) {
            this.destroyChart('country');

            if (typeof Chartist === 'undefined') {
                console.error('Chartist not loaded');
                return;
            }

            // Filter out "Other" and sort by percentage (ascending - reverse order)
            const filtered = countries.filter(c => c.country !== 'Other');
            const sorted = filtered.sort((a, b) => a.percentage - b.percentage); // Ascending

            const data = {
                labels: sorted.map(c => c.country),
                series: [sorted.map(c => c.percentage)]
            };

            const options = {
                seriesBarDistance: 10,
                reverseData: false,
                horizontalBars: true,
                axisX: {
                    labelInterpolationFnc: (value) => value.toFixed(1) + '%',
                    offset: 30
                },
                axisY: {
                    offset: 70
                },
                height: 180,
                plugins: []
            };

            // Add tooltip plugin if available
            if (typeof Chartist.plugins !== 'undefined' && Chartist.plugins.tooltip) {
                options.plugins.push(
                    Chartist.plugins.tooltip({
                        tooltipFnc: (meta, value) => {
                            return `${meta} ${parseFloat(value).toFixed(2)}%`;
                        }
                    })
                );
            }

            this.chartInstances.country = new Chartist.Bar('#sr-country-chart', data, options);
        },

        renderTrendChart(trend) {
            this.destroyChart('trend');

            if (typeof Chartist === 'undefined') {
                console.error('Chartist not loaded');
                return;
            }

            const data = {
                labels: trend.map(t => t.month),
                series: [trend.map(t => t.visits)]
            };

            const options = {
                axisY: {
                    labelInterpolationFnc: (value) => Utils.formatNumber(value),
                    offset: 40
                },
                axisX: {
                    offset: 30
                },
                height: 160,
                plugins: []
            };

            // Add tooltip plugin if available
            if (typeof Chartist.plugins !== 'undefined' && Chartist.plugins.tooltip) {
                options.plugins.push(
                    Chartist.plugins.tooltip({
                        tooltipFnc: (meta, value) => {
                            return `${meta} ${Utils.formatNumber(value)} visits`;
                        }
                    })
                );
            }

            this.chartInstances.trend = new Chartist.Bar('#sr-trend-chart', data, options);
        },

        renderAll() {
            const container = document.getElementById('tab-all');
            const cache = Cache.get();
            const domains = Object.keys(cache);

            if (domains.length === 0) {
                container.innerHTML = '<div class="sr-no-data">No cached sites yet</div>';
                return;
            }

            const sorted = domains.sort((a, b) => {
                const rankA = cache[a].globalRank || Infinity;
                const rankB = cache[b].globalRank || Infinity;
                return rankA - rankB;
            });

            let html = `
                <div class="sr-table-wrapper">
                    <table class="sr-table">
                        <thead>
                            <tr>
                                <th>Domain</th>
                                <th>Rank</th>
                                <th>Visits</th>
                            </tr>
                        </thead>
                        <tbody>
            `;

            sorted.forEach(domain => {
                const d = cache[domain];
                html += `
                    <tr>
                        <td><a href="https://${domain}" class="sr-link" target="_blank">${domain}</a></td>
                        <td>${d.globalRank ? '#' + Utils.formatNumber(d.globalRank) : 'N/A'}</td>
                        <td>${Utils.formatNumber(d.visits)}</td>
                    </tr>
                `;
            });

            html += `
                        </tbody>
                    </table>
                </div>
                <button class="sr-clear-cache" id="sr-clear-all">🗑️ Clear Cache (${domains.length})</button>
            `;

            container.innerHTML = html;

            document.getElementById('sr-clear-all')?.addEventListener('click', () => {
                if (confirm(`Clear all ${domains.length} cached sites?`)) {
                    Cache.clear();
                    this.renderAll();
                }
            });
        }
    };

    // Main App
    const App = {
        init() {
            // Don't run on SEMrush itself
            if (window.location.hostname.includes('semrush.com')) return;
            const domain = Utils.getRootDomain(window.location.href);
            if (!domain) return;

            UI.create();
            const observer = new MutationObserver(() => {
                if (!document.body.contains(UI.widget)) {
                    document.body.appendChild(UI.widget);
                }
            });
            observer.observe(document.body, { childList: true, subtree: true });
            this.setupShortcut();
            this.observeURLChanges();
            this.loadCurrent(domain);
        },

        setupShortcut() {
            const handleShortcut = (e) => {
                const altMatch = CONFIG.SHORTCUT_ALT ? e.altKey : !e.altKey;
                const ctrlMatch = CONFIG.SHORTCUT_CTRL ? e.ctrlKey : !e.ctrlKey;
                const shiftMatch = CONFIG.SHORTCUT_SHIFT ? e.shiftKey : !e.shiftKey;
                if (e.code === CONFIG.SHORTCUT_KEY && altMatch && ctrlMatch && shiftMatch) {
                    e.preventDefault();
                    e.stopImmediatePropagation();
                    UI.toggle();
                }
            };
            window.addEventListener('keydown', handleShortcut, { capture: true });
        },

        observeURLChanges() {
            let lastUrl = location.href;

            const checkURL = () => {
                if (location.href !== lastUrl) {
                    lastUrl = location.href;
                    const newDomain = Utils.getRootDomain(location.href);
                    if (newDomain) {
                        this.loadCurrent(newDomain);
                    }
                }
            };

            // Listen to history changes (SPA navigation)
            window.addEventListener('popstate', checkURL);

            // Intercept pushState/replaceState
            const originalPushState = history.pushState;
            const originalReplaceState = history.replaceState;

            history.pushState = function () {
                originalPushState.apply(this, arguments);
                checkURL();
            };

            history.replaceState = function () {
                originalReplaceState.apply(this, arguments);
                checkURL();
            };
        },

        loadCurrent(domain) {
            const cache = Cache.get();
            const cached = cache[domain];

            if (cached && Cache.isValid(cached)) {
                UI.renderCurrent(cached);
                return;
            }

            const container = document.getElementById('tab-current');
            container.innerHTML = '<div class="sr-loading">Fetching data...</div>';

            SEMrush.fetch(domain, (error, data) => {
                if (error) {
                    container.innerHTML = `<div class="sr-error">${error.message || 'Failed to fetch data'}</div>`;
                    return;
                }

                Cache.addDomain(domain, data);
                UI.renderCurrent(data);
            });
        },

        loadAll() {
            UI.renderAll();
        }
    };

    // Initialize
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => App.init());
    } else {
        App.init();
    }
})();