3rdUnknown / Spectre 5000

// ==UserScript==
// @name         Spectre 5000
// @namespace    https://spectre.5000
// @version      16.903
// @description  The feeling of not wanting to lose, everyone has it. If you find it unfair, you'll have to work harder yourself.
// @author       3rdUnknown
// @copyright    2025
// @license      MIT
// @match        http://62.210.192.50:3000/projects*
// @match        *://spectre.autotests-xbees.wildix.com/projects*
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip-utils/0.1.0/jszip-utils.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.0/FileSaver.min.js
// @require      https://unpkg.com/image-compare-viewer/dist/image-compare-viewer.min.js
// @resource     IMPORTED_CSS https://unpkg.com/image-compare-viewer/dist/image-compare-viewer.min.css
// @grant        GM_notification
// @grant        GM_xmlhttpRequest
// @grant        GM_openInTab
// @grant        GM_getResourceText
// @grant        GM_addStyle
// @run-at       document-idle
// @updateURL    https://openuserjs.org/meta/3rdUnknown/Spectre_5000.meta.js
// ==/UserScript==

(function() {
    'use strict';
    const my_css = GM_getResourceText("IMPORTED_CSS");
    GM_addStyle(my_css);
    GM_addStyle ( `
/* ==== Modal Styling ==== */
.modal {
  display: none;
  position: fixed;
  z-index: 1;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: auto;
  background-color: rgba(0, 0, 0, 0.4);
}

.modal-content {
  background-color: #fefefe;
  margin: 3% auto;
  padding: 20px;
  border: 1px solid #888;
  width: 85%;
  height: 85%;
  text-align: center;
}

.imgModal1 img {
  height: 95%;
}

.imgModal2 img {
  max-width: 95%;
}

#image-compare {
  width: 100%;
  height: 95%;
}

/* ==== Modal Close Button ==== */
.close {
  color: #aaa;
  float: right;
  font-size: 28px;
  font-weight: bold;
  cursor: pointer;
}

.close:hover,
.close:focus {
  color: black;
  text-decoration: none;
}

/* ==== Image Compare Wrapper ==== */
.icv__wrapper,
div.icv__fluidwrapper {
  background-size: contain;
  background-repeat: no-repeat;
}

.icv__label {
  font-size: 1.25rem;
}

/* ==== Metadata Styling ==== */
.date-time {
  color: black;
  font-size: 1.5rem;
}

.version {
  font-size: 0.8em;
  color: #909090;
  margin: -5px 0 -10px;
}

.greenBuildDate {
  color: green;
  font-weight: bold;
}

.redBuildDate {
  color: red;
  font-weight: bold;
}

/* ==== ZIP Download Block ==== */
#download-block {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* ==== Lightbox Border ==== */
.lightbox {
  border: 2px dashed red;
}

/* ==== Tabs: Product ==== */
#product-tabs {
  margin: 20px 0;
  border-bottom: 1px solid #ddd;
  display: flex;
  flex-wrap: wrap;
}

.product-tab {
  padding: 12px 20px !important;
  margin-right: 8px !important;
  border: 1px solid #ddd;
  border-bottom: none;
  background-color: #f8f8f8;
  border-radius: 5px 5px 0 0;
  font-size: 16px !important;
  font-weight: bold;
  min-width: 120px;
  text-align: center;
  cursor: pointer;
  transition: all 0.2s ease;
}

.product-tab:hover {
  background-color: #e9e9e9 !important;
}

.product-tab.x-bees-tab {
  color: #ff8c00 !important;
}

.product-tab.x-hoppers-tab {
  color: #006400 !important;
}

.product-tab.collaboration-tab {
  color: #00008b !important;
}

.product-tab.all-tab {
  color: #333 !important;
}

.product-tab.website-tab {
  color: #8b008b !important;
}

.active-tab {
  background-color: #f0f0f0 !important;
  border-bottom: 3px solid currentColor !important;
  font-weight: bold !important;
}

.hidden-project {
  display: none;
}

/* ==== Tabs: Environments ==== */
#environment-tabs {
  margin: 0;
  padding-left: 0;
  border-bottom: 1px solid #ddd;
  display: flex;
  flex-wrap: wrap;
}

.environment-tab {
  padding: 8px 15px;
  margin-right: 6px;
  border: 1px solid #ddd;
  border-bottom: none;
  background-color: #f8f8f8;
  border-radius: 5px 5px 0 0;
  font-size: 14px;
  color: #333;
  cursor: pointer;
  transition: all 0.2s ease;
}

.environment-tab:hover {
  background-color: #e9e9e9 !important;
}

.environment-tab.active-env-tab {
  background-color: #f0f0f0 !important;
  border-bottom: 3px solid currentColor !important;
  font-weight: bold !important;
  color: black !important;
}

.environment-tab.stage-tab {
  color: #9c27b0 !important;
}

.environment-tab.stable-tab {
  color: #ff9800 !important;
}

/* ==== Category Filter ==== */
#category-filters {
  margin: 10px 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  padding-left: 15px;
}

.category-label {
  font-weight: bold;
  margin-right: 10px;
  font-size: 14px;
}

.category-button {
  padding: 6px 12px !important;
  margin: 0 8px 5px 0 !important;
  border: 1px solid #ddd;
  background-color: #f1f1f1;
  border-radius: 4px;
  font-size: 13px !important;
  color: #333 !important;
  cursor: pointer;
  transition: all 0.2s ease;
}

.category-button:hover {
  background-color: #e0e0e0;
}

.category-button.active-category {
  background-color: #4caf50;
  color: white !important;
  font-weight: bold;
  box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}

.category-kite {
  border-left: 4px solid #2196F3;
}
.category-web {
  border-left: 4px solid #FF5722;
}
.category-android {
  border-left: 4px solid #4CAF50;
}
.category-ios {
  border-left: 4px solid #9C27B0;
}
.category-mobile-kite {
  border-left: 4px solid #FFC107;
}

/* ==== Project Rows ==== */
.old-project-row {
  background-color: #f7e6e6 !important;
}

.very-old-project-row {
  background-color: #ffe5b4 !important;
  font-weight: bold;
  border-left: 4px solid orange;
}

/* ==== Test Labels and Stats ==== */
.label--fail,
.label--pass,
.label--warning,
a.total-tests {
  font-size: 1.65rem !important;
  font-weight: bold !important;
  text-align: center !important;
  vertical-align: middle !important;
  border-radius: 6px !important;
  margin: 2px !important;
  display: inline-block !important;
  white-space: nowrap !important;
  overflow: hidden !important;
  text-overflow: ellipsis !important;
}

td.text-center {
  text-align: right !important; /* Изменяет выравнивание с center на left */
  padding-left: 15px !important; /* Добавляет отступ слева */
}

.label--fail,
.label--pass {
  width: 95px !important;
  height: 37px !important;
  line-height: 40px !important;
}

.label--warning {
  width: 100px !important;
  height: 37px !important;
  line-height: 40px !important;
}

a.total-tests {
  width: 95px !important;
  height: 35px !important;
  line-height: 40px !important;
}

/* ==== Row Dividers in Project Table ==== */
tr.project {
  border-bottom: 2px solid black !important;
}

tr.project td,
tr.project th {
  padding-top: 8px !important;
  padding-bottom: 8px !important;
}

/* ==== Count and Percentage Styling ==== */
#test_count > span > span,
.count-number,
.count-sup {
  font-size: 1.5rem !important;
}

.count-number {
  vertical-align: middle;
  font-size: 1.65rem;
}

.count-sup {
  vertical-align: super;
}

.icon-span {
  font-size: 2.2rem;
  margin-left: 5px;
}

.percent-mark {
  display: inline-block;
  background-color: #FF8C00;
  color: white;
  padding: 0.2rem 0.5rem;
  border-radius: 0.25rem;
  font-size: 1rem;
}

/* ==== Difference Count ==== */
.low-difference-count {
  font-size: 0.8em;
  color: #909090;
  font-weight: bold;
  margin-top: -20px;
}

/* ==== Adjust Suite Column Width ==== */
table#projects th:first-child,
table#projects td:first-child,
#suite th:first-child,
#suite td:first-child {
  max-width: 150px !important;
  width: 150px !important;
  white-space: nowrap !important;
  overflow: hidden !important;
  text-overflow: ellipsis !important;
}

/* Make sure links in Suite column also respect the width */
table#projects th:first-child a,
#suite th:first-child a {
  display: block !important;
  max-width: 100% !important;
  overflow: hidden !important;
  text-overflow: ellipsis !important;
  white-space: nowrap !important;
}

/* Ensure text inside cells is trimmed */
table#projects th:first-child *,
table#projects td:first-child *,
#suite th:first-child *,
#suite td:first-child * {
  white-space: nowrap !important;
  overflow: hidden !important;
  text-overflow: ellipsis !important;
}

.download-button {
  width: 250px;
  height: 60px;
  background-color: #28a745;
  color: white;
  font-size: 1.75rem;
  font-weight: 600;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin: 4px;
  transition: background-color 0.2s ease-in-out;
}

.download-button:hover {
  background-color: #218838;
}

td#test_count {
  padding-left: 8px !important;
  padding-right: 8px !important;
}

#environment-tabs {
        margin: 0;
        padding-left: 0;
        border-bottom: none !important;
    }

    #category-filters {
        margin: 0 !important;
    }

    .category-label {
        margin-left: 5px;
    }

    /* Ensure proper alignment */
    #combined-filters {
        padding-left: 15px;
    }

` );

        let totalTests = {
        '------x-bees------': 0,
        'monitoring chats list x-bees WEB': 7,
        'basic visual regression testing x-bees WEB': 424,
        'DMbasic visual regression testing x-bees WEB': 404,
        'basic visual regression testing x-bees iOS': 18,
        'basic visual regression testing simulator x-bees iOS': 190,
        'basic visual regression testing x-bees Android': 234,
        'DM basic visual regression testing x-bees Android': 234,
        'storybook visual regression testing x-bees Android': 77,
        'Kite basic visual regression testing (chrome) x-bees KITE': 96,
        'Kite basic visual regression testing (firefox) x-bees KITE': 92,
        'Kite basic visual regression testing (safari) x-bees KITE': 92,
        'chrome Kite mobile basic visual regression testing x-bees mobile KITE': 56,
        'safari Kite mobile basic visual regression testing x-bees mobile KITE': 35,
        'widget: visual regression testing WEBSITE' : 48,
        'website: visual regression testing WEBSITE' : 54,
        'expand widget: visual regression testing WEBSITE': 11,
        '------x-hoppers------': 0,
        'monitoring chats list x-hoppers WEB': 7,
        'basic visual regression testing x-hoppers WEB': 388,
        'DMbasic visual regression testing x-hoppers WEB': 369,
        'basic visual regression testing x-hoppers iOS': 18,
        'basic visual regression testing simulator x-hoppers iOS': 190,
        'basic visual regression testing x-hoppers Android': 234,
        '------collboration------': 0,
        'Kite basic visual regression testing (chrome) collaboration KITE': 79,
        'DMbasic visual regression testing collaboration WEB': 351,
        'basic visual regression testing collaboration WEB': 175,
        'basic visual regression testing collaboration Android': 241,
        'basic visual regression testing collaboration iOS': 18,
        'basic visual regression testing simulator collaboration iOS': 188,
        'chrome Kite mobile basic visual regression testing collaboration mobile KITE': 50,
        'safari Kite mobile basic visual regression testing collaboration mobile KITE': 33
    };

    // Tab functionality
  function addProductTabs() {
    if (window.location.pathname === '/projects') {
        console.log('Adding product tabs');

        // Create tab container
        const tabContainer = document.createElement('div');
        tabContainer.id = 'product-tabs';

        // Define products for tabs
        const products = [
            {name: 'All', class: 'all-tab'},
            {name: 'x-bees', class: 'x-bees-tab'},
            {name: 'x-hoppers', class: 'x-hoppers-tab'},
            {name: 'collaboration', class: 'collaboration-tab'},
            {name: 'Website', class: 'website-tab'}
        ];

        // Create tabs
        products.forEach(product => {
            const tab = document.createElement('button');
            tab.innerText = product.name;
            tab.dataset.product = product.name.toLowerCase();
            tab.classList.add('product-tab', product.class);

            // Default active tab
            if (product.name === 'All') {
                tab.classList.add('active-tab');
            }

            tab.addEventListener('click', function() {
                // Show environment tabs for all products
                const envTabs = document.getElementById('environment-tabs');
                if (envTabs) {
                    // Always show environment tabs, regardless of the tab
                    envTabs.style.display = 'flex';

                    // Update active environment tab when changing product
                    const activeEnvTab = document.querySelector('.environment-tab.active-env-tab');
                    if (!activeEnvTab) {
                        const allEnvTab = document.querySelector('.environment-tab[data-env="all environments"]');
                        if (allEnvTab) allEnvTab.classList.add('active-env-tab');
                    }
                }

                filterProjectsByProduct(this.dataset.product);

                // Update active tab styling
                document.querySelectorAll('.product-tab').forEach(t => {
                    t.classList.remove('active-tab');
                });

                this.classList.add('active-tab');
            });

            tabContainer.appendChild(tab);
        });

        // Insert tabs before the projects table
        const projectsHeader = document.querySelector('h1');
        if (projectsHeader) {
            projectsHeader.parentNode.insertBefore(tabContainer, projectsHeader.nextSibling);

            // Add environment tabs
            addEnvironmentTabs(projectsHeader.parentNode, tabContainer);

            // Add category filters
            addCategoryFilters(projectsHeader.parentNode);

            // Make environment tabs visible initially
            const envTabs = document.getElementById('environment-tabs');
            if (envTabs) {
                envTabs.style.display = 'flex';
            }
        }
    }
}
    function addRowDividers() {
  const projectRows = document.querySelectorAll('tr.project');
  projectRows.forEach(row => {
    row.style.borderBottom = '2px solid black';
  });
}


// Find and hide the Projects header
function hideProjectsHeader() {
    const projectsHeader = document.querySelector('h1');
    if (projectsHeader && projectsHeader.textContent.includes('Projects')) {
        projectsHeader.style.display = 'none';
    }
}

    // Add this function to your script
function adjustColumnWidths() {
  // Select the projects table
  const projectsTable = document.getElementById('projects');
  if (!projectsTable) return;

  // Get all project rows
  const projectRows = projectsTable.querySelectorAll('tr.project');
  if (!projectRows.length) return;

  console.log('Adjusting column widths for better label fitting');

  // Adjust name column width
  const nameHeaders = projectsTable.querySelectorAll('th:first-child, td:first-child');
  nameHeaders.forEach(header => {
    header.style.maxWidth = '290px';
    header.style.width = '220px';
    header.style.whiteSpace = 'nowrap';
    header.style.overflow = 'hidden';
    header.style.textOverflow = 'ellipsis';
  });

  // Adjust suite column width
  const suiteHeaders = projectsTable.querySelectorAll('th:nth-child(2), td:nth-child(2)');
  suiteHeaders.forEach(header => {
    header.style.maxWidth = '550px';
    header.style.width = '450px';
    header.style.whiteSpace = 'normal';
    header.style.overflow = 'hidden';
    header.style.textOverflow = 'ellipsis';
  });

  // Make the last column use remaining space
  const lastRunHeaders = projectsTable.querySelectorAll('th:nth-child(3), td:nth-child(3)');
  lastRunHeaders.forEach(header => {
    header.style.width = 'auto';
  });

  // Make test result labels more compact
  const testLabels = projectsTable.querySelectorAll('.label--fail, .label--pass, .label--warning, a.total-tests');
  testLabels.forEach(label => {
    label.style.padding = '1px 1px';
  });

  // Improve label layout using flexbox
  const labelContainers = projectsTable.querySelectorAll('td.text-center');
  labelContainers.forEach(container => {
    container.style.whiteSpace = 'nowrap';
  });
}




 // Add environment tabs for all products
function addEnvironmentTabs(parentNode, tabContainer) {
    const envTabContainer = document.createElement('div');
    envTabContainer.id = 'environment-tabs';

    const environments = [
        {name: 'All Environments', class: ''},
        {name: '🏗️ Stage', class: 'stage-tab'},
        {name: '🏭 Stable', class: 'stable-tab'}
    ];

    environments.forEach(env => {
        const tab = document.createElement('button');
        tab.innerText = env.name;
        tab.dataset.env = env.name.toLowerCase();
        tab.classList.add('environment-tab');
        if (env.class) tab.classList.add(env.class);

        // Default active tab
        if (env.name === 'All Environments') {
            tab.classList.add('active-env-tab');
        }

        tab.addEventListener('click', function() {
            filterProjectsByEnvironment(this.dataset.env);

            // Update active tab styling
            document.querySelectorAll('.environment-tab').forEach(t => {
                t.classList.remove('active-env-tab');
            });

            this.classList.add('active-env-tab');
        });

        envTabContainer.appendChild(tab);
    });

    // Insert after product tabs
    parentNode.insertBefore(envTabContainer, tabContainer.nextSibling);
}


    // Add category filters for platform filtering
    function addCategoryFilters(parentNode) {
        const categoryContainer = document.createElement('div');
        categoryContainer.id = 'category-filters';

        const labelSpan = document.createElement('span');
        labelSpan.classList.add('category-label');
        labelSpan.innerText = 'Filter by platform:';
        categoryContainer.appendChild(labelSpan);

        const categories = [
            {name: 'All', class: ''},
            {name: 'WEB', class: 'category-web'},
            {name: 'KITE', class: 'category-kite'},
            {name: 'Android', class: 'category-android'},
            {name: 'iOS', class: 'category-ios'},
            {name: 'Mobile KITE', class: 'category-mobile-kite'}
        ];

        categories.forEach(category => {
            const button = document.createElement('button');
            button.innerText = category.name;
            button.dataset.category = category.name.toLowerCase();
            button.classList.add('category-button');
            if (category.class) button.classList.add(category.class);

            // Default active button
            if (category.name === 'All') {
                button.classList.add('active-category');
            }

            button.addEventListener('click', function() {
                filterProjectsByCategory(this.dataset.category);

                // Update active button styling
                document.querySelectorAll('.category-button').forEach(b => {
                    b.classList.remove('active-category');
                });

                this.classList.add('active-category');
            });

            categoryContainer.appendChild(button);
        });

        // Insert after environment tabs or product tabs
        const envTabs = document.getElementById('environment-tabs');
        if (envTabs) {
            parentNode.insertBefore(categoryContainer, envTabs.nextSibling);
        } else {
            const prodTabs = document.getElementById('product-tabs');
            if (prodTabs) {
                parentNode.insertBefore(categoryContainer, prodTabs.nextSibling);
            }
        }
    }

    // Filter projects based on selected tab
    function filterProjectsByProduct(product) {
        const projects = document.querySelectorAll('tr.project');
        const activeEnvTab = document.querySelector('.environment-tab.active-env-tab');
        const activeCategoryTab = document.querySelector('.category-button.active-category');

        const envFilter = activeEnvTab ? activeEnvTab.dataset.env : 'all environments';
        const categoryFilter = activeCategoryTab ? activeCategoryTab.dataset.category : 'all';

        projects.forEach(project => {
            const projectNameElement = project.querySelector('th');
            if (!projectNameElement) return;

            const projectName = projectNameElement.innerText.toLowerCase();
            let showProject = true;

            // Apply product filter
            if (product !== 'all' && !projectName.includes(product)) {
                showProject = false;
            }

            // Apply environment filter for any product
            if (product !== 'all' && envFilter !== 'all environments') {
                if (!projectName.includes(envFilter.toLowerCase())) {
                    showProject = false;
                }
            }

            // Apply category filter
            if (categoryFilter !== 'all') {
                if (!projectName.includes(categoryFilter.toLowerCase())) {
                    showProject = false;
                }
            }

            if (showProject) {
                project.classList.remove('hidden-project');
            } else {
                project.classList.add('hidden-project');
            }
        });
    }

    // Filter projects based on selected environment
 function filterProjectsByEnvironment(environment) {
    const activeProductTab = document.querySelector('.product-tab.active-tab');
    if (!activeProductTab) return;

    const activeCategoryTab = document.querySelector('.category-button.active-category');
    const product = activeProductTab.dataset.product?.toLowerCase() || 'all';
    const categoryFilter = activeCategoryTab?.dataset.category?.toLowerCase() || 'all';
    const env = environment.toLowerCase();

    document.querySelectorAll('tr.project').forEach(project => {
        const projectNameElement = project.querySelector('th');
        if (!projectNameElement) return;

        const projectName = projectNameElement.innerText.toLowerCase();
        const projectEnv = project.dataset.project?.toLowerCase() || '';

        // Product filter
        if (product !== 'all' && !projectName.includes(product)) {
            project.classList.add('hidden-project');
            return;
        }

        // Environment filter
        if (env !== 'all environments') {
            const isStage = env.includes('stage');
            const isStable = env.includes('stable');

            if ((isStage && !projectEnv.includes('stage')) || (isStable && !projectEnv.includes('stable'))) {
                project.classList.add('hidden-project');
                return;
            }
        }

        // Category filter
        if (categoryFilter !== 'all') {
            const isMobileKite = categoryFilter === 'mobile kite';
            const categoryMatch = isMobileKite
                ? projectName.includes('mobile') && projectName.includes('kite')
                : projectName.includes(categoryFilter);

            if (!categoryMatch) {
                project.classList.add('hidden-project');
                return;
            }
        }

        // If all filters passed
        project.classList.remove('hidden-project');
    });
}


    // Filter projects based on selected category
    function filterProjectsByCategory(category) {
        const activeProductTab = document.querySelector('.product-tab.active-tab');
        const activeEnvTab = document.querySelector('.environment-tab.active-env-tab');

        if (!activeProductTab) return;

        const product = activeProductTab.dataset.product;
        const environment = activeEnvTab ? activeEnvTab.dataset.env : 'all environments';

        const projects = document.querySelectorAll('tr.project');

        projects.forEach(project => {
            const projectNameElement = project.querySelector('th');
            if (!projectNameElement) return;

            const projectName = projectNameElement.innerText.toLowerCase();
            let showProject = true;

            // Apply product filter
            if (product !== 'all' && !projectName.includes(product)) {
                showProject = false;
            }

            // Apply environment filter if not "all environments"
            if (environment !== 'all environments' && !projectName.includes(environment.toLowerCase())) {
                showProject = false;
            }

            // Apply category filter
            if (category !== 'all') {
                // Handle special case for mobile kite
                if (category === 'mobile kite') {
                    if (!projectName.includes('mobile') || !projectName.includes('kite')) {
                        showProject = false;
                    }
                } else if (!projectName.includes(category)) {
                    showProject = false;
                }
            }

            if (showProject) {
                project.classList.remove('hidden-project');
            } else {
                project.classList.add('hidden-project');
            }
        });
    }

    function combineFilterElements() {
    const envTabs = document.getElementById('environment-tabs');
    const categoryFilters = document.getElementById('category-filters');

    if (envTabs && categoryFilters) {
        // Create a container for both elements
        const combinedContainer = document.createElement('div');
        combinedContainer.id = 'combined-filters';
        combinedContainer.style.display = 'flex';
        combinedContainer.style.flexWrap = 'wrap';
        combinedContainer.style.alignItems = 'center';
        combinedContainer.style.marginTop = '10px';
        combinedContainer.style.marginBottom = '10px';

        // Make environment tabs take less space
        envTabs.style.marginRight = '25px';
        envTabs.style.borderBottom = 'none';

        // Move elements to the combined container
        if (envTabs.parentNode) {
            envTabs.parentNode.insertBefore(combinedContainer, envTabs);
        }

        combinedContainer.appendChild(envTabs);
        combinedContainer.appendChild(categoryFilters);
    }
}

    // Highlight project age with background colors
    function highlightProjectAge() {
        const projectRows = document.querySelectorAll('tr.project');
        const now = new Date();

        projectRows.forEach(row => {
            const dateTimeSpan = row.querySelector('td.text-muted span');
            if (!dateTimeSpan) return;

            const dateText = dateTimeSpan.innerText;

            // Check for old projects (> 1 month)
            if (dateText.includes('month ago') || dateText.includes('months ago') || dateText.includes('year ago') || dateText.includes('years ago')) {
                row.classList.add('old-project-row');
            }

            // Check for very old projects (> 3 days)
            if (dateText.includes('days ago') && parseInt(dateText) > 3) {
                row.classList.add('very-old-project-row');
            }
        });
    }

    let text, score, lowDiffCount = 0;
    let lowDiffCountUniq = 0;
    let testLowDiffTitle = '';
    let testNamePrev = '';

    // Selectors
    const differenceMarkElements = document.querySelectorAll('span.test__diff.text-muted');
    const testimgs = document.querySelectorAll('a.test__image');
    const baselineimgs = document.querySelectorAll('a.baseline__image');

    // Initialize all functions
    addDifferencesMarks();
    footerAndHeader();
    addVisualComparisonButtons();
    improveDateTime();
    lowDiffFilter();
    totalTestCount();
    sortingProjects();
    sortingTests();
    envStyle();
    productStyle();
    addModalsToImgs(testimgs);
    addModalsToImgs(baselineimgs);
    addProductTabs();
    highlightProjectAge();
    addRowDividers();
    adjustColumnWidths();
    hideProjectsHeader();
    combineFilterElements();

    // Improve visibility of difference marks
    function addDifferencesMarks() {
        let testRow, testName, testEnv;
        const negative = '<img src="https://github.githubassets.com/images/icons/emoji/unicode/2716.png" width="32" height="32">';
        const question = '<img src="https://github.githubassets.com/images/icons/emoji/unicode/2754.png" width="32" height="32">';
        const positive = '<img src="https://github.githubassets.com/images/icons/emoji/unicode/2714.png" width="32" height="32">';

        for (let elem of differenceMarkElements) {
            text = elem.innerHTML;
            testRow = elem.parentElement.parentElement;
            if (elem.innerHTML !== 'No difference') {
                console.log(elem.innerHTML);
                score = parseFloat(elem.innerHTML);
                if (score <= 0.05) {
                    elem.innerHTML = question + '<font color="orange" size="+2">' + score + '%</font>' + '<br />' + text;
                } else if (score > 0.05) {
                    elem.innerHTML = negative + '<font color="red" size="+2">' + score + '%</font>' + '<br />' + text;
                }
                if (score < 0.10) {
                    lowDiffCount++;
                    const testNameElement = testRow.querySelector('.test__name');
                    const testEnvElement = testRow.querySelector('th p');

                    if (testNameElement && testEnvElement) {
                        testName = testNameElement.innerText;
                        testEnv = testEnvElement.innerText;

                        if (testName !== testNamePrev) {
                            testLowDiffTitle = `${testLowDiffTitle} ${testName} - ${testEnv}\n`;
                            testNamePrev = testName;
                            lowDiffCountUniq++;
                        }
                    }
                }
                testRow.classList.add("diffed");
            } else {
                elem.innerHTML = positive + text;
                testRow.classList.add("pass");
            }
        }
    }

    // Improve footer and header: added download all button, added count low diff screenshots
    function footerAndHeader() {
        if (window.location.pathname.indexOf('/runs/') !== -1) {
            const h1 = document.querySelector('div h1');
            if (h1) {
                const lowDiffP = document.createElement("p");
                lowDiffP.classList.add("low-difference-count");
                lowDiffP.innerText = `Low diff tests: ${lowDiffCountUniq} / Low diff screenshots: ${lowDiffCount}`;
                lowDiffP.title = testLowDiffTitle;
                insertAfter(lowDiffP, h1);

                // DL IMAGES
                addDownloadBlock();
                const containerHeader = $(".container").find("h1");
                const archiveName = containerHeader.length ? containerHeader.text() : "spectre-export";
                const imageExtension = '.png';

                $("#download-images").on("click", function () {
                    resetMessage();
                    // JSZIP.js
                    var zip = new JSZip();
                    // find every checked item
                    $("tr.test").each(function () {
                        var $this = $(this);
                        var testImgLink = $this.find("td#image-test a.test__image");
                        if (testImgLink.length) {
                            var url = testImgLink.attr('data-href');
                            var testNameElement = $this.find("span.test__name");
                            var testInfoElement = $this.find("small.text-muted");

                            if (url && testNameElement.length && testInfoElement.length) {
                                var testName = testNameElement.text().replace(/\s\s+/g, ' ').replace(/\s+/g, '-');
                                var testInfo = testInfoElement.text().replace(/\s\s+/g, ' ').replace(/,/g, '').replace(/\s+/g, '-');
                                var filename = testName + testInfo;
                                zip.file(filename + imageExtension, urlToPromise(url), {binary:true});
                            }
                        }
                    });

                    // when everything has been downloaded, we can trigger the dl
                    zip.generateAsync({type:"blob"}, function updateCallback(metadata) {
                        var msg = "progression : " + metadata.percent.toFixed(2) + " %";
                        if(metadata.currentFile) {
                            msg += ", current file = " + metadata.currentFile;
                        }
                        showMessage(msg);
                        updatePercent(metadata.percent|0);
                    })
                    .then(function callback(blob) {
                        // FileSaver.js
                        saveAs(blob, archiveName + ".zip");
                        showMessage("Done! Archive created successfully.");
                    }, function (e) {
                        showError(e);
                    });
                    return false;
                });
            }
        }
    }

    // Open image in modal window
    function addModalsToImgs(imgs) {
        if (!imgs || imgs.length === 0) return;

        imgs.forEach(img => {
            if (img && img.href) {
                img.setAttribute("data-href", img.href);
                img.removeAttribute('href');
            }
        });

        let modalWindow = document.createElement("div");
        let imgClass = "imgModal1";

        if (window.location.pathname.indexOf('projects/website/suites') !== -1) {
            imgClass = "imgModal2";
        }

        modalWindow.classList.add("modal", imgClass);
        modalWindow.innerHTML = '<div class="modal-content"><span class="close">&times;</span><img class="lightbox" src=""/></div>';
        document.body.appendChild(modalWindow);

        imgs.forEach((element) => {
            if (element) {
                element.addEventListener('click', function(event){
                    const modal = document.querySelector(`div.${imgClass}`);
                    if (modal) {
                        modal.style.display = "block";
                        const modalimg = modal.querySelector("img");
                        if (modalimg) {
                            modalimg.src = element.getAttribute('data-href');
                        }

                        const close = modal.querySelector("span.close");
                        if (close) {
                            close.onclick = function() {
                                modal.style.display = "none";
                            };
                        }

                        window.onclick = function(event) {
                            if (event.target == modal) {
                                modal.style.display = "none";
                            }
                        };
                    }
                });
            }
        });
    }

// Add visual comparison button
function addVisualComparisonButtons() {
    let pics, picReference, picCurrent;
    let failedTests = document.querySelectorAll(".diffed");

    failedTests.forEach(test => {
        if (!test) return;

        pics = test.querySelectorAll("a.test__image");
        if (!pics || pics.length < 2) return;

        pics.forEach(pic => {
            if (pic) pic.classList.add("diffBtn");
        });

        picReference = pics[0].href;
        picCurrent = pics[1].href;

        if (test.id && picReference && picCurrent) {
            addModalWindows(test.id, picReference, picCurrent, test);
        }
    });

    const failedButtons = document.querySelectorAll(".diffed .test__diff");
    failedButtons.forEach(button => {
        if (!button || !button.parentElement || !button.parentElement.parentElement) return;

        let parentId = button.parentElement.parentElement.id;
        if (parentId) {
            addElementButton(parentId, button);
        }
    });

    setListener();
    setViewers();
}

// Baseline highlight
if ((window.location.pathname.includes('/projects/')) && (window.location.pathname.includes('/suites/')) && (window.location.pathname.indexOf('/runs/') === -1)) {
    let newBaselines = document.querySelectorAll("tr.new-baseline");
    let oldBaselines = document.querySelectorAll("tr.old-baseline");

    if (newBaselines && newBaselines.length > 0) {
        let breadcrumb = document.querySelector("ul.breadcrumb");
        if (breadcrumb) {
            let newButton = document.createElement("button");
            newButton.id = 'show-new-baselines';
            newButton.classList.add("label--all", "label");
            newButton.innerText = `New baselines: ${newBaselines.length}`;
            breadcrumb.appendChild(newButton);

            newBaselines.forEach(item => {
                const link = item.querySelector("th > a");
                if (link) link.style.color = 'red';
            });

            newButton.addEventListener('click', function(event){
                console.log('Button show-new-baselines clicked');
                oldBaselines.forEach(item => {
                    if (item) item.remove();
                });

                const filters = document.querySelector("div.filters");
                if (filters) {
                    filters.scrollIntoView({ behavior: "instant", block: "start", inline: "start" });
                }
            });
        }
    }
}

function improveDateTime() {
    // Improve date/time of test run
    let dateElements = document.querySelectorAll('td.text-muted span');
    dateElements.forEach(el => {
        if (!el) return;

        let dateTime = el.getAttribute('Title');
        if (dateTime) {
            addElementPTime(el, dateTime);
        }
    });

    // Improve date/time of project
    dateElements = document.querySelectorAll('#test_count span.text-muted span');
    let parentCells = document.querySelectorAll('td#test_count');
    let parentRows = document.querySelectorAll('tr.project');

    dateElements.forEach(function(el, index) {
        if (!el || !parentCells[index]) return;

        let dateTime = el.getAttribute('Title');
        let dateText = el.innerText;
        let row = parentRows[index];

        // Mark old runs
        if (dateText) {
            if (dateText.includes('month ago') || dateText.includes('months ago') || dateText.includes('year ago')) {
                // Change from deeppink to bold red
                el.style.color = 'red';
                el.style.fontWeight = 'bold';
                // Color the entire row light gray for runs older than 1 month
                if (row) row.style.backgroundColor = '#D3D3D3';
            } else if (dateText.includes('days ago')) {
                // Extract number of days
                const daysMatch = dateText.match(/(\d+) days ago/);
                if (daysMatch && daysMatch[1]) {
                    const days = parseInt(daysMatch[1]);
                    if (days > 3) {
                        // Mark text in red for runs older than 3 days
                        el.style.color = 'red';
                        el.style.fontWeight = 'bold';
                        // Also color the entire row light red
                        if (row) row.style.backgroundColor = '#FDF5E6';
                    }
                }
            }
        }

        if (dateTime) {
            let newP = document.createElement("p");
            newP.classList.add("date-time");
            newP.innerHTML = '🕒 ' + dateTime;
            parentCells[index].appendChild(newP);
        }
    });
}

// Highlight envs in suit names
function envStyle() {
    let nameElements = document.querySelectorAll('.project a');
    nameElements.forEach(el => {
        if (!el) return;

        let content = el.outerText ? el.outerText.toLowerCase() : '';

        if (content.includes('stage')) {
            el.innerHTML = '🏗️ ' + '<font color="darkviolet">' + el.innerHTML + '</font>';
        } else if (content.includes('prod')) {
            el.innerHTML = '🏢 ' + el.innerHTML;
        } else if (content.includes('stable')) {
            el.innerHTML = '🏭 ' + '<font color="darkorange">' + el.innerHTML + '</font>';
        }
    });
}

// Highlight product in suit names
function productStyle() {
    let nameElements = document.querySelectorAll('.project th');
    nameElements.forEach(el => {
        if (!el) return;

        let text = el.innerText ? el.innerText.toLowerCase() : '';

        if (text.includes('x-bees')) {
            el.innerHTML = '<font color="darkorange">' + el.innerHTML + '</font>';
        } else if (text.includes('x-hoppers')) {
            el.innerHTML = '<font color="darkgreen">' + el.innerHTML + '</font>';
        } else if (text.includes('collaboration')) {
            el.innerHTML = '<font color="darkblue">' + el.innerHTML + '</font>';
        }
    });
}

// Low diff filter options
function lowDiffFilter() {
    if ((window.location.pathname.indexOf('/suites/') !== -1) && (window.location.pathname.indexOf('/runs/') !== -1)) {
        let filtersForm = document.querySelector('div.filters form');
        if (!filtersForm) return;

        let selectDiff = document.createElement('select');
        selectDiff.name = 'diff';
        selectDiff.id = 'diff';
        selectDiff.classList.add('form-control');

        let optionAll = document.createElement('option');
        optionAll.value = 'All';
        optionAll.innerText = "All";

        let optionLowDiff = document.createElement('option');
        optionLowDiff.value = 'low-diff';
        optionLowDiff.innerText = "Low diff";

        selectDiff.appendChild(optionAll);
        selectDiff.appendChild(optionLowDiff);

        selectDiff.addEventListener('change', function() {
            let tests = document.querySelectorAll(".pass, .test--fail");

            if (this.value === 'low-diff') {
                console.log('low-diff filter ON');
                tests.forEach(test => {
                    if (test) test.remove();
                });
            } else {
                console.log('low-diff filter OFF');
                location.reload(true);
            }
        });

        filtersForm.appendChild(selectDiff);
    }
}

// Highlight total test count
function totalTestCount() {
    let executedTestsElements = document.querySelectorAll('a.total-tests');

    if (executedTestsElements && executedTestsElements.length > 0) {
        executedTestsElements.forEach(el => {
            if (!el) return;

            let newCount;
            let count = parseInt(el.textContent, 10);
            let suitName = el.getAttribute('data-suite');

            if (!suitName) return;

            console.log(suitName);
            suitName = suitName.replace('stable: ', '').replace('stage: ', '');

            if (totalTests[suitName]) {
                // Визуальное отображение числа с индексом степени
                if (count === totalTests[suitName]) {
                    newCount = `<span class="count-number">${count}</span><sup class="count-sup">${totalTests[suitName]}</sup>`;
                } else if (count < totalTests[suitName]) {
                    newCount = `
                    <font color="#FF8C00">
                    <b class="count-number">${count}</b>
                    </font><sup class="count-sup">${totalTests[suitName]}</sup>`;
                } else {
                    newCount = `<span class="count-number">${count}</span><sup class="count-sup"><font color="darkorange"><b class="count-number">${totalTests[suitName]}</b></font></sup>`;
                }

                // Вставка основного контента (число и степень)
                el.innerHTML = newCount;

                // Добавление иконки отдельно
                const iconSpan = document.createElement('span');
                iconSpan.classList.add('icon-span'); // Применение класса для иконки

                // Определяем иконку в зависимости от условий
                if (count < totalTests[suitName]) {
                    iconSpan.textContent = '⚠️';
                } else if (count > totalTests[suitName]) {
                    iconSpan.textContent = '⬆️';
                }

                // Вставка иконки в элемент, если она не пуста
                if (iconSpan.textContent) el.appendChild(iconSpan);

                // % при падении
                if (count < totalTests[suitName]) {
                    let percentMark = document.createElement("a");
                    percentMark.classList.add("label", "label--warning", "percent-mark");
                    percentMark.title = "Test Pass Rate";
                    percentMark.innerText = `${((count / totalTests[suitName]) * 100).toFixed(1)}%`;
                    insertAfter(percentMark, el);
                }
            }
        });
    }
}

// Re-sort projects
function sortingProjects() {
    let projectsTable = document.querySelector('#projects');

    if (!projectsTable) return;

    let projects = projectsTable.querySelectorAll(".project");

    if (!projects || projects.length === 0) return;

    console.log('-sort: projects-');

    let sortedProjectsArr = [];
    let projectName, projectNameEnv, projectNameNormalized;

    projects.forEach(project => {
        if (!project) return;

        projectName = project.getAttribute('data-project');

        if (!projectName) return;

        projectNameEnv = projectName.split(":")[0];

        // Set priority for suites
        if (projectName.includes('monitoring chats list')) {
            projectNameNormalized = projectNameEnv + '9';
        } else if (projectName.includes('widget:') || projectName.includes('website:')) {
            projectNameNormalized = '1website';
        } else {
            projectNameNormalized = projectNameEnv;
        }

        console.log(projectNameNormalized);
        sortedProjectsArr.push({name: projectNameNormalized, val: project});
    });

    if (sortedProjectsArr.length > 0) {
        sortedProjectsArr.sort((a, b) => b.name.localeCompare(a.name));

        // Clear the table before appending sorted elements
        projectsTable.innerHTML = '';

        sortedProjectsArr.forEach(project => {
            projectsTable.appendChild(project.val);
        });

        console.log('-sort: done-');
    }
}

// Re-sort tests
function sortingTests() {
    if (window.location.pathname.indexOf('projects/website/suites/website-visual-regression-testing/runs') !== -1) {
        let testsBlockArr = document.querySelectorAll("tr.test");

        if (!testsBlockArr || testsBlockArr.length === 0) return;

        let parentTests = testsBlockArr[0].parentElement;

        if (!parentTests) return;

        let sortedTestsBlockArr = [];
        console.log('-sort: tests-');

        testsBlockArr.forEach(testsBlock => {
            if (!testsBlock) return;

            const test = testsBlock.querySelector("th a");

            if (!test) return;

            const testName = test.innerHTML.replace(/\s+/g, '-').toLowerCase();
            console.log(testName);
            sortedTestsBlockArr.push({name: testName, val: testsBlock});
        });

        if (sortedTestsBlockArr.length > 0) {
            sortedTestsBlockArr.sort((a, b) => a.name.localeCompare(b.name));

            // Clear parent before appending sorted elements
            parentTests.innerHTML = '';

            sortedTestsBlockArr.forEach(testsBlock => {
                parentTests.appendChild(testsBlock.val);
            });

            console.log('-sort: done-');
        }
    }
}

// Filter projects based on all criteria
function filterByAllCriteria(product, environment, category) {
    const projects = document.querySelectorAll('tr.project');

    projects.forEach(project => {
        const projectNameElement = project.querySelector('th');
        if (!projectNameElement) return;

        const projectName = projectNameElement.innerText.toLowerCase();
        let showProject = true;

        // Apply product filter
        if (product !== 'all' && !projectName.includes(product)) {
            showProject = false;
        }

        // Apply environment filter
        if (environment !== 'all environments' && !projectName.includes(environment.toLowerCase())) {
            showProject = false;
        }

        // Apply category filter
        if (category !== 'all') {
            // Handle special case for mobile kite
            if (category === 'mobile kite') {
                if (!projectName.includes('mobile') || !projectName.includes('kite')) {
                    showProject = false;
                }
            } else if (!projectName.includes(category)) {
                showProject = false;
            }
        }

        if (showProject) {
            project.classList.remove('hidden-project');
        } else {
            project.classList.add('hidden-project');
        }
    });
}

// Helper functions
function addElementButton(parentId, test) {
    if (!parentId || !test) return;

    let newButton = document.createElement("button");
    newButton.id = parentId;
    newButton.classList.add("label--warning", "label", "diffBtn");
    newButton.innerText = "Compare";
    insertAfter(newButton, test);
}

function addModalWindows(parentId, reference, current) {
    if (!parentId || !reference || !current) return;

    let modalWindow = document.createElement("div");
    modalWindow.id = parentId;
    modalWindow.classList.add("modal");
    modalWindow.innerHTML = '<div class="modal-content"><span class="close">&times;</span><div id="image-compare"><img src="' + reference + '" alt="reference" /><img src="' + current + '" alt="current" /></div></div>';
    document.body.appendChild(modalWindow);
}

function addElementPTime(parent, text) {
    if (!parent || !text) return;

    const time = text.split(' ');

    if (time.length < 4) return;

    const timeParts = time[3].split(':');

    if (timeParts.length < 2) return;

    const hours = timeParts[0];
    const minutes = timeParts[1];

    let newP = document.createElement("p");
    newP.classList.add("date-time");
    newP.innerHTML = '🕒 ' + text;
    insertAfter(newP, parent);
}

function setViewers() {
    const options = {
        showLabels: true,
        labelOptions: {
            before: 'Baseline',
            after: 'Comparison',
            onHover: false
        },
        smoothing: false,
        startingPoint: 50,
        fluidMode: true,
        controlColor: "#FF0000",
        controlShadow: false,
        addCircle: false,
        addCircleBlur: false,
    };
    const viewers = document.querySelectorAll("#image-compare");
    viewers.forEach((element) => new ImageCompare(element, options).mount());
}

function setListener() {
    const btn = document.querySelectorAll('button.diffBtn');
    btn.forEach((element) => {
        element.addEventListener('click', function(event){
            console.log('Button Clicked');
            let modal = document.querySelector(`div#${event.target.id}`);
            if (!modal) return;

            modal.style.display = "block";
            let close = modal.querySelector("span.close");
            if (close) {
                close.onclick = function() {
                    modal.style.display = "none";
                };
            }

            window.onclick = function(event) {
                if (event.target == modal) {
                    modal.style.display = "none";
                }
            };
        });
    });
}

function insertAfter(newNode, existingNode) {
    if (!newNode || !existingNode || !existingNode.parentNode) return;
    existingNode.parentNode.insertBefore(newNode, existingNode.nextSibling);
}

// DL IMAGES
function addDownloadBlock() {
    let downloadBlock = document.createElement("div");
    downloadBlock.id = "download-block";
    downloadBlock.classList.add("container");

    let downloadButton = document.createElement("button");
downloadButton.id = "download-images";
downloadButton.classList.add("download-button"); // заменили класс
downloadButton.innerHTML = 'Download images';
downloadBlock.appendChild(downloadButton);

    let progressBlock = document.createElement("div");
    progressBlock.id = "progress_bar";
    progressBlock.classList.add("progress", "hide");
    progressBlock.innerHTML = '<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;"></div>';
    downloadBlock.appendChild(progressBlock);

    let progressStatus = document.createElement("p");
    progressStatus.id = "result";
    progressStatus.classList.add("hide");
    progressStatus.innerHTML = '';
    downloadBlock.appendChild(progressStatus);

    let footer = document.createElement("p");
    footer.innerHTML = 'All images from the current run will be archived';
    downloadBlock.appendChild(footer);

    // Add version info
    let versionInfo = document.createElement("p");
    versionInfo.classList.add("version");
    versionInfo.innerHTML = 'Spectre 5000 v.16.901';
    downloadBlock.appendChild(versionInfo);

    document.body.appendChild(downloadBlock);
}

/**
 * Reset the message.
 */
function resetMessage() {
    $("#result")
        .removeClass()
        .text("");
}

/**
 * Show a successful message.
 * @param {String} text the text to show.
 */
function showMessage(text) {
    resetMessage();
    $("#result")
        .addClass("alert alert-success")
        .text(text);
}

/**
 * Show an error message.
 * @param {String} text the text to show.
 */
function showError(text) {
    resetMessage();
    $("#result")
        .addClass("alert alert-danger")
        .text(text);
}

/**
 * Update the progress bar.
 * @param {Integer} percent the current percent
 */
function updatePercent(percent) {
    $("#progress_bar").removeClass("hide")
        .find(".progress-bar")
        .attr("aria-valuenow", percent)
        .css({
            width : percent + "%"
        });
}

var Promise = window.Promise;
if (!Promise) {
    Promise = JSZip.external.Promise;
}

/**
 * Fetch the content and return the associated promise.
 * @param {String} url the url of the content to fetch.
 * @return {Promise} the promise containing the data.
 */
function urlToPromise(url) {
    return new Promise(function(resolve, reject) {
        // JSZipUtils.js
        JSZipUtils.getBinaryContent(url, function(err, data) {
            if (err) {
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
}

})();