<?php
// Safety: do not allow direct access.
if (!defined('ABSPATH')) { exit; }

// ---------- Non-stock pages: compare this site's published pages to its cloned stock snapshot ----------
if (!function_exists('inception_qc_normalize_page_path')) {
    function inception_qc_normalize_page_path($url_or_path) {
        $value = trim((string) $url_or_path);
        if ($value === '') return '/';

        $parsed = wp_parse_url($value);
        $path = is_array($parsed) && isset($parsed['path']) ? $parsed['path'] : $value;
        $path = rawurldecode((string) $path);
        $path = '/' . trim($path, '/') . '/';

        return strtolower($path === '//' ? '/' : $path);
    }
}

if (!function_exists('inception_qc_excluded_stock_paths')) {
    /**
     * Routine build pages that are expected to be customized and should not be
     * treated as non-stock QC exceptions. Paths are authoritative; titles may
     * contain placeholders or be renamed during a build.
     */
    function inception_qc_excluded_stock_paths() {
        $paths = array(
            '/us/',
            '/blog/',
            '/office-tour/',
            '/contact-us/',
            '/describe-your-concern/',
            '/review-us/',
            '/new-patient-paperwork/',
            '/special/',
            '/schedule/',
            '/testimonials/',
            '/thank-you-form/',
        );
        $paths = apply_filters('inception_qc_excluded_stock_paths', $paths);
        $normalized = array();
        foreach ((array) $paths as $path) {
            $normalized[inception_qc_normalize_page_path($path)] = true;
        }
        return array_keys($normalized);
    }
}

if (!function_exists('inception_qc_excluded_stock_path_hashes')) {
    function inception_qc_excluded_stock_path_hashes() {
        $hashes = array();
        foreach (inception_qc_excluded_stock_paths() as $path) {
            $hashes[hash('sha256', $path)] = true;
        }
        return $hashes;
    }
}

if (!function_exists('inception_qc_is_template_landing_page')) {
    function inception_qc_is_template_landing_page($post_id, $path) {
        $normalized_path = inception_qc_normalize_page_path($path);
        $is_landing_page = function_exists('inception_techboost_is_template_landing_path')
            ? inception_techboost_is_template_landing_path($post_id, $normalized_path)
            : ((int) $post_id === (int) get_option('page_on_front')
                || (bool) preg_match('#^/city\d+-state\d+/$#', $normalized_path));
        return (bool) apply_filters(
            'inception_qc_is_template_landing_page',
            $is_landing_page,
            (int) $post_id,
            $normalized_path
        );
    }
}

if (!function_exists('inception_qc_numbered_location_reference_hashes')) {
    function inception_qc_numbered_location_reference_hashes() {
        static $hashes = null;
        if (is_array($hashes)) return $hashes;
        $hashes = array();
        // Master multi-location sets currently use city1-state1, city2-state2,
        // and the same numbered convention for additional locations.
        for ($location_number = 1; $location_number <= 50; $location_number++) {
            $path = '/city' . $location_number . '-state' . $location_number . '/';
            $hashes[hash('sha256', $path)] = true;
        }
        return $hashes;
    }
}

if (!function_exists('inception_qc_template_landing_reference_hashes')) {
    function inception_qc_template_landing_reference_hashes() {
        $hashes = inception_qc_numbered_location_reference_hashes();
        $hashes[hash('sha256', '/')] = true;
        return $hashes;
    }
}

if (!function_exists('inception_qc_is_template_landing_reference')) {
    function inception_qc_is_template_landing_reference($reference_page) {
        if (!is_array($reference_page)) return false;
        if (!empty($reference_page['template_landing'])) return true;
        if (isset($reference_page['path'])
            && preg_match('#^/city\d+-state\d+/$#', inception_qc_normalize_page_path($reference_page['path']))) return true;
        $hashes = inception_qc_template_landing_reference_hashes();
        return isset($reference_page['path_hash'])
            && isset($hashes[(string) $reference_page['path_hash']]);
    }
}

if (!function_exists('inception_qc_is_numbered_location_reference')) {
    function inception_qc_is_numbered_location_reference($reference_page) {
        if (!is_array($reference_page)) return false;
        if (isset($reference_page['path'])) {
            $path = inception_qc_normalize_page_path($reference_page['path']);
            if ((bool) preg_match('#^/city\d+-state\d+/$#', $path)) return true;
            if ($path === '/') return false;
        }
        $hashes = inception_qc_numbered_location_reference_hashes();
        if (isset($reference_page['path_hash'])
            && isset($hashes[(string) $reference_page['path_hash']])) return true;
        return !empty($reference_page['template_landing'])
            && (!isset($reference_page['path']) || inception_qc_normalize_page_path($reference_page['path']) !== '/');
    }
}

if (!function_exists('inception_qc_stock_text_containment_similarity')) {
    /**
     * Measure how much of the stock text remains, without penalizing additional
     * template text. The catalog's bottom-32 reference sketch is checked against
     * every normalized 5-word shingle in the current landing page. A smaller
     * 3-word sketch prevents expected launch substitutions from dominating.
     */
    function inception_qc_stock_text_containment_similarity($reference, $current) {
        if (!is_array($reference) || !is_array($current)) return null;
        if (!empty($reference['content_hash']) && !empty($current['content_hash'])
            && hash_equals((string) $reference['content_hash'], (string) $current['content_hash'])) return 1.0;

        $reference_sketch = isset($reference['sketch']) && is_array($reference['sketch'])
            ? array_values(array_unique(array_map('strval', $reference['sketch'])))
            : array();
        $current_hashes = isset($current['shingle_hashes']) && is_array($current['shingle_hashes'])
            ? array_values(array_unique(array_map('strval', $current['shingle_hashes'])))
            : (isset($current['sketch']) && is_array($current['sketch'])
                ? array_values(array_unique(array_map('strval', $current['sketch'])))
                : array());
        if (empty($reference_sketch) && empty($current_hashes)) return 1.0;
        if (empty($reference_sketch) || empty($current_hashes)) return 0.0;

        $primary_score = count(array_intersect($reference_sketch, $current_hashes)) / count($reference_sketch);
        $reference_stable = isset($reference['stable_sketch']) && is_array($reference['stable_sketch'])
            ? array_values(array_unique(array_map('strval', $reference['stable_sketch'])))
            : array();
        $current_stable = isset($current['stable_shingle_hashes']) && is_array($current['stable_shingle_hashes'])
            ? array_values(array_unique(array_map('strval', $current['stable_shingle_hashes'])))
            : (isset($current['stable_sketch']) && is_array($current['stable_sketch'])
                ? array_values(array_unique(array_map('strval', $current['stable_sketch'])))
                : array());
        if (empty($reference_stable) || empty($current_stable)) return $primary_score;
        $stable_score = count(array_intersect($reference_stable, $current_stable)) / count($reference_stable);
        return max($primary_score, $stable_score);
    }
}

if (!function_exists('inception_qc_rendered_page_fingerprint')) {
    function inception_qc_rendered_page_fingerprint($post_id) {
        if (!function_exists('inception_techboost_content_fingerprint_from_text')) return null;
        global $post;
        $previous_post = $post;
        $target_post = get_post($post_id);
        if ($target_post instanceof WP_Post) {
            $post = $target_post;
            setup_postdata($post);
        }
        $raw = (string) get_post_field('post_content', $post_id);
        $rendered = apply_filters('the_content', $raw);
        if ($previous_post instanceof WP_Post) {
            $post = $previous_post;
            setup_postdata($post);
        } else {
            wp_reset_postdata();
        }
        return function_exists('inception_techboost_legacy_catalog_fingerprint_from_rendered')
            ? inception_techboost_legacy_catalog_fingerprint_from_rendered($rendered)
            : inception_techboost_content_fingerprint_from_text($rendered);
    }
}

if (!function_exists('inception_qc_public_page_fingerprints')) {
    /**
     * Load this site's public REST-rendered page content. The legacy catalog was
     * generated from this same representation, while an authenticated admin
     * request can render dynamic builder modules differently.
     */
    function inception_qc_public_page_fingerprints() {
        $result = array('ids' => array(), 'paths' => array(), 'source' => 'internal_fallback');
        if (!function_exists('inception_techboost_content_fingerprint_from_text')) return $result;

        $browser_cache = get_option('inception_techboost_qc_browser_fingerprints', array());
        $current_site_hash = function_exists('inception_techboost_current_host_fingerprint')
            ? inception_techboost_current_host_fingerprint()
            : '';
        if (is_array($browser_cache)
            && isset($browser_cache['version'])
            && (int) $browser_cache['version'] >= 5
            && !empty($browser_cache['site_hash'])
            && $current_site_hash !== ''
            && hash_equals((string) $browser_cache['site_hash'], $current_site_hash)
            && !empty($browser_cache['ids'])
            && !empty($browser_cache['paths'])
            && is_array($browser_cache['ids'])
            && is_array($browser_cache['paths'])) {
            return array(
                'ids' => $browser_cache['ids'],
                'paths' => $browser_cache['paths'],
                'source' => 'browser_rest',
                'captured_at' => isset($browser_cache['captured_at']) ? (int) $browser_cache['captured_at'] : 0,
            );
        }

        for ($page_number = 1; $page_number <= 10; $page_number++) {
            $url = add_query_arg(array(
                'per_page' => 100,
                'page' => $page_number,
                '_fields' => 'id,link,content',
            ), rest_url('wp/v2/pages'));
            $response = wp_remote_get($url, array(
                'timeout' => 15,
                'redirection' => 3,
                'headers' => array('Accept' => 'application/json'),
                'user-agent' => 'WordPress; Inception-TechBoost-QC',
            ));
            if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) break;
            $batch = json_decode((string) wp_remote_retrieve_body($response), true);
            if (!is_array($batch)) break;

            foreach ($batch as $page) {
                if (!is_array($page) || !isset($page['id'])) continue;
                $rendered = isset($page['content']['rendered']) ? (string) $page['content']['rendered'] : '';
                $fingerprint = function_exists('inception_techboost_legacy_catalog_fingerprint_from_rendered')
                    ? inception_techboost_legacy_catalog_fingerprint_from_rendered($rendered)
                    : inception_techboost_content_fingerprint_from_text($rendered);
                $result['ids'][(int) $page['id']] = $fingerprint;
                if (!empty($page['link'])) {
                    $path = inception_qc_normalize_page_path($page['link']);
                    $result['paths'][hash('sha256', $path)] = $fingerprint;
                }
            }

            if (!empty($result['paths'])) $result['source'] = 'server_rest';

            $total_pages = (int) wp_remote_retrieve_header($response, 'x-wp-totalpages');
            if ($total_pages < 1 || $page_number >= $total_pages) break;
        }

        return $result;
    }
}

if (!function_exists('inception_qc_load_legacy_content_catalog')) {
    function inception_qc_load_legacy_content_catalog() {
        if (!defined('INCEPTION_TECHBOOST_DIR')) return array();
        $catalog_file = INCEPTION_TECHBOOST_DIR . 'qc-report/legacy-content-catalog.json';
        if (!is_readable($catalog_file)) return array();
        $catalog = json_decode((string) file_get_contents($catalog_file), true);
        if (!is_array($catalog) || empty($catalog['profiles']) || !is_array($catalog['profiles'])) return array();
        return $catalog;
    }
}

if (!function_exists('inception_qc_public_post_fingerprints')) {
    /** Load public REST-rendered posts so blog matching uses the catalog's representation. */
    function inception_qc_public_post_fingerprints() {
        $result = array('ids' => array(), 'paths' => array(), 'source' => 'internal_fallback');
        if (!function_exists('inception_techboost_content_fingerprint_from_text')) return $result;

        $browser_cache = get_option('inception_techboost_qc_browser_fingerprints', array());
        $current_site_hash = function_exists('inception_techboost_current_host_fingerprint')
            ? inception_techboost_current_host_fingerprint()
            : '';
        if (is_array($browser_cache)
            && isset($browser_cache['version'])
            && (int) $browser_cache['version'] >= 6
            && !empty($browser_cache['site_hash'])
            && $current_site_hash !== ''
            && hash_equals((string) $browser_cache['site_hash'], $current_site_hash)
            && isset($browser_cache['post_ids'], $browser_cache['post_paths'])
            && is_array($browser_cache['post_ids'])
            && is_array($browser_cache['post_paths'])) {
            return array(
                'ids' => $browser_cache['post_ids'],
                'paths' => $browser_cache['post_paths'],
                'source' => 'browser_rest',
            );
        }

        for ($page_number = 1; $page_number <= 10; $page_number++) {
            $url = add_query_arg(array(
                'per_page' => 100,
                'page' => $page_number,
                '_fields' => 'id,link,content',
            ), rest_url('wp/v2/posts'));
            $response = wp_remote_get($url, array(
                'timeout' => 15,
                'redirection' => 3,
                'headers' => array('Accept' => 'application/json'),
                'user-agent' => 'WordPress; Inception-TechBoost-QC',
            ));
            if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) break;
            $batch = json_decode((string) wp_remote_retrieve_body($response), true);
            if (!is_array($batch)) break;

            foreach ($batch as $blog_post) {
                if (!is_array($blog_post) || !isset($blog_post['id'])) continue;
                $rendered = isset($blog_post['content']['rendered']) ? (string) $blog_post['content']['rendered'] : '';
                $fingerprint = function_exists('inception_techboost_legacy_catalog_fingerprint_from_rendered')
                    ? inception_techboost_legacy_catalog_fingerprint_from_rendered($rendered)
                    : inception_techboost_content_fingerprint_from_text($rendered);
                $result['ids'][(int) $blog_post['id']] = $fingerprint;
                if (!empty($blog_post['link'])) {
                    $path = inception_qc_normalize_page_path($blog_post['link']);
                    $result['paths'][hash('sha256', $path)] = $fingerprint;
                }
            }
            if (!empty($result['paths'])) $result['source'] = 'server_rest';
            $total_pages = (int) wp_remote_retrieve_header($response, 'x-wp-totalpages');
            if ($total_pages < 1 || $page_number >= $total_pages) break;
        }
        return $result;
    }
}

if (!function_exists('inception_qc_resolve_legacy_snapshot')) {
    function inception_qc_resolve_legacy_snapshot($local_page_rows, $public_fingerprints = array()) {
        $catalog = inception_qc_load_legacy_content_catalog();
        if (empty($catalog['profiles']) || !function_exists('inception_techboost_content_similarity')) return new WP_Error('legacy_catalog_unavailable', 'The URL-free legacy content catalog is not installed yet.');

        $local_by_id = array();
        $local_by_path_hash = array();
        $excluded_path_hashes = inception_qc_excluded_stock_path_hashes();
        foreach ($local_page_rows as $row) {
            $post = $row['post'];
            $path_hash = hash('sha256', (string) $row['path']);
            $content = isset($public_fingerprints['paths'][$path_hash])
                ? $public_fingerprints['paths'][$path_hash]
                : (isset($public_fingerprints['ids'][(int) $post->ID])
                    ? $public_fingerprints['ids'][(int) $post->ID]
                    : inception_qc_rendered_page_fingerprint($post->ID));
            $entry = array(
                'id' => (int) $post->ID,
                'path_hash' => $path_hash,
                'content' => $content,
                'template_landing' => inception_qc_is_template_landing_page($post->ID, $row['path']),
            );
            $local_by_id[$entry['id']] = $entry;
            $local_by_path_hash[$entry['path_hash']] = $entry;
        }

        $ranked = array();
        foreach ($catalog['profiles'] as $profile) {
            if (empty($profile['id']) || empty($profile['pages']) || !is_array($profile['pages'])) continue;
            $matched = 0;
            $reference_count = 0;
            $similarities = array();
            foreach ($profile['pages'] as $reference_page) {
                if (!is_array($reference_page)) continue;
                if (isset($reference_page['path_hash'])
                    && isset($excluded_path_hashes[(string) $reference_page['path_hash']])) continue;
                $reference_count++;
                // Legacy profiles come from unrelated WordPress installs, so their
                // numeric post IDs are not globally unique. A path hash is the
                // authoritative page identity. The one narrow exception is a
                // recognized template landing page whose placeholder slug changes
                // after launch; cloned WordPress IDs identify that page safely once
                // the candidate profile is being scored.
                $reference_is_template_landing = inception_qc_is_template_landing_reference($reference_page);
                $local = isset($reference_page['path_hash'], $local_by_path_hash[(string) $reference_page['path_hash']])
                    ? $local_by_path_hash[(string) $reference_page['path_hash']]
                    : (($reference_is_template_landing || !isset($reference_page['path_hash']))
                        && isset($reference_page['id'], $local_by_id[(int) $reference_page['id']])
                        ? $local_by_id[(int) $reference_page['id']]
                        : null);
                if ($local === null) continue;
                $matched++;
                if (!empty($reference_page['content']) && !empty($local['content'])) {
                    $similarity = (!empty($local['template_landing']) || $reference_is_template_landing)
                        ? inception_qc_stock_text_containment_similarity($reference_page['content'], $local['content'])
                        : inception_techboost_content_similarity($reference_page['content'], $local['content']);
                    if ($similarity !== null) $similarities[] = $similarity;
                }
            }

            $coverage = $reference_count > 0 ? $matched / $reference_count : 0;
            $content_similarity = !empty($similarities) ? array_sum($similarities) / count($similarities) : 0;
            $ranked[] = array(
                'profile' => $profile,
                'coverage' => $coverage,
                'content_similarity' => $content_similarity,
                'score' => ($coverage * 0.45) + ($content_similarity * 0.55),
            );
        }

        if (empty($ranked)) return new WP_Error('legacy_match_unavailable', 'No usable content profiles were found in the legacy catalog.');
        usort($ranked, function($a, $b) { return $a['score'] === $b['score'] ? 0 : ($a['score'] > $b['score'] ? -1 : 1); });
        $best = $ranked[0];
        $runner_up_score = isset($ranked[1]) ? (float) $ranked[1]['score'] : 0;
        $margin = (float) $best['score'] - $runner_up_score;
        $strong_content_match = $best['coverage'] >= 0.90
            && $best['score'] >= 0.96
            && $best['content_similarity'] >= 0.90
            && $margin >= 0.015;
        $confident = $best['coverage'] >= 0.75
            && $best['content_similarity'] >= 0.70
            && ($margin >= 0.03 || $strong_content_match || count($ranked) === 1);
        $match_confidence = $confident
            ? 'high'
            : (($best['coverage'] >= 0.50 && $best['content_similarity'] >= 0.50) ? 'moderate' : 'low');

        $snapshot = array(
            'version' => 2,
            'origin' => 'legacy_catalog',
            'content_mode' => 'rendered',
            'source_host_hash' => hash('sha256', 'legacy-profile|' . (string) $best['profile']['id']),
            'captured_at_gmt' => isset($catalog['generated_at_gmt']) ? (string) $catalog['generated_at_gmt'] : current_time('mysql', true),
            'page_count' => count($best['profile']['pages']),
            'pages' => $best['profile']['pages'],
            'legacy_profile_id' => (string) $best['profile']['id'],
            'detected_master_candidates' => function_exists('inception_techboost_master_metadata_for_profile')
                ? inception_techboost_master_metadata_for_profile((string) $best['profile']['id'])
                : array(),
            'legacy_match_score' => (float) $best['score'],
            'legacy_content_similarity' => (float) $best['content_similarity'],
            'legacy_match_coverage' => (float) $best['coverage'],
            'legacy_match_margin' => $margin,
            'legacy_match_confidence' => $match_confidence,
            'legacy_resolver_version' => 11,
            'legacy_content_source' => !empty($public_fingerprints['source']) ? (string) $public_fingerprints['source'] : 'internal_fallback',
        );
        update_option('inception_techboost_stock_snapshot', $snapshot, false);
        return $snapshot;
    }
}

if (!function_exists('inception_qc_resolve_blog_profile')) {
    /** Independently identify the stock blog set, which may come from a different master. */
    function inception_qc_resolve_blog_profile($local_post_rows, $public_fingerprints = array()) {
        if (empty($local_post_rows)) return array('status' => 'no_posts', 'candidates' => array());
        $catalog = inception_qc_load_legacy_content_catalog();
        if (empty($catalog['blog_profiles']) || !is_array($catalog['blog_profiles'])) {
            return array('status' => 'unavailable', 'candidates' => array());
        }

        $local_items = array();
        $local_by_path_hash = array();
        foreach ($local_post_rows as $row) {
            $path_hash = hash('sha256', (string) $row['path']);
            $content = isset($public_fingerprints['paths'][$path_hash])
                ? $public_fingerprints['paths'][$path_hash]
                : (isset($public_fingerprints['ids'][(int) $row['post']->ID])
                    ? $public_fingerprints['ids'][(int) $row['post']->ID]
                    : inception_qc_rendered_page_fingerprint($row['post']->ID));
            $local_items[] = array(
                'id' => (int) $row['post']->ID,
                'path_hash' => $path_hash,
                'content' => $content,
            );
            $local_by_path_hash[$path_hash] = count($local_items) - 1;
        }

        $ranked = array();
        foreach ($catalog['blog_profiles'] as $profile) {
            if (empty($profile['id']) || empty($profile['posts']) || !is_array($profile['posts'])) continue;
            $used_local = array();
            $similarities = array();
            $matched = 0;
            foreach ($profile['posts'] as $reference_post) {
                if (!is_array($reference_post) || empty($reference_post['content'])) continue;
                $local_index = isset($reference_post['path_hash'], $local_by_path_hash[(string) $reference_post['path_hash']])
                    ? (int) $local_by_path_hash[(string) $reference_post['path_hash']]
                    : null;
                $similarity = null;
                if ($local_index !== null && !isset($used_local[$local_index])) {
                    $similarity = inception_techboost_content_similarity($reference_post['content'], $local_items[$local_index]['content']);
                } else {
                    $best_local_index = null;
                    $best_local_similarity = -1;
                    foreach ($local_items as $candidate_index => $local_item) {
                        if (isset($used_local[$candidate_index])) continue;
                        $candidate_similarity = inception_techboost_content_similarity($reference_post['content'], $local_item['content']);
                        if ($candidate_similarity !== null && $candidate_similarity > $best_local_similarity) {
                            $best_local_similarity = $candidate_similarity;
                            $best_local_index = $candidate_index;
                        }
                    }
                    if ($best_local_index !== null && $best_local_similarity >= 0.70) {
                        $local_index = $best_local_index;
                        $similarity = $best_local_similarity;
                    }
                }
                if ($local_index === null || $similarity === null || $similarity < 0.70) continue;
                $used_local[$local_index] = true;
                $matched++;
                $similarities[] = $similarity;
            }

            $reference_count = count($profile['posts']);
            $coverage = $reference_count > 0 ? $matched / $reference_count : 0;
            $content_similarity = !empty($similarities) ? array_sum($similarities) / count($similarities) : 0;
            $ranked[] = array(
                'id' => (string) $profile['id'],
                'matched' => $matched,
                'coverage' => $coverage,
                'content_similarity' => $content_similarity,
                'score' => ($coverage * 0.45) + ($content_similarity * 0.55),
            );
        }

        if (empty($ranked)) return array('status' => 'unavailable', 'candidates' => array());
        usort($ranked, function($left, $right) { return $left['score'] === $right['score'] ? 0 : ($left['score'] > $right['score'] ? -1 : 1); });
        $best = $ranked[0];
        $runner_up = isset($ranked[1]) ? (float) $ranked[1]['score'] : 0;
        if ($best['matched'] < 1 || $best['coverage'] < 0.20 || $best['content_similarity'] < 0.70) {
            return array('status' => 'unknown', 'candidates' => array(), 'score' => (float) $best['score']);
        }
        return array(
            'status' => 'matched',
            'profile_id' => $best['id'],
            'score' => (float) $best['score'],
            'content_similarity' => (float) $best['content_similarity'],
            'coverage' => (float) $best['coverage'],
            'margin' => (float) $best['score'] - $runner_up,
            'candidates' => function_exists('inception_techboost_master_metadata_for_blog_profile')
                ? inception_techboost_master_metadata_for_blog_profile($best['id'])
                : array(),
        );
    }
}

/**
 * AJAX: return HTTP status code for a URL (used to async-check iframe/form URLs without blocking initial page render).
 * NOTE: This does not cache; every request checks live.
 */
if (!function_exists('inception_qc_http_status')) {
    function inception_qc_http_status($url) {
        // Keep the URL as-close-to-original as possible (some embeds include characters like '*')
        // while still enforcing http/https only.
        $url = trim((string)$url);
        if (empty($url)) { return 0; }

        $parts = wp_parse_url($url);
        if (empty($parts['scheme']) || !in_array(strtolower($parts['scheme']), array('http','https'), true)) {
            return 0;
        }

        $host = isset($parts['host']) ? strtolower($parts['host']) : '';
        $path = isset($parts['path']) ? $parts['path'] : '';

        $request_args = array(
            'timeout' => 10,
            'redirection' => 3,
            'reject_unsafe_urls' => false,
            'headers' => array(
                'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                'User-Agent' => 'WordPress; Inception-QC',
            ),
        );

        // HighLevel widget URLs can behave differently for HEAD vs GET, so use GET for them.
        $is_hl_widget = (strpos($host, 'ap.inceptionchiro.com') !== false && strpos($path, '/widget/form/') !== false);

        if ($is_hl_widget) {
            $response = wp_remote_get($url, $request_args);
        } else {
            // Prefer HEAD; fall back to a lightweight GET if needed.
            $response = wp_remote_head($url, $request_args);
            if (is_wp_error($response) || !isset($response['response']['code'])) {
                $response = wp_remote_get($url, $request_args);
            }
        }

        if (is_wp_error($response) || !isset($response['response']['code'])) {
            return 0;
        }

        $code = (int) $response['response']['code'];

        // HighLevel embeds can sometimes return HTTP 200 with an on-page "not found" message (soft-404).
        // For known HighLevel widget URLs, scan the body for common 404 markers.
        if ($is_hl_widget && $code === 200) {
            $body = wp_remote_retrieve_body($response);

            if (!empty($body)) {
                $hay = strtolower(wp_strip_all_tags($body));

                // Common phrases seen on missing/disabled HighLevel forms (heuristic, no caching).
                $soft_404_markers = array(
                    '404', 'not found', 'page not found', 'form not found',
                    'this form is no longer available', 'this form is unavailable',
                    'we could not find', 'could not find the form', 'doesn\'t exist', 'does not exist',
                    'link expired', 'something went wrong',
                );

                foreach ($soft_404_markers as $marker) {
                    if (strpos($hay, $marker) !== false) {
                        return 404;
                    }
                }

                if (preg_match('/<title[^>]*>(.*?)<\/title>/is', $body, $tm)) {
                    $title = strtolower(trim(wp_strip_all_tags($tm[1])));
                    if (strpos($title, '404') !== false || strpos($title, 'not found') !== false) {
                        return 404;
                    }
                }
            }
        }

        return $code;
    }
}

if (!function_exists('inception_qc_extract_iframe_src')) {
    function inception_qc_extract_iframe_src($iframe_html) {
        if (preg_match('/src=[\"\']([^"\']+)[\"\']/i', (string)$iframe_html, $m)) {
            $src = $m[1];
            if (strpos($src, '/') === 0) { $src = home_url($src); }
            return $src;
        }
        return '';
    }
}

if (!function_exists('inception_qc_ajax_check_url_status')) {
    add_action('wp_ajax_inception_qc_check_url_status', 'inception_qc_ajax_check_url_status');
    function inception_qc_ajax_check_url_status() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'forbidden'), 403);
        }
        check_ajax_referer('inception_qc_report', 'nonce');

        $url = isset($_POST['url']) ? wp_unslash($_POST['url']) : '';
        $url = trim((string)$url);
        $code = inception_qc_http_status($url);

        wp_send_json_success(array('code' => $code));
    }
}

if (!function_exists('inception_qc_ajax_get_oembed_title')) {
    add_action('wp_ajax_inception_qc_get_oembed_title', 'inception_qc_ajax_get_oembed_title');
    function inception_qc_ajax_get_oembed_title() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'forbidden'), 403);
        }
        check_ajax_referer('inception_qc_report', 'nonce');

        $source = isset($_POST['source']) ? sanitize_text_field(wp_unslash($_POST['source'])) : '';
        $url    = isset($_POST['url']) ? sanitize_text_field(wp_unslash($_POST['url'])) : '';

        if (empty($source) || empty($url)) {
            wp_send_json_success(array('title' => '(unknown title)'));
        }

        // Use the same oEmbed endpoints as the synchronous version.
        if ('YouTube' === $source) {
            $endpoint = add_query_arg(array('url' => rawurlencode($url), 'format' => 'json'), 'https://www.youtube.com/oembed');
        } elseif ('Vimeo' === $source) {
            $endpoint = add_query_arg(array('url' => rawurlencode($url), 'format' => 'json'), 'https://vimeo.com/api/oembed.json');
        } else {
            wp_send_json_success(array('title' => '(unknown title)'));
        }

        $response = wp_remote_get($endpoint, array('timeout' => 5, 'redirection' => 3));
        if (is_wp_error($response) || 200 !== (int) wp_remote_retrieve_response_code($response)) {
            wp_send_json_success(array('title' => '(unknown title)'));
        }

        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);
        $title = isset($data['title']) ? esc_html($data['title']) : '(unknown title)';

        wp_send_json_success(array('title' => $title));
    }
}

// ---------- External images (not Media Library, not inceptionimages.com) ----------
$qc_external_images = array();
/**
 * Determine if an image URL should be considered "site media library" or allowed.
 * Allowed if:
 *  - URL contains inceptionimages.com
 *  - URL points to this site's uploads (wp-content/uploads) on the same host (or relative)
 */
if ( ! function_exists('inception_qc_is_allowed_image_src') ) {
    function inception_qc_is_allowed_image_src( $src ) {
        $src = trim((string)$src);
        if ( $src === '' ) return true;
        // Ignore data/blob URIs
        if ( preg_match('#^(data:|blob:)#i', $src) ) return true;

        if ( stripos($src, 'inceptionimages.com') !== false ) return true;

        // Make protocol-relative absolute
        if ( strpos($src, '//') === 0 ) {
            $src = (is_ssl() ? 'https:' : 'http:') . $src;
        }

        // If relative URL, treat as site URL
        if ( strpos($src, 'http://') !== 0 && strpos($src, 'https://') !== 0 ) {
            // allow relative media library paths
            if ( strpos($src, '/wp-content/uploads/') !== false || preg_match('#(^|/)(uploads)/#i', $src) ) {
                return true;
            }
            $src = home_url( $src );
        }

        $u = wp_parse_url( $src );
        if ( empty($u) ) return false;

        $site = wp_parse_url( home_url('/') );
        $host_ok = !empty($u['host']) && !empty($site['host']) && strtolower($u['host']) === strtolower($site['host']);
        $path = isset($u['path']) ? (string)$u['path'] : '';

        if ( $host_ok && strpos($path, '/wp-content/uploads/') !== false ) return true;

        return false;
    }
}

$qc_posts_for_external_images = get_posts(array(
    'post_type'      => array('page','post'),
    'posts_per_page' => -1,
    'post_status'    => array('publish','draft','private','future')
));

foreach ( $qc_posts_for_external_images as $qp ) {
    $content = apply_filters('the_content', $qp->post_content ?? '');
    if ( empty($content) ) continue;

    // Find all img src attributes (keep it light; DOMDocument is brittle with builder markup)
    if ( preg_match_all('#<img[^>]+src=["\']([^"\']+)["\']#i', $content, $mm) ) {
        $srcs = $mm[1];
        foreach ( $srcs as $src ) {
            $src = trim(html_entity_decode($src));
            if ( $src === '' ) continue;

            if ( ! inception_qc_is_allowed_image_src( $src ) ) {
                $perma = get_permalink($qp->ID);
                $edit  = get_edit_post_link($qp->ID, '');
                $bb    = $perma ? $perma . (strpos($perma, '?') === false ? '?fl_builder' : '&fl_builder') : '';

                $qc_external_images[] = array(
                    'id'        => (int) $qp->ID,
                    'title'     => get_the_title($qp->ID),
                    'type'      => get_post_type($qp->ID),
                    'permalink' => $perma,
                    'edit'      => $edit,
                    'bb'        => $bb,
                    'src'       => $src,
                );
            }
        }
    }
}


// ---------- Placeholder scan: capitalized CITY / STATE ----------
$qc_city_state_instances = array();
$qc_city_state_posts = get_posts(array(
    'post_type'      => array('page','post','fl-theme-layout'),
    'posts_per_page' => -1,
    'post_status'    => array('publish','draft','private','pending','future'),
    'orderby'        => 'title',
    'order'          => 'ASC',
));

foreach ($qc_city_state_posts as $p) {
    $where = array();
    $title = (string) get_the_title($p->ID);

    if (strpos($title, 'CITY') !== false || strpos($title, 'STATE') !== false) {
        $where[] = 'Title';
    }

    $content = (string) ($p->post_content ?? '');
    if ($content !== '' && (strpos($content, 'CITY') !== false || strpos($content, 'STATE') !== false)) {
        $where[] = 'Main Content';
    }

    $yoast_title = (string) get_post_meta($p->ID, '_yoast_wpseo_title', true);
    if ($yoast_title !== '' && (strpos($yoast_title, 'CITY') !== false || strpos($yoast_title, 'STATE') !== false)) {
        $where[] = 'Yoast SEO Title';
    }

    $yoast_desc = (string) get_post_meta($p->ID, '_yoast_wpseo_metadesc', true);
    if ($yoast_desc !== '' && (strpos($yoast_desc, 'CITY') !== false || strpos($yoast_desc, 'STATE') !== false)) {
        $where[] = 'Yoast SEO Description';
    }

    if (!empty($where)) {
        $post_type = get_post_type($p->ID);
        $type_label = ($post_type === 'page') ? 'Page' : (($post_type === 'post') ? 'Blog Post' : (($post_type === 'fl-theme-layout') ? 'Themer Layout' : $post_type));

        $perma = get_permalink($p->ID);
        if (!empty($perma) && isset($p->post_status) && $p->post_status !== 'publish') {
            $preview = get_preview_post_link($p->ID);
            if (!empty($preview)) {
                $perma = $preview;
            }
        }

        $bb = $perma ? $perma . (strpos($perma, '?') === false ? '?fl_builder' : '&fl_builder') : '';
        $edit = get_edit_post_link($p->ID, '');

        $qc_city_state_instances[] = array(
            'id'        => (int) $p->ID,
            'title'     => $title,
            'type'      => $type_label,
            'where'     => implode(', ', $where),
            'permalink' => $perma,
            'bb'        => $bb,
            'edit'      => $edit,
        );
    }
}

// Also scan Media Library (attachments): Title, Description, Alt Text
$qc_city_state_media = get_posts(array(
    'post_type'      => array('attachment'),
    'posts_per_page' => -1,
    'post_status'    => array('inherit','private','publish'),
    'orderby'        => 'title',
    'order'          => 'ASC',
));

foreach ($qc_city_state_media as $m) {
    $where = array();

    $mtitle = (string) ($m->post_title ?? '');
    if ($mtitle !== '' && (strpos($mtitle, 'CITY') !== false || strpos($mtitle, 'STATE') !== false)) {
        $where[] = 'Title';
    }

    // WordPress stores attachment "Description" in post_content.
    $mdesc = (string) ($m->post_content ?? '');
    if ($mdesc !== '' && (strpos($mdesc, 'CITY') !== false || strpos($mdesc, 'STATE') !== false)) {
        $where[] = 'Description';
    }

    $malt = (string) get_post_meta($m->ID, '_wp_attachment_image_alt', true);
    if ($malt !== '' && (strpos($malt, 'CITY') !== false || strpos($malt, 'STATE') !== false)) {
        $where[] = 'Alt Text';
    }

    if (!empty($where)) {
        $perma = get_attachment_link($m->ID);
        $edit  = get_edit_post_link($m->ID, '');

        $qc_city_state_instances[] = array(
            'id'        => (int) $m->ID,
            'title'     => $mtitle,
            'type'      => 'Media',
            'where'     => implode(', ', $where),
            'permalink' => $perma,
            'bb'        => '',
            'edit'      => $edit,
        );
    }
}

$qc_city_state_count = count($qc_city_state_instances);

// Drafted blog posts (Posts post_type in WordPress).
$qc_drafted_blogs_count = 0;
$qc_posts_count_obj = wp_count_posts('post');
if (is_object($qc_posts_count_obj) && isset($qc_posts_count_obj->draft)) {
    $qc_drafted_blogs_count = (int) $qc_posts_count_obj->draft;
}

// Badge border color (matches existing CSS: good/warn/bad)
$qc_drafted_blogs_badge_class = 'qc-badge--good';
if ($qc_drafted_blogs_count > 0 && $qc_drafted_blogs_count <= 3) {
    $qc_drafted_blogs_badge_class = 'qc-badge--warn';
} elseif ($qc_drafted_blogs_count > 3) {
    $qc_drafted_blogs_badge_class = 'qc-badge--bad';
}


if (!empty($qc_city_state_instances)) {
    usort($qc_city_state_instances, function($a, $b) {
        $t = strcmp((string)$a['type'], (string)$b['type']);
        if ($t !== 0) return $t;
        return strcasecmp((string)$a['title'], (string)$b['title']);
    });
}

// Opening the report on a master performs an immediate reconciliation in
// addition to the save hooks and hourly background check.
if (function_exists('inception_techboost_is_stock_master')
    && inception_techboost_is_stock_master()
    && function_exists('inception_techboost_capture_stock_snapshot')) {
    inception_techboost_capture_stock_snapshot();
}
$qc_stock_snapshot = get_option('inception_techboost_stock_snapshot', array());
$qc_snapshot_is_valid = is_array($qc_stock_snapshot)
    && isset($qc_stock_snapshot['version'], $qc_stock_snapshot['source_host_hash'], $qc_stock_snapshot['pages'])
    && is_array($qc_stock_snapshot['pages']);
// Re-resolve baselines produced by the original ID-first legacy matcher. IDs
// collide across independent master sites and could select the wrong profile.
if ($qc_snapshot_is_valid
    && isset($qc_stock_snapshot['origin'])
    && $qc_stock_snapshot['origin'] === 'legacy_catalog'
    && (!isset($qc_stock_snapshot['legacy_resolver_version']) || (int) $qc_stock_snapshot['legacy_resolver_version'] < 11)) {
    $qc_snapshot_is_valid = false;
}
if ($qc_snapshot_is_valid
    && isset($qc_stock_snapshot['origin'])
    && $qc_stock_snapshot['origin'] === 'master_clone'
    && (!isset($qc_stock_snapshot['version']) || (int) $qc_stock_snapshot['version'] < 5)) {
    $qc_snapshot_is_valid = false;
}
$qc_master_comparison = $qc_snapshot_is_valid ? array('paths' => array(), 'ids' => array(), 'pages' => array()) : new WP_Error(
    'stock_snapshot_missing',
    'No stock-page snapshot is available. Existing builds created before automatic snapshots were deployed need to be rebuilt from a prepared master or given a one-time imported snapshot.'
);
$qc_current_host = function_exists('inception_techboost_current_host')
    ? inception_techboost_current_host()
    : preg_replace('/^www\./', '', strtolower((string) wp_parse_url(home_url('/'), PHP_URL_HOST)));
$qc_current_host_fingerprint = function_exists('inception_techboost_current_host_fingerprint')
    ? inception_techboost_current_host_fingerprint()
    : hash('sha256', $qc_current_host);
$qc_is_snapshot_source = $qc_snapshot_is_valid
    && hash_equals((string) $qc_stock_snapshot['source_host_hash'], $qc_current_host_fingerprint);
$qc_non_stock_pages = array();
$qc_local_page_rows = array();
$qc_published_pages = get_posts(array(
    'post_type' => 'page',
    'post_status' => 'publish',
    'posts_per_page' => -1,
    'orderby' => 'title',
    'order' => 'ASC',
));
$qc_front_page_id = (int) get_option('page_on_front');

foreach ($qc_published_pages as $qc_page) {
    $page_path = ((int) $qc_page->ID === $qc_front_page_id)
        ? '/'
        : inception_qc_normalize_page_path(get_page_uri($qc_page->ID));
    $qc_local_page_rows[] = array('post' => $qc_page, 'path' => $page_path);
}

$qc_public_page_fingerprints = array('ids' => array(), 'paths' => array(), 'source' => 'internal_fallback');
if (!$qc_snapshot_is_valid
    || (isset($qc_stock_snapshot['origin']) && $qc_stock_snapshot['origin'] === 'legacy_catalog')) {
    $qc_public_page_fingerprints = inception_qc_public_page_fingerprints();
}

if (!$qc_snapshot_is_valid) {
    $qc_legacy_snapshot = inception_qc_resolve_legacy_snapshot($qc_local_page_rows, $qc_public_page_fingerprints);
    if (is_wp_error($qc_legacy_snapshot)) {
        $qc_master_comparison = $qc_legacy_snapshot;
    } else {
        $qc_stock_snapshot = $qc_legacy_snapshot;
        $qc_snapshot_is_valid = true;
        $qc_master_comparison = array('paths' => array(), 'ids' => array(), 'pages' => array());
    }
}

if ($qc_snapshot_is_valid) {
    foreach ($qc_stock_snapshot['pages'] as $qc_stock_page) {
        if (!is_array($qc_stock_page)) continue;
        if (isset($qc_stock_page['id'])) $qc_master_comparison['ids'][] = (int) $qc_stock_page['id'];
        if (isset($qc_stock_page['path'])) $qc_master_comparison['paths'][] = inception_qc_normalize_page_path($qc_stock_page['path']);
        $qc_master_comparison['pages'][] = $qc_stock_page;
    }
}

if (!is_wp_error($qc_master_comparison)) {
    $qc_master_path_lookup = array();
    $qc_master_path_hash_lookup = array();
    $qc_master_id_lookup = array();
    foreach ($qc_master_comparison['pages'] as $qc_reference_page) {
        if (isset($qc_reference_page['id'])) $qc_master_id_lookup[(int) $qc_reference_page['id']] = $qc_reference_page;
        if (isset($qc_reference_page['path'])) $qc_master_path_lookup[inception_qc_normalize_page_path($qc_reference_page['path'])] = $qc_reference_page;
        if (isset($qc_reference_page['path_hash'])) $qc_master_path_hash_lookup[(string) $qc_reference_page['path_hash']] = $qc_reference_page;
    }
    $qc_review_similarity = (float) apply_filters('inception_qc_content_review_similarity', 0.85);
    $qc_landing_review_similarity = (float) apply_filters('inception_qc_landing_content_review_similarity', 0.65);
    $qc_likely_non_stock_similarity = (float) apply_filters('inception_qc_content_non_stock_similarity', 0.60);
    $qc_excluded_stock_path_lookup = array_fill_keys(inception_qc_excluded_stock_paths(), true);
    $qc_numbered_location_references = array_values(array_filter(
        $qc_master_comparison['pages'],
        'inception_qc_is_numbered_location_reference'
    ));
    $qc_known_location_page_ids = array_values(array_unique(array_map(
        'intval',
        (array) get_option('inception_techboost_qc_location_page_ids', array())
    )));
    $qc_known_location_page_lookup = array_fill_keys($qc_known_location_page_ids, true);
    $qc_location_page_ids_changed = false;
    $qc_current_content_for_page = function($page, $path_hash) use (&$qc_stock_snapshot, &$qc_public_page_fingerprints) {
        if (isset($qc_stock_snapshot['origin']) && $qc_stock_snapshot['origin'] === 'legacy_catalog') {
            return isset($qc_public_page_fingerprints['paths'][$path_hash])
                ? $qc_public_page_fingerprints['paths'][$path_hash]
                : (isset($qc_public_page_fingerprints['ids'][(int) $page->ID])
                    ? $qc_public_page_fingerprints['ids'][(int) $page->ID]
                    : inception_qc_rendered_page_fingerprint($page->ID));
        }
        return isset($qc_stock_snapshot['content_mode']) && $qc_stock_snapshot['content_mode'] === 'rendered'
            ? inception_qc_rendered_page_fingerprint($page->ID)
            : inception_techboost_page_content_fingerprint($page->ID);
    };

    foreach ($qc_local_page_rows as $qc_local_page_row) {
        $qc_page = $qc_local_page_row['post'];
        $page_path = $qc_local_page_row['path'];
        if (isset($qc_excluded_stock_path_lookup[$page_path])) continue;
        $page_path_hash = hash('sha256', $page_path);
        $qc_current_content = null;
        if (isset($qc_stock_snapshot['origin']) && $qc_stock_snapshot['origin'] === 'legacy_catalog') {
            // Paths remain authoritative. A retained ID is used only for a
            // recognized location landing page whose placeholder slug changed.
            $qc_reference_page = isset($qc_master_path_hash_lookup[$page_path_hash])
                ? $qc_master_path_hash_lookup[$page_path_hash]
                : null;
            if ($qc_reference_page === null && isset($qc_master_id_lookup[(int) $qc_page->ID])) {
                $qc_id_candidate = $qc_master_id_lookup[(int) $qc_page->ID];
                if (inception_qc_is_template_landing_reference($qc_id_candidate)) {
                    $qc_reference_page = $qc_id_candidate;
                }
            }
        } else {
            $qc_reference_page = isset($qc_master_path_lookup[$page_path])
                ? $qc_master_path_lookup[$page_path]
                : (isset($qc_master_path_hash_lookup[$page_path_hash])
                    ? $qc_master_path_hash_lookup[$page_path_hash]
                    : (isset($qc_master_id_lookup[(int) $qc_page->ID]) ? $qc_master_id_lookup[(int) $qc_page->ID] : null));
        }

        if ($qc_reference_page === null && !empty($qc_numbered_location_references)) {
            $qc_current_content = $qc_current_content_for_page($qc_page, $page_path_hash);
            $qc_best_location_reference = null;
            $qc_best_location_similarity = -1;
            foreach ($qc_numbered_location_references as $qc_location_reference) {
                if (empty($qc_location_reference['content'])) continue;
                $qc_location_similarity = inception_qc_stock_text_containment_similarity(
                    $qc_location_reference['content'],
                    $qc_current_content
                );
                if ($qc_location_similarity !== null && $qc_location_similarity > $qc_best_location_similarity) {
                    $qc_best_location_similarity = $qc_location_similarity;
                    $qc_best_location_reference = $qc_location_reference;
                }
            }

            $qc_is_known_or_placeholder_location = inception_qc_is_template_landing_page($qc_page->ID, $page_path)
                || isset($qc_known_location_page_lookup[(int) $qc_page->ID]);
            $qc_location_discovery_similarity = (float) apply_filters(
                'inception_qc_location_discovery_similarity',
                $qc_landing_review_similarity,
                $qc_page->ID,
                $page_path
            );
            if ($qc_best_location_reference !== null
                && ($qc_is_known_or_placeholder_location || $qc_best_location_similarity >= $qc_location_discovery_similarity)) {
                $qc_reference_page = $qc_best_location_reference;
                if ($qc_best_location_similarity >= $qc_location_discovery_similarity
                    && !isset($qc_known_location_page_lookup[(int) $qc_page->ID])) {
                    $qc_known_location_page_lookup[(int) $qc_page->ID] = true;
                    $qc_location_page_ids_changed = true;
                }
            }
        }

        if ($qc_reference_page === null) {
            $qc_non_stock_pages[] = array(
                'id' => (int) $qc_page->ID,
                'title' => get_the_title($qc_page->ID),
                'path' => $page_path,
                'permalink' => get_permalink($qc_page->ID),
                'edit' => get_edit_post_link($qc_page->ID, ''),
                'result' => 'New Page',
                'similarity' => null,
            );
            continue;
        }

        if (!empty($qc_reference_page['content'])
            && function_exists('inception_techboost_page_content_fingerprint')
            && function_exists('inception_techboost_content_similarity')) {
            if ($qc_current_content === null) {
                $qc_current_content = $qc_current_content_for_page($qc_page, $page_path_hash);
            }
            $qc_is_landing_comparison = inception_qc_is_template_landing_page($qc_page->ID, $page_path)
                || inception_qc_is_template_landing_reference($qc_reference_page);
            $qc_similarity = $qc_is_landing_comparison
                ? inception_qc_stock_text_containment_similarity($qc_reference_page['content'], $qc_current_content)
                : inception_techboost_content_similarity($qc_reference_page['content'], $qc_current_content);
            $qc_page_review_similarity = $qc_is_landing_comparison
                ? $qc_landing_review_similarity
                : $qc_review_similarity;
            if ($qc_similarity !== null && $qc_similarity < $qc_page_review_similarity) {
                $qc_non_stock_pages[] = array(
                    'id' => (int) $qc_page->ID,
                    'title' => get_the_title($qc_page->ID),
                    'path' => $page_path,
                    'permalink' => get_permalink($qc_page->ID),
                    'edit' => get_edit_post_link($qc_page->ID, ''),
                    'result' => $qc_similarity < $qc_likely_non_stock_similarity ? 'Likely Non-Stock Content' : 'Modified — Review',
                    'similarity' => $qc_similarity,
                );
            }
        }
    }
    if ($qc_location_page_ids_changed) {
        update_option(
            'inception_techboost_qc_location_page_ids',
            array_values(array_map('intval', array_keys($qc_known_location_page_lookup))),
            false
        );
    }
}

$qc_local_blog_rows = array();
$qc_published_blog_posts = get_posts(array(
    'post_type' => 'post',
    'post_status' => 'publish',
    'posts_per_page' => -1,
    'orderby' => 'ID',
    'order' => 'ASC',
));
foreach ($qc_published_blog_posts as $qc_blog_post) {
    $qc_local_blog_rows[] = array(
        'post' => $qc_blog_post,
        'path' => inception_qc_normalize_page_path(get_permalink($qc_blog_post->ID)),
    );
}
$qc_public_blog_fingerprints = inception_qc_public_post_fingerprints();
$qc_blog_detection = inception_qc_resolve_blog_profile($qc_local_blog_rows, $qc_public_blog_fingerprints);
$qc_non_stock_blogs = array();
$qc_blog_comparison_available = ($qc_blog_detection['status'] ?? '') === 'matched';
$qc_blog_review_similarity = (float) apply_filters('inception_qc_blog_content_review_similarity', 0.85);
$qc_blog_likely_non_stock_similarity = (float) apply_filters('inception_qc_blog_content_non_stock_similarity', 0.60);

if ($qc_blog_comparison_available) {
    $qc_catalog = inception_qc_load_legacy_content_catalog();
    $qc_reference_blog_posts = array();
    foreach ((array) ($qc_catalog['blog_profiles'] ?? array()) as $qc_blog_profile) {
        if (is_array($qc_blog_profile)
            && isset($qc_blog_profile['id'])
            && (string) $qc_blog_profile['id'] === (string) ($qc_blog_detection['profile_id'] ?? '')) {
            $qc_reference_blog_posts = isset($qc_blog_profile['posts']) && is_array($qc_blog_profile['posts'])
                ? $qc_blog_profile['posts']
                : array();
            break;
        }
    }

    $qc_reference_blogs_by_path = array();
    foreach ($qc_reference_blog_posts as $qc_reference_blog_index => $qc_reference_blog) {
        if (is_array($qc_reference_blog) && !empty($qc_reference_blog['path_hash'])) {
            $qc_reference_blogs_by_path[(string) $qc_reference_blog['path_hash']] = (int) $qc_reference_blog_index;
        }
    }
    $qc_used_reference_blogs = array();
    foreach ($qc_local_blog_rows as $qc_local_blog_row) {
        $qc_blog_post = $qc_local_blog_row['post'];
        $qc_blog_path = $qc_local_blog_row['path'];
        $qc_blog_path_hash = hash('sha256', $qc_blog_path);
        $qc_current_blog_content = isset($qc_public_blog_fingerprints['paths'][$qc_blog_path_hash])
            ? $qc_public_blog_fingerprints['paths'][$qc_blog_path_hash]
            : (isset($qc_public_blog_fingerprints['ids'][(int) $qc_blog_post->ID])
                ? $qc_public_blog_fingerprints['ids'][(int) $qc_blog_post->ID]
                : inception_qc_rendered_page_fingerprint($qc_blog_post->ID));
        $qc_reference_blog_index = isset($qc_reference_blogs_by_path[$qc_blog_path_hash])
            ? $qc_reference_blogs_by_path[$qc_blog_path_hash]
            : null;
        $qc_blog_similarity = null;

        if ($qc_reference_blog_index !== null) {
            $qc_reference_blog = $qc_reference_blog_posts[$qc_reference_blog_index];
            $qc_used_reference_blogs[$qc_reference_blog_index] = true;
            $qc_blog_similarity = inception_techboost_content_similarity(
                $qc_reference_blog['content'] ?? array(),
                $qc_current_blog_content
            );
        } else {
            // A renamed stock post remains stock when its content still matches.
            $qc_best_reference_index = null;
            $qc_best_reference_similarity = -1;
            foreach ($qc_reference_blog_posts as $qc_candidate_index => $qc_reference_blog) {
                if (isset($qc_used_reference_blogs[$qc_candidate_index]) || empty($qc_reference_blog['content'])) continue;
                $qc_candidate_similarity = inception_techboost_content_similarity(
                    $qc_reference_blog['content'],
                    $qc_current_blog_content
                );
                if ($qc_candidate_similarity !== null && $qc_candidate_similarity > $qc_best_reference_similarity) {
                    $qc_best_reference_similarity = $qc_candidate_similarity;
                    $qc_best_reference_index = $qc_candidate_index;
                }
            }
            if ($qc_best_reference_index !== null && $qc_best_reference_similarity >= $qc_blog_review_similarity) {
                $qc_reference_blog_index = $qc_best_reference_index;
                $qc_used_reference_blogs[$qc_best_reference_index] = true;
                $qc_blog_similarity = $qc_best_reference_similarity;
            }
        }

        if ($qc_reference_blog_index === null) {
            $qc_non_stock_blogs[] = array(
                'id' => (int) $qc_blog_post->ID,
                'title' => get_the_title($qc_blog_post->ID),
                'path' => $qc_blog_path,
                'permalink' => get_permalink($qc_blog_post->ID),
                'edit' => get_edit_post_link($qc_blog_post->ID, ''),
                'result' => 'New Blog Post',
                'similarity' => null,
            );
        } elseif ($qc_blog_similarity !== null && $qc_blog_similarity < $qc_blog_review_similarity) {
            $qc_non_stock_blogs[] = array(
                'id' => (int) $qc_blog_post->ID,
                'title' => get_the_title($qc_blog_post->ID),
                'path' => $qc_blog_path,
                'permalink' => get_permalink($qc_blog_post->ID),
                'edit' => get_edit_post_link($qc_blog_post->ID, ''),
                'result' => $qc_blog_similarity < $qc_blog_likely_non_stock_similarity
                    ? 'Likely Non-Stock Content'
                    : 'Modified — Review',
                'similarity' => $qc_blog_similarity,
            );
        }
    }
}

$qc_blog_checked_count = count($qc_local_blog_rows);
$qc_non_stock_blog_count = $qc_blog_comparison_available ? count($qc_non_stock_blogs) : 0;
$qc_stock_blog_count = $qc_blog_comparison_available
    ? max($qc_blog_checked_count - $qc_non_stock_blog_count, 0)
    : 0;
$qc_stock_blog_percent = $qc_blog_comparison_available && $qc_blog_checked_count > 0
    ? (int) round(($qc_stock_blog_count / $qc_blog_checked_count) * 100)
    : 0;
$qc_non_stock_blog_percent = $qc_blog_comparison_available && $qc_blog_checked_count > 0
    ? max(100 - $qc_stock_blog_percent, 0)
    : 0;

$qc_stock_score_available = !is_wp_error($qc_master_comparison);
$qc_stock_checked_count = 0;
foreach ($qc_local_page_rows as $qc_score_page_row) {
    if (!in_array($qc_score_page_row['path'], inception_qc_excluded_stock_paths(), true)) {
        $qc_stock_checked_count++;
    }
}
$qc_non_stock_count = $qc_stock_score_available ? count($qc_non_stock_pages) : 0;
$qc_stock_count = $qc_stock_score_available ? max($qc_stock_checked_count - $qc_non_stock_count, 0) : 0;
$qc_stock_percent = $qc_stock_score_available && $qc_stock_checked_count > 0
    ? (int) round(($qc_stock_count / $qc_stock_checked_count) * 100)
    : 0;
$qc_non_stock_percent = $qc_stock_score_available && $qc_stock_checked_count > 0
    ? max(100 - $qc_stock_percent, 0)
    : 0;
$qc_stock_badge_class = !$qc_stock_score_available
    ? 'qc-badge--neutral'
    : ($qc_non_stock_count === 0 ? 'qc-badge--good' : ($qc_non_stock_count < 5 ? 'qc-badge--warn' : 'qc-badge--bad'));

?>

<div class="wrap">
    <h1>Inception QC Report</h1>

    <div id="qc-alerts" class="qc-alerts" aria-live="polite">
        <div class="qc-alerts__title">
            Site Checks
        </div>
        <div class="qc-alerts__items">
            <span class="qc-badge qc-badge--neutral" data-qc-badge="indexedPages">Indexed: <strong>…</strong><span class="qc-badge__sub">…</span></span>
            <span class="qc-badge qc-badge--neutral" data-qc-badge="multiH1">Multiple H1s: <strong>…</strong></span>
            <span class="qc-badge qc-badge--neutral" data-qc-badge="hpHeadings">HP Headings: <strong>…</strong></span>
            <span class="qc-badge qc-badge--neutral" data-qc-badge="missingBacklinks">Backlinks: <strong>…</strong></span>
            <span class="qc-badge qc-badge--neutral" data-qc-badge="dupImages">Duplicate Hero Photos: <strong>…</strong></span>
            <span class="qc-badge qc-badge--neutral" data-qc-badge="hl404">HighLevel 404s: <strong>…</strong></span>
            <span class="qc-badge qc-badge--neutral" data-qc-badge="imagesTooLarge"> Images Too Large: <strong>…</strong><span class="qc-badge__sub">…</span> </span>
            <span class="qc-badge qc-badge--neutral" data-qc-badge="altTextMissed">Alt Text Missed: <strong>…</strong></span>
            <span class="qc-badge qc-badge--neutral" data-qc-badge="extImages">External Images: <strong>…</strong></span>
            <span class="qc-badge qc-badge--neutral" data-qc-badge="cityState">CITY/STATE: <strong><?php echo (int) $qc_city_state_count; ?></strong></span>
            <a class="qc-badge <?php echo esc_attr($qc_stock_badge_class); ?> qc-badge--link"
               data-qc-badge="stockVsNonStock"
               href="#non-stock-pages-table">
                <span class="qc-stock-score__label">Stock vs Non-Stock:</span>
                <strong><?php echo $qc_stock_score_available ? esc_html($qc_stock_percent . '% / ' . $qc_non_stock_percent . '%') : 'Unavailable'; ?></strong>
                <?php if ($qc_stock_score_available) : ?><span class="qc-badge__sub"><?php echo esc_html($qc_stock_count . ' / ' . $qc_non_stock_count); ?></span><?php endif; ?>
            </a>
            <a class="qc-badge <?php echo esc_attr($qc_drafted_blogs_badge_class); ?> qc-badge--link"
               data-qc-badge="draftedBlogs"
               href="<?php echo esc_url( admin_url('edit.php') ); ?>"
               target="_blank"
               rel="noopener noreferrer">
               Drafted Blogs: <strong><?php echo (int) $qc_drafted_blogs_count; ?></strong>
            </a>
        </div>

    <!-- Floating Site Checks Bug (appears after scroll) -->
    <div id="qc-bug-collapsed" class="qc-bug__collapsed" aria-label="Open site checks">Checks <span class="qc-mini-count qc-badge--neutral" data-qc-badge="cityState"><?php echo (int) $qc_city_state_count; ?></span></div>
    <div id="qc-bug" class="qc-bug" aria-live="polite">
        <div class="qc-bug__header">
            <div class="qc-bug__title">Site Checks</div>

            <div style="display:flex; gap:6px;">
                <button type="button" class="qc-bug__btn" id="qc-bug-top" aria-label="Back to top"><strong>ALL</strong></button>
                <button type="button" class="qc-bug__btn" id="qc-bug-close" aria-label="Close">×</button>
            </div>
        </div>
        <div class="qc-bug__items">
            <span class="qc-badge qc-badge--neutral" data-qc-badge="indexedPages">Indexed: <strong>…</strong><span class="qc-badge__sub">…</span></span>
            <span class="qc-badge qc-badge--neutral" data-qc-badge="multiH1">Multiple H1s: <strong>…</strong></span>
            <span class="qc-badge qc-badge--neutral" data-qc-badge="hpHeadings">HP Headings: <strong>…</strong></span>
            <span class="qc-badge qc-badge--neutral" data-qc-badge="missingBacklinks">Backlinks: <strong>…</strong></span>
            <span class="qc-badge qc-badge--neutral" data-qc-badge="dupImages">Duplicate Hero Photos: <strong>…</strong></span>
            <button type="button" class="qc-badge qc-badge--neutral qc-badge--btn" data-qc-badge="allBlogPosts">All Blog Posts</button>
            <span class="qc-badge qc-badge--neutral" data-qc-badge="hl404">HighLevel 404s: <strong>…</strong></span>
            <button type="button" class="qc-badge qc-badge--neutral qc-badge--btn" data-qc-badge="videoLocations">Video Locations</button>
            <span class="qc-badge qc-badge--neutral" data-qc-badge="imagesTooLarge"> Images Too Large: <strong>…</strong><span class="qc-badge__sub">…</span> </span>
            <span class="qc-badge qc-badge--neutral" data-qc-badge="altTextMissed">Alt Text Missed: <strong>…</strong></span>
            <span class="qc-badge qc-badge--neutral" data-qc-badge="extImages">External Images: <strong>…</strong></span>
            <span class="qc-badge qc-badge--neutral" data-qc-badge="cityState">CITY/STATE: <strong><?php echo (int) $qc_city_state_count; ?></strong></span>
            <a class="qc-badge <?php echo esc_attr($qc_stock_badge_class); ?> qc-badge--link"
               data-qc-badge="stockVsNonStock"
               href="#non-stock-pages-table">
                <span class="qc-stock-score__label">Stock vs Non-Stock:</span>
                <strong><?php echo $qc_stock_score_available ? esc_html($qc_stock_percent . '% / ' . $qc_non_stock_percent . '%') : 'Unavailable'; ?></strong>
                <?php if ($qc_stock_score_available) : ?><span class="qc-badge__sub"><?php echo esc_html($qc_stock_count . ' / ' . $qc_non_stock_count); ?></span><?php endif; ?>
            </a>
            <a class="qc-badge <?php echo esc_attr($qc_drafted_blogs_badge_class); ?> qc-badge--link"
               data-qc-badge="draftedBlogs"
               href="<?php echo esc_url( admin_url('edit.php') ); ?>"
               target="_blank"
               rel="noopener noreferrer">
               Drafted Blogs: <strong><?php echo (int) $qc_drafted_blogs_count; ?></strong>
            </a>
        </div>
    </div>

</div>

    <p>Please use the dropdown menu to find your specific topic you'd like a report on.</p>
        <form method="post">
    </form>
    </p>
    <!-- Add table selector dropdown -->
    <div class="filter-container">
        <p class="filter-label">Filter By:</p>
        <select id="table-selector" onchange="changeTable()">
            <option value="all" selected>All</option>
            <option value="pages">Pages - All</option>
            <option value="non_stock_pages">Pages & Blogs - Non-Stock or Modified</option>
            <option value="headings">Pages & Blog Posts - Headings</option>
            <option value="backlinks">Pages & Blog Posts - Missing Backlinks</option>
            <option value="keyword">Keyword Search</option>
            <option value="pages_posts_metadata">Pages & Blog Posts - Metadata & Images</option>
            <option value="posts">Blog Posts - All</option>
            <option value="posts_uncategorized">Blog Posts - Uncategorized</option>
            <option value="posts_uplink">Blog Posts - Uplink</option>
            <option value="no_author">Blog Posts - No Inception As An Author</option>
            <option value="external-links">External Links Locations</option>
            <option value="highlevel">HighLevel & Doctible Form Locations</option>
            <option value="html-table">HTML Script Locations</option>
            <option value="rw-table">Review Wave Form Locations</option>
            <option value="video-table">YouTube/Vimeo Video Locations</option>
            <option value="media_library">Media Library Items Larger Than 100KB</option>
            <option value="media_library_large">Media Library Items Larger Than 200KB</option>
            <option value="media_library_search">Media Library Optimization Check</option>
            <option value="external_images">External Images</option>
            <option value="city_state">Pages, Blog Posts & Themer Layouts - CITY / STATE</option>
            </select>
    </div>

    <form id="inception-qc-report-form" method="post">
        <div id="pages-table" class="table-container">
            <h3>All Pages</h3>
            <p><strong>Reminder:</strong> Clear the cache and refresh this page after switching a page's indexing to get the most updated information.</p>
        
            <?php
            // Check if the "Only Non-Indexed" button is clicked
            $show_non_indexed = isset($_GET['show_non_indexed']) && $_GET['show_non_indexed'] === '1';
        
            // Check if the "Show Too Long" button is clicked
            $show_too_long = isset($_GET['show_too_long']) && $_GET['show_too_long'] === '1';
        
            // Check if the search query is set
            $search_query = isset($_GET['search_query']) ? sanitize_text_field($_GET['search_query']) : '';
        
            // Generate list of pages
            $args = array(
                'post_type'      => 'page',
                'posts_per_page' => -1,
                'post_status'    => array('publish', 'draft', 'private', 'pending', 'future'),
                'orderby'        => 'title',
                'order'          => 'ASC',
            );
        
            // Toggle between showing all pages and only non-indexed pages
            if ($show_non_indexed) {
                $args['meta_query'] = array(
                    array(
                        'key'     => '_yoast_wpseo_meta-robots-noindex',
                        'value'   => '1',
                        'compare' => '=',
                    ),
                );
            }
        
            // Toggle between showing all pages and only too long pages
            if ($show_too_long) {
                $args['meta_query'][] = array(
                    'relation' => 'OR',
                    array(
                        'key'     => '_yoast_wpseo_title',
                        'compare' => 'EXISTS',
                    ),
                    array(
                        'key'     => '_yoast_wpseo_metadesc',
                        'compare' => 'EXISTS',
                    ),
                );
            }
        
            // Add search functionality
            if (!empty($search_query)) {
                $args['s'] = $search_query;
            }
            
            /**
             * Get IDs of pages that appear in any of the site's header menus.
             *
             * Header menus are:
             *  1) Menus assigned to any of the known header locations (filterable).
             *  2) Menus whose term slug is "main-menu", "multi-main-menu",
             *     or matches "main-menu-location-{number}".
             *
             * Returns an associative array like [ page_id => true, ... ] for O(1) lookups.
             */
            function inception_get_header_menu_page_ids() {
                $page_ids = array();
            
                // 1) Collect menus by location (theme locations that represent header navs)
                $locations = get_nav_menu_locations();
            
                $header_location_slugs = apply_filters('inception_header_menu_locations', array(
                    'primary',
                    'menu-1',
                    'header',
                    'header-menu',
                    'main',
                    'primary-menu',
                    'top',
                    'top-menu',
                    'site-header',
                    'main-nav',
                ));
            
                $header_menu_term_ids = array();
            
                if (is_array($locations)) {
                    foreach ($header_location_slugs as $loc) {
                        if (!empty($locations[$loc])) {
                            $header_menu_term_ids[] = (int) $locations[$loc];
                        }
                    }
                }
            
                // 2) Collect menus by slug pattern: main-menu, multi-main-menu, main-menu-location-\d+
                $all_menus = wp_get_nav_menus();
                if (!empty($all_menus) && !is_wp_error($all_menus)) {
                    foreach ($all_menus as $menu) {
                        $slug = isset($menu->slug) ? (string) $menu->slug : '';
                        if (
                            $slug === 'main-menu' ||
                            $slug === 'multi-main-menu' ||
                            preg_match('/^main-menu-location-\d+$/', $slug)
                        ) {
                            $header_menu_term_ids[] = (int) $menu->term_id;
                        }
                    }
                }
            
                // Deduplicate menu term ids
                $header_menu_term_ids = array_values(array_unique($header_menu_term_ids));
            
                if (empty($header_menu_term_ids)) {
                    return $page_ids; // none found, everything will be "Hidden"
                }
            
                // Resolve internal page IDs from these menus (post_type items and custom links)
                $home_url  = home_url('/');
                $home_host = wp_parse_url($home_url, PHP_URL_HOST);
            
                foreach ($header_menu_term_ids as $term_id) {
                    $items = wp_get_nav_menu_items($term_id);
                    if (empty($items) || is_wp_error($items)) {
                        continue;
                    }
            
                    foreach ($items as $item) {
                        // Direct page items
                        if ($item->type === 'post_type' && $item->object === 'page' && !empty($item->object_id)) {
                            $page_ids[(int) $item->object_id] = true;
                            continue;
                        }
            
                        // Custom links that might point to internal pages
                        if ($item->type === 'custom' && isset($item->url)) {
                            $url_raw = trim((string) $item->url);
            
                            // --- Front page special cases ("/" or absolute home URL) ---
                            if (
                                $url_raw === '/' ||
                                trailingslashit($url_raw) === trailingslashit(home_url('')) ||
                                trailingslashit($url_raw) === trailingslashit(home_url('/')) ||
                                trailingslashit($url_raw) === trailingslashit(site_url('')) ||
                                trailingslashit($url_raw) === trailingslashit(site_url('/'))
                            ) {
                                $front_id = (int) get_option('page_on_front');
                                if ($front_id) {
                                    $page_ids[$front_id] = true;
                                }
                                continue;
                            }
            
                            // Normalize: drop fragment/query and convert protocol/root-relative to absolute
                            $url = preg_replace('/[#?].*$/', '', $url_raw);
            
                            // Protocol-relative (e.g., //example.com/)
                            if (strpos($url, '//') === 0) {
                                $url = (is_ssl() ? 'https:' : 'http:') . $url;
                            }
            
                            // Root-relative (e.g., "/about")
                            if (strpos($url, '/') === 0) {
                                $url = home_url($url);
                            }
            
                            // Only consider internal links
                            $host = wp_parse_url($url, PHP_URL_HOST);
                            $is_internal = (empty($host) || $host === $home_host);
            
                            if ($is_internal) {
                                // If normalized URL equals home, map to front page
                                if (trailingslashit($url) === trailingslashit($home_url)) {
                                    $front_id = (int) get_option('page_on_front');
                                    if ($front_id) {
                                        $page_ids[$front_id] = true;
                                    }
                                    continue;
                                }
            
                                // Resolve to a page ID
                                $maybe_id = url_to_postid($url);
                                if ($maybe_id && get_post_type($maybe_id) === 'page') {
                                    $page_ids[(int) $maybe_id] = true;
                                }
                            }
                        }
                    }
                }
            
                return $page_ids;
            }
        
            $header_menu_page_ids = inception_get_header_menu_page_ids();

            $pages_query = new WP_Query($args);
        
            // Output search bar and buttons on the same row
            echo '<p>';
            echo '<button id="toggle-non-indexed" class="button">Toggle Non-Indexed</button>';
            echo '&nbsp;&nbsp;';
            echo '<button id="toggle-too-long" class="button">Toggle Too Long</button>';
            echo '&nbsp;&nbsp;';
            echo '<button id="toggle-hidden-pages" class="button">Toggle Hidden Pages</button>';
            echo '&nbsp;&nbsp;';
            echo '<input type="text" name="search_query" value="' . esc_attr($search_query) . '" placeholder="Optimization Filter Pages Here..." style="width: 400px;" />';
            echo '</p>';
        
            if ($pages_query->have_posts()) {
                echo '<table class="wp-list-table widefat fixed striped inception-table">';
                echo '<thead><tr>
                <th style="width: auto; padding-left: 10px;">#</th>
                <th style="width: auto; padding-left: 10px;">Page Title</th>
                <th>SEO Title</th>
                <th>SEO Title Length</th>
                <th>Meta Description Length</th>
                <th>Index Status</th>
                <th>Menu</th>
                <th>Page Link</th>
                </tr></thead>';
                echo '<tbody>';
                $row_counter = 0; // Counter to track alternating rows
                $row_number = 1;
                while ($pages_query->have_posts()) {
                    $pages_query->the_post();
                
                    // Get the page title
                    $page_title = get_the_title();
                    
                    $page_id = get_the_ID();
                    $page_link = get_permalink($page_id);
                    $seo_title = get_post_meta($page_id, '_yoast_wpseo_title', true);
                    $seo_title_length = mb_strlen($seo_title);
                    $meta_description = get_post_meta($page_id, '_yoast_wpseo_metadesc', true);
                    $meta_description_length = mb_strlen($meta_description);
                    $index_status = get_post_meta($page_id, '_yoast_wpseo_meta-robots-noindex', true);
                    $is_in_header_menu = isset($header_menu_page_ids[$page_id]);
                    $menu_status_text  = $is_in_header_menu ? 'In Menu' : 'Hidden';
    
                    $is_too_long = $seo_title_length > 58 || $meta_description_length > 142;
        
                    if ($show_too_long && !$is_too_long) {
                        continue;
                    }
        
                    // Use a class for styling alternating rows
                    $row_class = $row_counter % 2 === 0 ? 'even-row' : 'odd-row';
        
                    echo '<tr class="' . $row_class . '">';
                    echo '<td style="width: 20px;">' . $row_number . '</td>';
                    echo '<td style="width: auto;">' . get_the_title() . '</td>';
                    echo '<td>' . $seo_title . '</td>';
                    echo '<td>' . ($seo_title_length <= 58 ? 'OK' : 'Too Long') . '</td>';
                    echo '<td>' . ($meta_description_length <= 142 ? 'OK' : 'Too Long') . '</td>';
                    echo '<td>' . ($index_status ? 'Non-Indexed' : 'Indexed') . '</td>';
                    echo '<td>' . $menu_status_text . '</td>';
                    echo '<td>
                            <a href="' . esc_url($page_link) . '" target="_blank">View</a> |
                            <a href="' . esc_url(admin_url('post.php?post=' . $page_id . '&action=edit')) . '" target="_blank">Edit</a>
                          </td>';
                    echo '</tr>';
        
                    $row_counter++;
                    $row_number++;
                }
                echo '</tbody>';
                echo '</table>';
            }
            ?>

<style>
/* === QC REPORT: conditional scroll buffer ===
   Only active when a single table is selected */
body.qc-single-table::after{
  content: "";
  display: block;
  height: calc(100vh - 60px); /* aligns table header 60px below WP admin bar */
}

/* Modern, lightweight UI polish (no functionality changes) */
.wrap h1{margin-bottom:12px;}
.table-container{background:#fff;border:1px solid #dcdcde;border-radius:12px;padding:16px;margin:16px 0;box-shadow:0 1px 2px rgba(0,0,0,.04);}
.table-container h3{margin-top:0;}
.qc-alerts{display:flex;gap:12px;align-items:flex-start;justify-content:space-between;background:#f6f7f7;border:1px solid #dcdcde;border-radius:12px;padding:12px 14px;margin:12px 0 16px;}
.qc-alerts__title{font-weight:700;margin-right:10px;white-space:nowrap;}
.qc-alerts__items{display:flex;flex-wrap:wrap;gap:8px;align-items:center;flex:1;}
.qc-alerts__note{color:#646970;font-size:12px;white-space:nowrap;}
.qc-badge{display:inline-flex;gap:6px;align-items:center;border-radius:999px;padding:6px 10px;border:1px solid #dcdcde;background:#fff;font-size:13px;}
a.qc-badge{color:inherit;text-decoration:none;}
.qc-badge--link{cursor:pointer;}
.qc-badge--link:hover{background:#f6f7f7;}
.qc-badge--good{border-color:#00a32a;}
.qc-badge--warn{border-color:#dba617;}
.qc-badge--bad{border-color:#d63638;}
.qc-badge--neutral{border-color:#dcdcde;}
.qc-badge__sub{margin-left:6px;font-size:11px;opacity:.85;}
.qc-bug__items .qc-badge__sub{display:block;margin-left:0;}

.qc-table-pager{display:flex;gap:8px;align-items:center;justify-content:flex-end;}
.qc-table-pager--top{margin:0 0 10px;}
.qc-table-title-row{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;}
.qc-table-title-row > h1,
.qc-table-title-row > h2,
.qc-table-title-row > h3,
.qc-table-title-row > h4{margin:0;}
.qc-table-title-row .qc-table-pager--top{margin:0;}
.qc-table-pager--bottom{margin:10px 0 0;}
.qc-table-pager .qc-page-info{color:#50575e;font-size:12px;margin-left:8px;}
tr.qc-issue td{font-weight:600;}

.qc-table-pager button.button{
    min-width:48px;
    height:40px;
    padding:0;
    line-height:38px;
    text-align:center;
    font-size:20px;
    font-weight:700;
    background:#2271b1;
    border-color:#2271b1;
    color:#fff;
    border-radius:8px;
    box-shadow:0 2px 4px rgba(0,0,0,.15);
}

.qc-table-pager button.button:hover{
    background:#135e96;
    border-color:#135e96;
    color:#fff;
}

.qc-table-pager button.button:disabled{
    background:#dcdcde;
    border-color:#dcdcde;
    color:#777;
    opacity:1;
}

.qc-table-pager .qc-page-info{
    font-size:14px;
    font-weight:600;
    margin-left:12px;
    color:#1d2327;
}

/* Do not bold text in the External Images or CITY/STATE tables */
#external-images-table tr.qc-issue td,
#city-state-table tr.qc-issue td,
#external-images-table th,
#external-images-table td,
#city-state-table th,
#city-state-table td{
  font-weight: normal !important;
}

.qc-count-bubble,
.qc-mini-count{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  border-radius: 999px;
  border: 1px solid rgba(0,0,0,.12);
  background: #fff;
  font-size: 12px;
  line-height: 1;
  margin-left: 8px;
  font-variant-numeric: tabular-nums;
}

/* Color highlight for CITY/STATE mini counters (matches badge colors) */
.qc-count-bubble.qc-badge--good,
.qc-mini-count.qc-badge--good{border-color:#00a32a !important;}
.qc-count-bubble.qc-badge--warn,
.qc-mini-count.qc-badge--warn{border-color:#dba617 !important;}
.qc-count-bubble.qc-badge--bad,
.qc-mini-count.qc-badge--bad{border-color:#d63638 !important;}
.qc-count-bubble.qc-badge--neutral,
.qc-mini-count.qc-badge--neutral{border-color:#dcdcde !important;}
tr.error-row td{background:rgba(214,54,56,.06);}
tr.qc-missing-alt td{background:rgba(214,54,56,.06) !important;}

/* === QC TABLE WIDTH NORMALIZATION === */
.table-container,
.wp-list-table {
    width: 100% !important;
    max-width: 100% !important;
}

.wp-list-table {
    table-layout: fixed;
}

.wp-list-table th,
.wp-list-table td {
    word-wrap: break-word;
    overflow-wrap: anywhere;
}

.wp-list-table th {
    white-space: nowrap;
}

/* === QC COLUMN WIDTH OVERRIDES === */
/* Global: keep the "#" column compact across tables */
.wp-list-table th:first-child,
.wp-list-table td:first-child{
  width: 30px !important;
  min-width: 30px !important;
  max-width: 30px !important;
  padding-left: 4px !important;
  padding-right: 6px !important;
  text-align: center;
  white-space: nowrap;
}
/* Ensure the gap between "#" and the next column is ~10px */
.wp-list-table th:nth-child(2),
.wp-list-table td:nth-child(2){
  padding-left: 10px !important;
}

/* All Pages: widen Page Title + SEO Title */
#pages-table .wp-list-table th:nth-child(2),
#pages-table .wp-list-table td:nth-child(2),
#pages-table .wp-list-table th:nth-child(3),
#pages-table .wp-list-table td:nth-child(3){
  width: 325px !important;
  max-width: 325px !important;
}

/* Pages & Blog Posts – Headings: title column and left-align heading details */
#pages-posts-headings-table .wp-list-table th:nth-child(2),
#pages-posts-headings-table .wp-list-table td:nth-child(2){
  width: 600px !important;
  max-width: 600px !important;
}
#pages-posts-headings-table .heading-seq,
#pages-posts-headings-table .heading-line{
  text-align: left !important;
}
/* === QC FLOATING SITE CHECKS BUG === */
:root{
  --qcBugWidth: 230px;
}
.qc-bug{
  position: fixed;
  top: 110px;
  right: 14px;
  width: var(--qcBugWidth);
  z-index: 9999;
  background: #fff;
  border: 1px solid rgba(0,0,0,.08);
  border-radius: 14px;
  box-shadow: 0 10px 30px rgba(0,0,0,.10);
  padding: 10px 10px 8px;
  box-sizing: border-box;

  /* Fade/slide transition */
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(6px);
  transition: opacity .25s ease, transform .25s ease, visibility 0s linear .25s;
}
.qc-bug.qc-bug--open{
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translateY(0);
  transition: opacity .25s ease, transform .25s ease, visibility 0s;
}
.qc-bug__header{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:8px;
  margin-bottom:8px;
}
.qc-bug__title{
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .02em;
  text-transform: uppercase;
  opacity: .85;
}
.qc-bug__btn{
  border: 0;
  background: transparent;
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  padding: 6px 8px;
  border-radius: 10px;
}
.qc-bug__btn:hover{ background: rgba(0,0,0,.06); }

.qc-bug__items{
  display:flex;
  flex-direction:column;
  gap:6px;
}
.qc-bug__items .qc-badge{
  display:flex;
  justify-content:space-between;
  align-items:center;
  width:100%;
}
.qc-bug__items .qc-badge strong{ margin-left:8px; }

.qc-bug__collapsed{
  position: fixed;
  top: 140px;
  right: 14px;
  z-index: 9999;
  border-radius: 999px;
  border: 1px solid rgba(0,0,0,.08);
  background:#fff;
  box-shadow: 0 10px 30px rgba(0,0,0,.10);
  padding: 10px 12px;
  font-size: 12px;
  font-weight: 700;
  cursor:pointer;

  /* Fade/slide transition */
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(6px);
  transition: opacity .25s ease, transform .25s ease, visibility 0s linear .25s;
}
.qc-bug__collapsed.qc-pill--open{
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translateY(0);
  transition: opacity .25s ease, transform .25s ease, visibility 0s;
}
.qc-bug__collapsed:hover{ background: rgba(0,0,0,.04); }

/* Reserve space so the bug doesn't cover table content on larger screens */
@media (min-width: 1024px){
  .wrap{ padding-right: calc(var(--qcBugWidth) + 28px); }
}
/* On smaller screens, don't reserve space and keep bug narrower */
@media (max-width: 1023px){
  :root{ --qcBugWidth: 200px; }
}


/* === QC BUG BUTTON SIZE FIX === */
.qc-bug__items .qc-badge{
  font-size: 12px;
  padding: 6px 8px;
  border-radius: 10px;
}
.qc-bug{
  box-sizing: border-box;
}



/* === QC BUG ULTRA-COMPACT BADGES === */
.qc-bug{
  width: 190px; /* shrink bug itself */
}
.qc-bug__items{ gap:3px; }
.qc-bug__items .qc-badge{
  font-size: 20px;
  padding: 2px 4px;
  border-radius: 8px;
  line-height: 1;
  gap: 4px;
}
.qc-bug__items .qc-badge strong{
  width:4ch;
}

.qc-bug__items .qc-badge strong{
  display:inline-block;
  width:4ch;
  text-align:right;
  font-variant-numeric: tabular-nums;
  margin-left:0;
}


/* === QC BUG OVERRIDES (FINAL – DO NOT DUPLICATE ABOVE) === */
@media (min-width: 1024px){
  :root{ --qcBugWidth: 190px; }
}

.qc-bug,
.qc-bug__collapsed{
  display: block !important;
}

.qc-bug{
  width: var(--qcBugWidth) !important;
}

/* Floating bug buttons (EDIT THESE VALUES) */
.qc-bug__items .qc-badge{
  width: fit-content !important;
  max-width: 100%;
  display: inline-flex !important;
  justify-content: flex-start !important;
  align-self: flex-start;
  gap: 3px !important;
  padding: 2px 4px !important;
  border-radius: 8px !important;
  font-size: 13px !important;
  line-height: 1 !important;
}

.qc-bug__items .qc-badge strong{
  width: 4ch !important;
  margin-left: 0 !important;
}


/* Allow long table header labels to wrap on smaller screens */
.wp-list-table thead th {
    white-space: normal;
    word-break: break-word;
    hyphens: auto;
    line-height: 1.25;
}

/* Slightly reduce padding on narrow screens to prevent collisions */
@media (max-width: 1100px) {
    .wp-list-table thead th {
        padding: 8px 6px;
        font-size: 13px;
    }
}

/* Neutral outlined buttons inside the QC bug */
.qc-bug .qc-badge--btn{
  appearance: none;
  background: #fff;
  border: 1px solid #cfcfcf; /* light grey outline */
  border-radius: 999px;      /* pill to match badges */
  padding: 4px 10px;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

.qc-bug .qc-badge--btn:hover{
  background: #f7f7f7;
}

.qc-bug .qc-badge--btn:active{
  background: #f0f0f0;
}

/* === Toggle buttons: active state (reuse WP primary blue) === */
#pages-table .button.is-active,
#pages-posts-keyword-table .button.is-active,
#pages-posts-metadata-table .button.is-active{
  background-color: #2271b1;
  border-color: #2271b1;
  color: #fff;
}

#pages-table .button.is-active:hover,
#pages-posts-keyword-table .button.is-active:hover,
#pages-posts-metadata-table .button.is-active:hover{
  background-color: #135e96;
  border-color: #135e96;
  color: #fff;
}
</style>
            <script>
                jQuery(document).ready(function ($) {
                    var activeToggles = {
                        'nonIndexed': false,
                        'tooLong': false,
                        'hidden': false
                    }; // Object to keep track of active toggles
                
                    // Function to filter pages based on search input
                    function filterPages(searchValue) {

                        if (!searchValue && !Object.values(activeToggles).some(toggle => toggle)) {
                            // If search input is empty and no toggle is active, show all rows
                            $('#pages-table tbody tr').show();

                            // Ensure toggle buttons start visually OFF
                            $('#toggle-non-indexed, #toggle-too-long, #toggle-hidden-pages')
                              .removeClass('is-active')
                              .data('showing', false);

                        } else {
                            // Apply toggles and search filter
                            $('#pages-table tbody tr').each(function () {
                                var pageTitle = $(this).find('td:nth-child(2)').text().toLowerCase();
                                var seoTitle  = $(this).find('td:nth-child(3)').text().toLowerCase();
                                var pageLink  = $(this).find('td:nth-child(6)').text().toLowerCase();
                
                                // Check active toggles
                                var indexStatus = $(this).find('td:nth-child(6)').text().toLowerCase() === 'non-indexed';
                                var isTooLong = $(this).find('td:nth-child(4)').text().toLowerCase() === 'too long' ||
                                    $(this).find('td:nth-child(5)').text().toLowerCase() === 'too long';
                                    
                                // Hidden pages live in the last column (7th)
                                var isHidden = $(this).find('td:nth-child(7)').text().toLowerCase() === 'hidden';
                
                                // Toggle based on active toggles and search input
                                var showRow =
                                    (!activeToggles['nonIndexed'] || indexStatus) &&
                                    (!activeToggles['tooLong']    || isTooLong)   &&
                                    (!activeToggles['hidden']     || isHidden)    &&
                                    (!searchValue || (!pageTitle.includes(searchValue) &&
                                                      !seoTitle.includes(searchValue) &&
                                                      !pageLink.includes(searchValue)));
                
                                $(this).toggle(showRow);
                            });
                        }

                        // ALWAYS update pager visibility after any filter/toggle/search change
                        updatePagesPagerVisibility();
                    }
                
                    // Button click event to toggle visibility of non-indexed pages
                    $('#toggle-non-indexed').on('click', function (e) {
                        e.preventDefault();
                        toggleButton($(this), 'nonIndexed');
                    });
                
                    // Button click event to toggle visibility of too long pages
                    $('#toggle-too-long').on('click', function (e) {
                        e.preventDefault();
                        toggleButton($(this), 'tooLong');
                    });
                    
                    // Hidden Pages toggle
                    $('#toggle-hidden-pages').on('click', function (e) {
                        e.preventDefault();
                        toggleButton($(this), 'hidden', 'Toggle Hidden Pages');
                    });
                
                    // Function to toggle buttons
                    function toggleButton(button, key, buttonText) {
                        var isShowing = button.data('showing');
                        var nowActive = !isShowing;
                    
                        activeToggles[key] = nowActive;
                    
                        // 🔵 Visual state
                        button.toggleClass('is-active', nowActive);
                    
                        // Remember the state of the search input when a toggle is turned on
                        var searchValue = $('[name="search_query"]').val().toLowerCase();
                    
                        filterPages(searchValue);
                    
                        button.data('showing', nowActive);
                    
                        // Optional: keep original label (remove this line if you prefer static text)
                        if (buttonText) {
                            button.text(buttonText);
                        }
                    
                        updateRows();
                    }
                
                    // Show all rows upon page load
                    $('#pages-table tbody tr').show();
                    updatePagesPagerVisibility();
                    // Ensure toggle buttons start visually OFF
                    $('#toggle-non-indexed, #toggle-too-long, #toggle-hidden-pages')
                      .removeClass('is-active')
                      .data('showing', false);

                    // Input event on the search bar
                    $('[name="search_query"]').on('input', function () {
                        var searchValue = $(this).val().toLowerCase();
                        filterPages(searchValue);
                        updatePagesPagerVisibility();
                
                        // Update rows
                        updateRows();
                    });
                    
                    // Prevent Enter key from triggering the Toggle Non-Index button
                    $('[name="search_query"]').on('keypress', function (e) {
                        if (e.which === 13) {
                            e.preventDefault();
                            // Do nothing
                        }
                    });
                
                    // Hide/show the All Pages pagination controls when filters/toggles are active
                    function updatePagesPagerVisibility(){
                      var hasAnyToggle = Object.values(activeToggles).some(function(v){ return !!v; });
                      var searchValue = ($('[name="search_query"]').val() || '').trim();
                      var hasSearch = searchValue.length > 0;
                    
                      // If any toggle OR any search is active, hide the pager UI
                      var shouldHidePager = hasAnyToggle || hasSearch;
                    
                      // Your pager markup is inserted as .qc-table-pager (top + bottom)
                      // Scope this ONLY to the All Pages table container
                      $('#pages-table').find('.qc-table-pager').toggle(!shouldHidePager);
                    }
                
                    // Function to update row numbers and background color
                    function updateRows() {
                        var visibleRows = $('#pages-table tbody tr:visible');
                        visibleRows.each(function (index) {
                            // Use a class for styling alternating rows
                            var rowClass = index % 2 === 0 ? 'even-row' : 'odd-row';
                            var backgroundColor = rowClass === 'even-row' ? '#f6f7f7' : '#ffffff';
                            $(this).removeClass().addClass(rowClass);
                            $(this).css('background-color', backgroundColor);
                            $(this).find('td:first-child').text(index + 1);
                        });
                    }
                });
            
// === QC ALERT CLICK SCROLLING ===
jQuery(function($){
  $('#qc-alerts [data-qc-badge], #qc-bug [data-qc-badge], #qc-bug-collapsed [data-qc-badge]')
    .css('cursor','pointer')
    .on('click', function(e){

      var key = $(this).data('qc-badge');

      // For each badge key, define what row counts as the "problem" to jump to.
      var problemRowSelectorByKey = {
        hl404: 'tr.error-row',
        multiH1: 'tr[data-qc-issue="multi-h1"]',
        hpHeadings: 'tr[data-qc-hp-error-count]', // first page that has any HP heading errors
        dupImages: 'tr[data-qc-issue="duplicate-image"]',
        altTextMissed: 'tr.qc-missing-alt',
        missingBacklinks: 'tbody tr', // if you want first backlink row; otherwise define a stricter selector
        extImages: 'tr[data-qc-issue="external-image"]',
        cityState: 'tr[data-qc-issue="city-state"]',
        stockVsNonStock: 'tbody tr.qc-issue',
        // your added buttons:
        allBlogPosts: 'tbody tr',
        videoLocations: 'tbody tr'
      };

      function jumpPagerToFirstProblem(containerSelector, key){
        var $container = $(containerSelector);
        if (!$container.length) return;

        var rowSel = problemRowSelectorByKey[key];
        if (!rowSel) return;

        var $table = $container.find('table.wp-list-table').first();
        if (!$table.length) return;

        var $problemRow = $container.find(rowSel).first();
        if (!$problemRow.length) return; // nothing to jump to

        var pager = $table.data('qcPager');
        if (!pager || !pager.getPageForRow || !pager.goToPage) return;

        var page = pager.getPageForRow($problemRow);
        pager.goToPage(page, false); // false = don't scroll yet (we scroll once, below)
      }

      // Allow Drafted Blogs link (<a target="_blank">) to behave normally
      if (key === 'draftedBlogs') {
        return;
      }

      // (Span click usually doesn't need this, but it prevents any weird bubbling/defaults)
      if (e) { e.preventDefault && e.preventDefault(); e.stopPropagation && e.stopPropagation(); }

      var target = null;

      if (key === 'hl404') target = '#highlevel-table';
      if (key === 'multiH1') target = '#pages-posts-headings-table';
      if (key === 'hpHeadings') target = '#pages-posts-headings-table';
      if (key === 'dupImages') target = '#pages-posts-metadata-table';
      if (key === 'altTextMissed') target = '#media-library-search-table';
      if (key === 'missingBacklinks') target = '#pages-posts-backlinks-table';
      if (key === 'indexedPages') target = '#pages-table';
      if (key === 'extImages') target = '#external-images-table';
      if (key === 'cityState') target = '#city-state-table';
      if (key === 'stockVsNonStock') target = '#non-stock-pages-table';
      if (key === 'allBlogPosts') target = '#posts-table';
      if (key === 'videoLocations') target = '#video-table';

      // SPECIAL: Images Too Large should show BOTH 100KB + 200KB tables
      if (key === 'imagesTooLarge') {

        // Always switch to ALL so both tables are visible
        var $sel = $('#table-selector');
        if ($sel.length){
          $sel.val('all');
          if (typeof window.changeTable === 'function') { window.changeTable(); }
        }

        // Decide where to scroll first:
        // - If 200KB table has rows, scroll there
        // - Otherwise scroll to 100KB table
        function countRows(selector){
          var $c = $(selector);
          if (!$c.length) return 0;
          return $c.find('table tbody tr').length;
        }

        var c200 = countRows('#media-library-large-table');
        var firstTarget = (c200 > 0) ? '#media-library-large-table' : '#media-library-table';

        function scrollToContainer(containerSel){
          var $t = $(containerSel);
          if (!$t.length) return;

          var $container = $t.closest('.table-container');
          var $anchor = $container.find('.qc-table-title-row').first();
          if (!$anchor.length) $anchor = $container.children('h1,h2,h3,h4').first();
          if (!$anchor.length) $anchor = $t;

          var off = $anchor.offset();
          var top = off ? (off.top - 60) : 0;
          top = Math.max(0, top);

          $('html, body').stop(true).animate({ scrollTop: top }, 500);
        }

        // Wait for layout after switching to ALL, then scroll.
        requestAnimationFrame(function(){
          requestAnimationFrame(function(){
            scrollToContainer(firstTarget);
          });
        });

        return; // IMPORTANT: stop the normal single-target flow
      }


      if (target && $(target).length){

        // Ensure the target table is visible (works with the Filter By dropdown)
        var map = {
          '#highlevel-table': 'highlevel',
          '#pages-posts-headings-table': 'headings',
          '#pages-posts-metadata-table': 'pages_posts_metadata',
          '#media-library-search-table': 'media_library_search',
          '#pages-posts-backlinks-table': 'backlinks',
          '#pages-table': 'pages',
          '#external-images-table': 'external_images',
          '#city-state-table': 'city_state',
          '#non-stock-pages-table': 'non_stock_pages',
          '#posts-table': 'posts',
          '#video-table': 'video-table'
        };

        var selVal = map[target];
        var $sel = $('#table-selector');
        if ($sel.length && selVal){
          $sel.val(selVal);
          if (typeof window.changeTable === 'function') { window.changeTable(); }
        }

        function doScroll(){
          var $t = $(target);
          if (!$t.length) return;

          // Scroll to the container’s title row when it exists (pagination creates qc-table-title-row)
          var $container = $t.closest('.table-container');
          var $anchor = $container.find('.qc-table-title-row').first();
          if (!$anchor.length) $anchor = $container.children('h1,h2,h3,h4').first();
          if (!$anchor.length) $anchor = $t;

          var off = $anchor.offset();
          var top = off ? (off.top - 60) : 0;
          top = Math.max(0, top);

          $('html, body').stop(true).animate({ scrollTop: top }, 500);
        }

        // Wait for display toggles + layout shrink to finish before measuring offset
        requestAnimationFrame(function(){
          requestAnimationFrame(function(){

            // Jump to the first page that contains a "problem" row
            jumpPagerToFirstProblem(target, key);

            // Now scroll to the table header (or title row)
            doScroll();

            // One extra safety pass in case a late reflow clamps scroll
            setTimeout(doScroll, 60);
          });
        });
      }
    });
});


// === QC FLOATING BUG BEHAVIOR ===
jQuery(function($){
    var $bug = $('#qc-bug');
    var $pill = $('#qc-bug-collapsed');
    var $close = $('#qc-bug-close');
    var $topBtn = $('#qc-bug-top');

    $topBtn.on('click', function(e){
        e.preventDefault();
    
        // Reset table dropdown to "All"
        var $sel = $('#table-selector');
        if ($sel.length) {
            $sel.val('all');
            if (typeof window.changeTable === 'function') {
                window.changeTable();
            }
        }
    
        // Scroll to top
        window.scrollTo({ top: 0, behavior: 'smooth' });
    });

    function showBug(){
        $pill.removeClass('qc-pill--open');
        $bug.addClass('qc-bug--open');
    }

    function showPill(){
        $bug.removeClass('qc-bug--open');
        $pill.addClass('qc-pill--open');
    }

    function hideBoth(){
        $bug.removeClass('qc-bug--open');
        $pill.removeClass('qc-pill--open');
    }

    function onScroll(){
        var y = window.scrollY || document.documentElement.scrollTop || 0;

        if (y > 80){
            if ($bug.data('qcCollapsed')) showPill();
            else showBug();
        } else {
            hideBoth(); // banner is visible at the top
        }
    }

    $(window).on('scroll', onScroll);
    onScroll();

    $close.on('click', function(e){
        e.preventDefault();
        $bug.data('qcCollapsed', true);
        // Only show pill if user is scrolled down
        var y = window.scrollY || document.documentElement.scrollTop || 0;
        if (y > 80) showPill();
        else hideBoth();
    });

    $pill.on('click', function(){
        $bug.data('qcCollapsed', false);
        showBug();
    });
});

  function qcCountTableRows($container){
    if (!$container || !$container.length) return 0;

    // Count data rows only (ignore any title rows / header rows outside tbody)
    var $rows = $container.find('table tbody tr');

    // If your tables sometimes include non-data rows in tbody (rare), you can filter here.
    return $rows.length;
  }

    function qcUpdateImagesTooLargeBadge(){
      var $t100 = $('#media-library-table');        // >100KB
      var $t200 = $('#media-library-large-table');  // >200KB
    
      var c100 = $t100.length ? $t100.find('table tbody tr').length : 0;
      var c200 = $t200.length ? $t200.find('table tbody tr').length : 0;
    
      // If any rows exist in 200KB table, use that count; otherwise use 100KB
      var count = (c200 > 0) ? c200 : c100;
      var sub   = (c200 > 0) ? '>200KB' : '>100KB';
    
      // Ensure we ALWAYS output a number (including 0)
      if (!Number.isFinite(count)) {
        count = 0;
      }
    
      $('[data-qc-badge="imagesTooLarge"]').each(function(){
        var $b = $(this);
    
        // Always update the number
        var $strong = $b.find('strong').first();
        if ($strong.length) {
          $strong.text(count);
        }
    
        // Update sublabel if present (top banner)
        var $sub = $b.find('.qc-badge__sub').first();
        if ($sub.length) {
          $sub.text(sub);
        }
      });
    }

  // Run once on load, and again whenever tables are toggled/paginated/etc.
  qcUpdateImagesTooLargeBadge();
  $(document).on('qc:pagechange', qcUpdateImagesTooLargeBadge);

</script>
        </div>

        <div id="non-stock-pages-table" class="table-container" style="display:none;">
            <h3>Non-Stock or Modified Content</h3>

            <?php if (is_wp_error($qc_master_comparison)) : ?>
                <div class="notice notice-error inline"><p><strong>Comparison unavailable:</strong> <?php echo esc_html($qc_master_comparison->get_error_message()); ?></p></div>
            <?php else : ?>
                <?php $qc_is_legacy_best_match = isset($qc_stock_snapshot['origin']) && $qc_stock_snapshot['origin'] === 'legacy_catalog'; ?>
                <?php $qc_detected_master_candidates = isset($qc_stock_snapshot['detected_master_candidates']) && is_array($qc_stock_snapshot['detected_master_candidates']) ? array_values($qc_stock_snapshot['detected_master_candidates']) : array(); ?>
                <p class="description" style="margin:6px 0 12px;">
                    <strong>Stock:</strong> <?php echo esc_html($qc_stock_percent . '%'); ?>
                    &nbsp;|&nbsp; <strong>Non-Stock:</strong> <?php echo esc_html($qc_non_stock_percent . '%'); ?>
                    <?php if ($qc_is_legacy_best_match) : ?>
                        &nbsp;|&nbsp; <strong>Legacy best-match:</strong> Yes
                    <?php else : ?>
                        &nbsp;|&nbsp; <strong>Baseline:</strong> Cloned snapshot
                    <?php endif; ?>
                    <?php if (count($qc_detected_master_candidates) === 1) : ?>
                        <?php $qc_detected_master = $qc_detected_master_candidates[0]; ?>
                        &nbsp;|&nbsp; <strong>Detected Content Set<?php if ($qc_is_legacy_best_match) : ?> (Match: <?php echo esc_html(number_format_i18n((float) ($qc_stock_snapshot['legacy_match_score'] ?? 0) * 100, 1) . '%'); ?>)<?php endif; ?>:</strong> <?php echo esc_html((string) ($qc_detected_master['content_set'] ?? '---')); ?>
                    <?php elseif (count($qc_detected_master_candidates) > 1) : ?>
                        <?php $qc_detected_content_sets = array_values(array_unique(array_map(function($candidate) { return (string) ($candidate['content_set'] ?? '---'); }, $qc_detected_master_candidates))); ?>
                        &nbsp;|&nbsp; <strong>Detected Content Set<?php if ($qc_is_legacy_best_match) : ?> (Match: <?php echo esc_html(number_format_i18n((float) ($qc_stock_snapshot['legacy_match_score'] ?? 0) * 100, 1) . '%'); ?>)<?php endif; ?>:</strong> <?php echo esc_html(implode(' / ', $qc_detected_content_sets)); ?>
                    <?php else : ?>
                        &nbsp;|&nbsp; <strong>Detected Content Set<?php if ($qc_is_legacy_best_match) : ?> (Match: <?php echo esc_html(number_format_i18n((float) ($qc_stock_snapshot['legacy_match_score'] ?? 0) * 100, 1) . '%'); ?>)<?php endif; ?>:</strong> Unknown
                    <?php endif; ?>
                    <?php $qc_blog_candidates = isset($qc_blog_detection['candidates']) && is_array($qc_blog_detection['candidates']) ? array_values($qc_blog_detection['candidates']) : array(); ?>
                    <?php if (($qc_blog_detection['status'] ?? '') === 'matched' && count($qc_blog_candidates) === 1) : ?>
                        <?php $qc_detected_blog_master = $qc_blog_candidates[0]; ?>
                        &nbsp;|&nbsp; <strong>Detected Blog Set (Match: <?php echo esc_html(number_format_i18n((float) ($qc_blog_detection['score'] ?? 0) * 100, 1) . '%'); ?>):</strong> <?php echo esc_html((string) ($qc_detected_blog_master['blog_set'] ?? '---')); ?>
                    <?php elseif (($qc_blog_detection['status'] ?? '') === 'matched' && count($qc_blog_candidates) > 1) : ?>
                        <?php $qc_detected_blog_sets = array_values(array_unique(array_map(function($candidate) { return (string) ($candidate['blog_set'] ?? '---'); }, $qc_blog_candidates))); ?>
                        &nbsp;|&nbsp; <strong>Detected Blog Set (Match: <?php echo esc_html(number_format_i18n((float) ($qc_blog_detection['score'] ?? 0) * 100, 1) . '%'); ?>):</strong> <?php echo esc_html(implode(' / ', $qc_detected_blog_sets)); ?>
                    <?php elseif (($qc_blog_detection['status'] ?? '') === 'no_posts') : ?>
                        &nbsp;|&nbsp; <strong>Detected Blog Set:</strong> None
                    <?php else : ?>
                        &nbsp;|&nbsp; <strong>Detected Blog Set:</strong> Unknown
                    <?php endif; ?>
                </p>
            <?php endif; ?>

            <p class="qc-stock-content-toggle" style="margin:0 0 12px;">
                <button type="button" class="button button-primary" data-qc-stock-panel="pages">Pages (<?php echo (int) count($qc_non_stock_pages); ?>)</button>
                <button type="button" class="button" data-qc-stock-panel="blogs">Blogs (<?php echo (int) count($qc_non_stock_blogs); ?>)</button>
            </p>

            <div data-qc-stock-content-panel="pages">
            <?php if (!is_wp_error($qc_master_comparison) && empty($qc_non_stock_pages)) : ?>
                <p><strong>No new or materially modified published pages were found.</strong></p>
            <?php elseif (!is_wp_error($qc_master_comparison)) : ?>
                <table class="wp-list-table widefat fixed striped inception-table">
                    <thead>
                        <tr>
                            <th>#</th>
                            <th>Page Title</th>
                            <th>Page Path</th>
                            <th>Result</th>
                            <th>Content Match</th>
                            <th>Page Links</th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php foreach ($qc_non_stock_pages as $qc_index => $qc_non_stock_page) : ?>
                            <tr class="qc-issue">
                                <td><?php echo (int) $qc_index + 1; ?></td>
                                <td><?php echo esc_html($qc_non_stock_page['title']); ?></td>
                                <td><code><?php echo esc_html($qc_non_stock_page['path']); ?></code></td>
                                <td><?php echo esc_html($qc_non_stock_page['result']); ?></td>
                                <td><?php echo $qc_non_stock_page['similarity'] === null ? '—' : esc_html(number_format_i18n($qc_non_stock_page['similarity'] * 100, 1) . '%'); ?></td>
                                <td>
                                    <a href="<?php echo esc_url($qc_non_stock_page['permalink']); ?>" target="_blank" rel="noopener noreferrer">View</a>
                                    <?php if (!empty($qc_non_stock_page['edit'])) : ?>
                                        <span style="opacity:.6;">|</span>
                                        <a href="<?php echo esc_url($qc_non_stock_page['edit']); ?>" target="_blank" rel="noopener noreferrer">Edit</a>
                                    <?php endif; ?>
                                </td>
                            </tr>
                        <?php endforeach; ?>
                    </tbody>
                </table>
            <?php endif; ?>
            </div>

            <div data-qc-stock-content-panel="blogs" style="display:none;">
                <?php if (!$qc_blog_comparison_available) : ?>
                    <p><strong>A blog baseline could not be identified, so individual blog posts cannot be compared.</strong></p>
                <?php else : ?>
                    <p class="description" style="margin:0 0 12px;">
                        <strong>Stock Blogs:</strong> <?php echo esc_html($qc_stock_blog_percent . '%'); ?>
                        &nbsp;|&nbsp; <strong>Non-Stock Blogs:</strong> <?php echo esc_html($qc_non_stock_blog_percent . '%'); ?>
                    </p>
                    <?php if (empty($qc_non_stock_blogs)) : ?>
                        <p><strong>No new or materially modified published blog posts were found.</strong></p>
                    <?php else : ?>
                        <table class="wp-list-table widefat fixed striped inception-table">
                            <thead>
                                <tr>
                                    <th>#</th>
                                    <th>Blog Title</th>
                                    <th>Blog Path</th>
                                    <th>Result</th>
                                    <th>Content Match</th>
                                    <th>Blog Links</th>
                                </tr>
                            </thead>
                            <tbody>
                                <?php foreach ($qc_non_stock_blogs as $qc_index => $qc_non_stock_blog) : ?>
                                    <tr class="qc-issue">
                                        <td><?php echo (int) $qc_index + 1; ?></td>
                                        <td><?php echo esc_html($qc_non_stock_blog['title']); ?></td>
                                        <td><code><?php echo esc_html($qc_non_stock_blog['path']); ?></code></td>
                                        <td><?php echo esc_html($qc_non_stock_blog['result']); ?></td>
                                        <td><?php echo $qc_non_stock_blog['similarity'] === null ? '—' : esc_html(number_format_i18n($qc_non_stock_blog['similarity'] * 100, 1) . '%'); ?></td>
                                        <td>
                                            <a href="<?php echo esc_url($qc_non_stock_blog['permalink']); ?>" target="_blank" rel="noopener noreferrer">View</a>
                                            <?php if (!empty($qc_non_stock_blog['edit'])) : ?>
                                                <span style="opacity:.6;">|</span>
                                                <a href="<?php echo esc_url($qc_non_stock_blog['edit']); ?>" target="_blank" rel="noopener noreferrer">Edit</a>
                                            <?php endif; ?>
                                        </td>
                                    </tr>
                                <?php endforeach; ?>
                            </tbody>
                        </table>
                    <?php endif; ?>
                <?php endif; ?>
            </div>

            <script>
            (function () {
                var container = document.getElementById('non-stock-pages-table');
                if (!container) return;
                var buttons = container.querySelectorAll('[data-qc-stock-panel]');
                var panels = container.querySelectorAll('[data-qc-stock-content-panel]');
                Array.prototype.forEach.call(buttons, function (button) {
                    button.addEventListener('click', function () {
                        var selected = button.getAttribute('data-qc-stock-panel');
                        Array.prototype.forEach.call(buttons, function (candidate) {
                            candidate.classList.toggle('button-primary', candidate === button);
                        });
                        Array.prototype.forEach.call(panels, function (panel) {
                            panel.style.display = panel.getAttribute('data-qc-stock-content-panel') === selected ? 'block' : 'none';
                        });
                    });
                });
            }());
            </script>

            <?php if (isset($qc_stock_snapshot['origin']) && $qc_stock_snapshot['origin'] === 'legacy_catalog') : ?>
                <div id="inception-qc-browser-refresh-status" class="notice notice-info inline" style="display:none;"><p></p></div>
                <script>
                (function () {
                    'use strict';
                    var statusBox = document.getElementById('inception-qc-browser-refresh-status');
                    var statusText = statusBox ? statusBox.querySelector('p') : null;
                    var restBase = <?php echo wp_json_encode(rest_url('wp/v2/pages')); ?>;
                    var postsRestBase = <?php echo wp_json_encode(rest_url('wp/v2/posts')); ?>;
                    var ajaxUrl = <?php echo wp_json_encode(admin_url('admin-ajax.php')); ?>;
                    var nonce = <?php echo wp_json_encode(wp_create_nonce('inception_techboost_qc_browser_pages')); ?>;

                    function showStatus(message, isError) {
                        if (!statusBox || !statusText) return;
                        statusBox.style.display = 'block';
                        statusBox.className = 'notice ' + (isError ? 'notice-error' : 'notice-info') + ' inline';
                        statusText.textContent = message;
                    }

                    async function loadPublicPages() {
                        var pages = [];
                        for (var pageNumber = 1; pageNumber <= 10; pageNumber++) {
                            var url = new URL(restBase, window.location.origin);
                            url.searchParams.set('per_page', '100');
                            url.searchParams.set('page', String(pageNumber));
                            url.searchParams.set('_fields', 'id,link,content');
                            var response = await fetch(url.toString(), { credentials: 'same-origin', cache: 'no-store' });
                            if (!response.ok) throw new Error('Public REST request returned HTTP ' + response.status + '.');
                            var batch = await response.json();
                            if (!Array.isArray(batch)) throw new Error('Public REST response was not a page list.');
                            pages = pages.concat(batch);
                            var totalPages = parseInt(response.headers.get('X-WP-TotalPages') || '1', 10);
                            if (pageNumber >= totalPages) break;
                        }
                        return pages;
                    }

                    async function loadPublicPosts() {
                        var posts = [];
                        for (var pageNumber = 1; pageNumber <= 10; pageNumber++) {
                            var url = new URL(postsRestBase, window.location.origin);
                            url.searchParams.set('per_page', '100');
                            url.searchParams.set('page', String(pageNumber));
                            url.searchParams.set('_fields', 'id,link,content');
                            var response = await fetch(url.toString(), { credentials: 'same-origin', cache: 'no-store' });
                            if (!response.ok) throw new Error('Public posts REST request returned HTTP ' + response.status + '.');
                            var batch = await response.json();
                            if (!Array.isArray(batch)) throw new Error('Public REST response was not a post list.');
                            posts = posts.concat(batch);
                            var totalPages = parseInt(response.headers.get('X-WP-TotalPages') || '1', 10);
                            if (pageNumber >= totalPages) break;
                        }
                        return posts;
                    }

                    async function refreshLegacyFingerprints() {
                        try {
                            showStatus('Checking this site\u2019s public page content against the saved legacy baseline\u2026', false);
                            var collections = await Promise.all([loadPublicPages(), loadPublicPosts()]);
                            var pages = collections[0];
                            var posts = collections[1];
                            var form = new FormData();
                            form.append('action', 'inception_techboost_store_qc_browser_pages');
                            form.append('nonce', nonce);
                            form.append('pages_json', JSON.stringify(pages));
                            form.append('posts_json', JSON.stringify(posts));
                            var response = await fetch(ajaxUrl, {
                                method: 'POST',
                                credentials: 'same-origin',
                                body: form,
                                cache: 'no-store'
                            });
                            var result = await response.json();
                            if (!response.ok || !result.success) {
                                throw new Error(result && result.data && result.data.message ? result.data.message : 'WordPress could not save the public page fingerprints.');
                            }
                            if (result.data.changed) {
                                showStatus('Public content fingerprints updated. The corrected comparison will be used the next time you manually refresh this report.', false);
                                return;
                            }
                            statusBox.style.display = 'none';
                        } catch (error) {
                            showStatus('Legacy content refresh failed: ' + (error && error.message ? error.message : String(error)), true);
                        }
                    }

                    refreshLegacyFingerprints();
                }());
                </script>
            <?php endif; ?>
        </div>
        
        <style>
            /* Row striping still handled by stripe-odd / stripe-even */
            #pages-posts-headings-table .inception-table tbody tr.stripe-odd  { background: #f6f7f7; }
            #pages-posts-headings-table .inception-table tbody tr.stripe-even { background: #ffffff; }
            
            /* Remove thin separator line between summary and headings rows */
            #pages-posts-headings-table .inception-table tbody tr.stripe-odd td { border-bottom: none !important; }
            #pages-posts-headings-table .inception-table tbody tr.stripe-odd + tr td.heading-seq { border-top: none !important; }
            #pages-posts-headings-table .inception-table tbody tr.stripe-even td { border-bottom: none !important; }
            #pages-posts-headings-table .inception-table tbody tr.stripe-even + tr td.heading-seq { border-top: none !important; }
        
            /* Headings table styles */
            #pages-posts-headings-table .inception-table thead th { white-space: nowrap; }
            #pages-posts-headings-table .inception-table td { vertical-align: top; }
            #pages-posts-headings-table .heading-seq {
              font-size: 12px;
              line-height: 1.45;
              padding: 10px 12px 12px 12px;
              background: transparent; /* inherit row color */
              border-top: 1px solid #e2e2e2;
            }
            /* Prevent global "anywhere" wrapping from breaking headings into single letters */
            #pages-posts-headings-table td.heading-seq{
              overflow-wrap: normal !important;
              word-break: normal !important;
              white-space: normal !important; /* allow row to grow vertically */
            }

            /* Indent wrapped lines as if the level label were present */
            #pages-posts-headings-table .heading-line{
              display: flex;
              align-items: flex-start;
              flex-wrap: nowrap;
              gap: 0;
              max-width: 100%;
            }
            #pages-posts-headings-table .level-label{
              flex: 0 0 28px;
              min-width: 28px;
              margin-right: 6px;
              white-space: nowrap;
            }
            #pages-posts-headings-table .heading-chips{
              display: flex;
              flex-wrap: wrap;      /* wraps chips onto next line */
              gap: 4px 6px;
              max-width: calc(100% - 34px);
            }

            /* Keep each heading chip unbroken (wrap happens between chips) */
            #pages-posts-headings-table .heading-chip{
              white-space: nowrap;
              margin: 0;            /* spacing handled by gap */
            }

            #pages-posts-headings-table .level-label { display: inline-block; min-width: 28px; font-weight: 600; }
            #pages-posts-headings-table .heading-chip {
              display: inline-block;
              border: 1px solid #d7d7d7;
              border-radius: 4px;
              padding: 2px 6px;
              margin: 2px 6px 2px 0;
              font-family: Menlo, Consolas, monospace;
            }
        
            /* RED border for multiple H1s (thicker) */
            #pages-posts-headings-table .heading-chip.warn { border: 3px solid #d80000; color: inherit; font-weight: 600; }
        
            /* Bold + Red H1 count cell in summary when multiple H1s */
            #pages-posts-headings-table td.h1-multi { color: #d80000; font-weight: 700; }
        
            #pages-posts-headings-table .muted { color: #666; }
        
            /* ORANGE for City/State H1 rule (chips only) */
            #pages-posts-headings-table .heading-chip.bad { color: #d96f00; font-weight: 700; }
            
            /* HP threshold: RED BORDER ONLY (match multi-H1 style, no red text) */
            #pages-posts-headings-table .heading-chip.hp-bad{
              border: 3px solid #d80000;
              color: inherit;        /* keep normal text color */
              font-weight: 700; /* do NOT bold */
            }
            
            #pages-posts-headings-table td.hp-bad{
              color: #d80000;
              font-weight: 700;
            }
            
            /* Conflict: warn + bad → orange text, red thicker border (City/State H1 rule) */
            #pages-posts-headings-table .heading-chip.warn.bad,
            #pages-posts-headings-table .heading-chip.bad.warn{
              color: #d96f00;
              border: 3px solid #d80000;
            }
            
            /* If something is both warn and hp-bad, keep it RED */
            #pages-posts-headings-table .heading-chip.warn.hp-bad,
            #pages-posts-headings-table .heading-chip.hp-bad.warn{
              color: #d80000;
              border: 3px solid #d80000;
            }
        
            /* Banner above table showing detected City & State */
            #pages-posts-headings-table .loc-banner {
              display: inline-block;
              margin: 4px 0 10px 0;
              padding: 6px 10px;
              background: #f6f7f7;
              border: 1px solid #e2e2e2;
              border-radius: 6px;
              font-size: 13px;
            }
            #pages-posts-headings-table .loc-banner strong { font-weight: 700; }
            #pages-posts-headings-table .loc-banner .src { color: #666; font-style: italic; margin-left: 6px; }
        
/* === QC TABLE WIDTH NORMALIZATION === */
.table-container,
.wp-list-table {
    width: 100% !important;
    max-width: 100% !important;
}

.wp-list-table {
    table-layout: fixed;
}

.wp-list-table th,
.wp-list-table td {
    word-wrap: break-word;
    overflow-wrap: anywhere;
}

.wp-list-table th {
    white-space: nowrap;
}


/* === QC FLOATING SITE CHECKS BUG === */
:root{
  --qcBugWidth: 230px;
}
.qc-bug{
  position: fixed;
  top: 110px;
  right: 14px;
  width: var(--qcBugWidth);
  z-index: 9999;
  display: none;
  background: #fff;
  border: 1px solid rgba(0,0,0,.08);
  border-radius: 14px;
  box-shadow: 0 10px 30px rgba(0,0,0,.10);
  padding: 10px 10px 8px;
}
.qc-bug.qc-bug--open{ display:block; }
.qc-bug__header{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:8px;
  margin-bottom:8px;
}
.qc-bug__title{
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .02em;
  text-transform: uppercase;
  opacity: .85;
}
.qc-bug__btn{
  border: 0;
  background: transparent;
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  padding: 6px 8px;
  border-radius: 10px;
}
.qc-bug__btn:hover{ background: rgba(0,0,0,.06); }

.qc-bug__items{
  display:flex;
  flex-direction:column;
  gap:6px;
}
.qc-bug__items .qc-badge{
  display:flex;
  justify-content:space-between;
  align-items:center;
  width:100%;
}
.qc-bug__items .qc-badge strong{ margin-left:8px; }

.qc-bug__collapsed{
  position: fixed;
  top: 140px;
  right: 14px;
  z-index: 9999;
  display:none;
  border-radius: 999px;
  border: 1px solid rgba(0,0,0,.08);
  background:#fff;
  box-shadow: 0 10px 30px rgba(0,0,0,.10);
  padding: 10px 12px;
  font-size: 12px;
  font-weight: 700;
  cursor:pointer;
}
.qc-bug__collapsed:hover{ background: rgba(0,0,0,.04); }

/* Reserve space so the bug doesn't cover table content on larger screens */
@media (min-width: 1024px){
  .wrap{ padding-right: calc(var(--qcBugWidth) + 28px); }
}
/* On smaller screens, don't reserve space and keep bug narrower */
@media (max-width: 1023px){
  :root{ --qcBugWidth: 200px; }
}


/* === QC BUG BUTTON SIZE FIX === */
.qc-bug__items .qc-badge{
  font-size: 12px;
  padding: 6px 8px;
  border-radius: 10px;
}
.qc-bug{
  box-sizing: border-box;
}


/* === QC BUG COMPACT BADGES === */
.qc-bug__items{ gap:4px; }
.qc-bug__items .qc-badge{
  font-size: 11px;
  padding: 5px 6px;
  border-radius: 10px;
  line-height: 1.1;
  justify-content: space-between;
}
.qc-bug__items .qc-badge strong{
  display:inline-block;
  width:4ch;
  text-align:right;
  font-variant-numeric: tabular-nums;
  margin-left:0;
}



</style>
        
        <?php
        /**
         * Utility: Extract headings (H1–H6) from post content
         */
        if ( ! function_exists( 'inception_extract_headings' ) ) {
            function inception_extract_headings( $post_id ) {
                $raw = get_post_field( 'post_content', $post_id );
            
                $result = array(
                    'counts' => array(1=>0,2=>0,3=>0,4=>0,5=>0,6=>0),
                    'list'   => array(),
                );
            
                // --- NEW: only use front-end rendered HTML for HP/LP pages (to avoid slowing down the report) ---
                $html = '';
            
                $post_type = get_post_type( $post_id );
                $is_page   = ($post_type === 'page');
            
                // Identify the actual front page
                $qc_front_page_id = (int) get_option('page_on_front');
            
                // Title-based flags (robust to hyphen/en-dash/em-dash and weird spaces)
                $title_raw     = (string) get_the_title( $post_id );
                $title_decoded = html_entity_decode($title_raw, ENT_QUOTES, 'UTF-8');
            
                // Matches: "- HP", "-HP", "– HP", "—HP", including NBSP/zero-width spaces between dash and HP
                $is_hp_title = (bool) preg_match(
                    '/[\p{Pd}\-](?:[\s\x{00A0}\x{202F}\x{2007}\x{2009}\x{200A}\x{200B}\x{FEFF}]*)hp\b/ui',
                    $title_decoded
                );
            
                // Matches "LP" as a standalone token (so it won’t match "help" etc.)
                $is_lp_title = (bool) preg_match('/\blp\b/ui', $title_decoded);
            
                // Only do the expensive fetch for: actual home page, "- HP" pages, or "LP" pages (pages only)
                $needs_frontend_fetch = $is_page && (
                    ($qc_front_page_id > 0 && (int)$post_id === $qc_front_page_id) ||
                    $is_hp_title ||
                    $is_lp_title
                );
            
                if ( $needs_frontend_fetch ) {
                    $permalink = get_permalink( $post_id );
                    if ( $permalink ) {
                        $resp = wp_remote_get( $permalink, array(
                            'timeout' => 15,
                            'headers' => array(
                                // Force a desktop-ish render; builders often change output based on UA/context
                                'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120 Safari/537.36',
                            ),
                        ) );
            
                        if ( !is_wp_error($resp) && (int) wp_remote_retrieve_response_code($resp) === 200 ) {
                            $body = (string) wp_remote_retrieve_body($resp);
                            if ( strlen($body) > 0 ) {
                                $html = $body;
                            }
                        }
                    }
                }
            
                // Fallback: fast original behavior (admin-side content render) for all other pages/posts
                if ( empty( $html ) ) {
                    $html = apply_filters( 'the_content', $raw );
                }
            
                if ( empty( $html ) ) {
                    return $result;
                }
            
                $dom = new DOMDocument();
                libxml_use_internal_errors(true);
                $dom->loadHTML( '<?xml encoding="utf-8" ?>' . $html );
                libxml_clear_errors();
                
                // --- NEW: if we parsed front-end HTML, remove headings inside header/footer/nav so theme chrome doesn't pollute counts ---
                $xpath = new DOMXPath($dom);
                $removeNodes = $xpath->query('//header//h1|//header//h2|//header//h3|//header//h4|//header//h5|//header//h6'
                    . '|//footer//h1|//footer//h2|//footer//h3|//footer//h4|//footer//h5|//footer//h6'
                    . '|//nav//h1|//nav//h2|//nav//h3|//nav//h4|//nav//h5|//nav//h6');
                if ($removeNodes && $removeNodes->length) {
                    foreach ($removeNodes as $n) {
                        if ($n->parentNode) {
                            $n->parentNode->removeChild($n);
                        }
                    }
                }
                
                for ( $i = 1; $i <= 6; $i++ ) {
                    $nodes = $dom->getElementsByTagName( 'h' . $i );
                    if ( $nodes && $nodes->length ) {
                        foreach ( $nodes as $node ) {
                            $text = trim( wp_strip_all_tags( $dom->saveHTML( $node ) ) );
                            if ( $text === '' ) { $text = trim( $node->textContent ); }
                            $result['counts'][$i]++;
                            $result['list'][] = array( 'level' => $i, 'text' => $text );
                        }
                    }
                }
        
                return $result;
            }
        }
        
        global $wpdb;
        
        /**
         * Get most-recent City/State from {prefix}business_info if available and non-empty.
         * Returns ['city' => '...', 'state' => '...'] or null.
         */
        function inception_get_latest_city_state( $wpdb ) {
            $table = $wpdb->prefix . 'business_info';
        
            // Safer table-exists check (works even if information_schema is restricted)
            $exists = $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table ) );
            if ( $exists !== $table ) {
                return null;
            }
        
            // Ensure required columns exist
            $cols1 = $wpdb->get_col( "SHOW COLUMNS FROM `$table` LIKE 'address_locality'" );
            $cols2 = $wpdb->get_col( "SHOW COLUMNS FROM `$table` LIKE 'address_region'" );
            if ( empty($cols1) || empty($cols2) ) { return null; }
        
            // Choose a reasonable "most recent" column if present
            $order_candidates = array('updated_at','modified','modified_at','created_at','date','ID','id');
            $existing_cols = $wpdb->get_col( "SHOW COLUMNS FROM `$table`" );
            $order_by = '';
            foreach ( $order_candidates as $cand ) {
                if ( in_array( $cand, $existing_cols, true ) ) {
                    $order_by = "ORDER BY `$cand` DESC";
                    break;
                }
            }
        
            // Fetch most recent non-empty city/state
            $row = $wpdb->get_row("
                SELECT address_locality AS city, address_region AS state
                FROM `$table`
                WHERE address_locality IS NOT NULL AND address_locality <> ''
                  AND address_region   IS NOT NULL AND address_region   <> ''
                $order_by
                LIMIT 1
            ", ARRAY_A );
        
            if ( empty($row) ) { return null; }
        
            return array(
                'city'  => trim( (string) $row['city'] ),
                'state' => trim( (string) $row['state'] ),
            );
        }
        
        /**
         * Recursively search JSON (object/array) for addressLocality/addressRegion.
         */
        if ( ! function_exists( 'inception_find_city_state_in_json' ) ) {
            function inception_find_city_state_in_json( $data, &$city = null, &$state = null ) {
                if ( is_array( $data ) ) {
                    foreach ( $data as $v ) {
                        inception_find_city_state_in_json( $v, $city, $state );
                    }
                } elseif ( is_object( $data ) ) {
                    // Direct fields
                    if ( isset( $data->address ) ) {
                        inception_find_city_state_in_json( $data->address, $city, $state );
                    }
                    if ( isset( $data->addressLocality ) && ! $city ) { $city  = trim( (string) $data->addressLocality ); }
                    if ( isset( $data->addressRegion )   && ! $state ) { $state = trim( (string) $data->addressRegion ); }
                    // Scan all properties
                    foreach ( $data as $k => $v ) {
                        if ( is_array($v) || is_object($v) ) {
                            inception_find_city_state_in_json( $v, $city, $state );
                        }
                    }
                }
            }
        }
        
        /**
         * Try to extract City/State from the H-Card Themer Layout (/fl-theme-layout/h-card).
         * Looks in JSON-LD <script> blocks and common schema/microformat attributes.
         * Returns ['city'=>..., 'state'=>...] or null.
         */
        if ( ! function_exists( 'inception_get_city_state_from_hcard' ) ) {
            function inception_get_city_state_from_hcard() {
                // Try rendering the layout via shortcode first (BB/Themer installed)
                $html = do_shortcode( '[fl_builder_insert_layout slug="h-card"]' );
        
                // Fallback: read the fl-theme-layout post if shortcode unavailable
                if ( empty( $html ) ) {
                    $hcard = get_page_by_path( 'h-card', OBJECT, 'fl-theme-layout' );
                    if ( $hcard ) {
                        $html = apply_filters( 'the_content', $hcard->post_content );
                    }
                }
        
                if ( empty( $html ) ) { return null; }
        
                // 1) JSON-LD parsing
                if ( preg_match_all( '#<script[^>]+type=["\']application/ld\+json["\'][^>]*>(.*?)</script>#is', $html, $m ) ) {
                    foreach ( $m[1] as $json ) {
                        $json = trim( $json );
                        $data = json_decode( $json );
                        if ( $data ) {
                            $city = $state = null;
                            inception_find_city_state_in_json( $data, $city, $state );
                            if ( $city || $state ) {
                                return array( 'city' => (string)$city, 'state' => (string)$state );
                            }
                        }
                    }
                }
        
                // 2) Microformat/Schema attributes in HTML
                $dom = new DOMDocument();
                libxml_use_internal_errors( true );
                $dom->loadHTML( '<?xml encoding="utf-8" ?>' . $html );
                libxml_clear_errors();
        
                $city  = '';
                $state = '';
        
                $all = $dom->getElementsByTagName('*');
                foreach ( $all as $el ) {
                    if ( $city && $state ) break;
        
                    $text = trim( $el->textContent );
                    if ( $text === '' && $el->hasAttribute('content') ) {
                        $text = trim( $el->getAttribute('content') );
                    }
        
                    $cls  = ' ' . strtolower( $el->getAttribute('class') ) . ' ';
                    $id   = ' ' . strtolower( $el->getAttribute('id') ) . ' ';
                    $prop = strtolower( $el->getAttribute('itemprop') );
        
                    // Common selectors for City
                    if ( ! $city && (
                            strpos( $cls, ' p-locality ' ) !== false ||
                            strpos( $cls, ' locality ' )   !== false ||
                            $prop === 'addresslocality'
                        ) && $text !== '' ) {
                        $city = $text;
                    }
        
                    // Common selectors for State/Region
                    if ( ! $state && (
                            strpos( $cls, ' p-region ' ) !== false ||
                            strpos( $cls, ' region ' )   !== false ||
                            $prop === 'addressregion'
                        ) && $text !== '' ) {
                        $state = $text;
                    }
        
                    // Fallback: sometimes classes like "h-card" wrap spans with data-region/locality ids
                    if ( ! $city && ( strpos( $id, ' locality ' ) !== false || strpos( $id, ' city ' ) !== false ) && $text !== '' ) {
                        $city = $text;
                    }
                    if ( ! $state && ( strpos( $id, ' region ' ) !== false || strpos( $id, ' state ' ) !== false ) && $text !== '' ) {
                        $state = $text;
                    }
                }
        
                if ( $city || $state ) {
                    return array( 'city' => $city, 'state' => $state );
                }
        
                return null;
            }
        }
        
        /**
         * Return the site's selected "Header" menu object, if any.
         * Tries common header locations; falls back to common header-like slugs.
         */
        if ( ! function_exists( 'inception_get_selected_header_menu' ) ) {
            function inception_get_selected_header_menu() {
                $locations = get_nav_menu_locations();
        
                // Common header locations (filterable)
                $header_location_slugs = apply_filters('inception_header_menu_locations', array(
                    'primary','menu-1','header','header-menu','main','primary-menu',
                    'top','top-menu','site-header','main-nav'
                ));
        
                if ( is_array( $locations ) ) {
                    foreach ( $header_location_slugs as $loc ) {
                        if ( ! empty( $locations[ $loc ] ) ) {
                            $menu_obj = wp_get_nav_menu_object( (int) $locations[ $loc ] );
                            if ( $menu_obj && ! is_wp_error( $menu_obj ) ) {
                                return $menu_obj;
                            }
                        }
                    }
                }
        
                // Fallback: find a menu whose slug implies "main" header
                $all = wp_get_nav_menus();
                if ( ! empty( $all ) && ! is_wp_error( $all ) ) {
                    foreach ( $all as $m ) {
                        $slug = isset($m->slug) ? strtolower( (string) $m->slug ) : '';
                        if ( $slug === 'main-menu' || $slug === 'multi-main-menu' || preg_match('/^main-menu-location-\d+$/', $slug) ) {
                            return $m;
                        }
                    }
                }
        
                return null;
            }
        }
        
        /**
         * Inspect the selected header menu to infer location mode.
         * Returns ['mode'=>'multi'|'single'|'unknown', 'menu_label'=>'Menu Name or slug'].
         */
        if ( ! function_exists( 'inception_detect_site_location_mode' ) ) {
            function inception_detect_site_location_mode() {
                $menu = inception_get_selected_header_menu();
                if ( $menu ) {
                    $name = (string) ($menu->name ?? '');
                    $slug = (string) ($menu->slug ?? '');
                    $label = $name !== '' ? $name : $slug;
                    if ( preg_match('/multi/i', $name) || preg_match('/multi/i', $slug) ) {
                        return array( 'mode' => 'multi',  'menu_label' => $label );
                    }
                    return array( 'mode' => 'single', 'menu_label' => $label );
                }
                // No obvious header menu → treat as single for your rule
                return array( 'mode' => 'single', 'menu_label' => '(none)' );
            }
        }

        /**
         * Try to extract City/State from the HIPAA Privacy Policy page (/hipaa-privacy-policy/).
         * Expected first lines pattern:
         *   NOTICE OF PRIVACY PRACTICES
         *   CLINIC NAME - ADDRESS, CITY STATE ZIP
         *   FULL NAME D.C. - (000) 000-0000
         *
         * Returns ['city'=>..., 'state'=>...] or null.
         */
        if ( ! function_exists( 'inception_get_city_state_from_hipaa_page' ) ) {
            function inception_get_city_state_from_hipaa_page() {
                // Get the page by path (common slug). If your sites sometimes use the misspelling, try that too.
                $page = get_page_by_path( 'hipaa-privacy-policy', OBJECT, 'page' );
                if ( ! $page ) {
                    // Uncomment if some sites use this typo:
                    // $page = get_page_by_path( 'hippa-privacy-policy', OBJECT, 'page' );
                }
                if ( ! $page ) { return null; }
        
                // Render content
                $html = apply_filters( 'the_content', $page->post_content );
                if ( empty( $html ) ) { return null; }
        
                // Preserve intentional line breaks
                $html = preg_replace( '#<\s*br\s*/?\s*>#i', "\n", $html );
                $html = preg_replace( '#</\s*p\s*>#i', "\n", $html );
        
                // Strip to plain text and normalize whitespace
                $text = wp_strip_all_tags( $html );
                $text = preg_replace( "/\r\n|\r/", "\n", $text );     // normalize newlines
                $text = preg_replace( "/[ \t]+/", ' ', $text );       // collapse spaces
                $text = preg_replace( "/\n{2,}/", "\n", $text );      // collapse empty lines
                $lines = array_map( 'trim', explode( "\n", trim( $text ) ) );
        
                // Search lines for: "... , CITY STATE ZIP"
                // CITY: letters, dots, apostrophes, dashes, spaces (non-greedy)
                // STATE: 2+ alpha (code or name)
                // ZIP: 5 digits, optional -4
                $city = '';
                $state = '';
        
                foreach ( $lines as $ln ) {
                    if ( $city && $state ) break;
                    // Look for last comma, then city/state/zip
                    if ( preg_match( '/,\s*([A-Za-z\.\'\- ]+?)\s+([A-Za-z]{2,})\s+\d{5}(?:-\d{4})?\b/', $ln, $m ) ) {
                        $city  = trim( $m[1] );
                        $state = trim( $m[2] );
                        if ( $city !== '' && $state !== '' ) {
                            return array( 'city' => $city, 'state' => $state );
                        }
                    }
                }
        
                // If not found with ZIP present, try without ZIP (less strict)
                foreach ( $lines as $ln ) {
                    if ( $city && $state ) break;
                    if ( preg_match( '/,\s*([A-Za-z\.\'\- ]+?)\s+([A-Za-z]{2,})\b/', $ln, $m ) ) {
                        $city  = trim( $m[1] );
                        $state = trim( $m[2] );
                        if ( $city !== '' && $state !== '' ) {
                            return array( 'city' => $city, 'state' => $state );
                        }
                    }
                }
        
                return null;
            }
        }

        /** String normalize for contains checks */
        function inception_norm( $s ) {
            $s = wp_strip_all_tags( (string) $s );
            $s = strtolower( $s );
            $s = preg_replace('/[^a-z0-9\s]/u', ' ', $s);
            $s = preg_replace('/\s+/u', ' ', $s);
            return trim($s);
        }
        
        /** True if $text contains BOTH $city and $state (normalized) */
        function inception_h1_contains_city_state( $text, $city, $state ) {
            if ( $city === '' || $state === '' ) return false;
            $t = inception_norm($text);
            $c = inception_norm($city);
            $s = inception_norm($state);
            return ( $c !== '' && $s !== '' && strpos($t, $c) !== false && strpos($t, $s) !== false );
        }
        
        /* ---- Detect City/State once (DB → H-Card → Header Menu → HIPAA if Single) ---- */
        $inception_is_multi     = false;
        $inception_location_src = '';
        $inception_city         = '';
        $inception_state        = '';
        
        $inception_location = inception_get_latest_city_state( $wpdb );
        if ( $inception_location && ( $inception_location['city'] || $inception_location['state'] ) ) {
            $inception_city  = $inception_location['city'];
            $inception_state = $inception_location['state'];
            $inception_location_src = 'Database Table (Set in TechBoost H-Card Generator)';
        } else {
            $hcard_loc = inception_get_city_state_from_hcard();
            if ( $hcard_loc && ( !empty($hcard_loc['city']) || !empty($hcard_loc['state']) ) ) {
                $inception_city  = $hcard_loc['city']  ?? '';
                $inception_state = $hcard_loc['state'] ?? '';
                $inception_location_src = 'H-Card';
            } else {
                // Infer Single vs Multi from Header Menu
                $mode = inception_detect_site_location_mode(); // ['mode'=>'multi'|'single'|'unknown', 'menu_label'=>...]
                if ( $mode['mode'] === 'multi' ) {
                    // Multi-Location → ignore City/State in checks
                    $inception_is_multi = true;
                    $inception_city  = '';
                    $inception_state = '';
                    $inception_location_src = 'Header Menu (“' . esc_html( $mode['menu_label'] ) . '”) — This is a Multi-Location';
                } else {
                    // Single Location: one last attempt from HIPAA page header
                    $hipaa_loc = inception_get_city_state_from_hipaa_page();
                    if ( $hipaa_loc && ( !empty($hipaa_loc['city']) || !empty($hipaa_loc['state']) ) ) {
                        $inception_city  = $hipaa_loc['city']  ?? '';
                        $inception_state = $hipaa_loc['state'] ?? '';
                        $inception_location_src = 'HIPAA Privacy Policy Page';
                    } else {
                        // Still nothing → mark as Single Location (Not Found)
                        $inception_is_multi = false;
                        $inception_city  = '';
                        $inception_state = '';
                        $inception_location_src = 'Header Menu (“' . esc_html( $mode['menu_label'] ) . '”) — Single Location (Not Found)';
                    }
                }
            }
        }
        ?>
        
        <div id="pages-posts-headings-table" class="table-container" style="display:none;">
          <h3>Pages &amp; Blog Posts – Headings</h3>
        
          <?php
            // Show the detected City/State banner above the table
            $loc_label = trim( $inception_city . ( $inception_city && $inception_state ? ' ' : '' ) . $inception_state );
            if ( $loc_label !== '' ) {
                echo '<div class="loc-banner">Detected City &amp; State: <strong>' . esc_html( $loc_label ) . '</strong>';
                if ( $inception_location_src ) {
                    echo '<span class="src">source: ' . esc_html( $inception_location_src ) . '</span>';
                }
                echo '</div>';
            } else {
                if ( ! empty( $inception_is_multi ) ) {
                    // Multi-Location → explicitly say it's ignored
                    echo '<div class="loc-banner muted">Detected City &amp; State: <strong>Ignored</strong>';
                    if ( $inception_location_src ) {
                        echo '<span class="src">reason: ' . esc_html( $inception_location_src ) . '</span>';
                    }
                    echo '</div>';
                } else {
                    echo '<div class="loc-banner muted">Detected City &amp; State: <strong>Not Found (Single Location)</strong>';
                    if ( $inception_location_src ) {
                        echo '<span class="src">source: ' . esc_html( $inception_location_src ) . '</span>';
                    }
                    echo '</div>';
                }
            }
          ?>
        
          <p class="muted">Counts for H1–H6 and the ordered headings detected. <strong>Multiple H1s</strong> will have a red border. If <strong>CITY &amp; STATE</strong> are set and not detected in an H1, it will show in orange. Home page rows have a yellow background.</p>
        
          <?php
          // Query all pages & posts
          $pp_args = array(
              'post_type'      => array( 'page', 'post' ),
              'posts_per_page' => -1,
              'post_status'    => array( 'publish', 'draft', 'private', 'pending', 'future' ),
              'orderby'        => 'title',
              'order'          => 'ASC',
          );
          $pp_query = new WP_Query( $pp_args );
        
          if ( $pp_query->have_posts() ) {
              echo '<table class="wp-list-table widefat fixed striped inception-table">';
              echo '<thead><tr>';
              echo '<th style="width: auto; padding-left: 10px;">#</th>';
              echo '<th>Page/Post Title</th>';
              echo '<th style="width:110px;">Type</th>';
              echo '<th style="width:140px;">Page Links</th>';
              echo '<th style="width:60px;">H1</th>';
              echo '<th style="width:60px;">H2</th>';
              echo '<th style="width:60px;">H3</th>';
              echo '<th style="width:60px;">H4</th>';
              echo '<th style="width:60px;">H5</th>';
              echo '<th style="width:60px;">H6</th>';
              echo '</tr></thead><tbody>';
        
            // Identify the Home Page (front page)
            $qc_front_page_id = (int) get_option('page_on_front');

            $row = 1;
            while ( $pp_query->have_posts() ) {
                $pp_query->the_post();
            
                $pid   = get_the_ID();
                $title = get_the_title( $pid );
                $plink = get_permalink( $pid );
                $etype = get_post_type( $pid ) === 'page' ? 'Page' : 'Blog Post';
            
                $heads   = inception_extract_headings( $pid );
                $counts  = $heads['counts'];
                $has_multi_h1 = (int)$counts[1] > 1;
                
                // ADD THIS: HP Headings logic (Home page OR title contains "-HP"/"- HP"/"– HP"/"—HP" etc.)
                $is_page = (get_post_type($pid) === 'page');
                
                // More robust: decode entities and allow many common "invisible" spaces between dash and HP
                $qc_title_decoded = html_entity_decode((string) $title, ENT_QUOTES, 'UTF-8');
                $is_hp_title = (bool) preg_match(
                    '/[\p{Pd}\-](?:[\s\x{00A0}\x{202F}\x{2007}\x{2009}\x{200A}\x{200B}\x{FEFF}]*)hp\b/ui',
                    $qc_title_decoded
                );
                
                /**
                 * Treat any page whose slug (last URL segment) ends in a valid US state/territory code
                 * like "bradenton-fl" as an HP page for this report.
                 */
                $is_city_state_slug = false;
                
                $u = wp_parse_url($plink);
                $path = isset($u['path']) ? trim((string)$u['path'], '/') : '';
                if ($path !== '') {
                    $parts = explode('/', $path);
                    $last  = strtolower((string) end($parts)); // e.g. "bradenton-fl"
                
                    // Valid US state + DC + territories (commonly used)
                    $valid_state_codes = array(
                        'al','ak','az','ar','ca','co','ct','de','fl','ga','hi','id','il','in','ia','ks','ky','la','me','md',
                        'ma','mi','mn','ms','mo','mt','ne','nv','nh','nj','nm','ny','nc','nd','oh','ok','or','pa','ri','sc',
                        'sd','tn','tx','ut','vt','va','wa','wv','wi','wy',
                        'dc',
                        'pr','vi','gu','mp','as'
                    );
                
                    // city can be multi-word hyphenated; must end in "-{state}"
                    if (preg_match('/^([a-z0-9]+(?:-[a-z0-9]+)*)-([a-z]{2})$/', $last, $m)) {
                        $state = $m[2];
                        $is_city_state_slug = in_array($state, $valid_state_codes, true);
                    }
                }
                
                
                $is_hp_page = $is_page && (
                    ($qc_front_page_id > 0 && $pid === $qc_front_page_id) ||
                    $is_hp_title ||
                    $is_city_state_slug
                );

                // HP threshold flags (used for counter + styling)
                $hp_h1_too_many = $is_hp_page && ((int)$counts[1] > 1);
                $hp_h2_too_many = $is_hp_page && ((int)$counts[2] > 2);
                $hp_h3_too_many = $is_hp_page && ((int)$counts[3] > 3);
                $hp_h4_too_many = $is_hp_page && ((int)$counts[4] > 4);

                // Count HP errors PER PAGE (so H1 + H3 on same page counts as 2)
                $hp_error_count = 0;
                if ( $hp_h1_too_many ) { $hp_error_count++; }
                if ( $hp_h2_too_many ) { $hp_error_count++; }
                if ( $hp_h3_too_many ) { $hp_error_count++; }
                if ( $hp_h4_too_many ) { $hp_error_count++; }

                // "Any HP issue on this page?"
                $hp_heading_violation = ($is_hp_page && $hp_error_count > 0);
            
                // Stripe based on the page/post # (both rows for this entry share the same class)
                $stripe_class = ( $row % 2 === 1 ) ? 'stripe-odd' : 'stripe-even';
            
                // Main summary row
                echo '<tr class="' . esc_attr(
                        $stripe_class
                        . ($is_hp_page ? ' hp-row' : '')
                        . ($has_multi_h1 ? ' qc-issue' : '')
                    ) . '" data-qc-issue="' . ($has_multi_h1 ? 'multi-h1' : '') . '" data-qc-hp-headings="' . ($hp_heading_violation ? '1' : '0') . '" data-qc-hp-error-count="' . (int)$hp_error_count . '" data-qc-hp-h1="' . ($hp_h1_too_many ? '1' : '0') . '" data-qc-hp-h2="' . ($hp_h2_too_many ? '1' : '0') . '" data-qc-hp-h3="' . ($hp_h3_too_many ? '1' : '0') . '" data-qc-hp-h4="' . ($hp_h4_too_many ? '1' : '0') . '">';
                echo '<td>' . intval($row) . '</td>';
                echo '<td>' . esc_html($title) . '</td>';
                echo '<td>' . esc_html($etype) . '</td>';
                echo '<td>';
                echo '  <a href="' . esc_url( $plink ) . '" target="_blank">View</a> | ';
                echo '  <a href="' . esc_url( admin_url( 'post.php?post=' . $pid . '&action=edit' ) ) . '" target="_blank">Edit</a> | ';
                echo '  <a href="' . esc_url( $plink . '/?fl_builder&fl_builder_ui' ) . '" target="_blank">Builder</a>';
                echo '</td>';
            
                // H1–H6 counts (HP pages: H2/H3/H4 turn RED when over thresholds)
                for ( $i = 1; $i <= 6; $i++ ) {
                    $classes = array();

                    // Existing multi-H1 indicator (leave as-is)
                    if ( $i === 1 && $has_multi_h1 ) {
                        $classes[] = 'h1-multi';
                    }

                    // ADD THIS: HP-only red count cells for H2/H3/H4 (no orange)
                    if ( $is_hp_page ) {
                        if ( $i === 2 && $hp_h2_too_many ) { $classes[] = 'hp-bad'; }
                        if ( $i === 3 && $hp_h3_too_many ) { $classes[] = 'hp-bad'; }
                        if ( $i === 4 && $hp_h4_too_many ) { $classes[] = 'hp-bad'; }
                    }

                    $cls = $classes ? ' class="' . esc_attr( implode(' ', $classes) ) . '"' : '';
                    echo '<td' . $cls . '>' . intval( $counts[$i] ) . '</td>';
                }
                echo '</tr>';
            
                // Group headings by level
                $by_level = array(1=>array(),2=>array(),3=>array(),4=>array(),5=>array(),6=>array());
                if ( ! empty( $heads['list'] ) ) {
                    foreach ( $heads['list'] as $h ) {
                        $lvl = (int) $h['level'];
                        if ( $lvl >= 1 && $lvl <= 6 ) {
                            $by_level[$lvl][] = $h['text'];
                        }
                    }
                }
            
                // Headings detail row (same stripe class). Only print lines for levels that exist.
                echo '<tr class="' . esc_attr($stripe_class) . '"><td colspan="10" class="heading-seq">';
                
                $printed_any = false;
                for ( $lvl = 1; $lvl <= 6; $lvl++ ) {
                    if ( empty( $by_level[$lvl] ) ) { continue; } // do not show missing levels
                    $printed_any = true;

                    // Existing H1 warn line (leave)
                    $line_warn = ($lvl === 1 && $has_multi_h1) ? ' warn' : '';

                    // ADD THIS: HP-only red line class for H2/H3/H4 when over thresholds
                    $line_bad = '';
                    if ( $is_hp_page ) {
                        if ( $lvl === 2 && $hp_h2_too_many ) { $line_bad = ' hp-bad'; }
                        if ( $lvl === 3 && $hp_h3_too_many ) { $line_bad = ' hp-bad'; }
                        if ( $lvl === 4 && $hp_h4_too_many ) { $line_bad = ' hp-bad'; }
                    }

                    echo '<div class="heading-line' . $line_warn . $line_bad . '">';
                    echo '<span class="level-label">H' . intval($lvl) . ':</span> ';
                    echo '<span class="heading-chips">';
                
                    foreach ( $by_level[$lvl] as $txt ) {
                        $classes = array('heading-chip');

                        // Existing multi-H1 indicator (leave)
                        if ( $lvl === 1 && $has_multi_h1 ) {
                            $classes[] = 'warn';
                        }

                        // ADD THIS: HP-only red chips for H2/H3/H4 when over thresholds (no orange)
                        if ( $is_hp_page ) {
                            if ( $lvl === 2 && $hp_h2_too_many ) { $classes[] = 'hp-bad'; }
                            if ( $lvl === 3 && $hp_h3_too_many ) { $classes[] = 'hp-bad'; }
                            if ( $lvl === 4 && $hp_h4_too_many ) { $classes[] = 'hp-bad'; }
                        }

                        // Existing City/State H1 rule (leave)
                        if ( $lvl === 1 && $inception_city !== '' && $inception_state !== '' ) {
                            if ( ! inception_h1_contains_city_state( $txt, $inception_city, $inception_state ) ) {
                                $classes[] = 'bad';
                            }
                        }

                        echo '<span class="' . esc_attr(implode(' ', $classes)) . '" title="' . esc_attr("City: $inception_city | State: $inception_state") . '">' . esc_html($txt) . '</span>';
                    }
                
                    echo '</span>'; // .heading-chips
                    echo '</div>';
                }
                
                if ( ! $printed_any ) {
                    echo '<span class="muted">No H1–H6 headings detected in content.</span>';
                }
                
                echo '</td></tr>';
                $row++;
            }
              echo '</tbody></table>';
              wp_reset_postdata();
          } else {
              echo '<p>No pages or posts found.</p>';
          }
          ?>
        </div>
        
        <style>
        /* Subtle highlight for Home Page / "- HP" rows */
        #pages-posts-headings-table .inception-table tbody tr.hp-row{
          background-color: #fff9e6; /* soft light yellow */
        }
        
        /* Keep the detail (heading chips) row consistent with its HP summary row */
        #pages-posts-headings-table .inception-table tbody tr.hp-row + tr{
          background-color: #fff9e6;
        }
        
        /* Zebra striping, borders aligned with your other tables */
        #pages-posts-backlinks-table .inception-table tbody tr:nth-child(odd)  { background: #f6f7f7; }
        #pages-posts-backlinks-table .inception-table tbody tr:nth-child(even) { background: #ffffff; }
        
        #pages-posts-backlinks-table .inception-table thead th { white-space: nowrap; }
        #pages-posts-backlinks-table .inception-table td { vertical-align: top; }
        
/* === QC TABLE WIDTH NORMALIZATION === */
.table-container,
.wp-list-table {
    width: 100% !important;
    max-width: 100% !important;
}

.wp-list-table {
    table-layout: fixed;
}

.wp-list-table th,
.wp-list-table td {
    word-wrap: break-word;
    overflow-wrap: anywhere;
}

.wp-list-table th {
    white-space: nowrap;
}


/* === QC FLOATING SITE CHECKS BUG === */
:root{
  --qcBugWidth: 230px;
}
.qc-bug{
  position: fixed;
  top: 110px;
  right: 14px;
  width: var(--qcBugWidth);
  z-index: 9999;
  display: none;
  background: #fff;
  border: 1px solid rgba(0,0,0,.08);
  border-radius: 14px;
  box-shadow: 0 10px 30px rgba(0,0,0,.10);
  padding: 10px 10px 8px;
}
.qc-bug.qc-bug--open{ display:block; }
.qc-bug__header{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:8px;
  margin-bottom:8px;
}
.qc-bug__title{
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .02em;
  text-transform: uppercase;
  opacity: .85;
}
.qc-bug__btn{
  border: 0;
  background: transparent;
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  padding: 6px 8px;
  border-radius: 10px;
}
.qc-bug__btn:hover{ background: rgba(0,0,0,.06); }

.qc-bug__items{
  display:flex;
  flex-direction:column;
  gap:6px;
}
.qc-bug__items .qc-badge{
  display:flex;
  justify-content:space-between;
  align-items:center;
  width:100%;
}
.qc-bug__items .qc-badge strong{ margin-left:8px; }

.qc-bug__collapsed{
  position: fixed;
  top: 140px;
  right: 14px;
  z-index: 9999;
  display:none;
  border-radius: 999px;
  border: 1px solid rgba(0,0,0,.08);
  background:#fff;
  box-shadow: 0 10px 30px rgba(0,0,0,.10);
  padding: 10px 12px;
  font-size: 12px;
  font-weight: 700;
  cursor:pointer;
}
.qc-bug__collapsed:hover{ background: rgba(0,0,0,.04); }

/* Reserve space so the bug doesn't cover table content on larger screens */
@media (min-width: 1024px){
  .wrap{ padding-right: calc(var(--qcBugWidth) + 28px); }
}
/* On smaller screens, don't reserve space and keep bug narrower */
@media (max-width: 1023px){
  :root{ --qcBugWidth: 200px; }
}


/* === QC BUG BUTTON SIZE FIX === */
.qc-bug__items .qc-badge{
  font-size: 12px;
  padding: 6px 8px;
  border-radius: 10px;
}
.qc-bug{
  box-sizing: border-box;
}


/* === QC BUG COMPACT BADGES === */
.qc-bug__items{ gap:4px; }
.qc-bug__items .qc-badge{
  font-size: 11px;
  padding: 5px 6px;
  border-radius: 10px;
  line-height: 1.1;
  justify-content: space-between;
}
.qc-bug__items .qc-badge strong{
  display:inline-block;
  width:4ch;
  text-align:right;
  font-variant-numeric: tabular-nums;
  margin-left:0;
}


</style>
        
        <?php
        /**
         * Remove only the Beaver Builder inserted layouts that match specific slugs (e.g., your global sidebar).
         * Defaults to ['global-sidebar'] but is filterable via 'inception_bb_ignored_inserted_layout_slugs'.
         */
        if ( ! function_exists( 'inception_strip_ignored_inserted_layouts' ) ) {
            function inception_strip_ignored_inserted_layouts( $raw ) {
                // Which layout slugs to strip out of content
                $slugs      = apply_filters( 'inception_bb_ignored_inserted_layout_slugs', array( 'global-sidebar' ) );
                if ( empty( $slugs ) ) { return $raw; }
                $slugs_lc   = array_map( 'strtolower', $slugs );
        
                // Match [fl_builder_insert_layout ...]...[/fl_builder_insert_layout] (enclosing or self-closing)
                $pattern = '/\[fl_builder_insert_layout\b([^\]]*)\](?:.*?\[\/fl_builder_insert_layout\])?/is';
        
                $raw = preg_replace_callback( $pattern, function( $m ) use ( $slugs_lc ) {
                    $atts = shortcode_parse_atts( $m[1] );
                    $slug = '';
                    if ( isset( $atts['slug'] ) ) {
                        $slug = strtolower( trim( (string) $atts['slug'], "\"' \t\n\r\0\x0B" ) );
                    }
                    // Only remove this shortcode if its slug is one of the ignored (e.g., 'global-sidebar')
                    if ( $slug !== '' && in_array( $slug, $slugs_lc, true ) ) {
                        return ''; // strip the sidebar layout
                    }
                    return $m[0]; // keep other inserted layouts intact
                }, $raw );
        
                return $raw;
            }
        }
        
        /**
         * Return true if the post contains <script type="cp"> ... </script>.
         * Checks:
         *  1) Raw post_content (unfiltered)
         *  2) Beaver Builder layout meta (_fl_builder_*) recursively (HTML modules, etc.)
         *  3) Rendered content (last resort; WP may strip <script> here)
         *
         * NOTE: We do NOT strip the global sidebar here — you asked to flag ANY page/post
         * that contains that script, even if in a module. If you need to ignore the sidebar
         * for cp detection, you can apply inception_strip_ignored_inserted_layouts($raw) before checks.
         */
        if ( ! function_exists( 'inception_post_has_cp_script' ) ) {
            function inception_post_has_cp_script( $post_id ) {
                // Case-insensitive; supports single/double quotes, extra attributes, whitespace inside
                $re_tag = '/<script\b[^>]*\btype\s*=\s*(["\'])cp\1[^>]*>\s*<\/script>/i';
        
                // --- 1) RAW post_content quick check
                $raw = (string) get_post_field( 'post_content', $post_id );
                if ( $raw !== '' && preg_match( $re_tag, $raw ) ) {
                    return true;
                }
        
                // --- 2) Beaver Builder meta (where HTML module contents live)
                // Try common keys; WP will unserialize to arrays if needed.
                $bb_keys = array(
                    '_fl_builder_data',
                    '_fl_builder_draft',
                    '_fl_builder_data_global',
                    '_fl_builder_data_secondary',
                );
        
                $stack = array();
                foreach ( $bb_keys as $k ) {
                    $v = get_post_meta( $post_id, $k, true );
                    if ( $v !== '' && $v !== null ) {
                        $stack[] = $v;
                    }
                }
        
                // Optional: some sites store JSON strings; push them too
                $bb_json = get_post_meta( $post_id, '_fl_builder_json', true );
                if ( is_string( $bb_json ) && $bb_json !== '' ) {
                    $stack[] = $bb_json;
                }
        
                // Depth-first walk of arrays/objects; test any string leaf against the regex
                $seen = new SplObjectStorage();
                $walker = function( $node ) use ( &$walker, $re_tag, $seen ) {
                    if ( is_string( $node ) ) {
                        return (bool) preg_match( $re_tag, $node );
                    }
                    if ( is_array( $node ) ) {
                        foreach ( $node as $v ) {
                            if ( $walker( $v ) ) return true;
                        }
                        return false;
                    }
                    if ( is_object( $node ) ) {
                        // guard against circular refs in objects
                        if ( $seen->contains( $node ) ) return false;
                        $seen->attach( $node );
                        foreach ( $node as $v ) {
                            if ( $walker( $v ) ) return true;
                        }
                        return false;
                    }
                    return false;
                };
        
                foreach ( $stack as $item ) {
                    // If JSON string, decode to array/object and walk
                    if ( is_string( $item ) ) {
                        $decoded = json_decode( $item );
                        if ( $decoded ) {
                            if ( $walker( $decoded ) ) return true;
                        } else {
                            // Just a string; test directly
                            if ( preg_match( $re_tag, $item ) ) return true;
                        }
                    } else {
                        // Array/object
                        if ( $walker( $item ) ) return true;
                    }
                }
        
                // --- 3) Rendered content (least reliable; scripts often removed by filters)
                $html = apply_filters( 'the_content', $raw );
                if ( is_string( $html ) && $html !== '' && preg_match( $re_tag, $html ) ) {
                    return true;
                }
        
                // No match anywhere
                return false;
            }
        }
        
        /**
         * Return an array of "home page" URLs that count as valid backlinks.
         * Includes:
         *  - the real homepage (/)
         *  - city-state landing pages like /bradenton-fl/
         * Excludes known non-home pages like contact-us, review-us.
         */
        if ( ! function_exists( 'inception_get_home_backlink_targets' ) ) {
            function inception_get_home_backlink_targets() {
                static $cache = null;
                if ( $cache !== null ) return $cache;
        
                $targets = array();
        
                // Always include true homepage
                $targets[] = trailingslashit( home_url( '/' ) );
        
                // Exclude slugs that accidentally match patterns but are not "home pages"
                $exclude_slugs = array(
                    'contact-us',
                    'review-us',
                );
        
                // Find additional "home" pages (city-state URLs)
                $pages = get_posts( array(
                    'post_type'      => 'page',
                    'posts_per_page' => -1,
                    'post_status'    => array( 'publish', 'draft', 'private', 'pending', 'future' ),
                    'fields'         => 'ids',
                    'no_found_rows'  => true,
                ) );
        
                foreach ( $pages as $pid ) {
                    $url  = get_permalink( $pid );
                    if ( ! $url ) continue;
        
                    $path = wp_parse_url( $url, PHP_URL_PATH );
                    $path = $path ? rtrim( $path, '/' ) : '';
                    $slug = $path ? basename( $path ) : '';
        
                    if ( $slug === '' ) continue;
                    if ( in_array( $slug, $exclude_slugs, true ) ) continue;
        
                    // City-state slug like "bradenton-fl" (two-letter state)
                    if ( preg_match( '/-[a-z]{2}$/i', $slug ) ) {
                        $targets[] = trailingslashit( $url );
                    }
                }
        
                // De-dupe
                $targets = array_values( array_unique( $targets ) );
        
                $cache = $targets;
                return $cache;
            }
        }
        
        if ( ! function_exists( 'inception_post_detect_home_backlinks' ) ) {
            /**
             * Detect backlinks in rendered content that point to:
             *  - the TRUE homepage (/)
             *  - other "home pages" (city-state pages, etc.)
             *
             * Returns:
             *  [
             *    'has_root' => bool,
             *    'others'   => array of absolute URLs (excluding the root)
             *  ]
             */
            function inception_post_detect_home_backlinks( $post_id ) {
                // Front page shouldn't be flagged, treat as having root backlink
                $front_id = (int) get_option( 'page_on_front' );
                if ( $front_id && (int) $post_id === $front_id ) {
                    return array( 'has_root' => true, 'others' => array() );
                }
        
                // Get raw content and remove ONLY the global sidebar inserted layout(s)
                $raw = (string) get_post_field( 'post_content', $post_id );
                if ( function_exists( 'inception_strip_ignored_inserted_layouts' ) ) {
                    $raw = inception_strip_ignored_inserted_layouts( $raw );
                }
        
                // Render the remaining content
                $html = apply_filters( 'the_content', $raw );
                if ( trim( $html ) === '' ) {
                    return array( 'has_root' => false, 'others' => array() );
                }
        
                // Targets
                $home_abs     = trailingslashit( home_url( '/' ) );
                $home_norm    = trailingslashit( $home_abs );
                $home_host    = wp_parse_url( $home_abs, PHP_URL_HOST );
        
                $targets = inception_get_home_backlink_targets();
                $targets = array_map( 'trailingslashit', (array) $targets );
        
                // Build a lookup for "other home pages" (exclude root)
                $other_lookup = array();
                foreach ( $targets as $t ) {
                    $t = trailingslashit( (string) $t );
                    if ( $t && $t !== $home_norm ) {
                        $other_lookup[ $t ] = true;
                    }
                }
        
                // Base URL for resolving relative links
                $base_url    = get_permalink( $post_id );
                $base_url    = $base_url ? $base_url : $home_abs;
                $base_scheme = wp_parse_url( $base_url, PHP_URL_SCHEME ) ?: ( is_ssl() ? 'https' : 'http' );
        
                // Normalizer: returns absolute w/ trailing slash, strips query/fragment
                $normalize = function( $href ) use ( $base_url, $base_scheme ) {
                    $u = trim( (string) $href );
                    if ( $u === '' ) return '';
        
                    // Drop fragment & query
                    $u = preg_replace( '/[#?].*$/', '', $u );
        
                    // Absolute (has a scheme)
                    if ( preg_match( '#^[a-z][a-z0-9+\-.]*:#i', $u ) ) {
                        return trailingslashit( $u );
                    }
        
                    // Protocol-relative
                    if ( strpos( $u, '//' ) === 0 ) {
                        return trailingslashit( $base_scheme . ':' . $u );
                    }
        
                    // Root-relative
                    if ( strpos( $u, '/' ) === 0 ) {
                        return trailingslashit( home_url( $u ) );
                    }
        
                    // Relative path
                    $base_parts = wp_parse_url( $base_url );
                    $base_path  = isset( $base_parts['path'] ) ? $base_parts['path'] : '/';
                    $base_dir   = preg_replace( '#[^/]*$#', '', $base_path );
        
                    $combined   = $base_dir . $u;
                    $segments   = explode( '/', $combined );
                    $stack      = array();
        
                    foreach ( $segments as $seg ) {
                        if ( $seg === '' || $seg === '.' ) continue;
                        if ( $seg === '..' ) array_pop( $stack );
                        else $stack[] = $seg;
                    }
                    $resolved_path = '/' . implode( '/', $stack ) . '/';
                    return trailingslashit( home_url( $resolved_path ) );
                };
        
                // Parse links
                $dom = new DOMDocument();
                libxml_use_internal_errors( true );
                $dom->loadHTML( '<?xml encoding="utf-8" ?>' . $html );
                libxml_clear_errors();
        
                $links = $dom->getElementsByTagName( 'a' );
                if ( ! $links || $links->length === 0 ) {
                    return array( 'has_root' => false, 'others' => array() );
                }
        
                $has_root = false;
                $others_found = array();
        
                foreach ( $links as $a ) {
                    $href = $a->getAttribute( 'href' );
                    if ( $href === '' ) continue;
        
                    // Quick accept for "/" = root backlink
                    if ( $href === '/' ) {
                        $has_root = true;
                        continue;
                    }
        
                    $norm = $normalize( $href );
                    if ( $norm === '' ) continue;
        
                    // Only internal host
                    $host = wp_parse_url( $norm, PHP_URL_HOST );
                    if ( $host && $host !== $home_host ) continue;
        
                    $norm = trailingslashit( $norm );
        
                    if ( $norm === $home_norm ) {
                        $has_root = true;
                        continue;
                    }
        
                    // If it matches an "other home page", collect it
                    if ( isset( $other_lookup[ $norm ] ) ) {
                        $others_found[ $norm ] = true;
                    }
                }
        
                return array(
                    'has_root' => $has_root,
                    'others'   => array_keys( $others_found ),
                );
            }
        }
        
        /**
         * Return true if the post's rendered MAIN content contains a backlink to site home.
         * - Keeps all content except the specific Beaver layout(s) you ignore (e.g., global sidebar).
         * - Resolves relative links (../, ./, foo/) against the post permalink before comparison.
         */
        if ( ! function_exists( 'inception_post_has_backlink' ) ) {
            function inception_post_has_backlink( $post_id ) {
                // Front page shouldn't be flagged
                $front_id = (int) get_option( 'page_on_front' );
                if ( $front_id && (int) $post_id === $front_id ) {
                    return true;
                }
        
                // Get raw content and remove ONLY the global sidebar inserted layout(s)
                $raw = (string) get_post_field( 'post_content', $post_id );
                if ( function_exists('inception_strip_ignored_inserted_layouts') ) {
                    $raw = inception_strip_ignored_inserted_layouts( $raw );
                }
        
                // Render the remaining content
                $html = apply_filters( 'the_content', $raw );
                if ( trim( $html ) === '' ) {
                    return false;
                }
        
                // Home references (now includes additional "home pages" like city-state pages)
                $home_abs   = trailingslashit( home_url( '/' ) );  // true homepage
                $home_host  = wp_parse_url( $home_abs, PHP_URL_HOST );

                // Allowed backlink targets (/, plus city-state landing pages)
                $home_targets = inception_get_home_backlink_targets();
                $home_targets = array_map( 'trailingslashit', (array) $home_targets );

                // Base for resolving relative links
                $base_url  = get_permalink( $post_id );
                $base_url  = $base_url ? $base_url : $home_abs;
        
                /**
                 * Normalize $href to an absolute URL (with trailing slash) using $base_url:
                 * - strip query/fragment
                 * - handle protocol-relative //host
                 * - handle root-relative /path
                 * - handle relative paths with ./ and ../
                 */
                $normalize = function( $href ) use ( $base_url, $base_scheme ) {
                    $u = trim( (string) $href );
                    if ( $u === '' ) return '';
        
                    // Drop fragment & query
                    $u = preg_replace( '/[#?].*$/', '', $u );
        
                    // Absolute (has a scheme)
                    if ( preg_match( '#^[a-z][a-z0-9+\-.]*:#i', $u ) ) {
                        return trailingslashit( $u );
                    }
        
                    // Protocol-relative //example.com/...
                    if ( strpos( $u, '//' ) === 0 ) {
                        return trailingslashit( $base_scheme . ':' . $u );
                    }
        
                    // Root-relative /path → use home_url
                    if ( strpos( $u, '/' ) === 0 ) {
                        return trailingslashit( home_url( $u ) );
                    }
        
                    // Relative path (../, ./, foo/)
                    $base_parts = wp_parse_url( $base_url );
                    $base_path  = isset( $base_parts['path'] ) ? $base_parts['path'] : '/';
                    // Directory of base (ensure trailing slash)
                    $base_dir   = preg_replace( '#[^/]*$#', '', $base_path );
        
                    // Combine and normalize path segments
                    $combined   = $base_dir . $u;
                    $segments   = explode( '/', $combined );
                    $stack      = array();
        
                    foreach ( $segments as $seg ) {
                        if ( $seg === '' || $seg === '.' ) {
                            continue;
                        }
                        if ( $seg === '..' ) {
                            array_pop( $stack );
                        } else {
                            $stack[] = $seg;
                        }
                    }
                    $resolved_path = '/' . implode( '/', $stack ) . '/';
        
                    // Rebuild absolute with site's host via home_url (preserves correct domain)
                    return trailingslashit( home_url( $resolved_path ) );
                };
        
                // Parse links and search for backlink to homepage
                $dom = new DOMDocument();
                libxml_use_internal_errors( true );
                $dom->loadHTML( '<?xml encoding="utf-8" ?>' . $html );
                libxml_clear_errors();
        
                $links = $dom->getElementsByTagName( 'a' );
                if ( ! $links || $links->length === 0 ) {
                    return false;
                }
        
                // Precompute normalized variants of allowed "home" targets
                $home_norms = array();
                foreach ( $home_targets as $t ) {
                    if ( $t ) $home_norms[ trailingslashit( $t ) ] = true;
                }

                foreach ( $links as $a ) {
                    $href = $a->getAttribute( 'href' );
                    if ( $href === '' ) continue;

                    // Exact root-relative "/" (quick accept)
                    if ( $href === '/' ) {
                        return true;
                    }
        
                    $norm = $normalize( $href );
                    if ( $norm === '' ) continue;
        
                    // Only consider internal host
                    $host = wp_parse_url( $norm, PHP_URL_HOST );
                    if ( $host && $host !== $home_host ) {
                        continue;
                    }
        
                    // Backlink if it resolves exactly to any allowed "home" target
                    if ( isset( $home_norms[ $norm ] ) ) {
                        return true;
                    }
                }

                return false;
            }
        }
        
        
        /**
         * Get IDs of posts/pages that appear in any of the site's FOOTER menus.
         *
         * Footer menus are:
         *  1) Menus assigned to common footer locations (filterable).
         *  2) Menus whose term slug suggests footer usage (e.g., "footer", "footer-menu", "footer-1", etc.).
         *
         * Returns an associative array like [ post_or_page_id => true, ... ] for O(1) lookups.
         */
        if ( ! function_exists( 'inception_get_footer_menu_item_ids' ) ) {
            function inception_get_footer_menu_item_ids() {
                $ids = array();
        
                // 1) Menus by theme location
                $locations = get_nav_menu_locations();
        
                $footer_location_slugs = apply_filters('inception_footer_menu_locations', array(
                    'footer',
                    'footer-1',
                    'footer-2',
                    'footer-3',
                    'footer_menu',
                    'footer-menu',
                    'bottom',
                    'site-footer',
                    'colophon',
                ));
        
                $footer_menu_term_ids = array();
        
                if ( is_array( $locations ) ) {
                    foreach ( $footer_location_slugs as $loc ) {
                        if ( ! empty( $locations[ $loc ] ) ) {
                            $footer_menu_term_ids[] = (int) $locations[ $loc ];
                        }
                    }
                }
        
                // 2) Menus by slug pattern suggesting "footer"
                $all_menus = wp_get_nav_menus();
                if ( ! empty( $all_menus ) && ! is_wp_error( $all_menus ) ) {
                    foreach ( $all_menus as $menu ) {
                        $slug = isset( $menu->slug ) ? (string) $menu->slug : '';
                        if ( preg_match( '/^footer(-\d+)?$/', $slug ) || $slug === 'footer-menu' || $slug === 'site-footer' ) {
                            $footer_menu_term_ids[] = (int) $menu->term_id;
                        }
                    }
                }
        
                // Deduplicate
                $footer_menu_term_ids = array_values( array_unique( $footer_menu_term_ids ) );
                if ( empty( $footer_menu_term_ids ) ) {
                    return $ids; // none found
                }
        
                // Resolve internal post/page IDs from these menus (post_type items and custom links)
                foreach ( $footer_menu_term_ids as $term_id ) {
                    $items = wp_get_nav_menu_items( $term_id );
                    if ( empty( $items ) || is_wp_error( $items ) ) {
                        continue;
                    }
        
                    foreach ( $items as $item ) {
                        // Direct objects (only exclude PAGES)
                        if ( $item->type === 'post_type' && ! empty( $item->object_id ) ) {
                            if ( get_post_type( (int) $item->object_id ) === 'page' ) {
                                $ids[ (int) $item->object_id ] = true;
                            }
                            continue;
                        }
        
                        // Custom links that might point to internal content
                        if ( $item->type === 'custom' && isset( $item->url ) ) {
                            $url_raw = trim( (string) $item->url );
                            if ( $url_raw === '' ) { continue; }
        
                            // Normalize: drop fragment/query
                            $url = preg_replace( '/[#?].*$/', '', $url_raw );
        
                            // Protocol-relative
                            if ( strpos( $url, '//' ) === 0 ) {
                                $url = ( is_ssl() ? 'https:' : 'http:' ) . $url;
                            }
                            // Root-relative
                            if ( strpos( $url, '/' ) === 0 ) {
                                $url = home_url( $url );
                            }
        
                            // Only consider internal links
                            $host = wp_parse_url( $url, PHP_URL_HOST );
                            $home_host = wp_parse_url( home_url( '/' ), PHP_URL_HOST );
                            $is_internal = ( empty( $host ) || $host === $home_host );
                            if ( ! $is_internal ) { continue; }
        
                            // Map to a post/page id if possible — and exclude ONLY if it's a PAGE
                            $maybe_id = url_to_postid( $url );
                            if ( $maybe_id && get_post_type( (int) $maybe_id ) === 'page' ) {
                                $ids[ (int) $maybe_id ] = true;
                            }
                        }
                    }
                }
        
                return $ids;
            }
        }
        ?>
        
        <div id="pages-posts-backlinks-table" class="table-container" style="display:none;">
          <h3>Pages &amp; Blog Posts – Missing Backlinks</h3>
          <p class="description">
            Shows pages and posts whose <em>rendered content</em> does not include a backlink to the homepage ("/").
            The front page is excluded. Items present in any footer menu are also excluded. Custom pages created after 9/10/25, even those with backlinks, will be listed here as well.
          </p>
          
          <?php
          // Build the exclusion set once
          $footer_item_ids = inception_get_footer_menu_item_ids();
        
          // Query all pages & posts
          $args = array(
              'post_type'      => array( 'page', 'post' ),
              'posts_per_page' => -1,
              'post_status'    => array( 'publish', 'draft', 'private', 'pending', 'future' ),
              'orderby'        => 'title',
              'order'          => 'ASC',
          );
          $q = new WP_Query( $args );
        
          if ( $q->have_posts() ) {
              echo '<table class="wp-list-table widefat fixed striped inception-table">';
              echo '<thead><tr>';
              echo '<th style="width: auto; padding-left: 10px;">#</th>';
              echo '<th>Page/Post Title</th>';
              echo '<th style="width:110px;">Type</th>';
              echo '<th>Detected Backlink URL</th>';
              echo '<th>Anchor Link Text</th>';
              echo '<th style="width:180px;">Page Links</th>';
              echo '</tr></thead><tbody>';
        
              $num = 1;
              while ( $q->have_posts() ) {
                    $q->the_post();
                    
                    $pid   = get_the_ID();
                    $title = get_the_title( $pid );
                    $link  = get_permalink( $pid );
                    $type  = ( get_post_type( $pid ) === 'page' ) ? 'Page' : 'Blog Post';
                    
                    // --- EXCLUDE specific utility / non-content pages by exact slug ---
                    $exclude_slugs = array(
                        'us',                    // About Us
                        'blog',                  // Blog index
                        'contact-us',
                        'describe-your-concern',
                        'office-tour',
                        'review-us',             // How Did We Do?
                        'new-patient-paperwork',
                        'schedule',
                        'special',
                        'testimonials',
                        'thank-you-form',
                    );
                    
                    $slug = get_post_field( 'post_name', $pid );
                    if ( $slug && in_array( $slug, $exclude_slugs, true ) ) {
                        continue;
                    }
                    
                    // Exclude anything that appears in a footer menu
                    if ( isset( $footer_item_ids[ $pid ] ) ) {
                      continue;
                    }
        
                    // Include if cp <script> is present, regardless of backlink
                    $has_cp = inception_post_has_cp_script( $pid );

                    // NEW: detect backlinks to "home targets" and whether it links to the TRUE homepage (/)
                    $detected = inception_post_detect_home_backlinks( $pid );
                    $has_root = ! empty( $detected['has_root'] );
                    $others   = ! empty( $detected['others'] ) ? (array) $detected['others'] : array();

                    // Exclude only if it links to the TRUE homepage and does not have cp script
                    if ( ! $has_cp && $has_root ) {
                        continue;
                    }

                    /**
                     * FIRST LINK (Option B behavior):
                     * - pick the first usable <a href="..."> in the rendered content (skip mailto/tel/javascript/#)
                     * - keep the href "as authored" (absolute stays absolute; /slug stays /slug)
                     * - keep the anchor text (trimmed)
                     */
                    $first_href_raw = '';
                    $first_text_raw = '';
                    $post_content = (string) get_post_field( 'post_content', $pid );

                    if ( function_exists( 'inception_strip_ignored_inserted_layouts' ) ) {
                        $post_content = inception_strip_ignored_inserted_layouts( $post_content );
                    }

                    if ( trim( $post_content ) !== '' ) {
                        $rendered = apply_filters( 'the_content', $post_content );

                        libxml_use_internal_errors( true );
                        $dom = new DOMDocument();
                        if ( $dom->loadHTML( '<?xml encoding="utf-8" ?>' . $rendered ) ) {
                            libxml_clear_errors();
                            $links = $dom->getElementsByTagName( 'a' );
                            if ( $links && $links->length > 0 ) {
                                foreach ( $links as $lnk ) {
                                    $href = trim( (string) $lnk->getAttribute( 'href' ) );
                                    if ( $href === '' ) continue;
                                    if ( preg_match('#^(mailto:|tel:|javascript:)#i', $href) ) continue;
                                    if ( $href[0] === '#' ) continue;

                                    // Skip anchors that look like they belong to header/menu/nav/logo containers
                                    $skip_anchor = false;
                                    $node = $lnk;
                                    while ( $node = $node->parentNode ) {
                                        if ( $node->nodeType !== XML_ELEMENT_NODE ) continue;

                                        if ( $node->hasAttribute( 'class' ) ) {
                                            $classes = strtolower( $node->getAttribute( 'class' ) );
                                            if ( strpos( $classes, 'menu' ) !== false ||
                                                 strpos( $classes, 'nav' ) !== false ||
                                                 strpos( $classes, 'header' ) !== false ||
                                                 strpos( $classes, 'logo' ) !== false ||
                                                 strpos( $classes, 'masthead' ) !== false ||
                                                 strpos( $classes, 'site-' ) !== false ) {
                                                $skip_anchor = true;
                                                break;
                                            }
                                        }
                                        if ( $node->hasAttribute( 'id' ) ) {
                                            $id = strtolower( $node->getAttribute( 'id' ) );
                                            if ( strpos( $id, 'menu' ) !== false ||
                                                 strpos( $id, 'nav' ) !== false ||
                                                 strpos( $id, 'header' ) !== false ||
                                                 strpos( $id, 'logo' ) !== false ||
                                                 strpos( $id, 'masthead' ) !== false ||
                                                 strpos( $id, 'site-' ) !== false ) {
                                                $skip_anchor = true;
                                                break;
                                            }
                                        }
                                    }
                                    if ( $skip_anchor ) continue;

                                    $first_href_raw = $href;
                                    $first_text_raw = trim( (string) $lnk->textContent );
                                    break;
                                }
                            }
                        } else {
                            libxml_clear_errors();
                        }

                        // Regex fallback if DOM didn't find anything usable
                        if ( $first_href_raw === '' ) {
                            if ( preg_match_all('/<a\s[^>]*href=[\"\\\']([^\"\\\']+)[\"\\\']/i', $rendered, $m) ) {
                                foreach ( $m[1] as $href_candidate ) {
                                    $href_candidate = trim( (string) $href_candidate );
                                    if ( $href_candidate === '' ) continue;
                                    if ( preg_match('#^(mailto:|tel:|javascript:)#i', $href_candidate) ) continue;
                                    if ( $href_candidate[0] === '#' ) continue;

                                    $first_href_raw = $href_candidate;
                                    $first_text_raw = ''; // not reliably available via regex; leave blank
                                    break;
                                }
                            }
                        }
                    }

                    /**
                     * NEW RULE:
                     * If the first link is the homepage, DO NOT show this row in the table.
                     */
                    if ( $first_href_raw !== '' ) {
                        $href_clean = preg_replace( '/[#?].*$/', '', trim( (string) $first_href_raw ) );

                        // Compare scheme-insensitive, trailing-slash-insensitive
                        $home_compare = preg_replace( '#^https?:#i', '', trailingslashit( home_url('/') ) );
                        $href_compare = preg_replace( '#^https?:#i', '', trailingslashit( $href_clean ) );

                        if ( $href_clean === '/' || $href_compare === $home_compare ) {
                            continue;
                        }
                    }

                    // Prepare display URL (show as authored) and clickable URL (absolute)
                    $display_url = $first_href_raw;
                    $click_url   = $first_href_raw;

                    if ( $click_url !== '' ) {
                        // If it's root-relative, make it absolute for the href attribute
                        if ( strpos( $click_url, '/' ) === 0 ) {
                            $click_url = home_url( $click_url );
                        }
                    }

                    $display_text = $first_text_raw;
                    if ( $first_href_raw !== '' && trim( $display_text ) === '' ) {
                        $display_text = '(no text)';
                    }

                    echo '<tr>';
                    echo '<td>' . intval( $num ) . '</td>';
                    echo '<td>' . esc_html( $title ) . '</td>';
                    echo '<td>' . esc_html( $type ) . '</td>';

                    // First Link URL column
                    echo '<td>';
                    if ( $first_href_raw !== '' ) {
                        echo '<a href="' . esc_url( $click_url ) . '" target="_blank" rel="noopener noreferrer">' . esc_html( $display_url ) . '</a>';
                    } else {
                        echo '<span style="opacity:.7;">None</span>';
                    }
                    echo '</td>';

                    // First Link Text column
                    echo '<td>';
                    if ( $first_href_raw !== '' ) {
                        echo esc_html( $display_text );
                    } else {
                        echo '<span style="opacity:.7;">None</span>';
                    }
                    echo '</td>';

                    // Page Links
                    echo '<td>';
                    echo '  <a href="' . esc_url( $link ) . '" target="_blank">View</a> | ';
                    echo '  <a href="' . esc_url( admin_url( 'post.php?post=' . $pid . '&action=edit' ) ) . '" target="_blank">Edit</a> | ';
                    echo '  <a href="' . esc_url( $link . '/?fl_builder&fl_builder_ui' ) . '" target="_blank">Builder</a>';
                    echo '</td>';

                    echo '</tr>';
                    
                    $num++;
              }
        
              if ( $num === 1 ) {
                  echo '<tr><td colspan="6">Great! No items are missing backlinks, and none include the cp script.</td></tr>';
              }
        
              echo '</tbody></table>';
              wp_reset_postdata();
          } else {
              echo '<p>No pages or posts found.</p>';
          }
          ?>
        </div>

        <div id="pages-posts-keyword-table" class="table-container">
          <h3>Keyword Search</h3>
          <p class="description">
            Type keywords below to find matches in page/post titles, main content, or custom fields/meta. Fuzzy Match catches up to 3 characters difference from the search term.
          </p>

          <div style="display:flex; align-items:center; flex-wrap:wrap; gap:10px;">
            <label for="qc-keyword-input" style="font-weight:600; margin:0;">Keyword(s):</label>
            <input type="text" id="qc-keyword-input" placeholder="e.g., treats, treatment" style="min-width:360px; padding:6px 10px; margin:0;">
            <input type="checkbox" id="qc-fuzzy-search" style="display:none;">
            <button type="button" id="qc-fuzzy-toggle" class="button" aria-pressed="false" style="margin:0;">
              Fuzzy Match
            </button>
          </div>

          <table class="widefat fixed striped" style="margin-top:12px;">
            <thead>
              <tr>
                <th style="width:48px;">#</th>
                <th>Title</th>
                <th>Type</th>
                <th>Where Found</th>
                <th>Page Links</th>
              </tr>
            </thead>
            <tbody id="qc-keyword-tbody">
              <tr><td colspan="5" style="text-align:center;">Start typing to see results...</td></tr>
            </tbody>
          </table>

          <?php
          // Build a lightweight client-side index of pages and posts.
          // NOTE: No AJAX used. We ship data once and filter in the browser.
            // 1) Pages, Posts, Themer Layouts (no attachments here)
            $qc_posts_main = get_posts(array(
                'post_type'      => array('page', 'post', 'fl-theme-layout'),
                'posts_per_page' => -1,
                'post_status'    => array('publish','draft','private','future')
            ));
            
            // 2) Media Library (attachments) — images only
            $qc_posts_media_raw = get_posts(array(
                'post_type'      => array('attachment'),
                'posts_per_page' => -1,
                'post_status'    => array('inherit'),
                'post_mime_type' => 'image'
            ));

            // Filter out "ghost" attachments (missing file or 0 bytes)
            $qc_posts_media = array();
            foreach ($qc_posts_media_raw as $att) {
                $file_path = get_attached_file($att->ID);

                // Must have a real file on disk and be non-zero size
                if (!$file_path) continue;
                if (!file_exists($file_path)) continue;

                $size = @filesize($file_path);
                if ($size === false || (int)$size <= 0) continue;

                $qc_posts_media[] = $att;
            }
            
            // Merge them
            $qc_posts = array_merge($qc_posts_main, $qc_posts_media);
          $qc_data = array();
          foreach ($qc_posts as $qp) {
              $post_type = get_post_type($qp->ID);
            
              $title   = get_the_title($qp->ID);
              $perma   = get_permalink($qp->ID);
              $edit    = get_edit_post_link($qp->ID, '');
            
              // Default (pages/posts/layouts)
              $content = apply_filters('the_content', $qp->post_content ?? '');
              $plain   = wp_strip_all_tags($content);
              $bb      = $perma ? $perma . (strpos($perma, '?') === false ? '?fl_builder' : '&fl_builder') : '';
            
              // Media Library (attachment) fields
              $media_item = '';
              $alt_text   = '';
              $desc_plain = '';
              $filename   = '';
            
              if ($post_type === 'attachment') {
                  // Attachment "Description" is post_content in WP
                  $desc_plain = wp_strip_all_tags((string)($qp->post_content ?? ''));
            
                  $alt_text = wp_strip_all_tags((string)get_post_meta($qp->ID, '_wp_attachment_image_alt', true));
            
                  $file_path = get_attached_file($qp->ID);
                  $size = ($file_path && file_exists($file_path)) ? @filesize($file_path) : 0;
                  if ((int)$size <= 0) {
                      continue; // don't include this attachment in qc_data at all
                  }
                  $filename  = $file_path ? wp_basename($file_path) : '';
            
                  // Link to Media Library item modal/page
                  $media_item = admin_url('upload.php?item=' . (int)$qp->ID);
            
                  // For attachments, we don't use Builder link
                  $bb = '';
                  // Optional: you can also blank permalink if you don't want attachment pages linked anywhere
                  // $perma = '';
              }

              // Collect meta fields (editor fields / ACF / etc.) to check where in the editor the term lives.
              $meta    = get_post_meta($qp->ID);
              $meta_flat = array();
              if (is_array($meta)) {
                  foreach ($meta as $mk => $mv) {
                      if (is_array($mv)) { $mv = implode(' ', array_map('strval', $mv)); }
                      $meta_flat[$mk] = wp_strip_all_tags((string)$mv);
                  }
              }

              $qc_data[] = array(
                  'id'          => (int)$qp->ID,
                  'title'       => $title,
                  'permalink'   => $perma,
                  'edit'        => $edit,
                  'bb'          => $bb,
                  'type'        => $post_type,
            
                  // Existing search fields
                  'content'     => ($post_type === 'attachment') ? $desc_plain : $plain,
                  'meta'        => $meta_flat,
            
                  // Media-only fields (empty for non-media)
                  'media_item'  => $media_item,
                  'alt_text'    => $alt_text,
                  'description' => $desc_plain,
                  'filename'    => $filename,
              );
          }
          ?>
          <script>
          (function(){
            const DATA = <?php echo wp_json_encode($qc_data); ?>;

            const input   = document.getElementById('qc-keyword-input');
            const fuzzy   = document.getElementById('qc-fuzzy-search');
            const tbody   = document.getElementById('qc-keyword-tbody');
            const fuzzyBtn = document.getElementById('qc-fuzzy-toggle');

            function syncFuzzyUI(){
              if (!fuzzyBtn || !fuzzy) return;
              const on = !!fuzzy.checked;
              fuzzyBtn.classList.toggle('is-active', on);
              fuzzyBtn.setAttribute('aria-pressed', on ? 'true' : 'false');
            }
            
            // Button click toggles the hidden checkbox, then triggers existing behavior
            if (fuzzyBtn && fuzzy) {
              fuzzyBtn.addEventListener('click', function(){
                fuzzy.checked = !fuzzy.checked;
                syncFuzzyUI();
                render(input.value);
              });
            }
            
            // Initial paint
            syncFuzzyUI();

            function normalize(s){ return (s||'').toString().toLowerCase(); }

            function typeLabel(type){
              switch ((type || '').toString()) {
                case 'page':            return 'Page';
                case 'post':            return 'Blog Post';
                case 'fl-theme-layout': return 'Themer Layout';
                case 'attachment':      return 'Media Library';
                default:                return type || '';
              }
            }

            function tokenizeTerms(raw){
              const s = (raw || '').toString();
              // Split on commas; allow multiple keywords.
              const parts = s.split(',').map(p => p.trim()).filter(Boolean);
              return parts.length ? parts : (s.trim() ? [s.trim()] : []);
            }

            // Levenshtein distance with early-exit at maxDist (fast enough for small maxDist).
            function levenshteinWithin(a, b, maxDist){
              a = normalize(a);
              b = normalize(b);
              if (a === b) return 0;
              const la = a.length, lb = b.length;
              if (!la || !lb) return Math.max(la, lb);
              if (Math.abs(la - lb) > maxDist) return maxDist + 1;

              // Ensure a is the shorter string.
              if (la > lb) { const tmp = a; a = b; b = tmp; }

              const m = a.length, n = b.length;
              let prev = new Array(m + 1);
              let curr = new Array(m + 1);

              for (let i = 0; i <= m; i++) prev[i] = i;

              for (let j = 1; j <= n; j++) {
                curr[0] = j;

                // Track best value in this row for early exit.
                let bestInRow = curr[0];

                const bj = b.charCodeAt(j - 1);
                for (let i = 1; i <= m; i++) {
                  const cost = (a.charCodeAt(i - 1) === bj) ? 0 : 1;
                  const del = prev[i] + 1;
                  const ins = curr[i - 1] + 1;
                  const sub = prev[i - 1] + cost;
                  let v = del < ins ? del : ins;
                  if (sub < v) v = sub;
                  curr[i] = v;
                  if (v < bestInRow) bestInRow = v;
                }

                if (bestInRow > maxDist) return maxDist + 1;

                const tmp = prev; prev = curr; curr = tmp;
              }

              return prev[m];
            }

            function fuzzyContains(haystack, needle, maxDist){
              const hay = normalize(haystack);
              const ned = normalize(needle);
              if (!hay || !ned) return false;

              if (hay.includes(ned)) return true;

              // To keep this fast on large content, only scan a prefix and compare against word tokens.
              const scan = hay.slice(0, 20000);
              const words = scan.match(/[a-z0-9]+/g) || [];
              for (const w of words) {
                if (Math.abs(w.length - ned.length) > maxDist) continue;
                if (levenshteinWithin(w, ned, maxDist) <= maxDist) return true;
              }
              return false;
            }

            function pushUnique(arr, val){
              if (arr.indexOf(val) === -1) arr.push(val);
            }

            // Build n-grams of words so multi-word phrases can be fuzzy matched.
            function extractNgrams(text, n){
              const words = normalize(text).match(/[a-z0-9]+/g) || [];
              if (!n || n <= 0 || words.length < n) return [];
              const out = [];
              for (let i = 0; i <= words.length - n; i++) {
                out.push(words.slice(i, i + n).join(' '));
              }
              return out;
            }

            // Find fuzzy phrase variants of "needle" inside "haystack".
            // ENHANCED: if the match in the content has a trailing "*" (e.g. FULL NAME*),
            // we keep the "*" in the returned fuzzy variant so it displays in the table.
            function fuzzyPhraseMatches(haystack, needle, maxDist, limit){
              const original = (haystack || '').toString();
              const hayNorm  = normalize(original);
              const nedNorm  = normalize(needle);
              if (!hayNorm || !nedNorm) return [];

              const needleWords = nedNorm.match(/[a-z0-9]+/g) || [];
              const n = needleWords.length || 1;

              // Tokenize preserving optional trailing "*"
              // Examples: "NAME*" stays "NAME*", "NAME*," -> "NAME*"
              const wordsOrig = original.match(/\b[\w#]+(?:\*)?\b/g) || [];
              const wordsNorm = wordsOrig.map(w => normalize(w));

              const found = [];

              for (let i = 0; i <= wordsNorm.length - n; i++) {
                const gramNorm = wordsNorm.slice(i, i + n).join(' ');
                const gramOrig = wordsOrig.slice(i, i + n).join(' ');

                // Skip exact match — we only want fuzzy variants
                if (gramNorm === nedNorm) continue;

                // First-word initial must match (prevents junk like "E3 Austin")
                if (needleWords[0] && gramNorm[0] !== needleWords[0][0]) continue;

                if (Math.abs(gramNorm.length - nedNorm.length) > maxDist) continue;

                if (levenshteinWithin(gramNorm, nedNorm, maxDist) <= maxDist) {
                  // If the next token in the original is a bare "*" (rare), append it.
                  // Otherwise, if the LAST token of the gram already ends with "*", keep it.
                  let out = gramOrig;

                  // Special case: content may be "FULL NAME *" (space before star)
                  const nextToken = wordsOrig[i + n] || '';
                  const lastToken = wordsOrig[i + n - 1] || '';

                  if (!out.endsWith('*')) {
                    if (nextToken === '*') out = out + '*';
                    // Also handle last token like "NAME*" already (this check is redundant but safe)
                    else if (lastToken.endsWith('*')) out = out; 
                  }

                  if (found.indexOf(out) === -1) {
                    found.push(out);
                    if (found.length >= (limit || 5)) break;
                  }
                }
              }

              return found;
            }

            // Returns detail per field:
            // { exact: boolean, fuzzy: string[] } where fuzzy includes fuzzy variants even if exact is true.
            function matchFieldDetail(haystack, needle, useFuzzy, maxDist){
              const hayNorm = normalize(haystack);
              const nedNorm = normalize(needle);

              const exact = !!(hayNorm && nedNorm && hayNorm.includes(nedNorm));
              
              // If exact match is true but the content contains a starred version (e.g. "FULL NAME*"),
              // include that in fuzzy list so the UI shows the asterisk.
              if (exact && useFuzzy) {
                const starred = (haystack || '').toString().match(new RegExp(
                  nedNorm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\*', 'i'
                ));
                if (starred && starred[0]) {
                  return { exact, fuzzy: [starred[0]] };
                }
              }

              if (!useFuzzy) {
                return { exact, fuzzy: [] };
              }

              // Gather fuzzy variants (phrase-level, e.g. "austinn tx")
              const fuzzy = fuzzyPhraseMatches(haystack, needle, maxDist, 5);
              return { exact, fuzzy };
            }

            // Now returns a richer object:
            // {
            //   where: [ 'Main Content', 'Main Content (fuzzy)', ... ],
            //   fuzzyTexts: [ 'austinn tx', ... ],
            //   hasFuzzy: true/false
            // }
            function findMatches(row, rawTerm, useFuzzy){
              const terms = tokenizeTerms(rawTerm);
              if (!terms.length) return { where: [], fuzzyTexts: [], hasFuzzy: false };

              const where = [];
              const fuzzyTexts = [];
              const maxDist = 3; // Fuzzy Match catches up to 3 characters off
              
              const isMedia = (row.type === 'attachment');

                for (const term of terms) {
                    const isMedia = (row.type === 'attachment');
                    
                    // Title
                    const titleDetail = matchFieldDetail(row.title || '', term, useFuzzy, maxDist);
                    if (titleDetail.exact) pushUnique(where, 'Title');
                    if (titleDetail.fuzzy.length) {
                      pushUnique(where, 'Title (fuzzy)');
                      for (const ft of titleDetail.fuzzy) pushUnique(fuzzyTexts, ft);
                    }
                    
                    if (isMedia) {
                      // Alt Text
                      const altDetail = matchFieldDetail(row.alt_text || '', term, useFuzzy, maxDist);
                      if (altDetail.exact) pushUnique(where, 'Alt Text');
                      if (altDetail.fuzzy.length) {
                        pushUnique(where, 'Alt Text (fuzzy)');
                        for (const ft of altDetail.fuzzy) pushUnique(fuzzyTexts, ft);
                      }
                    
                      // Description
                      const descDetail = matchFieldDetail(row.description || '', term, useFuzzy, maxDist);
                      if (descDetail.exact) pushUnique(where, 'Description');
                      if (descDetail.fuzzy.length) {
                        pushUnique(where, 'Description (fuzzy)');
                        for (const ft of descDetail.fuzzy) pushUnique(fuzzyTexts, ft);
                      }
                    
                      // Filename
                      const fileDetail = matchFieldDetail(row.filename || '', term, useFuzzy, maxDist);
                      if (fileDetail.exact) pushUnique(where, 'Filename');
                      if (fileDetail.fuzzy.length) {
                        pushUnique(where, 'Filename (fuzzy)');
                        for (const ft of fileDetail.fuzzy) pushUnique(fuzzyTexts, ft);
                      }
                    
                      // Skip page/post content & Yoast for media items
                      continue;
                    }
                
                  // Main Content (non-media)
                  const contentDetail = matchFieldDetail(row.content || '', term, useFuzzy, maxDist);
                  if (contentDetail.exact) pushUnique(where, 'Main Content');
                  if (contentDetail.fuzzy.length) {
                    pushUnique(where, 'Main Content (fuzzy)');
                    for (const ft of contentDetail.fuzzy) pushUnique(fuzzyTexts, ft);
                  }
                
                  // Yoast fields only (non-media)
                  if (row.meta && typeof row.meta === 'object') {
                    const yoastTitle = row.meta['_yoast_wpseo_title'] || '';
                    const yoastDesc  = row.meta['_yoast_wpseo_metadesc'] || '';
                
                    const ytDetail = matchFieldDetail(yoastTitle, term, useFuzzy, maxDist);
                    if (ytDetail.exact) pushUnique(where, 'Yoast SEO Title');
                    if (ytDetail.fuzzy.length) {
                      pushUnique(where, 'Yoast SEO Title (fuzzy)');
                      for (const ft of ytDetail.fuzzy) pushUnique(fuzzyTexts, ft);
                    }
                
                    const ydDetail = matchFieldDetail(yoastDesc, term, useFuzzy, maxDist);
                    if (ydDetail.exact) pushUnique(where, 'Yoast SEO Description');
                    if (ydDetail.fuzzy.length) {
                      pushUnique(where, 'Yoast SEO Description (fuzzy)');
                      for (const ft of ydDetail.fuzzy) pushUnique(fuzzyTexts, ft);
                    }
                  }
                }

              return {
                where,
                fuzzyTexts,
                hasFuzzy: fuzzyTexts.length > 0
              };
            }

            function ensureFuzzyColumn(show){
              const table = tbody && tbody.closest('table');
              if (!table) return;

              const headRow = table.querySelector('thead tr');
              if (!headRow) return;

              const existing = headRow.querySelector('th[data-qc-fuzzy-col="1"]');

              if (show && !existing) {
                const th = document.createElement('th');
                th.textContent = 'Fuzzy Match Text';
                th.setAttribute('data-qc-fuzzy-col', '1');
                th.style.width = '220px';
                headRow.appendChild(th);
              }

              if (!show && existing) {
                existing.remove();
              }
            }

            function emptyRow(colspan, text){
              const tr = document.createElement('tr');
              tr.innerHTML = '<td colspan="' + colspan + '" style="text-align:center;">' + text + '</td>';
              return tr;
            }

            function render(term){
              const value = (term || '').trim();
              tbody.innerHTML = '';

              const fuzzyOn = !!(fuzzy && fuzzy.checked);
              ensureFuzzyColumn(fuzzyOn);

              const baseCols = 5;
              const cols = fuzzyOn ? baseCols + 1 : baseCols;

              if (!value) {
                tbody.appendChild(emptyRow(cols, 'Start typing to see results...'));
                return;
              }

              let count = 0;
              for (const row of DATA) {
                const match = findMatches(row, value, fuzzyOn);

                // Limit to the fields we care about
                const allowedAll = match.where.filter(w =>
                  w.indexOf('Main Content') === 0  ||
                  w.indexOf('Yoast SEO Title') === 0 ||
                  w.indexOf('Yoast SEO Description') === 0 ||
                  w.indexOf('Title') === 0 ||
                  w.indexOf('Alt Text') === 0 ||
                  w.indexOf('Description') === 0 ||
                  w.indexOf('Filename') === 0
                );

                // If fuzzy is ON, only show fuzzy locations in "Where Found"
                const allowed = fuzzyOn
                  ? allowedAll
                      .filter(w => w.indexOf('(fuzzy)') !== -1)
                      .map(w => w.replace(/\s*\(fuzzy\)/i, '')) // strip label for display
                  : allowedAll;

                // If fuzzy is ON: only show rows that have at least one fuzzy variant somewhere.
                // If fuzzy is OFF: show normal matches (exact-only is fine).
                if (fuzzyOn) {
                  if (!match.hasFuzzy) continue;
                } else {
                  if (!allowed.length) continue;
                }

                count++;
                const tr = document.createElement('tr');

                // Highlight if fuzzy variants exist (even if exact also exists)
                if (fuzzyOn && match.hasFuzzy) {
                  tr.style.backgroundColor = '#ffecec'; // light red
                }

                const highlightValue = (fuzzyOn && match.fuzzyTexts.length)
                  ? match.fuzzyTexts.join(';')
                  : value;
                
                const viewUrl = (function(){
                  if (!row.permalink) return '';
                  try {
                    const u = new URL(row.permalink, window.location.origin);
                    if (!u.searchParams) return row.peralink;
                    u.searchParams.set('qc_highlight', highlightValue);
                    return u.toString();
                  } catch(e) {
                    return row.permalink + (row.permalink.indexOf('?') === -1 ? '?' : '&') +
                      'qc_highlight=' + encodeURIComponent(highlightValue);
                  }
                })();

                let pageLinks = '';
                
                if (row.type === 'attachment') {
                  const mediaLink = row.media_item
                    ? '<a href="'+row.media_item+'" target="_blank" rel="noopener">Edit</a>'
                    : '';
                  pageLinks = mediaLink;
                } else {
                  const viewLink = row.permalink ? '<a href="'+viewUrl+'" target="_blank" rel="noopener">View</a>' : '';
                  const editLink = row.edit ? '<a href="'+row.edit+'" target="_blank" rel="noopener">Edit</a>' : '';
                  const bbLink   = row.bb ? '<a href="'+row.bb+'" target="_blank" rel="noopener">Builder</a>' : '';
                  pageLinks = [viewLink, editLink, bbLink].filter(Boolean).join(' <span style="opacity:.6;">|</span> ');
                }

                // Show the fuzzy variants (normalize'd phrases). If you prefer original casing, we can enhance later.
                const fuzzyTextCell = (fuzzyOn ? ('<td>' + (match.fuzzyTexts.length ? match.fuzzyTexts.join('; ') : '') + '</td>') : '');

                tr.innerHTML =
                  '<td>'+count+'</td>' +
                  '<td>' + (row.title ? row.title : 'Untitled') + '</td>' +
                  '<td>' + typeLabel(row.type) + '</td>' +
                  '<td>'+ allowed.join(', ') +'</td>' +
                  '<td>'+ pageLinks +'</td>' +
                  fuzzyTextCell;

                tbody.appendChild(tr);
              }

              if (count === 0) {
                tbody.appendChild(emptyRow(cols, 'No matches found.'));
              }
            }

            let timer = null;
            input.addEventListener('input', function(){
              if (timer) { clearTimeout(timer); }
              timer = setTimeout(() => render(input.value), 120);
            });

            if (fuzzy) {
              fuzzy.addEventListener('change', function(){
                render(input.value);
              });
            }
          })();
          </script>
        </div>

        <div id="pages-posts-metadata-table" class="table-container" style="display: none;">
            <h3>Pages & Blog Posts - Metadata & Images</h3>
            <p><strong>Reminder:</strong> Any pages or blog posts that have duplicate images will cause those rows to have red text. Dark orange text is caused by a hero image that does not match the featured images. Orange text is caused by a missing featured image.</p>
            <p>Hero images must be the first image on the page and are either 714 x 250 pixels or 1100 x 320 pixels.</p>
        
            <?php
            // Buttons to filter by type
            echo '<p>';
            echo '<button type="button" id="filter-all"   class="button" style="margin-right: 10px;">Show All Pages & Blogs Posts</button>';
            echo '<button type="button" id="filter-pages" class="button" style="margin-right: 10px;">Show Only Pages</button>';
            echo '<button type="button" id="filter-posts" class="button">Show Only Blog Posts</button>';
            echo '</p>';
            ?>
            
            <?php
            // Query pages + posts
            $args = array(
                'post_type'      => array('page', 'post'),
                'posts_per_page' => -1,
                'post_status'    => array('publish','draft','private','pending','future'),
                'orderby'        => 'title',
                'order'          => 'ASC',
            );
            $metadata_query = new WP_Query($args);
        
            if ($metadata_query->have_posts()) {
                echo '<table class="wp-list-table widefat fixed striped blogs-table">';
                echo '<thead><tr>'
                    . '<th style="width:20px; padding-left: 10px;">#</th>'
                    . '<th style="width:80px;">Type</th>'
                    . '<th style="width:auto;">Page Title</th>'
                    . '<th style="width:150px;">Page Link</th>'
                    . '<th style="width:auto;">Meta Description</th>'
                    . '<th style="width:auto; text-align:center;">Hero Image?</th>'
                    . '</tr></thead>';
                echo '<tbody>';
                $row_number = 1;
                while ($metadata_query->have_posts()) {
                    $metadata_query->the_post();
                    $id   = get_the_ID();
                    $link = get_permalink($id);
                    $desc = get_post_meta($id, '_yoast_wpseo_metadesc', true);
        
                    // first image from content
                    $first = get_first_image_from_content_page_post(get_the_content());
                    $yoast = get_the_post_thumbnail_url($id, 'full');
                    $fifu  = get_post_meta($id, 'fifu_image_url', true);
                    $imgJS = get_image_size_via_javascript_page_post($first, $yoast, $fifu);
        
                    // Type column
                    $type = get_post_type() === 'page' ? 'Page' : 'Blog Post';
        
                    echo '<tr>';
                    echo '<td>' . $row_number . '</td>';
                    echo '<td class="row-type">' . esc_html($type) . '</td>';
                    echo '<td>' . get_the_title() . '</td>';
                    echo '<td style="width:150px;">
                            <a href="' . esc_url( $link ) . '" target="_blank">View</a> |
                            <a href="' . esc_url( admin_url( 'post.php?post=' . $id . '&action=edit' ) ) . '" target="_blank">Edit</a> |
                            <a href="' . esc_url( $link . '/?fl_builder&fl_builder_ui' ) . '" target="_blank">Builder</a>
                          </td>';
                    echo '<td>' . esc_html($desc) . '</td>';
                    echo '<td>' . $imgJS . '</td>';
                    echo '</tr>';
        
                    $row_number++;
                }
                echo '</tbody>';
                echo '</table>';
            }
            wp_reset_postdata();
        
            // Helper functions (keep as you had them)
            function get_first_image_from_content_page_post($content) {
                $pattern = '/<img.+?src=[\'"]([^\'"]+)[\'"].*?>/i';
                preg_match($pattern, $content, $m);
                return $m[1] ?? false;
            }
            function get_image_size_via_javascript_page_post($image_url, $yoast_image_url, $fifu_image_url) {
                return '<span class="image-size-js" '
                     . 'data-src="'   . esc_url($image_url) . '" '
                     . 'data-yoast="' . esc_url($yoast_image_url) . '" '
                     . 'data-fifu="'  . esc_url($fifu_image_url) . '"></span>';
            }
            ?>
        </div>
        
        <script>
        document.addEventListener('DOMContentLoaded', function () {
            const normalizeUrl = url => url ? url.trim().replace(/^https?:\/\//i,'').replace(/\/$/,'') : '';
        
            const setRowTextColor = (el,color) => {
                let row = el.closest('tr');
                if (!row) return;
                row.querySelectorAll('td').forEach(td => td.style.color = color);
            };
        
            const processTable = tableId => {
                let tbl = document.getElementById(tableId);
                if (!tbl) return;
                let seen = {};
        
                tbl.querySelectorAll('.image-size-js').forEach(el => {
                    let raw = el.getAttribute('data-src'),
                        y   = el.getAttribute('data-yoast'),
                        f   = el.getAttribute('data-fifu'),
                        nrm = normalizeUrl(raw);
        
                    if (!raw) {
                        el.innerHTML = '<div style="text-align:center">NO HERO IMAGE</div>';
                        setRowTextColor(el,'#636063');
                        return;
                    }
        
                    if (seen[nrm]) {
                        // We'll handle coloring during image load
                    } else {
                        seen[nrm] = el;
                    }
        
                    let img = new Image();
                    img.onload = () => {
                        const hero = (img.width === 714 && img.height === 250) || (img.width === 1100 && img.height === 320);
                        if (hero) {
                            el.innerHTML = `
                                <div style="text-align:center">
                                    <img src="${raw}" alt="Hero Image" style="max-height:100px"><br>
                                    <small>
                                        <a href="${raw}" target="_blank" rel="noopener noreferrer" style="word-break:break-all">
                                            ${raw}
                                        </a>
                                    </small>
                                </div>`;
                    
                            // PRIORITY FLAGS
                            let row = el.closest('tr');
                            let alreadyColored = false;
                    
                            // Check if this image is a duplicate
                            if (seen[nrm] && seen[nrm] !== el) {
                                setRowTextColor(el, 'red');
                                setRowTextColor(seen[nrm], 'red');
                                alreadyColored = true;
                                // Tag duplicates for the summary banner
                                const row = el.closest('tr');
                                if (row) {
                                    row.classList.add('qc-issue');
                                    row.setAttribute('data-qc-issue','duplicate-image');
                                    if (window.jQuery) { jQuery(document).trigger('qc:pagechange'); }
                                }
                            }
                    
                            // If not a duplicate, check for missing featured images
                            if (!alreadyColored && !y && !f) {
                                setRowTextColor(el, 'orange');
                                alreadyColored = true;
                            }
                    
                            // If not a duplicate or missing, check for mismatch
                            if (!alreadyColored) {
                                const yoastMismatch = y && normalizeUrl(raw) !== normalizeUrl(y);
                                const fifuMismatch  = f && normalizeUrl(raw) !== normalizeUrl(f);
                                if (yoastMismatch || fifuMismatch) {
                                    setRowTextColor(el, '#d35400');
                                }
                            }
                    
                        } else {
                            el.innerHTML = '<div style="text-align:center">NO HERO IMAGE</div>';
                            setRowTextColor(el, '#636063');
                        }
                    };

                    img.onerror = () => {
                        el.innerHTML = '<div style="text-align:center">NO HERO IMAGE</div>';
                        setRowTextColor(el,'#636063');
                    };
                    img.src = raw;
                });
            };
        
            // Show + process on load
            const tableId = 'pages-posts-metadata-table';
            let container = document.getElementById(tableId);
            if (container) {
                container.style.display = 'block';
                processTable(tableId);
            }
        
            // FILTER + REINDEX
            const table = document.querySelector(`#${tableId} table`);
            const rows  = table ? Array.from(table.tBodies[0].rows) : [];
            
            const btnAll   = document.getElementById('filter-all');
            const btnPages = document.getElementById('filter-pages');
            const btnPosts = document.getElementById('filter-posts');
            
            const allFilterButtons = [btnAll, btnPages, btnPosts];
            
            function setActiveMetadataButton(activeBtn){
              allFilterButtons.forEach(btn => {
                if (!btn) return;
                btn.classList.toggle('is-active', btn === activeBtn);
              });
            }
            
            function reindexVisible() {
                let idx = 1;
                rows.forEach(r => {
                    if (r.style.display !== 'none') {
                        // Update the row number cell
                        r.querySelector('td:first-child').textContent = idx;
                        // Set background: odd → #f6f7f7, even → #ffffff
                        r.style.backgroundColor = (idx % 2 === 1) ? '#f6f7f7' : '#ffffff';
                        idx++;
                    }
                });
            }
        
            function updateMetadataPagerVisibility(type){
                // Hide pagination when filtering to only Page or only Blog Post
                // Keep pagination for "All"
                const hidePager = (type !== 'All');

                // Your pager UI is injected as .qc-table-pager (top + bottom)
                // Scope to JUST this container
                const c = document.getElementById(tableId);
                if (!c) return;
                c.querySelectorAll('.qc-table-pager').forEach(el => {
                    el.style.display = hidePager ? 'none' : '';
                });
            }

            // NEW: when switching back to "All", hand control back to the paginator
            function reapplyMetadataPagination(){
                if (!window.jQuery) return;
                const tbl = document.querySelector(`#${tableId} table.wp-list-table`);
                if (!tbl) return;

                const pager = window.jQuery(tbl).data('qcPager');
                if (pager && typeof pager.goToPage === 'function') {
                    pager.goToPage(1, false); // render page 1 without scrolling
                }
            }

            function filter(type) {
                rows.forEach(r => {
                    let cell = r.querySelector('.row-type');
                    let show = (type === 'All' || (cell && cell.textContent === type));
                    r.style.display = show ? '' : 'none';
                });

                // NEW: hide/show pagination controls based on filter
                updateMetadataPagerVisibility(type);

                reindexVisible();

                // switching back to All should re-enable actual pagination
                if (type === 'All') {
                    reapplyMetadataPagination();
                    if (window.jQuery) { window.jQuery(document).trigger('qc:pagechange'); }
                }

                // 1) count URLs among visible rows
                const urlCounts = {};
                rows.forEach(r => {
                    if (r.style.display !== 'none') {
                        const imgEl = r.querySelector('.image-size-js');
                        const url   = normalizeUrl(imgEl.getAttribute('data-src') || '');
                        if (url) urlCounts[url] = (urlCounts[url] || 0) + 1;
                    }
                });
                // 2) apply or clear red text based on current filter
                rows.forEach(r => {
                    if (r.style.display === 'none') return;
                    const imgEl    = r.querySelector('.image-size-js');
                    const url      = normalizeUrl(imgEl.getAttribute('data-src') || '');
                    const rowType  = r.querySelector('.row-type').textContent;  
                    // only color if at least two in the *same* filter group
                    const isDuplicate = url &&
                        urlCounts[url] > 1 &&
                        (type === 'All' || rowType === type);
                    r.querySelectorAll('td').forEach(td => {
                        if (isDuplicate)       td.style.color = 'red';
                        else if (td.style.color === 'red') td.style.color = '';
                    });
                });
                // ——————————————————————————————————————————————
            }
            
            // initialize to "All" so pagination is visible
            updateMetadataPagerVisibility('All');
        
            if (btnAll) {
              btnAll.addEventListener('click', () => {
                setActiveMetadataButton(btnAll);
                filter('All');
              });
            }
            
            if (btnPages) {
              btnPages.addEventListener('click', () => {
                setActiveMetadataButton(btnPages);
                filter('Page');
              });
            }
            
            if (btnPosts) {
              btnPosts.addEventListener('click', () => {
                setActiveMetadataButton(btnPosts);
                filter('Blog Post');
              });
            }
            
            // Default active
            if (btnAll) setActiveMetadataButton(btnAll);
        });
        </script>

        <div id="posts-table" class="table-container" style="display: none;">
            <h3>All Blog Posts</h3>
        
            <?php
            // Generate list of posts
            $args = array(
                'post_type'      => 'post',
                'posts_per_page' => -1,
                'post_status'    => array( 'publish', 'draft', 'private', 'pending', 'future' ),
                'orderby'        => 'title',
                'order'          => 'ASC',
            );
        
            $posts_query = new WP_Query( $args );
        
            if ( $posts_query->have_posts() ) {
                echo '<p>';
                echo '<input type="text" name="blog_search_query" placeholder="Optimization Filter Blog Posts Here..." style="width: 400px;" />';
                echo '</p>';
        
                echo '<table class="wp-list-table widefat fixed striped blogs-table">';
                echo '<thead><tr><th style="width: auto; padding-left: 10px;">#</th><th style="width: auto; padding-left: 10px;">Blog Post Title</th><th>Blog Post Link</th><th>Author</th><th>Publish Date</th></tr></thead>';
                echo '<tbody>';
                $row_number = 1;
        
                while ( $posts_query->have_posts() ) {
                    $posts_query->the_post();
                    $post_id = get_the_ID();
                    $post_link = get_permalink( $post_id );
                    $publish_date = get_the_date();
                    $author_name = get_the_author();
                    echo '<tr><td style="width: 20px;">' . $row_number . '</td><td style="width: auto;">' . get_the_title() . '</td><td><a href="' . $post_link . '">' . $post_link . '</a></td><td>' . esc_html( $author_name ) . '</td><td>' . esc_html( $publish_date ) . '</td></tr>';
                    $row_number++;
                }
        
                echo '</tbody>';
                echo '</table>';
            }
            ?>
        <script>
            jQuery(document).ready(function ($) {
                // Function to filter blog posts based on search input
                $('[name="blog_search_query"]').on('input', function () {
                    var searchValue = $(this).val().toLowerCase();
                    $('#posts-table tbody tr').each(function () {
                        var postTitle = $(this).find('td:nth-child(2)').text().toLowerCase();
                        var postLink = $(this).find('td:nth-child(3)').text().toLowerCase();
                        if (postTitle.includes(searchValue) || postLink.includes(searchValue)) {
                            $(this).hide();
                        } else {
                            $(this).show();
                        }
                    });
        
                    // If search input is empty, show all rows
                    if (searchValue === '') {
                        $('#posts-table tbody tr').show();
                    }
        
                    // Update rows
                    updateRows();
                });
                
                // Prevent Enter key from triggering the Toggle Non-Index button
                $('[name="blog_search_query"]').on('keypress', function (e) {
                    if (e.which === 13) {
                        e.preventDefault();
                        // Do nothing
                    }
                });
        
                // Show all rows upon page load
                $('#posts-table tbody tr').show();
        
                // Function to update row numbers and background color
                function updateRows() {
                    var visibleRows = $('#posts-table tbody tr:visible');
                    visibleRows.each(function (index) {
                        // Use a class for styling alternating rows
                        var rowClass = index % 2 === 0 ? 'even-row' : 'odd-row';
                        var backgroundColor = rowClass === 'even-row' ? '#f6f7f7' : '#ffffff';
                        $(this).removeClass().addClass(rowClass);
                        $(this).css('background-color', backgroundColor);
                        $(this).find('td:first-child').text(index + 1);
                    });
                }
            });
        </script>
        </div>
        
        <div id="postsuncategorized-table" class="table-container" style="display: none;">
            <h3>Uncategorized Blog Posts</h3>
            <?php
            // Generate list of uncategorized posts
            $args = array(
                'post_type'      => 'post',
                'posts_per_page' => -1,
                'post_status'    => array( 'publish', 'draft', 'private' ),
                'orderby'        => 'title',
                'order'          => 'ASC',
                'category_name'  => 'uncategorized'
            );
            $postsuncategorized_query = new WP_Query( $args );
            if ( $postsuncategorized_query->have_posts() ) {
                echo '<table class="wp-list-table widefat fixed striped blogs-table">';
                echo '<thead><tr><th style="width: auto; padding-left: 10px;">#</th><th style="width: auto; padding-left: 10px;">Uncategorized Blog Post Title</th><th>Blog Post Link</th><th>Author</th><th>Publish Date</th></tr></thead>';
                echo '<tbody>';
                $row_number = 1;
                while ( $postsuncategorized_query->have_posts() ) {
                    $postsuncategorized_query->the_post();
                    $postsuncategorized_id = get_the_ID();
                    $postuncategorized_link = get_permalink($postsuncategorized_id);
                    $uncategorizedpublish_date = get_the_date();
                    $uncategorizedauthor_name = get_the_author();
                    echo '<tr><td style="width: 20px;">' . $row_number . '</td><td style="width: auto;">' . get_the_title() . '</td><td><a href="' . $postuncategorized_link . '">' . $postuncategorized_link . '</a></td><td>' . esc_html($uncategorizedauthor_name) . '</td><td>' . esc_html($uncategorizedpublish_date) . '</td></tr>';
                    $row_number++;
                }
                echo '</tbody>';
                echo '</table>';
            }
            ?>
        </div>
        
        <div id="postsuplink-table" class="table-container" style="display: none;">
            <h3>Uplink Blog Posts</h3>
            <?php
            // Generate list of uplink posts
            $args = array(
                'post_type'      => 'post',
                'posts_per_page' => -1,
                'post_status'    => array( 'publish', 'draft', 'private' ),
                'orderby'        => 'title',
                'order'          => 'ASC',
                'category_name'  => 'uplink'
            );
            $postsuplink_query = new WP_Query( $args );
            if ( $postsuplink_query->have_posts() ) {
                echo '<table class="wp-list-table widefat fixed striped blogs-table">';
                echo '<thead><tr><th style="width: auto; padding-left: 10px;">#</th><th style="width: auto; padding-left: 10px;">Uplink Blog Post Title</th><th>Blog Post Link</th><th>Author</th><th>Publish Date</th></tr></thead>';
                echo '<tbody>';
                $row_number = 1;
                while ( $postsuplink_query->have_posts() ) {
                    $postsuplink_query->the_post();
                    $postsuplink_id = get_the_ID();
                    $postuplink_link = get_permalink($postsuplink_id);
                    $uplinkpublish_date = get_the_date();
                    $uplinkauthor_name = get_the_author();
                    echo '<tr><td style="width: 20px;">' . $row_number . '</td><td style="width: auto;">' . get_the_title() . '</td><td><a href="' . $postuplink_link . '">' . $postuplink_link . '</a></td><td>' . esc_html($uplinkauthor_name) . '</td><td>' . esc_html($uplinkpublish_date) . '</td></tr>';
                    $row_number++;
                }
                echo '</tbody>';
                echo '</table>';
            }
            ?>
        </div>
        
        <div id="no_author-table" class="table-container" style="display: none;">
            <h3>Blog Posts Without Inception As The Author</h3>
            <?php
            // Get the user IDs based on the emails to be excluded
            $excluded_users_emails = array('support@inceptiononlinemarketing.com', 'support@inception-websites.com');
            $excluded_users = array();
        
            foreach ($excluded_users_emails as $email) {
                $user = get_user_by('email', $email);
                if ($user) {
                    $excluded_users[] = $user->ID;
                }
            }
        
            // Generate list of posts without Inception as the author or with no author assigned
            $args = array(
                'post_type'      => 'post',
                'posts_per_page' => -1,
                'post_status'    => array('publish', 'draft', 'private', 'pending', 'future'),
                'orderby'        => 'title',
                'order'          => 'ASC',
                'author__not_in' => $excluded_users, // Exclude specified authors
                'author__in'     => array(0), // Include posts with no author assigned
                'meta_query'     => array(
                    'relation' => 'OR',
                    array(
                        'key'     => '_author_email', // Custom field key for author email
                        'compare' => 'NOT EXISTS', // Check if the custom field doesn't exist
                    ),
                    array(
                        'key'     => '_author_email',
                        'value'   => $excluded_users_emails, // Exclude specified emails
                        'compare' => 'NOT IN',
                    ),
                ),
            );
            $no_author_posts_query = new WP_Query($args);
        
            if ($no_author_posts_query->have_posts()) {
                echo '<table class="wp-list-table widefat fixed striped blogs-table">';
                echo '<thead><tr><th style="width: auto; padding-left: 10px;">#</th><th style="width: auto; padding-left: 10px;">Blog Post Title Without Inception As Author</th><th>Blog Post Link</th><th>Author</th><th>Publish Date</th></tr></thead>';
                echo '<tbody>';
                $row_number = 1;
                while ($no_author_posts_query->have_posts()) {
                    $no_author_posts_query->the_post();
                    $no_author_post_id = get_the_ID();
                    $no_author_post_link = get_permalink($no_author_post_id);
                    $no_author_publish_date = get_the_date();
                    $no_author_author_name = get_the_author();
                    echo '<tr><td style="width: 20px;">' . $row_number . '</td><td style="width: auto;">' . get_the_title() . '</td><td><a href="' . esc_url($no_author_post_link) . '">' . esc_url($no_author_post_link) . '</a></td><td>' . esc_html($no_author_author_name) . '</td><td>' . esc_html($no_author_publish_date) . '</td></tr>';
                    $row_number++;
                }
                echo '</tbody>';
                echo '</table>';
            }
            ?>
        </div>

        <div id="external-links-table" class="table-container" style="display: none;">
          <h3>External Links Locations</h3>
          <p><strong>Reminder:</strong> These pages and menus contain links to external websites and should be reviewed.</p>
          <?php
          /**
           * Recursively extract any links from builder data.
           *
           * @param mixed $data The builder data (array/object).
           * @param array &$builder_links Array to which any found links are added.
           */
          function extract_links_from_builder_data($data, &$builder_links) {
              if (is_array($data)) {
                  foreach ($data as $key => $value) {
                      if ($key === 'link' && is_string($value)) {
                          $builder_links[] = $value;
                      } elseif (is_array($value) || is_object($value)) {
                          extract_links_from_builder_data($value, $builder_links);
                      }
                  }
              } elseif (is_object($data)) {
                  foreach ($data as $key => $value) {
                      if ($key === 'link' && is_string($value)) {
                          $builder_links[] = $value;
                      } elseif (is_array($value) || is_object($value)) {
                          extract_links_from_builder_data($value, $builder_links);
                      }
                  }
              }
          }
          
          // Get site URL and home URL for filtering.
          $site_url = get_site_url();
          $home_url = untrailingslashit(home_url('/'));
          
          // -------------------------------
          // Get external links from Header Menu.
          // -------------------------------
          $header_menu_links = array();
          $menu_locations = get_nav_menu_locations();
          if (isset($menu_locations['header'])) {
              $menu = wp_get_nav_menu_object($menu_locations['header']);
              $menu_items = wp_get_nav_menu_items($menu->term_id);
              if ($menu_items) {
                  foreach ($menu_items as $item) {
                      $menu_url = $item->url;
                      if (empty($menu_url) || strpos($menu_url, 'inceptionimages.com') !== false || strpos($menu_url, 'inception-example36.com') !== false) { 
                          continue;
                      }
                      if (strpos($menu_url, $site_url) === false && strpos($menu_url, '/') !== 0 && strpos($menu_url, 'mailto:') !== 0 && strpos($menu_url, 'tel:') !== 0) {
                          $header_menu_links[] = untrailingslashit($menu_url);
                      }
                  }
              }
          }
          
          // Initialize aggregation array.
          // "post_" keys are for content anchors,
          // "menu_" keys for header/footer menu items,
          // "builder_" keys for Fl Builder data.
          $aggregated_links = array();
          $row_number = 1;
          
          // Query pages, posts, and fl-theme-layouts.
          $args = array(
                'ignore_sticky_posts' => true,
                'no_found_rows' => true,
              'post_type'      => array('page', 'post', 'fl-theme-layout'),
              'posts_per_page' => -1,
              'post_status'    => array('publish', 'draft', 'private', 'pending', 'future'),
              'orderby'        => 'title',
              'order'          => 'ASC'
          );
          $external_links_query = new WP_Query($args);
          
          if ($external_links_query->have_posts()) {
              while ($external_links_query->have_posts()) {
                  $external_links_query->the_post();
                  $post_id      = get_the_ID();
                  $post_title   = get_the_title();
                  $post_link    = get_permalink($post_id);
                  $post_content = get_post_field('post_content', $post_id);
          
                  // ------------------------------------------------------------------
                  // Process post content using DOMDocument to extract <a> tags.
                  // ------------------------------------------------------------------
                  libxml_use_internal_errors(true);
                  $doc = new DOMDocument();
                  $doc->loadHTML('<?xml encoding="utf-8" ?>' . $post_content);
                  libxml_clear_errors();
                  $xpath = new DOMXPath($doc);
                  $anchors = $xpath->query('//a');
          
                  foreach ($anchors as $anchor) {
                      // Check ancestry – if any parent element’s class or id contains "menu" or "nav", skip.
                      $skip = false;
                      $node = $anchor;
                      while ($node = $node->parentNode) {
                          if ($node->nodeType === XML_ELEMENT_NODE) {
                              if ($node->hasAttribute('class')) {
                                  $classes = $node->getAttribute('class');
                                  if (stripos($classes, 'menu') !== false || stripos($classes, 'nav') !== false) {
                                      $skip = true;
                                      break;
                                  }
                              }
                              if ($node->hasAttribute('id')) {
                                  $id = $node->getAttribute('id');
                                  if (stripos($id, 'menu') !== false || stripos($id, 'nav') !== false) {
                                      $skip = true;
                                      break;
                                  }
                              }
                          }
                      }
                      if ($skip) {
                          continue;
                      }
          
                      $url = $anchor->getAttribute('href');
                      $target = $anchor->getAttribute('target');
                      $opens_new_tab = (strtolower($target) === '_blank') ? 'Yes' : 'No';
                      $text = trim($anchor->textContent);
                      if (empty($text)) {
                          $text = '(Image, Button, or Module Link)';
                      }
          
                      // Skip unwanted patterns.
                      if (empty($url) || strpos($url, '#') === 0 || strpos($url, 'tel:') === 0 ||
                          strpos($url, 'mailto:') === 0 || strpos($url, 'void(0)') !== false) {
                          continue;
                      }
                      // Skip embedded video links.
                      if (stripos($url, 'youtube.com') !== false || stripos($url, 'youtu.be') !== false) {
                          if (stripos($url, 'watch?v=') !== false) {
                              continue;
                          }
                          $parsed = parse_url($url);
                          if (isset($parsed['host']) && stripos($parsed['host'], 'youtu.be') !== false) {
                              if (isset($parsed['path']) && preg_match('/^\/[A-Za-z0-9_-]{11}$/', $parsed['path'])) {
                                  continue;
                              }
                          }
                      }
                      if (stripos($url, 'vimeo.com') !== false) {
                          $parsed = parse_url($url);
                          if (isset($parsed['path'])) {
                              $vimeo_path = ltrim($parsed['path'], '/');
                              if (preg_match('/^\d+$/', $vimeo_path)) {
                                  continue;
                              }
                          }
                      }
                      if (stripos($url, 'facebook.com/video') !== false || stripos($url, 'fb.watch') !== false) {
                          continue;
                      }
                      if (stripos($url, 'instagram.com/embed') !== false) {
                          continue;
                      }
                      if (stripos($url, 'twitter.com/i/status') !== false) {
                          continue;
                      }
                      // Ignore links with unwanted domains/admin patterns.
                      if (strpos($url, 'inceptionimages.com') !== false || strpos($url, 'inception-example36.com') !== false ||
                          strpos($url, '/wp-admin/profile.php') !== false || strpos($url, '/wp-admin/post.php?post=') !== false ||
                          strpos($url, '/?attachment_id=') !== false) {
                          continue;
                      }
          
                      // Check if the link is external.
                      if (strpos($url, $site_url) === false && strpos($url, '/') !== 0) {
                          $normalized_link = untrailingslashit($url);
                          $key = 'post_' . $post_id . '_' . md5($normalized_link);
                          if (!isset($aggregated_links[$key])) {
                              $aggregated_links[$key] = array(
                                  'row_data' => array(
                                      'page_menu_title' => $post_title,
                                      'page_link'       => $post_link,
                                      'anchor_text'     => array($text),
                                      'external_link'   => $url,
                                      'opens_new_tab'   => array($opens_new_tab),
                                  ),
                                  'count' => 1,
                              );
                          } else {
                              $aggregated_links[$key]['count']++;
                              $aggregated_links[$key]['row_data']['anchor_text'][] = $text;
                              $aggregated_links[$key]['row_data']['opens_new_tab'][] = $opens_new_tab;
                          }
                      }
                  } // End processing post content anchors.
          
                  // ------------------------------------------------------------------
                  // Process Beaver Builder data for fl-theme-layouts (Global Header, etc.)
                  // ------------------------------------------------------------------
                  if (get_post_type() === 'fl-theme-layout') {
                      $builder_data = get_post_meta($post_id, '_fl_builder_data', true);
                      if (!empty($builder_data)) {
                          $builder_data = maybe_unserialize($builder_data);
                          $builder_links = array();
                          extract_links_from_builder_data($builder_data, $builder_links);
          
                          foreach ($builder_links as $builder_link) {
                              if (empty($builder_link)) {
                                  continue;
                              }
                              // Only add if the link is external (not containing the site URL or starting with a slash).
                              if (strpos($builder_link, $site_url) !== false || strpos($builder_link, '/') === 0) {
                                  continue;
                              }
                              
                              // Skip unwanted patterns.
                              if (empty($builder_link) || strpos($builder_link, '#') === 0 || strpos($builder_link, 'tel:') === 0 ||
                                  strpos($builder_link, 'mailto:') === 0 || strpos($builder_link, 'void(0)') !== false) {
                                  continue;
                              }
                              
                              // Ignore links with unwanted domains/admin patterns.
                              if (strpos($builder_link, 'inceptionimages.com') !== false || strpos($builder_link, 'inception-example36.com') !== false ||
                                  strpos($builder_link, '/wp-admin/profile.php') !== false || strpos($builder_link, '/wp-admin/post.php?post=') !== false ||
                                  strpos($builder_link, '/?attachment_id=') !== false) {
                                  continue;
                              }
                              $text = '(Image, Button, or Module Link)';
                              $opens_new_tab = 'Yes';
                              $normalized_link = untrailingslashit($builder_link);
                              $key = 'builder_' . $post_id . '_' . md5($normalized_link);
                              if (!isset($aggregated_links[$key])) {
                                  $aggregated_links[$key] = array(
                                      'row_data' => array(
                                          'page_menu_title' => $post_title . '',
                                          'page_link'       => $post_link,
                                          'anchor_text'     => array($text),
                                          'external_link'   => $builder_link,
                                          'opens_new_tab'   => array($opens_new_tab),
                                      ),
                                      'count' => 1,
                                  );
                              } else {
                                  $aggregated_links[$key]['count']++;
                                  $aggregated_links[$key]['row_data']['anchor_text'][] = $text;
                                  $aggregated_links[$key]['row_data']['opens_new_tab'][] = $opens_new_tab;
                              }
                          }
                      }
                  }
              }
          }
          // Reset post data.
          wp_reset_postdata();
          
          // -------------------------------
          // Add Header and Footer Menu Items
          // (These are always added from the menu and appear in their own row.)
          // -------------------------------
          $menu_keys = array('header', 'footer');
          foreach ($menu_keys as $location) {
              if (isset($menu_locations[$location])) {
                  $menu = wp_get_nav_menu_object($menu_locations[$location]);
                  $menu_items = wp_get_nav_menu_items($menu->term_id);
                  if ($menu_items) {
                      foreach ($menu_items as $item) {
                          $menu_url = $item->url;
                          $menu_text = $item->title;
                          if (empty($menu_url)) {
                              continue;
                          }
                          if (strpos($menu_url, $site_url) === false && strpos($menu_url, '/') !== 0 &&
                              strpos($menu_url, 'mailto:') === false && strpos($menu_url, 'tel:') === false) {
                              $opens_new_tab = 'Yes'; // Assume menu items always open in a new tab.
                              $menu_label = isset($menu->name) ? $menu->name : ucfirst($location) . ' Menu';
                              $admin_menu_link = admin_url('nav-menus.php');
          
                              $normalized_menu_url = untrailingslashit($menu_url);
                              $key = 'menu_' . $location . '_' . md5($normalized_menu_url);
                              if (!isset($aggregated_links[$key])) {
                                  $aggregated_links[$key] = array(
                                      'row_data' => array(
                                          'page_menu_title' => $menu_label,
                                          'page_link'       => $admin_menu_link,
                                          'anchor_text'     => array($menu_text),
                                          'external_link'   => $menu_url,
                                          'opens_new_tab'   => array($opens_new_tab),
                                      ),
                                      'count' => 1,
                                  );
                              } else {
                                  $aggregated_links[$key]['count']++;
                                  $aggregated_links[$key]['row_data']['anchor_text'][] = $menu_text;
                                  $aggregated_links[$key]['row_data']['opens_new_tab'][] = $opens_new_tab;
                              }
                          }
                      }
                  }
              }
          }
          
          // ------------------------------------------------------------------
          // Build and output the table rows from the aggregated links.
          // ------------------------------------------------------------------
          $rows = '';
          foreach ($aggregated_links as $data) {
              $row_data = $data['row_data'];
              
              // If multiple occurrences have different anchor text or new-tab values, display "--"
              $unique_anchor = array_unique($row_data['anchor_text']);
              $display_anchor = (count($unique_anchor) === 1) ? current($unique_anchor) : '-- Multiple Locations On Page --';
          
              $unique_opens = array_unique($row_data['opens_new_tab']);
              $display_opens = (count($unique_opens) === 1) ? current($unique_opens) : '-- Yes & No --';
          
              // Only display the count if more than one occurrence.
              $display_count = ($data['count'] > 1) ? $data['count'] : '';
          
              $rows .= '<tr>';
              $rows .= '<td>' . $row_number . '</td>';
              $rows .= '<td><a href="' . esc_url($row_data['page_link']) . '" target="_blank">' . esc_html($row_data['page_menu_title']) . '</a></td>';
              $rows .= '<td>' . esc_html($display_anchor) . '</td>';
              $rows .= '<td><a href="' . esc_url($row_data['external_link']) . '" target="_blank">' . esc_html($row_data['external_link']) . '</a></td>';
              $rows .= '<td style="width: 60px; text-align: center;">' . esc_html($display_count) . '</td>';
              $rows .= '<td style="width: 125px; text-align: center;">' . esc_html($display_opens) . '</td>';
              $rows .= '</tr>';
              $row_number++;
          }
          
          if (!empty($rows)) {
              echo '<table class="wp-list-table widefat fixed striped external-table">';
              echo '<thead><tr>';
              echo '<th style="width: 20px; padding-left: 10px;">#</th>';
              echo '<th style="width: auto; padding-left: 10px;">Page/Menu Title</th>';
              echo '<th>Anchor Text/Location</th>';
              echo '<th>External Link</th>';
              echo '<th style="width: 60px; text-align: center;"># on Page</th>';
              echo '<th style="width: 125px;">Opens in New Tab?</th>';
              echo '</tr></thead>';
              echo '<tbody>' . $rows . '</tbody>';
              echo '</table>';
          } else {
              echo '<p>No external links found.</p>';
          }
          ?>
        </div>

        <div id="highlevel-table" class="table-container" style="display: none;">
            <h3>HighLevel &amp; Doctible/Patient Link Form Locations</h3>
            <p><strong>Reminder:</strong> Any form IDs that are orange have multiple instances on the site. Any pages that have red text are getting a 404 error on their forms. The Link Status column only displays a warning when a page is not linked from a menu or any other published content.</p>
            <?php
            if (!function_exists('inception_qc_link_target_post_id')) {
                /** Resolve an internal URL or root-relative path to its WordPress post ID. */
                function inception_qc_link_target_post_id($url) {
                    $url = html_entity_decode(trim((string) $url), ENT_QUOTES | ENT_HTML5, 'UTF-8');
                    if ($url === '' || $url[0] === '#' || preg_match('#^(?:mailto|tel|javascript|data):#i', $url)) return 0;
                    if (strpos($url, '//') === 0) $url = (is_ssl() ? 'https:' : 'http:') . $url;
                    if (strpos($url, '/') === 0) $url = home_url($url);
                    if (!preg_match('#^https?://#i', $url)) $url = home_url('/' . ltrim($url, '/'));

                    $target_host = preg_replace('/^www\./', '', strtolower((string) wp_parse_url($url, PHP_URL_HOST)));
                    $home_host = preg_replace('/^www\./', '', strtolower((string) wp_parse_url(home_url('/'), PHP_URL_HOST)));
                    $site_host = preg_replace('/^www\./', '', strtolower((string) wp_parse_url(site_url('/'), PHP_URL_HOST)));
                    if ($target_host !== '' && $target_host !== $home_host && $target_host !== $site_host) return 0;

                    $url_without_fragment = preg_replace('/#.*$/', '', $url);
                    $target_id = (int) url_to_postid($url_without_fragment);
                    if ($target_id > 0) return $target_id;

                    $path = (string) wp_parse_url($url_without_fragment, PHP_URL_PATH);
                    if ($path === '' || $path === '/') return (int) get_option('page_on_front');
                    $page = get_page_by_path(trim(rawurldecode($path), '/'), OBJECT, array('page', 'post'));
                    return $page instanceof WP_Post ? (int) $page->ID : 0;
                }
            }

            if (!function_exists('inception_qc_collect_link_urls')) {
                /** Extract anchor/shortcode URLs and builder link fields from mixed content. */
                function inception_qc_collect_link_urls($value, &$urls, $field_name = '') {
                    if (is_string($value)) {
                        $decoded = maybe_unserialize($value);
                        if ($decoded !== $value && (is_array($decoded) || is_object($decoded))) {
                            inception_qc_collect_link_urls($decoded, $urls, $field_name);
                            return;
                        }
                        if (preg_match_all('/(?:href|url|link)\s*=\s*["\']([^"\']+)["\']/i', $value, $matches)) {
                            foreach ($matches[1] as $match) $urls[] = $match;
                        }
                        if (preg_match('/(?:^|_)(?:link|url)(?:$|_)/i', (string) $field_name)
                            && preg_match('#^(?:https?:)?//|^/#i', trim($value))) {
                            $urls[] = trim($value);
                        }
                        return;
                    }
                    if (is_array($value) || is_object($value)) {
                        foreach ((array) $value as $key => $child) {
                            inception_qc_collect_link_urls($child, $urls, (string) $key);
                        }
                    }
                }
            }

            if (!function_exists('inception_qc_linked_content_ids')) {
                /** Return IDs that have at least one inbound link from live content or any nav menu. */
                function inception_qc_linked_content_ids() {
                    static $linked_ids = null;
                    if (is_array($linked_ids)) return $linked_ids;
                    $linked_ids = array();
                    $front_page_id = (int) get_option('page_on_front');
                    if ($front_page_id > 0) $linked_ids[$front_page_id] = true;

                    // Only count menus that are assigned or follow the site's known live
                    // header/footer naming conventions; stale unassigned menus do not count.
                    $active_menu_ids = array_values(array_filter(array_map('intval', (array) get_nav_menu_locations())));
                    $menus = wp_get_nav_menus();
                    if (!empty($menus) && !is_wp_error($menus)) {
                        foreach ($menus as $menu) {
                            $menu_slug = isset($menu->slug) ? (string) $menu->slug : '';
                            $is_known_live_menu = in_array((int) $menu->term_id, $active_menu_ids, true)
                                || $menu_slug === 'main-menu'
                                || $menu_slug === 'multi-main-menu'
                                || (bool) preg_match('/^main-menu-location-\d+$/', $menu_slug)
                                || (bool) preg_match('/^footer(?:-menu|-\d+)?$/', $menu_slug)
                                || $menu_slug === 'site-footer';
                            if (!$is_known_live_menu) continue;
                            $items = wp_get_nav_menu_items($menu->term_id);
                            if (empty($items) || is_wp_error($items)) continue;
                            foreach ($items as $item) {
                                $target_id = 0;
                                if ($item->type === 'post_type' && !empty($item->object_id)) {
                                    $target_id = (int) $item->object_id;
                                } elseif (!empty($item->url)) {
                                    $target_id = inception_qc_link_target_post_id($item->url);
                                }
                                if ($target_id > 0) $linked_ids[$target_id] = true;
                            }
                        }
                    }

                    $sources = get_posts(array(
                        'post_type' => array('page', 'post', 'fl-theme-layout'),
                        'post_status' => 'publish',
                        'posts_per_page' => -1,
                        'orderby' => 'ID',
                        'order' => 'ASC',
                    ));
                    global $post;
                    $previous_post = $post;
                    foreach ($sources as $source) {
                        $source_id = (int) $source->ID;
                        $urls = array();
                        inception_qc_collect_link_urls((string) $source->post_content, $urls, 'post_content');

                        $post = $source;
                        setup_postdata($post);
                        $rendered = apply_filters('the_content', (string) $source->post_content);
                        inception_qc_collect_link_urls($rendered, $urls, 'rendered_content');

                        foreach ((array) get_post_meta($source_id) as $meta_key => $meta_values) {
                            if (preg_match('/(?:draft|revision|autosave)/i', (string) $meta_key)) continue;
                            foreach ((array) $meta_values as $meta_value) {
                                inception_qc_collect_link_urls($meta_value, $urls, (string) $meta_key);
                            }
                        }
                        foreach (array_unique(array_filter(array_map('trim', $urls))) as $url) {
                            $target_id = inception_qc_link_target_post_id($url);
                            if ($target_id > 0 && $target_id !== $source_id) $linked_ids[$target_id] = true;
                        }
                    }
                    if ($previous_post instanceof WP_Post) {
                        $post = $previous_post;
                        setup_postdata($post);
                    } else {
                        wp_reset_postdata();
                    }
                    return $linked_ids;
                }
            }

            $highlevel_linked_content_ids = inception_qc_linked_content_ids();

            // Generate list of posts/pages and global footer containing iframes
            $args = array(
                'post_type'      => array('page', 'post', 'fl-theme-layout'),
                'posts_per_page' => -1,
                'post_status'    => array('publish', 'draft', 'private', 'pending', 'future'),
                'orderby'        => 'title',
                'order'          => 'ASC',
            );
            $highlevel_query = new WP_Query($args);

            // Array to keep track of Form IDs and duplicates
            $form_ids = array();
            $duplicate_form_ids = array();

            // Simple cache so we don't fetch the same Doctible form multiple times
            $doctible_name_cache = array();

            // Per-request cache so we don't check the same iframe URL multiple times (no persistent caching)
            $iframe_status_cache = array();
            
            if (!function_exists('inception_qc_is_invalid_hl_form_id')) {
                function inception_qc_is_invalid_hl_form_id($form_id) {
                    $id = trim((string)$form_id);
                    if ($id === '') return true;
            
                    // Strip query/fragment just in case the captured ID includes it
                    $id = preg_replace('/[?#].*$/', '', $id);
            
                    // Obvious placeholders / junk patterns from embeds
                    if (stripos($id, 'insert') !== false) return true;
                    if (strpos($id, '*') !== false) return true;
            
                    // Only allow typical safe chars
                    if (!preg_match('/^[A-Za-z0-9_-]+$/', $id)) return true;
            
                    return false;
                }
            }

            if ($highlevel_query->have_posts()) {
                echo '<table class="wp-list-table widefat fixed striped highlevel-table">';
                echo '<thead><tr><th style="width: 40px; padding: 0 10px;">#</th><th style="width: auto; padding-left: 10px;">Page Title</th><th>Page Link</th><th>Form Name</th><th>Form ID</th><th>Link Status</th></tr></thead>';
                echo '<tbody>';
                $row_number = 1;

                while ($highlevel_query->have_posts()) {
                    $highlevel_query->the_post();
                    $highlevel_post_id = get_the_ID();
                    $highlevel_post_link = get_permalink($highlevel_post_id);
                    $content = get_the_content();
                    $highlevel_is_unlinked_page = get_post_type($highlevel_post_id) === 'page'
                        && !isset($highlevel_linked_content_ids[$highlevel_post_id]);
                    $highlevel_link_status = $highlevel_is_unlinked_page
                        ? '<span class="qc-unlinked-page-warning">Not linked anywhere on site</span>'
                        : '';

                    // --------------------------------------------
                    // HIGHLEVEL: find iframes like https://ap.inceptionchiro.com/widget/form/{id}
                    // --------------------------------------------
                    $hl_iframes = array();
                    $hl_ids = array();
                    if (preg_match_all('/<iframe\s[^>]*src=["\']https:\/\/ap\.inceptionchiro\.com\/widget\/form\/([^"\']+)["\'][^>]*>/i', $content, $hl_matches)) {
                        $hl_iframes = isset($hl_matches[0]) ? $hl_matches[0] : array();
                        $hl_ids     = isset($hl_matches[1]) ? $hl_matches[1] : array();
                    }

                    if (!empty($hl_iframes)) {
                        foreach ($hl_iframes as $idx => $iframe) {
                            $form_id = isset($hl_ids[$idx]) ? $hl_ids[$idx] : '';

                            // Attempt to read "data-form-name" from the iframe markup (HighLevel embeds often include this)
                            $form_name = '';
                            if (preg_match('/data-form-name="([^"]+)"/i', $iframe, $form_name_matches)) {
                                $form_name = isset($form_name_matches[1]) ? $form_name_matches[1] : '';
                            }

                            // Track duplicates by raw form_id (keep legacy behavior)
                            if (!empty($form_id)) {
                                if (isset($form_ids[$form_id])) {
                                    $duplicate_form_ids[$form_id] = true;
                                } else {
                                    $form_ids[$form_id] = true;
                                }
                            }
                            // Check iframe HTTP status inline (deduped per-request) to reliably flag real 404s like the legacy version.
                            $iframe_url = inception_qc_extract_iframe_src($iframe);
                            $iframe_status = 0;
                            if (!empty($iframe_url)) {
                                if (isset($iframe_status_cache[$iframe_url])) {
                                    $iframe_status = $iframe_status_cache[$iframe_url];
                                } else {
                                    $iframe_status = inception_qc_http_status($iframe_url);
                                    $iframe_status_cache[$iframe_url] = $iframe_status;
                                }
                            }
                            $error_class = ($iframe_status == 404) ? ' class="error-row"' : '';

                            $is_invalid_id = inception_qc_is_invalid_hl_form_id($form_id);
                            
                            $row_classes = 'qc-highlevel-row';
                            if ($iframe_status == 404 || $is_invalid_id) {
                                $row_classes .= ' error-row';
                            }
                            if ($is_invalid_id) {
                                $row_classes .= ' qc-invalid-id';
                            }
                            
                            echo '<tr class="' . esc_attr($row_classes) . '" data-check-url="' . esc_attr($iframe_url) . '">';

                            echo '<td style="width: 40px; padding: 0 10px;">' . intval($row_number) . '</td>';
                            echo '<td style="width: auto; padding-left: 10px;" class="page-title">' . esc_html(get_the_title($highlevel_post_id)) . '</td>';
                            echo '<td class="page-link"><a href="' . esc_url($highlevel_post_link) . '" target="_blank">' . esc_url($highlevel_post_link) . '</a></td>';
                            echo '<td>' . esc_html($form_name) . '</td>';
                            echo '<td>' . esc_html($form_id) . '</td>';
                            echo '<td>' . $highlevel_link_status . '</td>';
                            echo '</tr>';
                            $row_number++;
                        }
                    }

                    // --------------------------------------------
                    // DOCTIBLE: find iframes like:
                    // https://form.doctible.com/forms/{id}
                    // https://www.doctible.com/forms/{id}
                    // --------------------------------------------
                    $doc_iframes = array();
                    $doc_ids = array();
                    
                    if (preg_match_all(
                        '/<iframe\s[^>]*src=["\']https:\/\/(?:[a-z0-9-]+\.)?doctible\.com\/forms\/([^?"\'#\/]+)(?:[?#][^"\']*)?["\'][^>]*>/i',
                        $content,
                        $doc_matches
                    )) {
                        $doc_iframes = isset($doc_matches[0]) ? $doc_matches[0] : array();
                        $doc_ids     = isset($doc_matches[1]) ? $doc_matches[1] : array();
                    }

                    if (!empty($doc_iframes)) {
                        foreach ($doc_iframes as $idx => $iframe) {
                            $form_id = isset($doc_ids[$idx]) ? $doc_ids[$idx] : '';
                            $form_name = '';

                            // Try to avoid multiple remote requests for the same Doctible ID
                            if (!empty($form_id) && isset($doctible_name_cache[$form_id])) {
                                $form_name = $doctible_name_cache[$form_id];
                            } else {
                                // Build the URL and try to read form name from window.__INITIAL__DATA__
                                $form_url = 'https://www.doctible.com/forms/' . rawurlencode($form_id);
                                $form_name = get_doctible_form_name($form_url);
                                if (!empty($form_id)) {
                                    $doctible_name_cache[$form_id] = $form_name;
                                }
                            }

                            // Track duplicates by raw form_id (shared behavior with HighLevel)
                            if (!empty($form_id)) {
                                if (isset($form_ids[$form_id])) {
                                    $duplicate_form_ids[$form_id] = true;
                                } else {
                                    $form_ids[$form_id] = true;
                                }
                            }
                            // Check iframe HTTP status inline (deduped per-request) to reliably flag real 404s like the legacy version.
                            $iframe_url = inception_qc_extract_iframe_src($iframe);
                            $iframe_status = 0;
                            if (!empty($iframe_url)) {
                                if (isset($iframe_status_cache[$iframe_url])) {
                                    $iframe_status = $iframe_status_cache[$iframe_url];
                                } else {
                                    $iframe_status = inception_qc_http_status($iframe_url);
                                    $iframe_status_cache[$iframe_url] = $iframe_status;
                                }
                            }
                            $error_class = ($iframe_status == 404) ? ' class="error-row"' : '';

                            echo '<tr class="qc-highlevel-row' . (($iframe_status == 404) ? ' error-row' : '') . '" data-check-url="' . esc_attr($iframe_url) . '">';

                            echo '<td style="width: 40px; padding: 0 10px;">' . intval($row_number) . '</td>';
                            echo '<td style="width: auto; padding-left: 10px;" class="page-title">' . esc_html(get_the_title($highlevel_post_id)) . '</td>';
                            echo '<td class="page-link"><a href="' . esc_url($highlevel_post_link) . '" target="_blank">' . esc_url($highlevel_post_link) . '</a></td>';
                            echo '<td>' . esc_html($form_name) . '</td>';
                            echo '<td>' . esc_html($form_id) . '</td>';
                            echo '<td>' . $highlevel_link_status . '</td>';
                            echo '</tr>';
                            $row_number++;
                        }
                    }
                }

                // Apply duplicate class to all rows associated with duplicate Form IDs
                foreach ($duplicate_form_ids as $form_id => $_) {
                    // Note: this keeps the original selector behavior (matches any cell containing the ID)
                    echo "<script>jQuery('td:contains(\"" . esc_js($form_id) . "\")').parent().addClass('duplicate-row');</script>";
                }

                echo '</tbody>';
                echo '</table>';
            }

            // Reset Post Data
            wp_reset_postdata();

            /**
             * Function to check the HTTP status code of iframe URLs (generic for any provider)
             * @param string $iframe The iframe HTML code
             * @return int HTTP status code of the iframe URL
             */
            function check_iframe_status($iframe) {
                // Grab the first src attribute we find
                if (!preg_match('/\s+src=["\']([^"\']+)["\']/i', $iframe, $matches)) {
                    return 0;
                }
                $iframe_url = isset($matches[1]) ? $matches[1] : '';

                if (empty($iframe_url)) {
                    return 0;
                }

                // Make a HEAD request to the iframe URL using WordPress HTTP API
                $response = wp_remote_head($iframe_url, array('timeout' => 10));

                // If HEAD fails (some servers block HEAD), try a lightweight GET
                if (is_wp_error($response) || !isset($response['response']['code'])) {
                    $response = wp_remote_get($iframe_url, array('timeout' => 10, 'redirection' => 3));
                }

                if (!is_wp_error($response) && isset($response['response']['code'])) {
                    return intval($response['response']['code']);
                } else {
                    return 0; // Unable to retrieve HTTP status code
                }
            }

            /**
             * Fetch a Doctible form page and try to extract the form "name"
             * from window.__INITIAL__DATA__ (falls back to scanning for "formData"->"name").
             *
             * @param string $form_url  The full https://form.doctible.com/forms/{id} URL
             * @return string The form "name" if found, otherwise empty string
             */
            function get_doctible_form_name($form_url) {
                $response = wp_remote_get($form_url, array(
                    'timeout' => 12,
                    'redirection' => 3,
                ));

                if (is_wp_error($response)) {
                    return '';
                }

                $code = wp_remote_retrieve_response_code($response);
                if (intval($code) !== 200) {
                    return '';
                }

                $body = wp_remote_retrieve_body($response);
                if (empty($body)) {
                    return '';
                }

                // Try to capture the JSON assigned to window.__INITIAL__DATA__
                // This aims to match: <script>window.__INITIAL__DATA__ = { ... }</script>
                if (preg_match('/<script[^>]*>\s*window\.__INITIAL__DATA__\s*=\s*(\{.*?\})\s*;?\s*<\/script>/is', $body, $m)) {
                    $json_str = $m[1];
                    $data = json_decode($json_str, true);
                    if (json_last_error() === JSON_ERROR_NONE && is_array($data)) {
                        if (isset($data['formData']) && is_array($data['formData'])) {
                            if (!empty($data['formData']['name'])) {
                                return sanitize_text_field($data['formData']['name']);
                            }
                            // (Optional) if you prefer the display title when name is missing:
                            if (!empty($data['formData']['title'])) {
                                return sanitize_text_field($data['formData']['title']);
                            }
                        }
                    }
                }

                // Fallback regex: look for ..."formData":{..."name":"..."}
                if (preg_match('/"formData"\s*:\s*\{.*?"name"\s*:\s*"([^"]+)"/is', $body, $m2)) {
                    return sanitize_text_field($m2[1]);
                }

                return '';
            }
            ?>
        </div>

        <div id="html-table" class="table-container" style="display: none;">
            <h3>HTML Script Locations</h3>
            <p><strong>Reminder:</strong> Any pages that have scripts should be reviewed. This table includes any High Level forms on the website.</p>
            <?php
            // Generate list of posts/pages and search for scripts in HTML modules
            $args = array(
                'ignore_sticky_posts' => true,
                'no_found_rows' => true,
                'post_type'      => array('page', 'post'),
                'posts_per_page' => -1,
                'post_status'    => array('publish', 'draft', 'private', 'pending', 'future'),
                'orderby'        => 'title',
                'order'          => 'ASC',
                'meta_query'     => array(
                    'relation' => 'AND',
                    array(
                        'key'     => '_fl_builder_data',
                        'compare' => 'EXISTS',
                    ),
                    array(
                        'key'     => '_fl_builder_data',
                        'value'   => 'html',
                        'compare' => 'LIKE',
                    ),
                    array(
                        'key'     => '_fl_builder_data',
                        'value'   => '<script',
                        'compare' => 'LIKE',
                    ),
                ),
            );
            $html_query = new WP_Query($args);
        
            if ($html_query->have_posts()) {
                echo '<table class="wp-list-table widefat fixed striped html-table">';
                echo '<thead><tr><th style="width: 40px; padding: 0 10px;">#</th><th style="width: auto; padding-left: 10px;">Page Title</th><th>Page Link</th></tr></thead>';
                echo '<tbody>';
                $row_number = 1;
                while ($html_query->have_posts()) {
                    $html_query->the_post();
                    $html_post_id = get_the_ID();
                    $html_post_link = get_permalink($html_post_id);
        
                    echo '<tr><td style="width: 40px; padding: 0 10px;">' . $row_number . '</td><td style="width: auto;" class="page-title">' . get_the_title() . '</td><td><a href="' . esc_url($html_post_link) . '" target="_blank" rel="noopener noreferrer">' . esc_url($html_post_link) . '</a></td></tr>';
                    $row_number++;
                }
                echo '</tbody>';
                echo '</table>';
            }
        
            // Reset Post Data
            wp_reset_postdata();
            ?>
        </div>

        <div id="rw-table" class="table-container" style="display: none;">
            <h3>Review Wave Form Locations</h3>
            <p><strong>Reminder:</strong> Any pages that have Review Wave code should be reviewed.</p>
            <?php
            // Generate list of posts/pages and search for scripts in HTML modules
            $args = array(
                'post_type'      => array('page', 'post'),
                'posts_per_page' => -1,
                'post_status'    => array('publish', 'draft', 'private', 'pending', 'future'),
                'orderby'        => 'title',
                'order'          => 'ASC',
                'meta_query'     => array(
                    'relation' => 'AND',
                    array(
                        'key'     => '_fl_builder_data',
                        'compare' => 'EXISTS',
                    ),
                    array(
                        'key'     => '_fl_builder_data',
                        'value'   => 'html',
                        'compare' => 'LIKE',
                    ),
                    array(
                        'key'     => '_fl_builder_data',
                        'value'   => 'cdn.reviewwave.com',
                        'compare' => 'LIKE',
                    ),
                ),
            );
            $rw_query = new WP_Query($args);
        
            if ($rw_query->have_posts()) {
                echo '<table class="wp-list-table widefat fixed striped rw-table">';
                echo '<thead><tr><th style="width: 40px; padding: 0 10px;">#</th><th style="width: auto; padding-left: 10px;">Page Title</th><th>Page Link</th></tr></thead>';
                echo '<tbody>';
                $rw_row_number = 1;
                while ($rw_query->have_posts()) {
                    $rw_query->the_post();
                    $rw_post_id = get_the_ID();
                    $rw_post_link = get_permalink($rw_post_id);
        
                    echo '<tr><td style="width: 40px; padding: 0 10px;">' . $rw_row_number . '</td><td style="width: auto;" class="page-title">' . get_the_title() . '</td><td><a href="' . esc_url($rw_post_link) . '" target="_blank">' . esc_url($rw_post_link) . '</a></td></tr>';
                    $rw_row_number++;
                }
                echo '</tbody>';
                echo '</table>';
            }
        
            // Reset Post Data
            wp_reset_postdata();
            ?>
        </div>

        <div id="video-table" class="table-container" style="display: none;">
            <h3>YouTube/Vimeo Video Locations</h3>
            <?php
            /**
             * Normalize a video URL to a canonical watch URL.
             */
            if ( ! function_exists( 'qc_normalize_video_url' ) ) {
                function qc_normalize_video_url( $source, $url ) {
                    if ( 'Vimeo' === $source ) {
                        if ( preg_match( '/vimeo\.com\/(?:video\/)?(\d+)/', $url, $m ) ) {
                            return 'https://vimeo.com/' . $m[1];
                        }
                    } elseif ( 'YouTube' === $source ) {
                        if ( preg_match( '/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([A-Za-z0-9_-]+)/', $url, $m ) ) {
                            return 'https://www.youtube.com/watch?v=' . $m[1];
                        }
                    }
                    return $url;
                }
            }
        
            /**
             * Fetch a video title via oEmbed (cached).
             */
            if ( ! function_exists( 'qc_get_video_title' ) ) {
                function qc_get_video_title( $source, $url ) {
                    $url = (string) $url;
        
                    // Cache per source+url to avoid hammering oEmbed.
                    $cache_key = 'qc_video_title_' . md5( $source . '|' . $url );
                    $cached = get_transient( $cache_key );
                    if ( false !== $cached && '' !== $cached ) {
                        return $cached;
                    }
        
                    if ( 'YouTube' === $source ) {
                        $endpoint = add_query_arg(
                            array(
                                'url'    => rawurlencode( $url ),
                                'format' => 'json',
                            ),
                            'https://www.youtube.com/oembed'
                        );
                    } elseif ( 'Vimeo' === $source ) {
                        $endpoint = add_query_arg(
                            array(
                                'url'    => rawurlencode( $url ),
                                'format' => 'json',
                            ),
                            'https://vimeo.com/api/oembed.json'
                        );
                    } else {
                        return '(unknown title)';
                    }
        
                    $response = wp_remote_get( $endpoint, array( 'timeout' => 8 ) );
                    if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
                        // Cache failures briefly so the report still loads fast.
                        set_transient( $cache_key, '(unknown title)', 6 * HOUR_IN_SECONDS );
                        return '(unknown title)';
                    }
        
                    $body = wp_remote_retrieve_body( $response );
                    $data = json_decode( $body, true );
                    $title = isset( $data['title'] ) ? esc_html( $data['title'] ) : '(unknown title)';
        
                    // Cache successes longer.
                    set_transient( $cache_key, $title, 7 * DAY_IN_SECONDS );
                    return $title;
                }
            }
        
            /**
             * Extract YouTube/Vimeo URLs from Beaver Builder meta + content.
             */
            if ( ! function_exists( 'qc_extract_video_links' ) ) {
                function qc_extract_video_links( $post_id ) {
                    // Get Beaver Builder meta data.
                    $raw = get_post_meta( $post_id, '_fl_builder_data', true );
                    if ( is_array( $raw ) ) {
                        $meta_array = $raw;
                        $meta_json  = json_encode( $raw );
                    } else {
                        $meta_array = json_decode( $raw, true ) ?: array();
                        $meta_json  = $raw;
                    }
        
                    // Get page content.
                    $content  = get_the_content( '', false, $post_id );
                    // Combined string for regex scanning.
                    $haystack = (string) $meta_json . ' ' . (string) $content;
        
                    $patterns = array(
                        'YouTube' => array(
                            '/https?:\/\/(?:www\.)?youtube\.com\/watch\?v=[A-Za-z0-9_-]+(?:&[A-Za-z0-9_=-]+)*/i',
                            '/https?:\/\/(?:www\.)?youtu\.be\/[A-Za-z0-9_-]+/i',
                            '/https?:\/\/(?:www\.)?youtube\.com\/embed\/[A-Za-z0-9_-]+/i',
                            '/https?:\/\/(?:www\.)?youtube\.com\/playlist\?list=[A-Za-z0-9_-]+/i',
                        ),
                        'Vimeo'   => array(
                            '/https?:\/\/(?:www\.)?vimeo\.com\/\d+/i',
                            '/https?:\/\/(?:player\.)?vimeo\.com\/video\/\d+/i',
                        ),
                    );
        
                    $videos = array();
        
                    // Scan the combined JSON+content string.
                    foreach ( $patterns as $source => $pats ) {
                        foreach ( $pats as $pat ) {
                            if ( preg_match_all( $pat, $haystack, $m ) ) {
                                foreach ( $m[0] as $url ) {
                                    $videos[] = compact( 'source', 'url' );
                                }
                            }
                        }
                    }
        
                    // Also recursively scan the Beaver Builder meta data array.
                    if ( ! empty( $meta_array ) ) {
                        $it = new RecursiveIteratorIterator( new RecursiveArrayIterator( $meta_array ) );
                        foreach ( $it as $val ) {
                            if ( ! is_string( $val ) ) {
                                continue;
                            }
                            foreach ( $patterns as $source => $pats ) {
                                foreach ( $pats as $pat ) {
                                    if ( preg_match( $pat, $val ) ) {
                                        $videos[] = array( 'source' => $source, 'url' => $val );
                                        continue 3;
                                    }
                                }
                            }
                        }
                    }
        
                    // Known placeholder URLs (normalized form).
                    $placeholders = array(
                        'https://www.youtube.com/watch?v=oznr-1-poSU',
                        'https://www.youtube.com/watch?v=A7ZkZazfvao',
                        'https://vimeo.com/103344490',
                        'https://vimeo.com/965293524',
                    );
        
                    // Normalize, deduplicate, and filter out placeholder URLs not actually present.
                    $unique = array();
                    foreach ( $videos as $v ) {
                        $norm = qc_normalize_video_url( $v['source'], $v['url'] );
        
                        // If this is one of the placeholders, only include if the *original* URL appears in haystack.
                        if ( in_array( $norm, $placeholders, true ) ) {
                            if ( false === strpos( $haystack, $v['url'] ) ) {
                                continue;
                            }
                        }
        
                        $key = $v['source'] . '|' . $norm;
                        $unique[ $key ] = array(
                            'source' => $v['source'],
                            'url'    => $norm,
                        );
                    }
        
                    return array_values( $unique );
                }
            }
        
            // Query posts/pages.
            $args = array(
                'post_type'      => array( 'page', 'post' ),
                'posts_per_page' => -1,
                'post_status'    => array( 'publish', 'draft', 'private', 'pending', 'future' ),
                'orderby'        => 'title',
                'order'          => 'ASC',
                'meta_query'     => array(
                    'relation' => 'AND',
                    array( 'key' => '_fl_builder_data', 'compare' => 'EXISTS' ),
                    array(
                        'relation' => 'OR',
                        array( 'key' => '_fl_builder_data', 'value' => 'watch?v=', 'compare' => 'LIKE' ),
                        array( 'key' => '_fl_builder_data', 'value' => 'youtu.be',   'compare' => 'LIKE' ),
                        array( 'key' => '_fl_builder_data', 'value' => 'embed/',     'compare' => 'LIKE' ),
                        array( 'key' => '_fl_builder_data', 'value' => 'playlist',   'compare' => 'LIKE' ),
                        array( 'key' => '_fl_builder_data', 'value' => 'vimeo.com',  'compare' => 'LIKE' ),
                    ),
                ),
            );
            $q = new WP_Query( $args );
        
            $has = false;
            if ( $q->have_posts() ) {
                echo '<table class="wp-list-table widefat striped video-table">';
                echo '<thead><tr>';
                echo '<th style="width:40px;">#</th><th>Page Title</th><th>Page Link</th><th>Video Source</th><th>Video Title</th><th>Video URL</th>';
                echo '</tr></thead><tbody>';
        
                $i = 1;
                while ( $q->have_posts() ) {
                    $q->the_post();
                    $pid    = get_the_ID();
                    $title  = get_the_title();
                    $plink  = get_permalink( $pid );
                    $videos = qc_extract_video_links( $pid );
        
                    if ( ! empty( $videos ) ) {
                        $has = true;
                        foreach ( $videos as $v ) {
                            $video_title = qc_get_video_title( $v['source'], $v['url'] );
                            echo '<tr>';
                            echo '<td>' . esc_html( $i ) . '</td>';
                            echo '<td>' . esc_html( $title ) . '</td>';
                            echo '<td><a href="' . esc_url( $plink ) . '" target="_blank" rel="noopener noreferrer">' . esc_html( $plink ) . '</a></td>';
                            echo '<td>' . esc_html( $v['source'] ) . '</td>';
                            echo '<td>' . $video_title . '</td>';
                            echo '<td><a href="' . esc_url( $v['url'] ) . '" target="_blank" rel="noopener noreferrer">' . esc_html( $v['url'] ) . '</a></td>';
                            echo '</tr>';
                            $i++;
                        }
                    }
                }
                echo '</tbody></table>';
            }
            wp_reset_postdata();
        
            if ( ! $has ) {
                echo '<p>No pages with embedded video URLs were found.</p>';
            }
            ?>
        </div>

        <div id="media-library-table" class="table-container">
            <h3>Media Library Images Larger Than 100KB</h3>
        
            <?php
            // Generate a list of media items larger than 200KB
            $args = array(
                'post_type'      => 'attachment',
                'post_status'    => 'inherit',
                'posts_per_page' => -1,
                'meta_query'     => array(
                    array(
                        'key'     => '_wp_attached_file',
                        'compare' => 'EXISTS',
                    ),
                ),
                'orderby'        => 'title', // Sort by title
                'order'          => 'ASC',   // in ascending order
            );
            $media_query = new WP_Query($args);
        
            if ($media_query->have_posts()) {
                echo '<table class="wp-list-table widefat fixed striped media-table">';
                echo '<thead><tr><th style="width: 40px;">#</th><th style="width: auto; padding-left: 10px;">Media File</th><th>File Name</th><th>File Size</th><th>Upload Date</th><th>Image Preview</th></tr></thead>';
                echo '<tbody>';
                $row_counter = 0; // Counter to track alternating rows
                $row_number = 1;
                while ($media_query->have_posts()) {
                    $media_query->the_post();
                    $attachment_id   = get_the_ID();
                    $attachment_url  = wp_get_attachment_url($attachment_id);
                    $file_size       = filesize(get_attached_file($attachment_id));
        
                    // Check if the file size is larger than 100KB
                        if ($file_size > 100 * 1024 && !preg_match('/\.pdf$/i', $attachment_url)) {
                        // Convert file size to KB
                        $file_size_kb = round($file_size / 1024, 2);
        
                        // Get the filename
                        $filename = basename(get_attached_file($attachment_id));
        
                        // Get the upload date
                        $upload_date = get_the_date('Y-m-d', $attachment_id);
        
                        // Set background color based on row count
                        $background_color = ($row_counter % 2 === 0) ? '#e3e3e5' : '#ebedef';
        
                        // Output the table row with the image
                        echo '<tr class="media-row" style="background-color: ' . $background_color . ';">';
                        echo '<td style="width: 40px;">' . $row_number . '</td>';
                        echo '<td style="width: auto;"><a href="' . esc_url($attachment_url) . '" target="_blank">' . get_the_title() . '</a></td>';
                        echo '<td>' . $filename . '</td>';
                        echo '<td>' . $file_size_kb . ' KB</td>';
        
                        // Output the upload date column
                        echo '<td>' . $upload_date . '</td>';
        
                        // Display the image with a height of 100 pixels
                        echo '<td><img src="' . esc_url($attachment_url) . '" alt="' . get_the_title() . '" style="height: 100px;"></td>';
        
                        echo '</tr>';
        
                        // Increment the row counter
                        $row_counter++;
                        $row_number++;
                    }
                }
                echo '</tbody>';
                echo '</table>';
            }
            ?>
            <script>
                jQuery(document).ready(function ($) {
                    // Function to update row numbers and background color
                    function updateRows() {
                        var visibleRows = $('.media-row:visible');
                        visibleRows.each(function (index) {
                            var background_color = (index % 2 === 0) ? '#e3e3e5' : '#ebedef';
                            $(this).css('background-color', background_color);
                            $(this).find('td:first-child').text(index + 1);
                        });
                    }
            
                    // Initial row styling
                    updateRows();
                });
            </script>
        </div>
        
        <div id="media-library-large-table" class="table-container">
            <h3>Media Library Images Larger Than 200KB</h3>
        
            <?php
            // Generate a list of media items larger than 200KB
            $args = array(
                'post_type'      => 'attachment',
                'post_status'    => 'inherit',
                'posts_per_page' => -1,
                'meta_query'     => array(
                    array(
                        'key'     => '_wp_attached_file',
                        'compare' => 'EXISTS',
                    ),
                ),
                'orderby'        => 'title', // Sort by title
                'order'          => 'ASC',   // in ascending order
            );
            $media_query = new WP_Query($args);
        
            if ($media_query->have_posts()) {
                echo '<table class="wp-list-table widefat fixed striped media-large-table">';
                echo '<thead><tr><th style="width: 40px;">#</th><th style="width: auto; padding-left: 10px;">Media File</th><th>File Name</th><th>File Size</th><th>Upload Date</th><th>Image Preview</th></tr></thead>';
                echo '<tbody>';
                $row_counter = 0; // Counter to track alternating rows
                $row_number = 1;
                while ($media_query->have_posts()) {
                    $media_query->the_post();
                    $attachment_id   = get_the_ID();
                    $attachment_url  = wp_get_attachment_url($attachment_id);
                    $file_size       = filesize(get_attached_file($attachment_id));
        
                    // Check if the file size is larger than 200KB
                        if ($file_size > 200 * 1024 && !preg_match('/\.pdf$/i', $attachment_url)) {
                        // Convert file size to KB
                        $file_size_kb = round($file_size / 1024, 2);
        
                        // Get the filename
                        $filename = basename(get_attached_file($attachment_id));
        
                        // Get the upload date
                        $upload_date = get_the_date('Y-m-d', $attachment_id);
        
                        // Set background color based on row count
                        $background_color = ($row_counter % 2 === 0) ? '#e3e3e5' : '#ebedef';
        
                        // Output the table row with the image
                        echo '<tr class="media-large-row" style="background-color: ' . $background_color . ';">';
                        echo '<td style="width: 40px;">' . $row_number . '</td>';
                        echo '<td style="width: auto;"><a href="' . esc_url($attachment_url) . '" target="_blank">' . get_the_title() . '</a></td>';
                        echo '<td>' . $filename . '</td>';
                        echo '<td>' . $file_size_kb . ' KB</td>';
        
                        // Output the upload date column
                        echo '<td>' . $upload_date . '</td>';
        
                        // Display the image with a height of 100 pixels
                        echo '<td><img src="' . esc_url($attachment_url) . '" alt="' . get_the_title() . '" style="height: 100px;"></td>';
        
                        echo '</tr>';
        
                        // Increment the row counter
                        $row_counter++;
                        $row_number++;
                    }
                }
                echo '</tbody>';
                echo '</table>';
            }
            ?>
            <script>
                jQuery(document).ready(function ($) {
                    // Function to update row numbers and background color
                    function updateRows() {
                        var visibleRows = $('.media-large-row:visible');
                        visibleRows.each(function (index) {
                            var background_color = (index % 2 === 0) ? '#e3e3e5' : '#ebedef';
                            $(this).css('background-color', background_color);
                            $(this).find('td:first-child').text(index + 1);
                        });
                    }
            
                    // Initial row styling
                    updateRows();
                });
            </script>
        </div>

        <div id="media-library-search-table" class="table-container">
            <h3>Media Library Optimization Check</h3>
            <p><strong>Reminder:</strong> Any items that match the text you enter into the Optimization Filter fields will be hidden from the table. Typing in "CITY STATE" will show you all photos that <strong>DO NOT HAVE</strong> "CITY STATE" in their name.</p>
            <br>
            <br>
            <div style="margin-top: -40px;">
                <input type="text" class="media-search media-search-1" placeholder="Optimization Filter #1..." style="width: 400px;">
                <input type="text" class="media-search media-search-2" placeholder="Optimization Filter #2..." style="width: 400px;">
                <input type="text" class="media-search media-search-3" placeholder="Optimization Filter #3..." style="width: 400px;">
            </div>
            <br>
            <table id="media-sortable" class="wp-list-table widefat fixed striped media-table">
                <thead>
                    <tr>
                        <th style="width: 40px;">#</th>
                        <th>Media File</th>
                        <th>File Name</th>
                        <th>Alt Text</th> <!-- Changed column header to Alt Text -->
                        <th>File Size</th>
                        <th>Image Preview</th>
                    </tr>
                </thead>
                <tbody>
                    <?php
                    // Generate a list of all media items
                    $args = array(
                        'post_type'      => 'attachment',
                        'post_status'    => 'inherit',
                        'posts_per_page' => -1,
                        'orderby'        => 'title', // Sort by title
                        'order'          => 'ASC',   // in ascending order
                    );
                    $media_query = new WP_Query($args);
        
                    if ($media_query->have_posts()) {
                        $row_counter = 0; // Counter to track alternating rows
                        $row_number = 1;
                        while ($media_query->have_posts()) {
                            $media_query->the_post();
                            $attachment_id   = get_the_ID();
                            $attachment_url  = wp_get_attachment_url($attachment_id);
                            $file_size       = filesize(get_attached_file($attachment_id));
        
                            // Convert file size to KB
                            $file_size_kb = round($file_size / 1024, 2);
        
                            // Exclude images with size 0 and .PDFs
                            if ($file_size > 0 && !preg_match('/\.pdf$/i', $attachment_url)) {
                                // Get the filename
                                $filename = basename(get_attached_file($attachment_id));
        
                                // Get the Alt Text
                                $alt_text = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);
                                $missing_alt = (trim((string)$alt_text) === '');

                                // Set background color based on row count
                                $background_color = ($row_counter % 2 === 0) ? '#e3e3e5' : '#ebedef';
        
                                // Output the table row with the image
                                $row_class = $missing_alt ? ' qc-missing-alt' : '';
                                echo '<tr class="qc-media-row' . $row_class . '" style="background-color: ' . $background_color . ';">';
                                echo '<td style="width: 40px;">' . $row_number . '</td>';
                                echo '<td><a href="' . esc_url($attachment_url) . '" target="_blank">' . get_the_title() . '</a></td>';
                                echo '<td>' . $filename . '</td>';
                                echo '<td>' . ($missing_alt ? '<span class="qc-badge qc-badge--bad qc-inline-flag">Missing</span>' : esc_html($alt_text)) . '</td>';
                                echo '<td>' . $file_size_kb . ' KB</td>';
                                // Display the image with a height of 100 pixels
                                echo '<td><img src="' . esc_url($attachment_url) . '" alt="' . get_the_title() . '" style="height: 100px;"></td>';
        
                                echo '</tr>';
        
                                // Increment the row counter
                                $row_counter++;
                                $row_number++;
                            }
                        }
                    }
                    ?>
                </tbody>
            </table>
                <script>
                    jQuery(document).ready(function ($) {
                        // Function to update row numbers and background color
                        function updateRows() {
                            var visibleRows = $('#media-sortable tbody tr:visible');
                            visibleRows.each(function (index) {
                                var background_color = (index % 2 === 0) ? '#e3e3e5' : '#ebedef';
                                $(this).css('background-color', background_color);
                                $(this).find('td:first-child').text(index + 1);
                            });
                        }
                    
                        // Function to filter media items based on search inputs
                        function filterRows() {
                            var searchValues = [];
                            // Collect values from all search inputs
                            $('.media-search').each(function () {
                                searchValues.push($(this).val().toLowerCase());
                            });
                    
                            $('#media-sortable tbody tr').each(function () {
                                var row = $(this);
                                var shouldShow = true;
                    
                                // Check if any search input has a non-empty value
                                $.each(searchValues, function (index, value) {
                                    var title = row.find('td:nth-child(2)').text().toLowerCase();
                                    var filename = row.find('td:nth-child(3)').text().toLowerCase();
                                    var altText = row.find('td:nth-child(4)').text().toLowerCase(); // Add alt text
                    
                                    if (value && (title.includes(value) || filename.includes(value) || altText.includes(value))) {
                                        shouldShow = false;
                                        return false; // Break the loop if any search condition is met
                                    }
                                });
                    
                                if (shouldShow) {
                                    row.show();
                                } else {
                                    row.hide();
                                }
                            });
                    
                            // Update rows
                            updateRows();
                        }
                    
                        // Bind the filterRows function to the input event of all search inputs
                        $('.media-search').on('input', function () {
                            filterRows();
                        });
                    
                        // Capture Enter key press on the search inputs
                        $('.media-search').on('keydown', function (e) {
                            if (e.key === 'Enter') {
                                e.preventDefault(); // Prevent form submission
                            }
                        });
                    
                        // Initial row styling
                        updateRows();
                    });
                </script>
        </div>

    </div>


        <div id="external-images-table" class="table-container">
            <h3>External Images (Not Media Library / Not inceptionimages.com)</h3>
            <p><strong>Reminder:</strong> This flags <code>&lt;img&gt;</code> sources found in page/post content that are not served from this site's <code>/wp-content/uploads/</code> path and do not include <code>inceptionimages.com</code>.</p>

            <table class="wp-list-table widefat fixed striped external-media-table">
                <thead>
                    <tr>
                        <th style="width:30px;">#</th>
                        <th>Page/Post Title</th>
                        <th style="width:110px;">Type</th>
                        <th>Image URL</th>
                        <th style="width:160px;">Page Links</th>
                    </tr>
                </thead>
                <tbody>
                    <?php
                    $qc_ext_i = 0;
                    foreach ( $qc_external_images as $row ) :
                        $qc_ext_i++;
                        $viewLink = !empty($row['permalink']) ? '<a href="' . esc_url($row['permalink']) . '" target="_blank" rel="noopener">View</a>' : '';
                        $editLink = !empty($row['edit']) ? '<a href="' . esc_url($row['edit']) . '" target="_blank" rel="noopener">Edit</a>' : '';
                        $bbLink   = !empty($row['bb']) ? '<a href="' . esc_url($row['bb']) . '" target="_blank" rel="noopener">Builder</a>' : '';
                        
                        $parts = array_filter(array($viewLink, $editLink, $bbLink));
                        $pageLinks = $parts ? implode(' <span style="opacity:.6;">|</span> ', $parts) : '—';
                    ?>
                    <tr class="qc-issue" data-qc-issue="external-image">
                        <td><?php echo (int) $qc_ext_i; ?></td>
                        <td><?php echo esc_html( $row['title'] ? $row['title'] : 'Untitled' ); ?></td>
                        <td><?php echo esc_html( $row['type'] === 'page' ? 'Page' : ($row['type'] === 'post' ? 'Blog Post' : $row['type']) ); ?></td>
                        <td style="word-break:break-all;">
                            <a href="<?php echo esc_url($row['src']); ?>" target="_blank" rel="noopener"><?php echo esc_html($row['src']); ?></a>
                        </td>
                        <td><?php echo $pageLinks; ?></td>
                    </tr>
                    <?php endforeach; ?>

                    <?php if ( empty($qc_external_images) ) : ?>
                        <tr><td colspan="5" style="text-align:center;">No external images found </td></tr>
                    <?php endif; ?>
                </tbody>
            </table>
        </div>
        <div id="city-state-table" class="table-container">
            <h3>Pages, Blog Posts &amp; Themer Layouts with "CITY" / "STATE"</h3>
            <p>Shows any content that still contains the <strong>capitalized</strong> placeholders <code>CITY</code> or <code>STATE</code> (case-sensitive).</p>

            <table class="wp-list-table widefat fixed striped">
                <thead>
                    <tr>
                        <th style="width:30px;">#</th>
                        <th>Title</th>
                        <th style="width:110px;">Type</th>
                        <th>Where Found</th>
                        <th style="width:160px;">Page Links</th>
                    </tr>
                </thead>
                <tbody>
                    <?php
                    $qc_cs_i = 0;
                    foreach ($qc_city_state_instances as $row) :
                        $qc_cs_i++;
                        $viewLink = !empty($row['permalink']) ? '<a href="' . esc_url($row['permalink']) . '" target="_blank" rel="noopener">View</a>' : '';
                        $editLink = !empty($row['edit']) ? '<a href="' . esc_url($row['edit']) . '" target="_blank" rel="noopener">Edit</a>' : '';
                        $bbLink   = !empty($row['bb']) ? '<a href="' . esc_url($row['bb']) . '" target="_blank" rel="noopener">Builder</a>' : '';
                        
                        $parts = array_filter(array($viewLink, $editLink, $bbLink));
                        $pageLinks = $parts ? implode(' <span style="opacity:.6;">|</span> ', $parts) : '—';
                    ?>
                    <tr class="qc-issue" data-qc-issue="city-state">
                        <td><?php echo (int) $qc_cs_i; ?></td>
                        <td><?php echo esc_html(!empty($row['title']) ? $row['title'] : 'Untitled'); ?></td>
                        <td><?php echo esc_html($row['type']); ?></td>
                        <td><?php echo esc_html($row['where']); ?></td>
                        <td><?php echo $pageLinks; ?></td>
                    </tr>
                    <?php endforeach; ?>

                    <?php if (empty($qc_city_state_instances)) : ?>
                    <tr><td colspan="5" style="text-align:center;">No capitalized CITY / STATE placeholders found.</td></tr>
                    <?php endif; ?>
                </tbody>
            </table>
        </div>

    </form>
</div>
    
<style>
    .filter-container {
        display: flex;
        align-items: center;
        margin-bottom: 3px;
        margin-top: -8px;
    }

    .filter-label {
        margin-right: 5px;
    }

    .inception-table,
    .blogs-table,
    .external-table {
        width: auto;
    }

    .inception-table th:first-child,
    .inception-table td:first-child,
    .blogs-table th:first-child,
    .blogs-table td:first-child,
    .external-table th:first-child,
    .external-table td:first-child {
        width: 25px;
    }

    .inception-table th:nth-child(1),
    .blogs-table th:nth-child(1),
    .external-table th:nth-child(1) {
        padding-left: 3px;
    }

.table-container {
    display: none;
    margin-bottom: 10px;
    /* float: left; */
    /* margin-right: 50px; */

    float: none;
    margin-right: 0;
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
}
    
    #wpfooter {
        display: none !important;  /* Hide the WP footer on the page */
    }

    /* Style for media library tables */
    #media-library-table table,
    #media-library-large-table table,
    #media-library-search-table table {
        width: 1500px;
    }

    #media-library-table img,
    #media-library--large-table img,
    #media-library-search-table img {
        max-width: 100%;
        height: auto;
    }
    
    .highlevel-table th, .highlevel-table td {
        vertical-align: middle;
    }
    .highlevel-table .qc-unlinked-page-warning {
        color: #8a6116;
        font-weight: 600;
    }
    
    .html-table th, .html-table td {
        vertical-align: middle;
    }

    .error-row td {
        color: red;
    }

    .duplicate-row td:last-child {
        color: orange;
    }


/* === QC TABLE WIDTH NORMALIZATION === */
.table-container,
.wp-list-table {
    width: 100% !important;
    max-width: 100% !important;
}

@media (min-width: 1024px){
  .wrap{ padding-right: calc(var(--qcBugWidth) + 28px); }
}

.wp-list-table {
    table-layout: fixed;
}

.wp-list-table th,
.wp-list-table td {
    word-wrap: break-word;
    overflow-wrap: anywhere;
}

.wp-list-table th {
    white-space: nowrap;
}


/* === QC FLOATING SITE CHECKS BUG === */
:root{
  --qcBugWidth: 230px;
}
.qc-bug{
  position: fixed;
  top: 110px;
  right: 14px;
  width: var(--qcBugWidth);
  z-index: 9999;
  display: none;
  background: #fff;
  border: 1px solid rgba(0,0,0,.08);
  border-radius: 14px;
  box-shadow: 0 10px 30px rgba(0,0,0,.10);
  padding: 10px 10px 8px;
}
.qc-bug.qc-bug--open{ display:block; }
.qc-bug__header{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:8px;
  margin-bottom:8px;
}
.qc-bug__title{
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .02em;
  text-transform: uppercase;
  opacity: .85;
}
.qc-bug__btn{
  border: 0;
  background: transparent;
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  padding: 6px 8px;
  border-radius: 10px;
}
.qc-bug__btn:hover{ background: rgba(0,0,0,.06); }

.qc-bug__items{
  display:flex;
  flex-direction:column;
  gap:6px;
}
.qc-bug__items .qc-badge{
  display:flex;
  justify-content:space-between;
  align-items:center;
  width:100%;
}
.qc-bug__items .qc-badge strong{ margin-left:8px; }

.qc-bug__collapsed{
  position: fixed;
  top: 140px;
  right: 14px;
  z-index: 9999;
  display:none;
  border-radius: 999px;
  border: 1px solid rgba(0,0,0,.08);
  background:#fff;
  box-shadow: 0 10px 30px rgba(0,0,0,.10);
  padding: 10px 12px;
  font-size: 12px;
  font-weight: 700;
  cursor:pointer;
}
.qc-bug__collapsed:hover{ background: rgba(0,0,0,.04); }

/* Reserve space so the bug doesn't cover table content on larger screens */
@media (min-width: 1024px){
  .wrap{ padding-right: calc(var(--qcBugWidth) + 28px); }
}
/* On smaller screens, don't reserve space and keep bug narrower */
@media (max-width: 1023px){
  :root{ --qcBugWidth: 200px; }
}


/* === QC BUG BUTTON SIZE FIX === */
.qc-bug__items .qc-badge{
  font-size: 12px;
  padding: 6px 8px;
  border-radius: 10px;
}
.qc-bug{
  box-sizing: border-box;
}


/* === QC BUG COMPACT BADGES === */
.qc-bug__items{ gap:4px; }
.qc-bug__items .qc-badge{
  font-size: 20px;
  padding: 5px 6px;
  border-radius: 10px;
  line-height: 1.1;
  justify-content: space-between;
}
.qc-bug__items .qc-badge strong{
  display:inline-block;
  width:4ch;
  text-align:right;
  font-variant-numeric: tabular-nums;
  margin-left:0;
}

/* === External Images: match the "leave room for the QC bug" look === */
@media (min-width: 1024px){
  #external-images-table{
    float: none !important;
    margin-right: 0 !important;

    /* This is the key: leave the same gutter as the .wrap padding-right */
    max-width: calc(100% - var(--qcBugWidth) - 48px) !important;
    width: 100% !important;
    box-sizing: border-box;
  }

  #external-images-table .wp-list-table{
    width: 100% !important;
    max-width: 100% !important;
    table-layout: fixed !important;
  }

  /* Keep long URLs from forcing width */
  #external-images-table td:nth-child(4){
    white-space: normal !important;
    overflow-wrap: anywhere !important;
    word-break: break-word !important;
  }

  /* Match the same width constraints for the CITY/STATE table */
  #city-state-table{
    float: none !important;
    margin-right: 0 !important;
    max-width: calc(100% - var(--qcBugWidth) - 48px) !important;
    width: 100% !important;
    box-sizing: border-box;
  }

  #city-state-table .wp-list-table{
    width: 100% !important;
    max-width: 100% !important;
    table-layout: fixed !important;
  }
}

/* Images Too Large severity states */
.qc-badge--images-orange{
  border-color: #f0a500 !important; /* orange */
}

.qc-badge--images-red{
  border-color: #d63638 !important; /* WP red */
}

/* Keep the two-part stock score readable inside the narrow floating bug. */
.qc-bug__items [data-qc-badge="stockVsNonStock"]{
  display:grid !important;
  grid-template-columns:minmax(0, 1fr) auto;
  grid-template-areas:
    "label score"
    "label count";
  width:100% !important;
  box-sizing:border-box;
  align-items:center;
  column-gap:8px !important;
  row-gap:2px !important;
  padding:5px 7px !important;
  line-height:1.15 !important;
}
.qc-bug__items [data-qc-badge="stockVsNonStock"] .qc-stock-score__label{
  grid-area:label;
  min-width:0;
}
.qc-bug__items [data-qc-badge="stockVsNonStock"] strong{
  grid-area:score;
  width:auto !important;
  margin:0 !important;
  white-space:nowrap;
  text-align:right;
}
.qc-bug__items [data-qc-badge="stockVsNonStock"] .qc-badge__sub{
  grid-area:count;
  display:block !important;
  margin:0 !important;
  white-space:nowrap;
  text-align:right;
  font-size:11px;
}

</style>

<script>
  // Call the changeTable function on page load
  window.addEventListener('DOMContentLoaded', function () {
    // Display the Pages table initially
    var pagesTable = document.getElementById('pages-table');
    pagesTable.style.display = 'block';

    // Display the Non-Stock Pages table initially
    var nonStockPagesTable = document.getElementById('non-stock-pages-table');
    nonStockPagesTable.style.display = 'block';
    
    // Display the Headings table initially
    var headingsTable = document.getElementById('pages-posts-headings-table');
    headingsTable.style.display = 'block';

    // Display the Backlinks table initially
    var backlinksTable = document.getElementById('pages-posts-backlinks-table');
    backlinksTable.style.display = 'block';
    
    // Display the Keyword table initially
    var keywordTable = document.getElementById('pages-posts-keyword-table');
    keywordTable.style.display = 'block';

    // Display the Pages & Blog Posts Metadata table initially
    var pagepostmetadataTable = document.getElementById('pages-posts-metadata-table');
    pagepostmetadataTable.style.display = 'block';

    // Display the Posts table initially
    var postsTable = document.getElementById('posts-table');
    postsTable.style.display = 'block';
    
    // Display the Posts Uncategorized table initially
    var postsuncategorizedTable = document.getElementById('postsuncategorized-table');
    postsuncategorizedTable.style.display = 'block';
    
    // Display the Posts Uplink table initially
    var postsuplinkTable = document.getElementById('postsuplink-table');
    postsuplinkTable.style.display = 'block';
    
    // Display the No Author table initially
    var postsnoauthorTable = document.getElementById('no_author-table');
    postsnoauthorTable.style.display = 'block';
    
    // Display the Highlevel table initially
    var highlevelTable = document.getElementById('highlevel-table');
    highlevelTable.style.display = 'block';
    
    // Display the HTML table initially
    var htmlTable = document.getElementById('html-table');
    htmlTable.style.display = 'block';

    // Display the Review Wave table initially
    var rwTable = document.getElementById('rw-table');
    rwTable.style.display = 'block';

    // Display the Videos table initially
    var videoTable = document.getElementById('video-table');
    videoTable.style.display = 'block';

    // Display the Media Library table initially
    var mediaLibraryTable = document.getElementById('media-library-table');
    mediaLibraryTable.style.display = 'block';
    
    // Display the Media Library table initially
    var mediaLibraryLargeTable = document.getElementById('media-library-large-table');
    mediaLibraryLargeTable.style.display = 'block';
    
    // Display the Media Library Search table initially
    var mediaLibrarySearchTable = document.getElementById('media-library-search-table');
        var externalImagesTable = document.getElementById('external-images-table');
    mediaLibrarySearchTable.style.display = 'block';
    
    // Display the External Links table initially
    var externalLinksTable = document.getElementById('external-links-table');
    externalLinksTable.style.display = 'block';

    // Display the CITY/STATE placeholders table initially
    var cityStateTable = document.getElementById('city-state-table');
    cityStateTable.style.display = 'block';
    
    // Set the table selector value to 'all'
    var tableSelector = document.getElementById('table-selector');
    tableSelector.value = 'all';

    // Trigger the changeTable function manually
    changeTable();
  });

    // Function to change the table based on the selected option
    function changeTable() {
        var tableSelector = document.getElementById('table-selector');
        var selectedOption = tableSelector.value;
        
        // Toggle scroll buffer depending on selection
        if (selectedOption === 'all') {
            document.body.classList.remove('qc-single-table');
        } else {
            document.body.classList.add('qc-single-table');
        }

        var pagesTable = document.getElementById('pages-table');
        var nonStockPagesTable = document.getElementById('non-stock-pages-table');
        var headingsTable = document.getElementById('pages-posts-headings-table');
        var backlinksTable = document.getElementById('pages-posts-backlinks-table');
        var keywordTable = document.getElementById('pages-posts-keyword-table');
        var pagepostmetadataTable = document.getElementById('pages-posts-metadata-table');
        var postsTable = document.getElementById('posts-table');
        var postsUncategorizedTable = document.getElementById('postsuncategorized-table');
        var postsUplinkTable = document.getElementById('postsuplink-table');
        var noauthorTable = document.getElementById('no_author-table');
        var highlevelTable = document.getElementById('highlevel-table');
        var htmlTable = document.getElementById('html-table');
        var rwTable = document.getElementById('rw-table');
        var videoTable = document.getElementById('video-table');
        var mediaLibraryTable = document.getElementById('media-library-table');
        var mediaLibraryLargeTable = document.getElementById('media-library-large-table');
        var mediaLibrarySearchTable = document.getElementById('media-library-search-table');
        var externalImagesTable = document.getElementById('external-images-table');
        var externalLinksTable = document.getElementById('external-links-table');
        var cityStateTable = document.getElementById('city-state-table');

        if (selectedOption === 'all') {
            // Display all tables
            pagesTable.style.display = 'block';
            nonStockPagesTable.style.display = 'block';
            headingsTable.style.display = 'block';
            backlinksTable.style.display = 'block';
            keywordTable.style.display = 'block';
            pagepostmetadataTable.style.display = 'block';
            postsTable.style.display = 'block';
            postsUncategorizedTable.style.display = 'block';
            postsUplinkTable.style.display = 'block';
            noauthorTable.style.display = 'block';
            highlevelTable.style.display = 'block';
            htmlTable.style.display = 'block';
            rwTable.style.display = 'block';
            videoTable.style.display = 'block';
            mediaLibraryTable.style.display = 'block';
            mediaLibraryLargeTable.style.display = 'block';
            mediaLibrarySearchTable.style.display = 'block';
            externalImagesTable.style.display = 'block';
            externalLinksTable.style.display = 'block';
            cityStateTable.style.display = 'block';
        } else {
            // Hide all tables initially
            pagesTable.style.display = 'none';
            nonStockPagesTable.style.display = 'none';
            headingsTable.style.display = 'none';
            backlinksTable.style.display = 'none';
            keywordTable.style.display = 'none';
            pagepostmetadataTable.style.display = 'none';
            postsTable.style.display = 'none';
            postsUncategorizedTable.style.display = 'none';
            postsUplinkTable.style.display = 'none';
            noauthorTable.style.display = 'none';
            highlevelTable.style.display = 'none';
            htmlTable.style.display = 'none';
            rwTable.style.display = 'none';
            videoTable.style.display = 'none';
            mediaLibraryTable.style.display = 'none';
            mediaLibraryLargeTable.style.display = 'none';
            mediaLibrarySearchTable.style.display = 'none';
            externalImagesTable.style.display = 'none';
            externalLinksTable.style.display = 'none';
            cityStateTable.style.display = 'none';

            // Display selected table based on the selected option
            if (selectedOption === 'pages') {
                pagesTable.style.display = 'block';
            } else if (selectedOption === 'non_stock_pages') {
                nonStockPagesTable.style.display = 'block';
            } else if (selectedOption === 'headings') {
                headingsTable.style.display = 'block';
            } else if (selectedOption === 'backlinks') {
                backlinksTable.style.display = 'block';
            } else if (selectedOption === 'keyword') {
                keywordTable.style.display = 'block';
            } else if (selectedOption === 'pages_posts_metadata') {
                pagepostmetadataTable.style.display = 'block';
            } else if (selectedOption === 'posts') {
                postsTable.style.display = 'block';
            } else if (selectedOption === 'posts_uncategorized') {
                postsUncategorizedTable.style.display = 'block';
            } else if (selectedOption === 'posts_uplink') {
                postsUplinkTable.style.display = 'block';
            } else if (selectedOption === 'no_author') {
                noauthorTable.style.display = 'block';
            } else if (selectedOption === 'highlevel') {
                highlevelTable.style.display = 'block';
            } else if (selectedOption === 'html-table') {
                htmlTable.style.display = 'block';
            } else if (selectedOption === 'rw-table') {
                rwTable.style.display = 'block';
            } else if (selectedOption === 'video-table') {
                videoTable.style.display = 'block';
            } else if (selectedOption === 'media_library') {
                mediaLibraryTable.style.display = 'block';
            } else if (selectedOption === 'media_library_large') {
                mediaLibraryLargeTable.style.display = 'block';
            } else if (selectedOption === 'media_library_search') {
                mediaLibrarySearchTable.style.display = 'block';
            } else if (selectedOption === 'external_images') {
                externalImagesTable.style.display = 'block';
            } else if (selectedOption === 'external-links') {
                externalLinksTable.style.display = 'block';
            }
            else if (selectedOption === 'city_state') {
                cityStateTable.style.display = 'block';
            }
        }
    }
</script>

<script>
jQuery(function($){
    // Nonce + AJAX URL (admin-ajax.php)
    var qcAjax = {
        url: (typeof ajaxurl !== 'undefined') ? ajaxurl : '<?php echo esc_js(admin_url('admin-ajax.php')); ?>',
        nonce: '<?php echo esc_js(wp_create_nonce('inception_qc_report')); ?>'
    };

    // ---------- Lightweight client-side pagination (per table) ----------
    function paginateTable($table, pageSize){
        var $tbody = $table.find('tbody');
        if (!$tbody.length) return;

        var $rows = $tbody.find('tr');
        if ($rows.length <= pageSize) return; // no need

        // Avoid double-init
        if ($table.data('qcPaginated')) return;
        $table.data('qcPaginated', true);

        // Some tables use multi-row "records" (e.g., summary row + detail row). Group them so a record is never split across pages.
        var grouped = false;
        var groups = [];
        $rows.each(function(){
            var $r = $(this);
            if ($r.find('td.heading-seq').length){
                grouped = true;
                if (groups.length){
                    groups[groups.length - 1].push($r);
                } else {
                    groups.push([$r]);
                }
            } else {
                groups.push([$r]);
            }
        });

        var totalUnits = grouped ? groups.length : $rows.length;
        var state = { page: 1, pageSize: pageSize, total: totalUnits, pages: Math.ceil(totalUnits / pageSize) };
        var unitLabel = grouped ? 'items' : 'rows';
        
        // Map each <tr> DOM node -> its "unit index" (row index or group index)
        var rowToUnitIndex = new Map();
        if (grouped){
            for (var gi = 0; gi < groups.length; gi++){
                groups[gi].forEach(function($r){
                    rowToUnitIndex.set($r.get(0), gi);
                });
            }
        } else {
            $rows.each(function(idx){
                rowToUnitIndex.set(this, idx);
            });
        }

        var $pagerTop = $('<div class="qc-table-pager qc-table-pager--top"></div>');
        var $pagerBottom = $('<div class="qc-table-pager qc-table-pager--bottom"></div>');

        var $prevTop = $('<button type="button" class="button">‹</button>');
        var $nextTop = $('<button type="button" class="button">›</button>');
        var $infoTop = $('<span class="qc-page-info"></span>');

        var $prevBottom = $('<button type="button" class="button">‹</button>');
        var $nextBottom = $('<button type="button" class="button">›</button>');
        var $infoBottom = $('<span class="qc-page-info"></span>');

        function syncPagers(){
            var text = 'Page ' + state.page + ' of ' + state.pages + ' • ' + state.total + ' ' + unitLabel;
            $prevTop.prop('disabled', state.page === 1);
            $nextTop.prop('disabled', state.page === state.pages);
            $infoTop.text(text);

            $prevBottom.prop('disabled', state.page === 1);
            $nextBottom.prop('disabled', state.page === state.pages);
            $infoBottom.text(text);
        }

        function render(){
            var start = (state.page - 1) * state.pageSize;
            var end = start + state.pageSize;

            $rows.hide();
            if (grouped){
                // show whole groups so records never split between pages
                for (var i = start; i < end && i < groups.length; i++){
                    groups[i].forEach(function($r){ $r.show(); });
                }
            } else {
                $rows.slice(start, end).show();
            }

            syncPagers();

            // kick async checks for visible rows
            $(document).trigger('qc:pagechange');
        }
        
        function goToPage(page, shouldScroll){
            var p = parseInt(page, 10);
            if (!p || p < 1) p = 1;
            if (p > state.pages) p = state.pages;

            state.page = p;
            render();

            if (shouldScroll) scrollToTableTop();
        }

        // After paging, keep the user oriented by scrolling back to the top of this table.
        function scrollToTableTop(){
            var $container = $table.closest('.table-container');
            var $anchor = $container.find('.qc-table-title-row').first();
            if (!$anchor.length) $anchor = $table;

            var top = $anchor.offset() ? $anchor.offset().top : 0;
            // Account for the WP admin bar and a small breathing room.
            top = Math.max(0, top - 60);

            // Use jQuery animate for broad admin/browser compatibility.
            $('html, body').stop(true).animate({ scrollTop: top }, 200);
        }

        function goPrev(){
            if (state.page > 1) {
                state.page--;
                render();
                scrollToTableTop();
            }
        }
        function goNext(){
            if (state.page < state.pages) {
                state.page++;
                render();
                scrollToTableTop();
            }
        }

        $prevTop.on('click', goPrev);
        $nextTop.on('click', goNext);
        $prevBottom.on('click', goPrev);
        $nextBottom.on('click', goNext);

        $pagerTop.append($prevTop, $nextTop, $infoTop);
        $pagerBottom.append($prevBottom, $nextBottom, $infoBottom);

        // Place the top pager on the SAME ROW as the table title (when a title exists).
        var $container = $table.closest('.table-container');
        var $headerRow = $container.children('.qc-table-title-row').first();

        if ($headerRow.length){
            $headerRow.append($pagerTop);
        } else {
            var $heading = $container.children('h1,h2,h3,h4').first();
            if ($heading.length){
                $headerRow = $('<div class="qc-table-title-row"></div>');
                $heading.before($headerRow);
                $headerRow.append($heading, $pagerTop);
            } else {
                // Fallback: no obvious title, keep previous behavior
                $table.before($pagerTop);
            }
        }
        $table.after($pagerBottom);

        // Expose a tiny API so badge clicks can jump to the page containing the first "problem" row.
        $table.data('qcPager', {
            goToPage: goToPage,
            getPageForRow: function($row){
                if (!$row || !$row.length) return 1;
                var unitIdx = rowToUnitIndex.get($row.get(0));
                if (typeof unitIdx === 'undefined') return 1;
                return Math.floor(unitIdx / state.pageSize) + 1;
            },
            getState: function(){ return state; }
        });

        render();
        }

    function initPagination(){
        // Paginate any admin tables inside the report containers
        $('.table-container table.wp-list-table').each(function(){
            paginateTable($(this), 25);
        });
    }

    // ---------- Async URL status checks (HighLevel / Doctible iframes) ----------
    var urlCheckQueue = {};
    var urlPending = [];
    var urlWorkerActive = 0;
    var urlWorkerMax = 3;

    function enqueueUrlRow($tr){
        if ($tr.data('qcChecked') || $tr.data('qcEnqueued')) return;

        var url = $tr.attr('data-check-url') || '';
        if (!url) { $tr.data('qcChecked', true); return; }

        $tr.data('qcEnqueued', true);

        if (!urlCheckQueue[url]) {
            urlCheckQueue[url] = { trs: [], done: false, inFlight: false, code: 0 };
            urlPending.push(url);
        }

        urlCheckQueue[url].trs.push($tr);
    }

    function isInvalidHighLevelFormId(formId){
        if (!formId) return true;
    
        var id = String(formId).trim();
    
        // Obvious placeholders / typos
        if (id.indexOf('*') !== -1) return true;
        if (/insert/i.test(id)) return true;
    
        // Must be alphanumeric only
        if (!/^[a-z0-9_-]+$/i.test(id)) return true;
    
        return false;
    }

    function applyUrlResult(url, code){
        var entry = urlCheckQueue[url];
        if (!entry) return;

        entry.trs.forEach(function($tr){
            $tr.data('qcChecked', true);

        var formId = $tr.find('td').eq(4).text().trim(); // Form ID column
        
        var isInvalidId = isInvalidHighLevelFormId(formId);
        var isReal404   = (code === 404);
        
        // Treat invalid IDs as 404-equivalent
        if (isReal404 || isInvalidId){
            $tr.addClass('error-row');
        
            // Inline badge stays accurate
            var $last = $tr.find('td').last();
            if ($last.find('.qc-inline-flag').length === 0){
                $last.append(' <span class="qc-badge qc-badge--bad qc-inline-flag">404</span>');
            }
        }
        });

        // mark as done so any future rows with the same URL get the result immediately
        entry.done = true;
        entry.code = code;

        updateAlerts();
    }

    function runUrlWorkers(){
        while (urlWorkerActive < urlWorkerMax && urlPending.length){
            var url = urlPending.shift();
            var entry = urlCheckQueue[url];
            if (!entry) continue;

            if (entry.done){
                applyUrlResult(url, entry.code);
                continue;
            }

            if (entry.inFlight) continue;

            entry.inFlight = true;
            urlWorkerActive++;

            $.post(qcAjax.url, {
                action: 'inception_qc_check_url_status',
                nonce: qcAjax.nonce,
                url: url
            }).done(function(resp){
                var code = 0;
                if (resp && resp.success && resp.data && typeof resp.data.code !== 'undefined'){
                    code = parseInt(resp.data.code, 10) || 0;
                }
                applyUrlResult(url, code);
            }).always(function(){
                entry.inFlight = false;
                urlWorkerActive--;
                runUrlWorkers();
            });
        }
    }

    // Prioritize visible rows (current page), but also check everything in the background.
    function checkVisibleUrls(){
        $('.qc-async-row:visible').each(function(){ enqueueUrlRow($(this)); });
        runUrlWorkers();
    }
    function checkAllUrls(){
        $('.qc-async-row').each(function(){ enqueueUrlRow($(this)); });
        runUrlWorkers();
    }

    // ---------- Async oEmbed title checks (YouTube/Vimeo) ---------- (YouTube/Vimeo) ----------
    var titleQueue = {};
    function checkVisibleTitles(){
        $('.qc-oembed-title:visible').each(function(){
            var $el = $(this);
            if ($el.data('qcTitleDone')) return;

            var source = $el.attr('data-source') || '';
            var url = $el.attr('data-url') || '';
            if (!source || !url) { $el.text('(unknown title)'); $el.data('qcTitleDone', true); return; }

            var key = source + '|' + url;
            if (titleQueue[key]) return;
            titleQueue[key] = true;

            $.post(qcAjax.url, {
                action: 'inception_qc_get_oembed_title',
                nonce: qcAjax.nonce,
                source: source,
                url: url
            }).done(function(resp){
                var title = (resp && resp.success && resp.data && resp.data.title) ? resp.data.title : '(unknown title)';
                $('.qc-oembed-title[data-source="'+source.replace(/"/g,'\\"')+'"][data-url="'+url.replace(/"/g,'\\"')+'"]').each(function(){
                    $(this).html(title).data('qcTitleDone', true);
                });
            }).always(function(){
                delete titleQueue[key];
            });
        });
    }

    // ---------- Summary banner ----------
    function setBadge(key, count){
        var $b = $('[data-qc-badge="'+key+'"]');

        // Display counts compactly (max ~4 chars) for the floating bug.
        var display = String(count);
        if (count > 999) display = '999+';

        $b.find('strong').text(display);

        $b.removeClass('qc-badge--good qc-badge--warn qc-badge--bad qc-badge--neutral');
        if (count === 0) $b.addClass('qc-badge--good');
        else if (count < 5) $b.addClass('qc-badge--warn');
        else $b.addClass('qc-badge--bad');

        // If this is the CITY/STATE counter, also update the header/bug mini counters
        if (key === 'cityState') {
            var $mini = $('.qc-count-bubble[data-qc-badge="cityState"], .qc-mini-count[data-qc-badge="cityState"]');
            $mini.text(display);
            $mini.removeClass('qc-badge--good qc-badge--warn qc-badge--bad qc-badge--neutral');
            if (count === 0) $mini.addClass('qc-badge--good');
            else if (count < 5) $mini.addClass('qc-badge--warn');
            else $mini.addClass('qc-badge--bad');
        }
    }

    // Special badge: Indexed pages (shows % and count/total)
    function setIndexedBadge(indexed, total){
        var $b = $('[data-qc-badge="indexedPages"]');
        if (!$b.length) return;

        var pct = 0;
        if (total > 0){
            pct = Math.round((indexed / total) * 100);
        }

        // Primary number: percentage (fits the compact bug UI)
        $b.find('strong').text(String(pct) + '%');

        // Secondary: count/total
        $b.find('.qc-badge__sub').text(String(indexed) + '/' + String(total));

        // Color based on how many are NOT indexed (0 good, <5 warn, else bad)
        var notIndexed = Math.max(total - indexed, 0);

        $b.removeClass('qc-badge--good qc-badge--warn qc-badge--bad qc-badge--neutral');
        if (notIndexed === 0) $b.addClass('qc-badge--good');
        else if (notIndexed < 5) $b.addClass('qc-badge--warn');
        else $b.addClass('qc-badge--bad');
    }

    function setImagesTooLargeBadge(count, subLabel){
      var has200 = (subLabel === '>200KB');
      var has100 = (subLabel === '>100KB' && count > 0);
    
      $('[data-qc-badge="imagesTooLarge"]').each(function(){
        var $b = $(this);
    
        // number
        var $strong = $b.find('strong').first();
        if ($strong.length) $strong.text(String(count));
    
        // sublabel
        var $sub = $b.find('.qc-badge__sub').first();
        if (!$sub.length){
          $sub = $('<span class="qc-badge__sub"></span>').appendTo($b);
        }
        $sub.text(subLabel);
    
        // reset severity classes
        $b.removeClass('qc-badge--images-red qc-badge--images-orange');
    
        // 200KB exists → red
        if (has200 && count > 0){
          $b.addClass('qc-badge--images-red');
        }
        // only 100KB exists → orange
        else if (has100){
          $b.addClass('qc-badge--images-orange');
        }
      });
    }


    function updateAlerts(){
        // Count ONLY real 404s (exclude duplicate forms that are styled as duplicates)
        var hl404 = $('#highlevel-table tr.error-row').length;
        var multiH1 = $('tr[data-qc-issue="multi-h1"]').length;
    
        // HP Headings: SUM the number of HP errors (so H1+H3 on same page counts as 2)
        var hpHeadings = 0;
        $('tr[data-qc-hp-error-count]').each(function(){
          var n = parseInt($(this).attr('data-qc-hp-error-count'), 10);
          if (!isNaN(n)) hpHeadings += n;
        });
    
        var dupImages = $('tr[data-qc-issue="duplicate-image"]').length;
        var altTextMissed = $('#media-library-search-table tr.qc-missing-alt').length;
    
        var missingBacklinks = $('#pages-posts-backlinks-table tbody tr').filter(function(){
            // Exclude the empty-state row (colspan)
            return $(this).find('td[colspan]').length === 0;
        }).length;
    
        var extImages = $('tr[data-qc-issue="external-image"]').length;
        var cityState = $('tr[data-qc-issue="city-state"]').length;
    
        // Images Too Large: prefer >200KB count when any exist, else >100KB
        function countDataRows($container){
            if (!$container || !$container.length) return 0;
            return $container.find('table tbody tr').filter(function(){
                return $(this).find('td[colspan]').length === 0;
            }).length;
        }
    
        var imgOver100 = countDataRows($('#media-library-table'));        // >100KB
        var imgOver200 = countDataRows($('#media-library-large-table'));  // >200KB
    
        var imagesTooLargeCount = (imgOver200 > 0) ? imgOver200 : imgOver100;
        if (!Number.isFinite(imagesTooLargeCount)) imagesTooLargeCount = 0;
    
        var imagesTooLargeSub = (imgOver200 > 0) ? '>200KB' : '>100KB';
        setImagesTooLargeBadge(imagesTooLargeCount, imagesTooLargeSub);
    
        // Indexed pages (% of total) from the All Pages table
        var $pageRows = $('#pages-table tbody tr');
        var totalPages = $pageRows.length;
        var nonIndexedPages = $pageRows.filter(function(){
            return $(this).find('td:nth-child(6)').text().trim().toLowerCase() === 'non-indexed';
        }).length;
        var indexedPages = Math.max(totalPages - nonIndexedPages, 0);
    
        setBadge('hl404', hl404);
        setBadge('multiH1', multiH1);
        setBadge('hpHeadings', hpHeadings);
        setBadge('dupImages', dupImages);
        setBadge('altTextMissed', altTextMissed);
        setBadge('missingBacklinks', missingBacklinks);
        setBadge('extImages', extImages);
        setBadge('cityState', cityState);
        setIndexedBadge(indexedPages, totalPages);
    }

    // When pagination changes or tables filter changes, re-run checks.
    $(document).on('qc:pagechange', function(){
        checkVisibleUrls();
        checkVisibleTitles();
        updateAlerts();
    });

    // Init
    initPagination();
    // First pass (for tables without pagination)
    checkVisibleUrls();
    // Background pass: check all form iframe URLs (keeps counts accurate without blocking initial render)
    setTimeout(checkAllUrls, 50);
    checkVisibleTitles();
    updateAlerts();
});
</script>
