SEO Analyzer Pro

Comprehensive SEO analysis tool with floating window and persistent filters

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         SEO Analyzer Pro
// @namespace    http://tampermonkey.net/
// @version      1.7.0
// @description  Comprehensive SEO analysis tool with floating window and persistent filters
// @author       jotsaru0
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

/*
 * ============================================================================
 * SEO ANALYZER PRO v1.7.0
 * ============================================================================
 *
 * FEATURES:
 * --------
 * ✓ Meta Tag Analysis (Title, Description, OG Tags, Twitter Cards)
 * ✓ Header Hierarchy Analysis (H1-H6)
 * ✓ Image Analysis (Alt tags, Types, Statistics)
 * ✓ Link Analysis (Internal, External, NoFollow, Anchors)
 * ✓ Schema Markup Detection (JSON-LD, Microdata, RDFa)
 * ✓ Interactive Filtering (Max 3 selections per tab)
 * ✓ Persistent Filter Memory (NEW - Saves across sessions)
 * ✓ Clickable Links (Open in new tab)
 * ✓ SERP Preview
 * ✓ Word Count
 * ✓ Keyboard Shortcut (Ctrl+M)
 *
 * USAGE:
 * ------
 * - Press Ctrl+M to toggle the SEO Analyzer window
 * - Click on stat cards/table rows to filter content (max 3 selections)
 * - All links are clickable and open in new tabs
 * - Filter selections are automatically saved and restored
 *
 * CHANGELOG:
 * ----------
 * v1.7.0 (2025-01-15)
 *   - NEW: Persistent filter memory - selections saved across browser sessions
 *   - NEW: Filters automatically restore when reopening the tool
 *   - Enhanced: Improved stability and error handling for storage
 *   - Fixed: Filter restoration now works correctly on first load
 *
 * v1.6.0 (2025-01-14)
 *   - Added Tampermonkey memory preferences (GM_setValue/GM_getValue)
 *   - Filter selections persist across sessions
 *   - Improved filtering logic for images and links
 *   - Added clickable links with hover effects
 *
 * v1.5.0 (2025-01-13)
 *   - Added interactive filtering for all tabs
 *   - Maximum 3 selections per tab
 *   - Selection counter with visual feedback
 *   - Schema tab with expandable code blocks
 *
 * ============================================================================
 */

(function() {
    'use strict';

    // Toggle with Ctrl+M
    document.addEventListener('keydown', (e) => {
        if (e.ctrlKey && e.key === 'm') {
            e.preventDefault();
            toggleWindow();
        }
    });

    let isVisible = false;
    let floatingWindow = null;
    let selectedFilters = {
        headers: [],
        images: [],
        links: []
    };

    // Load saved preferences
    function loadPreferences() {
        try {
            const saved = GM_getValue('seoAnalyzerFilters', null);
            if (saved) {
                selectedFilters = JSON.parse(saved);
                console.log('SEO Analyzer: Loaded saved filters', selectedFilters);
            }
        } catch (e) {
            console.error('SEO Analyzer: Failed to load preferences', e);
        }
    }

    // Save preferences
    function savePreferences() {
        try {
            GM_setValue('seoAnalyzerFilters', JSON.stringify(selectedFilters));
            console.log('SEO Analyzer: Saved filters', selectedFilters);
        } catch (e) {
            console.error('SEO Analyzer: Failed to save preferences', e);
        }
    }

    function toggleWindow() {
        if (!floatingWindow) {
            createFloatingWindow();
        }
        isVisible = !isVisible;
        floatingWindow.style.display = isVisible ? 'flex' : 'none';
        if (isVisible) {
            loadPreferences();
            analyzePageSEO();
        }
    }

    function createFloatingWindow() {
        floatingWindow = document.createElement('div');
        floatingWindow.id = 'seo-analyzer-window';
        floatingWindow.innerHTML = `
            <div class="seo-header">
                <h2>SEO Analyzer Pro v1.7</h2>
                <button class="close-btn" id="close-seo">×</button>
            </div>
            <div class="seo-tabs">
                <button class="tab-btn active" data-tab="meta">Meta</button>
                <button class="tab-btn" data-tab="headers">Headers</button>
                <button class="tab-btn" data-tab="images">Images</button>
                <button class="tab-btn" data-tab="links">Links</button>
                <button class="tab-btn" data-tab="schema">Schema</button>
            </div>
            <div class="seo-content">
                <div class="tab-content active" id="tab-meta"></div>
                <div class="tab-content" id="tab-headers"></div>
                <div class="tab-content" id="tab-images"></div>
                <div class="tab-content" id="tab-links"></div>
                <div class="tab-content" id="tab-schema"></div>
            </div>
        `;

        document.body.appendChild(floatingWindow);
        addStyles();
        attachEventListeners();
    }

    function addStyles() {
        const style = document.createElement('style');
        style.textContent = `
            #seo-analyzer-window {
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                width: 800px;
                max-height: 80vh;
                background: #ffffff;
                border-radius: 12px;
                box-shadow: 0 10px 40px rgba(0,0,0,0.15);
                z-index: 999999;
                display: none;
                flex-direction: column;
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
            }

            .seo-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 20px 24px;
                border-bottom: 1px solid #e5e7eb;
            }

            .seo-header h2 {
                margin: 0;
                font-size: 18px;
                font-weight: 600;
                color: #111827;
            }

            .close-btn {
                background: none;
                border: none;
                font-size: 28px;
                color: #6b7280;
                cursor: pointer;
                padding: 0;
                width: 32px;
                height: 32px;
                border-radius: 6px;
                transition: all 0.2s;
            }

            .close-btn:hover {
                background: #f3f4f6;
                color: #111827;
            }

            .seo-tabs {
                display: flex;
                padding: 0 24px;
                border-bottom: 1px solid #e5e7eb;
                gap: 8px;
            }

            .tab-btn {
                background: none;
                border: none;
                padding: 12px 16px;
                font-size: 14px;
                font-weight: 500;
                color: #6b7280;
                cursor: pointer;
                border-bottom: 2px solid transparent;
                transition: all 0.2s;
            }

            .tab-btn:hover {
                color: #111827;
            }

            .tab-btn.active {
                color: #2563eb;
                border-bottom-color: #2563eb;
            }

            .seo-content {
                overflow-y: auto;
                padding: 24px;
                max-height: calc(80vh - 140px);
            }

            .tab-content {
                display: none;
            }

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

            .seo-section {
                margin-bottom: 24px;
            }

            .seo-section h3 {
                font-size: 14px;
                font-weight: 600;
                color: #111827;
                margin: 0 0 12px 0;
                text-transform: uppercase;
                letter-spacing: 0.5px;
            }

            .seo-field {
                margin-bottom: 16px;
                padding: 12px;
                background: #f9fafb;
                border-radius: 6px;
                border-left: 3px solid #e5e7eb;
            }

            .seo-field.warning {
                border-left-color: #f59e0b;
                background: #fffbeb;
            }

            .seo-field.error {
                border-left-color: #ef4444;
                background: #fef2f2;
            }

            .seo-field.success {
                border-left-color: #10b981;
                background: #f0fdf4;
            }

            .seo-label {
                font-size: 12px;
                font-weight: 600;
                color: #6b7280;
                margin-bottom: 4px;
                text-transform: uppercase;
                letter-spacing: 0.3px;
            }

            .seo-value {
                font-size: 14px;
                color: #111827;
                word-wrap: break-word;
            }

            .seo-value code {
                background: #f3f4f6;
                padding: 2px 6px;
                border-radius: 3px;
                font-size: 13px;
                font-family: 'Courier New', monospace;
            }

            .serp-preview {
                background: #ffffff;
                border: 1px solid #e5e7eb;
                border-radius: 6px;
                padding: 16px;
                margin-top: 8px;
            }

            .serp-url {
                color: #1a0dab;
                font-size: 14px;
                margin-bottom: 4px;
            }

            .serp-title {
                color: #1a0dab;
                font-size: 18px;
                font-weight: 400;
                margin-bottom: 4px;
                line-height: 1.3;
            }

            .serp-description {
                color: #545454;
                font-size: 13px;
                line-height: 1.4;
            }

            table {
                width: 100%;
                border-collapse: collapse;
                background: #ffffff;
                border-radius: 6px;
                overflow: hidden;
                margin-top: 8px;
            }

            th, td {
                padding: 12px;
                text-align: left;
                border-bottom: 1px solid #e5e7eb;
                font-size: 13px;
            }

            th {
                background: #f9fafb;
                font-weight: 600;
                color: #111827;
                text-transform: uppercase;
                font-size: 11px;
                letter-spacing: 0.5px;
            }

            td {
                color: #374151;
            }

            tr:last-child td {
                border-bottom: none;
            }

            .stats-grid {
                display: grid;
                grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
                gap: 12px;
                margin-top: 8px;
            }

            .stat-card {
                background: #f9fafb;
                padding: 16px;
                border-radius: 6px;
                text-align: center;
                cursor: pointer;
                transition: all 0.2s;
                border: 2px solid transparent;
            }

            .stat-card:hover {
                background: #f3f4f6;
                transform: translateY(-2px);
            }

            .stat-card.selected {
                background: #eff6ff;
                border-color: #2563eb;
                box-shadow: 0 2px 8px rgba(37, 99, 235, 0.15);
            }

            .stat-number {
                font-size: 24px;
                font-weight: 700;
                color: #111827;
                margin-bottom: 4px;
            }

            .stat-label {
                font-size: 12px;
                color: #6b7280;
                font-weight: 500;
            }

            .link-list {
                max-height: 300px;
                overflow-y: auto;
                margin-top: 8px;
            }

            .link-item {
                padding: 8px 12px;
                background: #f9fafb;
                border-radius: 4px;
                margin-bottom: 6px;
                font-size: 13px;
                word-break: break-all;
                transition: all 0.3s;
            }

            .link-item.hidden {
                display: none;
            }

            .link-item.broken {
                background: #fef2f2;
                color: #ef4444;
            }

            .hierarchy-item {
                margin-bottom: 8px;
                padding: 8px 12px;
                background: #f9fafb;
                border-radius: 4px;
            }

            .hierarchy-level {
                display: inline-block;
                width: 40px;
                font-weight: 600;
                color: #2563eb;
            }

            .schema-block {
                background: #f9fafb;
                border: 1px solid #e5e7eb;
                border-radius: 6px;
                padding: 16px;
                margin-bottom: 12px;
                cursor: pointer;
                transition: all 0.2s;
            }

            .schema-block:hover {
                background: #f3f4f6;
                border-color: #d1d5db;
            }

            .schema-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
            }

            .schema-type {
                font-weight: 600;
                color: #2563eb;
                font-size: 14px;
                padding: 4px 8px;
                background: #eff6ff;
                border-radius: 4px;
                display: inline-block;
            }

            .schema-toggle {
                color: #6b7280;
                font-size: 20px;
                transition: transform 0.2s;
            }

            .schema-toggle.expanded {
                transform: rotate(180deg);
            }

            .schema-code {
                background: #1f2937;
                color: #f3f4f6;
                padding: 12px;
                border-radius: 4px;
                font-family: 'Courier New', monospace;
                font-size: 12px;
                overflow-x: auto;
                white-space: pre-wrap;
                word-wrap: break-word;
                margin-top: 12px;
                display: none;
            }

            .schema-code.visible {
                display: block;
            }

            .schema-list {
                background: #f9fafb;
                border-radius: 6px;
                padding: 16px;
            }

            .schema-list-item {
                padding: 8px 12px;
                background: #ffffff;
                border-left: 3px solid #2563eb;
                border-radius: 4px;
                margin-bottom: 8px;
                font-size: 14px;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }

            .schema-list-type {
                font-weight: 600;
                color: #111827;
            }

            .schema-list-format {
                font-size: 12px;
                color: #6b7280;
                background: #f3f4f6;
                padding: 2px 8px;
                border-radius: 3px;
            }

            .no-schema {
                text-align: center;
                padding: 40px;
                color: #6b7280;
            }

            .selection-counter {
                background: #2563eb;
                color: white;
                padding: 6px 12px;
                border-radius: 6px;
                font-size: 12px;
                font-weight: 600;
                display: inline-block;
                margin-bottom: 16px;
            }

            .selection-counter.hidden {
                display: none;
            }

            .filterable-section {
                transition: all 0.3s;
            }

            .filterable-section.hidden {
                display: none;
            }

            .header-row {
                cursor: pointer;
                transition: all 0.2s;
            }

            .header-row:hover {
                background: #f3f4f6;
            }

            .header-row.selected {
                background: #eff6ff;
                font-weight: 600;
            }

            .clickable-link {
                color: #374151;
                text-decoration: none;
                transition: all 0.2s;
            }

            .clickable-link:hover {
                text-decoration: underline;
                font-weight: 700;
                color: #2563eb;
            }
        `;
        document.head.appendChild(style);
    }

    function attachEventListeners() {
        document.getElementById('close-seo').addEventListener('click', toggleWindow);

        document.querySelectorAll('.tab-btn').forEach(btn => {
            btn.addEventListener('click', (e) => {
                const tab = e.target.dataset.tab;
                document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
                document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
                e.target.classList.add('active');
                document.getElementById(`tab-${tab}`).classList.add('active');
            });
        });
    }

    function getMetaTag(name) {
        const meta = document.querySelector(`meta[name="${name}"], meta[property="${name}"]`);
        return meta ? meta.getAttribute('content') : 'Not found';
    }

    function analyzePageSEO() {
        analyzeMetaTab();
        analyzeHeadersTab();
        analyzeImagesTab();
        analyzeLinksTab();
        analyzeSchemaTab();
    }

    function analyzeMetaTab() {
        const title = document.title || 'No title';
        const description = getMetaTag('description');
        const canonical = document.querySelector('link[rel="canonical"]')?.href || 'Not found';
        const robots = getMetaTag('robots');
        const viewport = getMetaTag('viewport');
        const wordCount = document.body.innerText.split(/\s+/).length;

        const ogTitle = getMetaTag('og:title');
        const ogDescription = getMetaTag('og:description');
        const ogImage = getMetaTag('og:image');

        const twitterCard = getMetaTag('twitter:card');
        const twitterTitle = getMetaTag('twitter:title');
        const twitterDescription = getMetaTag('twitter:description');
        const twitterImage = getMetaTag('twitter:image');
        const twitterSite = getMetaTag('twitter:site');
        const twitterCreator = getMetaTag('twitter:creator');
        const twitterUrl = getMetaTag('twitter:url');
        const twitterDomain = getMetaTag('twitter:domain');

        const url = window.location.href;
        const displayUrl = new URL(url).hostname + new URL(url).pathname;

        document.getElementById('tab-meta').innerHTML = `
            <div class="seo-section">
                <div class="seo-field ${title.length > 60 ? 'warning' : 'success'}">
                    <div class="seo-label">Meta Title (${title.length} chars)</div>
                    <div class="seo-value">${title}</div>
                </div>

                <div class="seo-field ${description === 'Not found' ? 'error' : description.length > 160 ? 'warning' : 'success'}">
                    <div class="seo-label">Meta Description (${description.length} chars)</div>
                    <div class="seo-value">${description}</div>
                </div>

                <div class="seo-field">
                    <div class="seo-label">SERP Preview</div>
                    <div class="serp-preview">
                        <div class="serp-url">${displayUrl}</div>
                        <div class="serp-title">${title.substring(0, 60)}${title.length > 60 ? '...' : ''}</div>
                        <div class="serp-description">${description.substring(0, 160)}${description.length > 160 ? '...' : ''}</div>
                    </div>
                </div>

                <div class="seo-field ${canonical === 'Not found' ? 'warning' : 'success'}">
                    <div class="seo-label">Canonical URL</div>
                    <div class="seo-value">${canonical}</div>
                </div>

                <div class="seo-field">
                    <div class="seo-label">Robots</div>
                    <div class="seo-value">${robots}</div>
                </div>

                <div class="seo-field">
                    <div class="seo-label">Viewport</div>
                    <div class="seo-value">${viewport}</div>
                </div>

                <div class="seo-field">
                    <div class="seo-label">Word Count</div>
                    <div class="seo-value">${wordCount} words</div>
                </div>
            </div>

            <div class="seo-section">
                <h3>Open Graph Tags</h3>
                <div class="seo-field">
                    <div class="seo-label">OG:Title</div>
                    <div class="seo-value">${ogTitle}</div>
                </div>
                <div class="seo-field">
                    <div class="seo-label">OG:Description</div>
                    <div class="seo-value">${ogDescription}</div>
                </div>
                <div class="seo-field">
                    <div class="seo-label">OG:Image</div>
                    <div class="seo-value">${ogImage}</div>
                </div>
            </div>

            <div class="seo-section">
                <h3>Twitter Card Tags</h3>
                <div class="seo-field">
                    <div class="seo-label">Twitter:Card</div>
                    <div class="seo-value">${twitterCard}</div>
                </div>
                <div class="seo-field">
                    <div class="seo-label">Twitter:Title</div>
                    <div class="seo-value">${twitterTitle}</div>
                </div>
                <div class="seo-field">
                    <div class="seo-label">Twitter:Description</div>
                    <div class="seo-value">${twitterDescription}</div>
                </div>
                <div class="seo-field">
                    <div class="seo-label">Twitter:Image</div>
                    <div class="seo-value">${twitterImage}</div>
                </div>
                <div class="seo-field">
                    <div class="seo-label">Twitter:Site</div>
                    <div class="seo-value">${twitterSite}</div>
                </div>
                <div class="seo-field">
                    <div class="seo-label">Twitter:Creator</div>
                    <div class="seo-value">${twitterCreator}</div>
                </div>
                <div class="seo-field">
                    <div class="seo-label">Twitter:Url</div>
                    <div class="seo-value">${twitterUrl}</div>
                </div>
                <div class="seo-field">
                    <div class="seo-label">Twitter:Domain</div>
                    <div class="seo-value">${twitterDomain}</div>
                </div>
            </div>
        `;
    }

    function analyzeHeadersTab() {
        const headers = [];
        ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].forEach(tag => {
            document.querySelectorAll(tag).forEach(el => {
                headers.push({
                    level: tag.toUpperCase(),
                    text: el.innerText.trim()
                });
            });
        });

        const headerCounts = {
            H1: document.querySelectorAll('h1').length,
            H2: document.querySelectorAll('h2').length,
            H3: document.querySelectorAll('h3').length,
            H4: document.querySelectorAll('h4').length,
            H5: document.querySelectorAll('h5').length,
            H6: document.querySelectorAll('h6').length
        };

        const tableRows = Object.entries(headerCounts)
            .map(([level, count]) => `<tr class="header-row" data-header-level="${level}"><td>${level}</td><td>${count}</td></tr>`)
            .join('');

        const hierarchyHTML = headers
            .map(h => `<div class="hierarchy-item filterable-section" data-header-type="${h.level}"><span class="hierarchy-level">${h.level}</span>${h.text}</div>`)
            .join('');

        document.getElementById('tab-headers').innerHTML = `
            <div class="selection-counter hidden" id="header-counter">0 selected</div>
            <div class="seo-section">
                <h3>Header Count (Click to filter)</h3>
                <table>
                    <thead>
                        <tr>
                            <th>Header Level</th>
                            <th>Count</th>
                        </tr>
                    </thead>
                    <tbody>
                        ${tableRows}
                    </tbody>
                </table>
            </div>

            <div class="seo-section">
                <h3>Heading Hierarchy</h3>
                ${hierarchyHTML || '<p>No headers found</p>'}
            </div>
        `;

        // Add click handlers for header filtering
        document.querySelectorAll('.header-row').forEach(row => {
            row.addEventListener('click', function() {
                const level = this.dataset.headerLevel;
                filterHeaders(level, this);
            });
        });

        // Restore saved selections
        selectedFilters.headers.forEach(level => {
            const row = document.querySelector(`.header-row[data-header-level="${level}"]`);
            if (row) {
                row.classList.add('selected');
            }
        });
        updateHeaderDisplay();
    }

    function filterHeaders(level, row) {
        const index = selectedFilters.headers.indexOf(level);

        if (index > -1) {
            selectedFilters.headers.splice(index, 1);
            row.classList.remove('selected');
        } else {
            if (selectedFilters.headers.length >= 3) {
                return;
            }
            selectedFilters.headers.push(level);
            row.classList.add('selected');
        }

        savePreferences();
        updateHeaderDisplay();
    }

    function updateHeaderDisplay() {
        const counter = document.getElementById('header-counter');
        const count = selectedFilters.headers.length;

        if (count > 0) {
            counter.textContent = `${count} selected`;
            counter.classList.remove('hidden');
        } else {
            counter.classList.add('hidden');
        }

        document.querySelectorAll('.hierarchy-item').forEach(item => {
            const type = item.dataset.headerType;
            if (count === 0 || selectedFilters.headers.includes(type)) {
                item.classList.remove('hidden');
            } else {
                item.classList.add('hidden');
            }
        });
    }

    function analyzeImagesTab() {
        const images = Array.from(document.querySelectorAll('img'));
        const withAlt = images.filter(img => img.alt && img.alt.trim() !== '');
        const withoutAlt = images.filter(img => !img.alt || img.alt.trim() === '');

        const imageTypes = {};
        images.forEach(img => {
            const ext = img.src.split('.').pop().split('?')[0].toLowerCase();
            imageTypes[ext] = (imageTypes[ext] || 0) + 1;
        });

        const withAltRows = withAlt.map((img, idx) => {
            const ext = img.src.split('.').pop().split('?')[0].toLowerCase();
            return `<tr class="filterable-section" data-image-type="${ext}" data-alt-status="with"><td>${img.alt}</td><td>${ext}</td><td style="max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"><a href="${img.src}" target="_blank" class="clickable-link">${img.src}</a></td></tr>`;
        }).join('');

        const withoutAltRows = withoutAlt.map((img, idx) => {
            const ext = img.src.split('.').pop().split('?')[0].toLowerCase();
            return `<tr class="filterable-section" data-image-type="${ext}" data-alt-status="without"><td>${ext}</td><td style="max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"><a href="${img.src}" target="_blank" class="clickable-link">${img.src}</a></td></tr>`;
        }).join('');

        const typeStats = Object.entries(imageTypes)
            .map(([type, count]) => `<div class="stat-card" data-image-filter="${type}"><div class="stat-number">${count}</div><div class="stat-label">${type.toUpperCase()}</div></div>`)
            .join('');

        document.getElementById('tab-images').innerHTML = `
            <div class="selection-counter hidden" id="image-counter">0 selected</div>
            <div class="seo-section">
                <h3>Image Statistics (Click to filter by alt status)</h3>
                <div class="stats-grid">
                    <div class="stat-card">
                        <div class="stat-number">${images.length}</div>
                        <div class="stat-label">Total Images</div>
                    </div>
                    <div class="stat-card" data-image-filter="with">
                        <div class="stat-number">${withAlt.length}</div>
                        <div class="stat-label">With Alt</div>
                    </div>
                    <div class="stat-card" data-image-filter="without">
                        <div class="stat-number">${withoutAlt.length}</div>
                        <div class="stat-label">Without Alt</div>
                    </div>
                </div>
            </div>

            <div class="seo-section">
                <h3>Image Types (Click to filter by type)</h3>
                <div class="stats-grid">
                    ${typeStats}
                </div>
            </div>

            <div class="seo-section">
                <h3>Images with Alt Tags</h3>
                <table>
                    <thead>
                        <tr>
                            <th>Alt Text</th>
                            <th>Type</th>
                            <th>Source</th>
                        </tr>
                    </thead>
                    <tbody id="with-alt-tbody">
                        ${withAltRows || '<tr><td colspan="3">No images with alt tags</td></tr>'}
                    </tbody>
                </table>
            </div>

            <div class="seo-section">
                <h3>Images without Alt Tags</h3>
                <table>
                    <thead>
                        <tr>
                            <th>Type</th>
                            <th>Source</th>
                        </tr>
                    </thead>
                    <tbody id="without-alt-tbody">
                        ${withoutAltRows || '<tr><td colspan="2">All images have alt tags!</td></tr>'}
                    </tbody>
                </table>
            </div>
        `;

        // Add click handlers for image filtering
        document.querySelectorAll('[data-image-filter]').forEach(card => {
            card.addEventListener('click', function() {
                const type = this.dataset.imageFilter;
                filterImages(type, this);
            });
        });

        // Restore saved selections
        selectedFilters.images.forEach(filter => {
            const card = document.querySelector(`[data-image-filter="${filter}"]`);
            if (card) {
                card.classList.add('selected');
            }
        });
        updateImageDisplay();
    }

    function filterImages(type, card) {
        const index = selectedFilters.images.indexOf(type);

        if (index > -1) {
            selectedFilters.images.splice(index, 1);
            card.classList.remove('selected');
        } else {
            if (selectedFilters.images.length >= 3) {
                return;
            }
            selectedFilters.images.push(type);
            card.classList.add('selected');
        }

        savePreferences();
        updateImageDisplay();
    }

    function updateImageDisplay() {
        const counter = document.getElementById('image-counter');
        const count = selectedFilters.images.length;

        if (count > 0) {
            counter.textContent = `${count} selected`;
            counter.classList.remove('hidden');
        } else {
            counter.classList.add('hidden');
        }

        document.querySelectorAll('#with-alt-tbody .filterable-section, #without-alt-tbody .filterable-section').forEach(item => {
            const imageType = item.dataset.imageType;
            const altStatus = item.dataset.altStatus;

            let shouldShow = false;

            if (count === 0) {
                shouldShow = true;
            } else {
                for (const filter of selectedFilters.images) {
                    if (filter === imageType || filter === altStatus) {
                        shouldShow = true;
                        break;
                    }
                }
            }

            if (shouldShow) {
                item.classList.remove('hidden');
            } else {
                item.classList.add('hidden');
            }
        });
    }

    function analyzeLinksTab() {
        const links = Array.from(document.querySelectorAll('a[href]'));
        const currentHost = window.location.hostname;

        const internal = links.filter(a => {
            try {
                const url = new URL(a.href, window.location.href);
                return url.hostname === currentHost;
            } catch {
                return false;
            }
        });

        const external = links.filter(a => {
            try {
                const url = new URL(a.href, window.location.href);
                return url.hostname !== currentHost;
            } catch {
                return false;
            }
        });

        const unique = [...new Set(links.map(a => a.href))];
        const noFollow = links.filter(a => a.rel.includes('nofollow'));
        const noAnchor = links.filter(a => !a.innerText.trim());

        const internalList = internal.map(a => `<div class="link-item filterable-section" data-link-type="internal"><a href="${a.href}" target="_blank" class="clickable-link">${a.href}</a></div>`).join('');
        const externalList = external.map(a => `<div class="link-item filterable-section" data-link-type="external"><a href="${a.href}" target="_blank" class="clickable-link">${a.href}</a></div>`).join('');

        document.getElementById('tab-links').innerHTML = `
            <div class="selection-counter hidden" id="link-counter">0 selected</div>
            <div class="seo-section">
                <h3>Link Statistics (Click to filter)</h3>
                <div class="stats-grid">
                    <div class="stat-card" data-link-filter="all">
                        <div class="stat-number">${links.length}</div>
                        <div class="stat-label">Total Links</div>
                    </div>
                    <div class="stat-card" data-link-filter="internal">
                        <div class="stat-number">${internal.length}</div>
                        <div class="stat-label">Internal Links</div>
                    </div>
                    <div class="stat-card" data-link-filter="external">
                        <div class="stat-number">${external.length}</div>
                        <div class="stat-label">External Links</div>
                    </div>
                    <div class="stat-card" data-link-filter="unique">
                        <div class="stat-number">${unique.length}</div>
                        <div class="stat-label">Unique Links</div>
                    </div>
                    <div class="stat-card" data-link-filter="nofollow">
                        <div class="stat-number">${noFollow.length}</div>
                        <div class="stat-label">NoFollow Links</div>
                    </div>
                    <div class="stat-card" data-link-filter="noanchor">
                        <div class="stat-number">${noAnchor.length}</div>
                        <div class="stat-label">No Anchor Text</div>
                    </div>
                </div>
            </div>

            <div class="seo-section">
                <h3>Internal Links</h3>
                <div class="link-list">
                    ${internalList || '<p>No internal links found</p>'}
                </div>
            </div>

            <div class="seo-section">
                <h3>External Links</h3>
                <div class="link-list">
                    ${externalList || '<p>No external links found</p>'}
                </div>
            </div>
        `;

        window.linkData = {
            all: links,
            internal: internal,
            external: external,
            unique: unique.map(href => links.find(a => a.href === href)),
            nofollow: noFollow,
            noanchor: noAnchor
        };

        document.querySelectorAll('[data-link-filter]').forEach(card => {
            card.addEventListener('click', function() {
                const type = this.dataset.linkFilter;
                filterLinks(type, this);
            });
        });

        // Restore saved selections
        selectedFilters.links.forEach(filter => {
            const card = document.querySelector(`[data-link-filter="${filter}"]`);
            if (card) {
                card.classList.add('selected');
            }
        });
        updateLinkDisplay();
    }

    function filterLinks(type, card) {
        const index = selectedFilters.links.indexOf(type);

        if (index > -1) {
            selectedFilters.links.splice(index, 1);
            card.classList.remove('selected');
        } else {
            if (selectedFilters.links.length >= 3) {
                return;
            }
            selectedFilters.links.push(type);
            card.classList.add('selected');
        }

        savePreferences();
        updateLinkDisplay();
    }

    function updateLinkDisplay() {
        const counter = document.getElementById('link-counter');
        const count = selectedFilters.links.length;

        if (count > 0) {
            counter.textContent = `${count} selected`;
            counter.classList.remove('hidden');
        } else {
            counter.classList.add('hidden');
        }

        if (!window.linkData) return;

        let filteredLinks = new Set();

        if (count === 0) {
            document.querySelectorAll('.link-list .filterable-section').forEach(item => {
                item.classList.remove('hidden');
            });
            return;
        }

        selectedFilters.links.forEach(filter => {
            if (window.linkData[filter]) {
                window.linkData[filter].forEach(link => {
                    filteredLinks.add(link.href);
                });
            }
        });

        document.querySelectorAll('.link-list .filterable-section').forEach(item => {
            const link = item.querySelector('a');
            if (link && filteredLinks.has(link.href)) {
                item.classList.remove('hidden');
            } else {
                item.classList.add('hidden');
            }
        });
    }

    function analyzeSchemaTab() {
        const schemas = [];

        const jsonLdScripts = document.querySelectorAll('script[type="application/ld+json"]');
        jsonLdScripts.forEach((script, index) => {
            try {
                const data = JSON.parse(script.textContent);
                const type = data['@type'] || (Array.isArray(data) ? data.map(d => d['@type']).join(', ') : 'Unknown');
                schemas.push({
                    type: type,
                    format: 'JSON-LD',
                    content: JSON.stringify(data, null, 2)
                });
            } catch (e) {
                schemas.push({
                    type: 'Invalid JSON-LD',
                    format: 'JSON-LD',
                    content: 'Error parsing JSON-LD schema'
                });
            }
        });

        const microdataItems = document.querySelectorAll('[itemscope]');
        if (microdataItems.length > 0) {
            const microdataTypes = [...new Set(Array.from(microdataItems).map(item => {
                return item.getAttribute('itemtype') || 'No type specified';
            }))];

            microdataTypes.forEach(type => {
                schemas.push({
                    type: type.split('/').pop() || 'Microdata',
                    format: 'Microdata',
                    content: `Found ${Array.from(microdataItems).filter(i => i.getAttribute('itemtype') === type).length} instance(s)`
                });
            });
        }

        const rdfaItems = document.querySelectorAll('[vocab], [typeof]');
        if (rdfaItems.length > 0) {
            const rdfaTypes = [...new Set(Array.from(rdfaItems).map(item => {
                return item.getAttribute('typeof') || 'No type specified';
            }))];

            rdfaTypes.forEach(type => {
                schemas.push({
                    type: type || 'RDFa',
                    format: 'RDFa',
                    content: `Found ${Array.from(rdfaItems).filter(i => i.getAttribute('typeof') === type).length} instance(s)`
                });
            });
        }

        let schemaHTML = '';

        if (schemas.length === 0) {
            schemaHTML = '<div class="no-schema">No schema markup found on this page</div>';
        } else {
            const stats = `
                <div class="stats-grid">
                    <div class="stat-card">
                        <div class="stat-number">${schemas.length}</div>
                        <div class="stat-label">Total Schemas</div>
                    </div>
                    <div class="stat-card">
                        <div class="stat-number">${schemas.filter(s => s.format === 'JSON-LD').length}</div>
                        <div class="stat-label">JSON-LD</div>
                    </div>
                    <div class="stat-card">
                        <div class="stat-number">${schemas.filter(s => s.format === 'Microdata').length}</div>
                        <div class="stat-label">Microdata</div>
                    </div>
                    <div class="stat-card">
                        <div class="stat-number">${schemas.filter(s => s.format === 'RDFa').length}</div>
                        <div class="stat-label">RDFa</div>
                    </div>
                </div>
            `;

            const schemaList = schemas.map((schema, index) => `
                <div class="schema-list-item">
                    <span class="schema-list-type">${schema.type}</span>
                    <span class="schema-list-format">${schema.format}</span>
                </div>
            `).join('');

            const schemaBlocks = schemas.map((schema, index) => `
                <div class="schema-block" data-schema-index="${index}">
                    <div class="schema-header">
                        <div class="schema-type">${schema.format}: ${schema.type}</div>
                        <div class="schema-toggle">▼</div>
                    </div>
                    <div class="schema-code" id="schema-code-${index}">${schema.content}</div>
                </div>
            `).join('');

            schemaHTML = `
                <div class="seo-section">
                    <h3>Schema Statistics</h3>
                    ${stats}
                </div>
                <div class="seo-section">
                    <h3>All Schemas Found</h3>
                    <div class="schema-list">
                        ${schemaList}
                    </div>
                </div>
                <div class="seo-section">
                    <h3>Schema Markup Details</h3>
                    ${schemaBlocks}
                </div>
            `;
        }

        document.getElementById('tab-schema').innerHTML = schemaHTML;

        document.querySelectorAll('.schema-block').forEach(block => {
            block.addEventListener('click', function() {
                const index = this.dataset.schemaIndex;
                const codeBlock = document.getElementById(`schema-code-${index}`);
                const toggle = this.querySelector('.schema-toggle');

                codeBlock.classList.toggle('visible');
                toggle.classList.toggle('expanded');
            });
        });
    }

})();