Motherless.com - Advanced Gallery Sorter

Sorts videos on motherless.com with an advanced, panel-based UI. Supports Title, Views, Duration, Uploader, Random, and "Deep Sort" options (Upload Date, Favorites, Comments, Resolution, Engagement). Fetches all gallery pages for comprehensive sorting and provides pagination. Features a sleek dark mode UI.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

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

(I already have a user script manager, let me install it!)

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

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

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

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

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

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

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Motherless.com - Advanced Gallery Sorter
// @namespace    http://tampermonkey.net/
// @version      3.4
// @description  Sorts videos on motherless.com with an advanced, panel-based UI. Supports Title, Views, Duration, Uploader, Random, and "Deep Sort" options (Upload Date, Favorites, Comments, Resolution, Engagement). Fetches all gallery pages for comprehensive sorting and provides pagination. Features a sleek dark mode UI.
// @author       baratheonblight75
// @match        *://*.motherless.com/*
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js
// @grant        GM_xmlhttpRequest
// @license      MIT
// ==/UserScript==

(function($) {
    'use strict';

    const VIDEO_ITEM_SELECTOR = 'div.thumb-container.video';
    const VIDEOS_PER_PAGE_DISPLAY = 2500;
    const MAX_CONCURRENT_DEEP_FETCHES = 5;
    const DEFAULT_SORT_ID = 'views';
    const DEFAULT_SORT_DIR = 'desc';

    // --- SVG Icons ---
    const SVG_ICONS = {
        filter: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M14 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M4 6l8 0" /><path d="M16 6l4 0" /><path d="M8 12m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M4 12l2 0" /><path d="M10 12l10 0" /><path d="M17 18m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M4 18l11 0" /><path d="M19 18l1 0" /></svg>',
        close: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M18 6l-12 12" /><path d="M6 6l12 12" /></svg>',
        title: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M16 8h4l-4 8h4" /><path d="M4 16v-6a2 2 0 1 1 4 0v6" /><path d="M4 13h4" /><path d="M11 12h2" /></svg>',
        views: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /><path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" /></svg>',
        duration: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M12 12l3 2" /><path d="M12 7v5" /></svg>',
        uploader: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" /><path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" /></svg>',
        uploadDate: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M11.795 21h-6.795a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v4" /><path d="M18 18m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" /><path d="M15 3v4" /><path d="M7 3v4" /><path d="M3 11h16" /><path d="M18 16.496v1.504l1 1" /></svg>',
        favorites: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M19.5 12.572l-7.5 7.428l-7.5 -7.428a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572" /></svg>',
        comments: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 9h8" /><path d="M8 13h6" /><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12z" /></svg>',
        resolution: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 5m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M14 9v6h1a2 2 0 0 0 2 -2v-2a2 2 0 0 0 -2 -2h-1z" /><path d="M7 15v-6" /><path d="M10 15v-6" /><path d="M7 12h3" /></svg>',
        random: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M18 4l3 3l-3 3" /><path d="M18 20l3 -3l-3 -3" /><path d="M3 7h3a5 5 0 0 1 5 5a5 5 0 0 0 5 5h5" /><path d="M21 7h-5a4.978 4.978 0 0 0 -3 1m-4 8a4.984 4.984 0 0 1 -3 1h-3" /></svg>',
        arrowUp: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 5l0 14" /><path d="M18 11l-6 -6" /><path d="M6 11l6 -6" /></svg>',
        arrowDown: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 5l0 14" /><path d="M18 13l-6 6" /><path d="M6 13l6 6" /></svg>'
    };

    // --- Sort Definitions ---
    const SORT_DEFINITIONS = [
        { group: "Basic Sorts", id: 'title', label: 'Title', svgIcon: SVG_ICONS.title, defaultDir: 'asc', deepScan: false, keys: { asc: 'titleAZ', desc: 'titleZA' } },
        { group: "Basic Sorts", id: 'views', label: 'Views', svgIcon: SVG_ICONS.views, defaultDir: 'desc', deepScan: false, keys: { asc: 'viewsLeast', desc: 'viewsMost' } },
        { group: "Basic Sorts", id: 'duration', label: 'Duration', svgIcon: SVG_ICONS.duration, defaultDir: 'desc', deepScan: false, keys: { asc: 'durationShortest', desc: 'durationLongest' } },
        { group: "Basic Sorts", id: 'uploader', label: 'Uploader', svgIcon: SVG_ICONS.uploader, defaultDir: 'asc', deepScan: false, keys: { asc: 'uploaderAZ', desc: 'uploaderZA' } },
        { group: "Advanced Sorts (Deep Scan)", id: 'uploadDate', label: 'Upload Date', svgIcon: SVG_ICONS.uploadDate, defaultDir: 'desc', deepScan: true, keys: { asc: 'deep:uploadDateOldest', desc: 'deep:uploadDateNewest' } },
        { group: "Advanced Sorts (Deep Scan)", id: 'favorites', label: 'Favorites', svgIcon: SVG_ICONS.favorites, defaultDir: 'desc', deepScan: true, keys: { asc: 'deep:favoritesLeast', desc: 'deep:favoritesMost' } },
        { group: "Advanced Sorts (Deep Scan)", id: 'comments', label: 'Comments', svgIcon: SVG_ICONS.comments, defaultDir: 'desc', deepScan: true, keys: { asc: 'deep:commentsLeast', desc: 'deep:commentsMost' } },
        { group: "Advanced Sorts (Deep Scan)", id: 'resolution', label: 'Resolution', svgIcon: SVG_ICONS.resolution, defaultDir: 'desc', deepScan: true, keys: { asc: 'deep:resolutionLowest', desc: 'deep:resolutionHighest' } },
        {
            group: "Engagement Sorts (Deep Scan)", id: 'engagementComments', label: 'Comments/View',
            svgMain: SVG_ICONS.comments, svgBadge: SVG_ICONS.views,
            defaultDir: 'desc', deepScan: true,
            keys: { asc: 'deep:engagementCommentsLeast', desc: 'deep:engagementCommentsMost' }
        },
        {
            group: "Engagement Sorts (Deep Scan)", id: 'engagementFavorites', label: 'Favorites/View',
            svgMain: SVG_ICONS.favorites, svgBadge: SVG_ICONS.views,
            defaultDir: 'desc', deepScan: true,
            keys: { asc: 'deep:engagementFavoritesLeast', desc: 'deep:engagementFavoritesMost' }
        },
        { group: "Other", id: 'random', label: 'Random', svgIcon: SVG_ICONS.random, noDirection: true, deepScan: false, key: 'random' }
    ];

    // --- Helper Functions ---
    function parseViews(text) {
        if (typeof text !== 'string') return 0;
        text = text.trim().toLowerCase();
        let multiplier = 1;
        if (text.includes('k')) multiplier = 1000;
        if (text.includes('m')) multiplier = 1000000;
        let number = parseFloat(text.replace(/[^0-9.]/g, ''));
        return isNaN(number) ? 0 : Math.round(number * multiplier);
    }

    function parseDurationToSeconds(durationStr) {
        if (!durationStr || typeof durationStr !== 'string') return 0;
        const parts = durationStr.trim().split(':').map(part => parseInt(part, 10));
        let seconds = 0;
        const validParts = parts.filter(p => !isNaN(p));
        if (validParts.length === 2) seconds = validParts[0] * 60 + validParts[1];
        else if (validParts.length === 3) seconds = validParts[0] * 3600 + validParts[1] * 60 + validParts[2];
        else if (validParts.length === 1) seconds = validParts[0];
        return isNaN(seconds) ? 0 : seconds;
    }

    function shuffleArray(array) {
        for (let i = array.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            [array[i], array[j]] = [array[j], array[i]];
        }
    }

    // --- Deep Data Parsing Functions ---
    function parseUploadDate(dateStr) {
        if (!dateStr || typeof dateStr !== 'string') return null;
        try { const date = new Date(dateStr.trim()); return isNaN(date.getTime()) ? null : date; }
        catch (e) { console.warn("Could not parse date:", dateStr, e); return null; }
    }
    function parseFavorites(favStr) {
        if (!favStr || typeof favStr !== 'string') return null;
        const num = parseInt(favStr.replace(/[^0-9]/g, ''), 10); return isNaN(num) ? null : num;
    }
    function parseComments(commentStr) {
        if (!commentStr || typeof commentStr !== 'string') return null;
        const num = parseInt(commentStr.replace(/[^0-9]/g, ''), 10); return isNaN(num) ? null : num;
    }
    function parseResolution(resStr) {
        if (!resStr || typeof resStr !== 'string') return null;
        const num = parseInt(resStr.replace(/[^0-9]/g, ''), 10); return isNaN(num) ? null : num;
    }

    // --- UI Styling ---
    const newStyles = `
    :root {
        --mvs-bg-deep: #1A1A1A;
        --mvs-bg-panel: #282828;
        --mvs-bg-header-footer: #333333;
        --mvs-bg-element: #3C3C3C;
        --mvs-bg-hover: #454545;
        --mvs-bg-active: #505050;
        --mvs-border-strong: #555555;
        --mvs-border-medium: #4A4A4A;
        --mvs-border-light: #404040;
        --mvs-text-primary: #E0E0E0;
        --mvs-text-secondary: #AAAAAA;
        --mvs-text-disabled: #777777;
        --mvs-accent-primary: #606060;
        --mvs-accent-hover: #707070;
    }

    #mvs-sort-toggle-btn {
        position: fixed;
        bottom: 20px;
        right: 20px;
        z-index: 100000;
        background-color: var(--mvs-accent-primary);
        color: var(--mvs-text-primary);
        border: none;
        padding: 0;
        width: 52px;
        height: 52px;
        border-radius: 50%;
        cursor: pointer;
        box-shadow: 0 3px 10px rgba(0,0,0,0.5);
        display: flex;
        align-items: center;
        justify-content: center;
        transition: background-color 0.2s, transform 0.2s, box-shadow 0.2s;
    }
    #mvs-sort-toggle-btn:hover {
        background-color: var(--mvs-accent-hover);
        transform: translateY(-2px);
        box-shadow: 0 5px 12px rgba(0,0,0,0.6);
    }
    #mvs-sort-toggle-btn svg {
        width: 28px;
        height: 28px;
        stroke: currentColor;
    }

    #mvs-sort-panel-container {
        position: fixed;
        bottom: 85px;
        right: 20px;
        width: 280px; /* Further reduced width for "squished" look */
        max-height: auto; /* Height will be determined by content */
        background-color: var(--mvs-bg-panel);
        color: var(--mvs-text-primary);
        border: 1px solid var(--mvs-border-medium);
        border-radius: 8px; /* Slightly less rounded for compact look */
        box-shadow: 0 4px 15px rgba(0,0,0,0.35);
        z-index: 99999;
        font-family: 'Segoe UI', Arial, sans-serif;
        display: none;
        flex-direction: column;
        overflow: hidden;
    }
    .mvs-panel-header {
        padding: 10px 15px; /* Reduced padding */
        background-color: var(--mvs-bg-header-footer);
        /* No border-bottom needed if panel body is gone and footer is next */
        display: flex;
        flex-direction: column;
        gap: 8px; /* Reduced gap */
        align-items: flex-start;
    }
    .mvs-panel-header-top {
        display: flex;
        justify-content: space-between;
        align-items: center;
        width: 100%;
    }
    .mvs-panel-header h3 {
        margin: 0;
        font-size: 15px;
        font-weight: 600;
        color: var(--mvs-text-primary);
    }
    .mvs-panel-close-btn {
        background: none;
        border: none;
        color: var(--mvs-text-secondary);
        cursor: pointer;
        padding: 5px;
        line-height: 1;
        border-radius: 50%;
        transition: background-color 0.15s, color 0.15s;
    }
    .mvs-panel-close-btn:hover {
        color: var(--mvs-text-primary);
        background-color: var(--mvs-bg-hover);
    }
    .mvs-panel-close-btn svg {
        width: 18px; /* Smaller close icon */
        height: 18px;
        stroke: currentColor;
    }
    #mvs-primary-sort-select {
        width: 100%;
        padding: 8px 10px; /* Reduced padding */
        background-color: var(--mvs-bg-element);
        color: var(--mvs-text-primary);
        border: 1px solid var(--mvs-border-medium);
        border-radius: 5px; /* Reduced radius */
        font-size: 13px; /* Reduced font size */
        -webkit-appearance: none;
        -moz-appearance: none;
        appearance: none;
        background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23E0E0E0%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.4-5.4-13z%22%2F%3E%3C%2Fsvg%3E');
        background-repeat: no-repeat;
        background-position: right 8px center;
        background-size: 10px 10px;
        padding-right: 30px;
        transition: border-color 0.15s, background-color 0.15s;
    }
    #mvs-primary-sort-select:hover {
        border-color: var(--mvs-border-strong);
    }
    #mvs-primary-sort-select option {
        background-color: var(--mvs-bg-element);
        color: var(--mvs-text-primary);
        font-size: 13px; /* Ensure option font size matches select */
    }
    #mvs-primary-sort-select optgroup {
        background-color: var(--mvs-bg-header-footer);
        color: var(--mvs-text-secondary);
        font-style: italic;
        font-weight: normal;
    }
    #mvs-primary-sort-select optgroup option {
        background-color: var(--mvs-bg-element);
        color: var(--mvs-text-primary);
        font-style: normal;
    }

    .mvs-panel-body {
        display: none; /* Hide panel body as it's no longer used */
        padding: 0;
    }

    .mvs-sort-direction-controls { /* This is inside .mvs-panel-header in HTML */
        display: flex;
        align-items: center;
        margin-left: 0;
        margin-top: 4px; /* Reduced top margin */
        background-color: var(--mvs-bg-element);
        border-radius: 5px; /* Reduced radius */
        padding: 3px; /* Reduced padding */
        border: 1px solid var(--mvs-border-medium);
    }
    .mvs-dir-btn {
        flex-grow: 1;
        background: transparent;
        border: none;
        color: var(--mvs-text-secondary);
        padding: 5px; /* Reduced padding */
        margin: 0 1px; /* Reduced margin */
        border-radius: 3px; /* Reduced radius */
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        transition: background-color 0.15s, color 0.15s;
    }
    .mvs-dir-btn:hover {
        color: var(--mvs-text-primary);
        background-color: var(--mvs-bg-hover);
    }
    .mvs-dir-btn.mvs-active-dir {
        background-color: var(--mvs-accent-primary);
        color: var(--mvs-text-primary);
    }
    .mvs-dir-btn svg {
        width: 16px; /* Reduced icon size */
        height: 16px;
        stroke: currentColor;
    }
    .mvs-panel-footer {
        padding: 10px 15px; /* Reduced padding */
        background-color: var(--mvs-bg-header-footer);
        border-top: 1px solid var(--mvs-border-medium); /* Keep top border for separation */
        display: flex;
        justify-content: space-between; /* Space out buttons for compact footer */
        gap: 8px;
    }
    .mvs-panel-action-btn {
        padding: 8px 12px; /* Reduced padding */
        flex-grow: 1; /* Make buttons take equal space in footer */
        text-align: center; /* Center text in button */
        border-radius: 5px; /* Reduced radius */
        font-size: 12px; /* Reduced font size */
        font-weight: 600;
        cursor: pointer;
        border: none;
        color: var(--mvs-text-primary);
        transition: background-color 0.2s, transform 0.1s;
    }
    .mvs-panel-action-btn:active {
        transform: scale(0.98);
    }
    .mvs-panel-action-btn.mvs-apply-btn {
        background-color: var(--mvs-accent-primary);
    }
    .mvs-panel-action-btn.mvs-apply-btn:hover {
        background-color: var(--mvs-accent-hover);
    }
    .mvs-panel-action-btn.mvs-clear-btn {
        background-color: var(--mvs-bg-element);
    }
    .mvs-panel-action-btn.mvs-clear-btn:hover {
        background-color: var(--mvs-bg-hover);
    }

    #loading-indicator {
        background-color: rgba(30, 30, 30, 0.9) !important;
        color: var(--mvs-text-primary) !important;
        padding: 25px 30px !important;
        border-radius: 8px !important;
        border: 1px solid var(--mvs-border-medium) !important;
        box-shadow: 0 4px 15px rgba(0,0,0,0.5) !important;
        font-family: 'Segoe UI', Arial, sans-serif !important;
        font-size: 18px !important;
        font-weight: 500 !important;
    }
    #pagination-controls {
        background-color: var(--mvs-bg-header-footer) !important;
        color: var(--mvs-text-primary) !important;
        padding: 10px 15px !important;
        border: 1px solid var(--mvs-border-medium) !important;
        border-radius: 6px !important;
        box-shadow: 0 -2px 8px rgba(0,0,0,0.3) !important;
        font-family: 'Segoe UI', Arial, sans-serif !important;
        font-size: 14px !important;
        bottom: 20px !important;
    }
    #pagination-controls button {
        background-color: var(--mvs-bg-element);
        color: var(--mvs-text-primary);
        border: 1px solid var(--mvs-border-medium);
        padding: 7px 14px;
        margin: 0 5px;
        cursor: pointer;
        border-radius: 5px;
        font-family: 'Segoe UI', Arial, sans-serif;
        font-size: 13px;
        font-weight: 500;
        transition: background-color 0.15s, border-color 0.15s;
    }
    #pagination-controls button:hover:not(:disabled) {
        background-color: var(--mvs-bg-hover);
        border-color: var(--mvs-border-strong);
    }
    #pagination-controls button:disabled {
        background-color: var(--mvs-bg-header-footer);
        color: var(--mvs-text-disabled);
        cursor: default;
        border-color: var(--mvs-border-light);
    }
    #pagination-controls span {
        color: var(--mvs-text-primary);
        margin: 0 10px;
        font-family: 'Segoe UI', Arial, sans-serif;
        font-size: 14px;
    }
    `;

    function addGlobalStyle(css) {
        var head, style;
        head = document.getElementsByTagName('head')[0];
        if (!head) { return; }
        style = document.createElement('style');
        style.type = 'text/css';
        style.innerHTML = css;
        head.appendChild(style);
    }

    // --- Sorting Logic (largely unchanged) ---
    function nullSafeSort(valA, valB, ascending = true, aIsBetter = false) {
        const factor = ascending ? 1 : -1;
        const aIsNull = valA === null || valA === undefined;
        const bIsNull = valB === null || valB === undefined;
        if (aIsNull && bIsNull) return 0;
        if (aIsNull) return aIsBetter ? -1 * factor : 1 * factor;
        if (bIsNull) return aIsBetter ? 1 * factor : -1 * factor;
        if (valA < valB) return -1 * factor;
        if (valA > valB) return 1 * factor;
        return 0;
    }

    const Sorters = {
        'titleAZ': (a, b) => (a.title || '').localeCompare(b.title || ''),
        'titleZA': (a, b) => (b.title || '').localeCompare(a.title || ''),
        'viewsMost': (a, b) => nullSafeSort(a.views, b.views, false),
        'viewsLeast': (a, b) => nullSafeSort(a.views, b.views, true),
        'durationLongest': (a, b) => nullSafeSort(a.duration, b.duration, false),
        'durationShortest': (a, b) => nullSafeSort(a.duration, b.duration, true),
        'uploaderAZ': (a, b) => (a.uploader || '').localeCompare(b.uploader || ''),
        'uploaderZA': (a, b) => (b.uploader || '').localeCompare(a.uploader || ''),
        'deep:uploadDateNewest': (a, b) => nullSafeSort(a.uploadDate ? a.uploadDate.getTime() : null, b.uploadDate ? b.uploadDate.getTime() : null, false),
        'deep:uploadDateOldest': (a, b) => nullSafeSort(a.uploadDate ? a.uploadDate.getTime() : null, b.uploadDate ? b.uploadDate.getTime() : null, true),
        'deep:favoritesMost': (a, b) => nullSafeSort(a.favorites, b.favorites, false),
        'deep:favoritesLeast': (a, b) => nullSafeSort(a.favorites, b.favorites, true),
        'deep:commentsMost': (a, b) => nullSafeSort(a.commentsCount, b.commentsCount, false),
        'deep:commentsLeast': (a, b) => nullSafeSort(a.commentsCount, b.commentsCount, true),
        'deep:resolutionHighest': (a, b) => nullSafeSort(a.resolution, b.resolution, false),
        'deep:resolutionLowest': (a, b) => nullSafeSort(a.resolution, b.resolution, true),
        'deep:engagementCommentsMost': (a,b) => {
            const valA = (a.views > 0 && a.commentsCount !== null) ? a.commentsCount / a.views : null;
            const valB = (b.views > 0 && b.commentsCount !== null) ? b.commentsCount / b.views : null;
            return nullSafeSort(valA, valB, false);
        },
        'deep:engagementCommentsLeast': (a,b) => {
            const valA = (a.views > 0 && a.commentsCount !== null) ? a.commentsCount / a.views : null;
            const valB = (b.views > 0 && b.commentsCount !== null) ? b.commentsCount / b.views : null;
            return nullSafeSort(valA, valB, true);
        },
        'deep:engagementFavoritesMost': (a,b) => {
            const valA = (a.views > 0 && a.favorites !== null) ? a.favorites / a.views : null;
            const valB = (b.views > 0 && b.favorites !== null) ? b.favorites / b.views : null;
            return nullSafeSort(valA, valB, false);
        },
        'deep:engagementFavoritesLeast': (a,b) => {
            const valA = (a.views > 0 && a.favorites !== null) ? a.favorites / a.views : null;
            const valB = (b.views > 0 && b.favorites !== null) ? b.favorites / b.views : null;
            return nullSafeSort(valA, valB, true);
        },
    };

    function applySorting(dataArray, sortKey) {
        if (sortKey === 'random') {
            shuffleArray(dataArray);
        } else if (Sorters[sortKey]) {
            dataArray.sort(Sorters[sortKey]);
        } else if (sortKey === 'NO_SORT') {
            // Do nothing, keep original order (assuming dataArray is already in original order)
        } else {
            console.warn(`Unknown sort key: ${sortKey}. Defaulting to viewsMost.`);
            dataArray.sort(Sorters['viewsMost']);
        }
    }

    // --- UI & Pagination (largely unchanged logic, but invocation changes) ---
    function displayPage($container, videoData, page, videosPerPage) {
        $container.empty();
        const start = (page - 1) * videosPerPage;
        const end = Math.min(start + videosPerPage, videoData.length);
        for (let i = start; i < end; i++) {
            if (videoData[i] && videoData[i].element) {
                $container.append(videoData[i].element);
            }
        }
        updatePaginationControls($container, videoData, page, videosPerPage);
    }

    function updatePaginationControls($container, videoData, currentPage, videosPerPage) {
        const totalVideos = videoData.length;
        const totalPages = Math.ceil(totalVideos / videosPerPage);
        let $pagination = $('#pagination-controls');
        if ($pagination.length) $pagination.remove();
        $pagination = $('<div id="pagination-controls"></div>').css({
            position: 'fixed', left: '50%', transform: 'translateX(-50%)',
            zIndex: 9999, textAlign: 'center'
        });
        if (totalPages <= 1) {
            if (totalVideos > 0) $pagination.text(`Total videos: ${totalVideos}`);
            else $pagination.text('');
            $('body').append($pagination); return;
        }
        const $prev = $('<button>Previous</button>');
        const $next = $('<button>Next</button>');
        const $pageInfo = $('<span></span>').text(` Page ${currentPage} of ${totalPages} (${totalVideos} videos) `);
        if (currentPage === 1) $prev.prop('disabled', true);
        if (currentPage === totalPages) $next.prop('disabled', true);
        $prev.click(() => displayPage($container, videoData, currentPage - 1, videosPerPage));
        $next.click(() => displayPage($container, videoData, currentPage + 1, videosPerPage));
        $pagination.append($prev, $pageInfo, $next);
        $('body').append($pagination);
    }

    // --- Data Extraction (unchanged) ---
    function extractVideoData($videoElement) {
        const title = ($videoElement.find('a.caption.title').text() || '').trim();
        const uploader = ($videoElement.find('a.uploader').text() || '').trim();
        const viewsText = ($videoElement.find('span.hits span.value').text() || '0').trim();
        const durationText = ($videoElement.find('a.img-container span.size').text() || '00:00').trim();
        const pageUrlAnchor = $videoElement.find('a.img-container[href], a.caption.title[href]').first();
        let pageUrl = pageUrlAnchor.attr('href');
        if (pageUrl && !pageUrl.startsWith('http')) {
            try { pageUrl = new URL(pageUrl, window.location.origin).href; }
            catch (e) { console.error("Error constructing absolute URL:", pageUrl, e); pageUrl = null; }
        }
        return {
            element: $videoElement.clone(), title: title, uploader: uploader,
            views: parseViews(viewsText), duration: parseDurationToSeconds(durationText),
            pageUrl: pageUrl, uploadDate: null, favorites: null, commentsCount: null,
            resolution: null, deepDataFetched: false, deepDataError: false, originalIndex: -1 // For potential revert
        };
    }
    
    let initialVideoDataCache = []; // Cache for "Clear Sort" to revert to original page 1

    // Promisified GM_xmlhttpRequest (unchanged)
    function GM_fetch(details) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                ...details,
                onload: function(response) {
                    if (response.status >= 200 && response.status < 300) resolve(response);
                    else reject(new Error(`Request failed: ${response.status} ${response.statusText} for ${details.url}`));
                },
                onerror: function(response) { reject(new Error(`Request error: ${response.statusText} for ${details.url}`)); },
                ontimeout: function() { reject(new Error(`Request timed out for ${details.url}`)); }
            });
        });
    }

    // fetchDeepVideoDetails and related orchestration (unchanged logic)
    async function fetchDeepVideoDetails(videoObject) {
        if (!videoObject.pageUrl) {
            videoObject.deepDataError = true;
            return Promise.reject("No pageUrl");
        }
        try {
            const response = await GM_fetch({ method: "GET", url: videoObject.pageUrl, timeout: 15000 });
            const parser = new DOMParser();
            const doc = parser.parseFromString(response.responseText, "text/html");
            const dateStr = $(doc).find('#media-info .media-meta-stats span.count:nth-of-type(3)').text();
            const favStr = $(doc).find('#media-info .media-meta-stats span.count:nth-of-type(2)').text();
            const commentStr = $(doc).find('#media-comments .comments-count span.count').text();
            const videoElem = $(doc).find('video[id^="ml-video-"]').first();
            const qualityStr = videoElem.length ? videoElem.attr('data-quality') : null;
            videoObject.uploadDate = parseUploadDate(dateStr);
            videoObject.favorites = parseFavorites(favStr);
            videoObject.commentsCount = parseComments(commentStr);
            videoObject.resolution = parseResolution(qualityStr);
            videoObject.deepDataFetched = true;
            videoObject.deepDataError = false;
        } catch (error) {
            console.error(`Failed to fetch or parse deep data for ${videoObject.pageUrl}:`, error);
            videoObject.deepDataError = true;
            throw error;
        }
    }

    let activeDeepFetches = 0;
    let deepFetchQueue = [];
    let allVideoDataForSort = []; // Renamed for clarity, holds all videos after gallery fetch
    let currentSortKeyBeingApplied = '';
    let currentContainerForDisplay = null;
    let currentLoadingIndicator = null;

    function updateDeepFetchProgress(processedCount, totalToFetch) {
        if (currentLoadingIndicator && currentLoadingIndicator.length) {
            currentLoadingIndicator.text(`Fetching video details: ${processedCount}/${totalToFetch}...`);
        }
    }

    function processNextInDeepFetchQueue() {
        if (deepFetchQueue.length === 0 && activeDeepFetches === 0) {
            if (currentLoadingIndicator) currentLoadingIndicator.text('Sorting...');
            applySorting(allVideoDataForSort, currentSortKeyBeingApplied);
            if (currentLoadingIndicator) currentLoadingIndicator.remove(); currentLoadingIndicator = null;
            if (currentContainerForDisplay) {
                displayPage(currentContainerForDisplay, allVideoDataForSort, 1, VIDEOS_PER_PAGE_DISPLAY);
            }
            return;
        }
        while (activeDeepFetches < MAX_CONCURRENT_DEEP_FETCHES && deepFetchQueue.length > 0) {
            activeDeepFetches++;
            const { video, totalToFetch } = deepFetchQueue.shift();
            fetchDeepVideoDetails(video)
                .catch(err => { /* Error handled */ })
                .finally(() => {
                    activeDeepFetches--;
                    const processedCount = totalToFetch - deepFetchQueue.length - activeDeepFetches;
                    updateDeepFetchProgress(processedCount, totalToFetch);
                    processNextInDeepFetchQueue();
                });
        }
    }

    function initiateDeepSortProcess(sortKeyToApply) {
        const itemsToFetch = allVideoDataForSort.filter(v => !v.deepDataFetched && !v.deepDataError && v.pageUrl);
        if (itemsToFetch.length === 0) {
            if (currentLoadingIndicator) currentLoadingIndicator.text('Sorting...');
            applySorting(allVideoDataForSort, sortKeyToApply);
            if (currentLoadingIndicator) currentLoadingIndicator.remove(); currentLoadingIndicator = null;
            displayPage(currentContainerForDisplay, allVideoDataForSort, 1, VIDEOS_PER_PAGE_DISPLAY);
            return;
        }
        deepFetchQueue = itemsToFetch.map(video => ({ video: video, totalToFetch: itemsToFetch.length }));
        updateDeepFetchProgress(0, itemsToFetch.length);
        processNextInDeepFetchQueue();
    }

    // --- Main Sorting and Pagination Process ---
    function prepareAndSortGallery($container, $initialVideosOnPage, sortOptionKey) {
        currentSortKeyBeingApplied = sortOptionKey;
        currentContainerForDisplay = $container;

        if (currentLoadingIndicator && currentLoadingIndicator.length) currentLoadingIndicator.remove();
        currentLoadingIndicator = $('<div id="loading-indicator">Loading page 1...</div>').css({
            position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', zIndex: 10000
        });
        $('body').append(currentLoadingIndicator);

        if (allVideoDataForSort.length > 0 && !sortOptionKey.startsWith('deep:')) {
            console.log('Using cached gallery data. Total videos:', allVideoDataForSort.length);
            currentLoadingIndicator.text('Sorting...');
            applySorting(allVideoDataForSort, currentSortKeyBeingApplied);
            currentLoadingIndicator.remove(); currentLoadingIndicator = null;
            displayPage(currentContainerForDisplay, allVideoDataForSort, 1, VIDEOS_PER_PAGE_DISPLAY);
            return;
        }
         if (allVideoDataForSort.length > 0 && sortOptionKey.startsWith('deep:')) {
            console.log('Using cached gallery data, initiating deep sort. Total videos:', allVideoDataForSort.length);
            initiateDeepSortProcess(currentSortKeyBeingApplied);
            return;
        }

        allVideoDataForSort = [];
        $initialVideosOnPage.each(function(index) {
            const videoEntry = extractVideoData($(this));
            videoEntry.originalIndex = index;
            allVideoDataForSort.push(videoEntry);
        });
        initialVideoDataCache = [...allVideoDataForSort];
        console.log(`Initial videos processed: ${allVideoDataForSort.length}`);

        let $pageLinks = $('a[href*="page="], .page, .page-item').filter(function() {
            return $(this).text().match(/^\d+$/) || $(this).attr('href')?.match(/page=(\d+)/);
        });
        let totalSitePages = 1;
        if ($pageLinks.length) {
            let maxPage = 0;
            $pageLinks.each(function() {
                let pageNumText = $(this).text().trim();
                let pageNumHref = $(this).attr('href');
                let pageNum = parseInt(pageNumText, 10);
                if (isNaN(pageNum) && pageNumHref) {
                    const match = pageNumHref.match(/page=(\d+)/);
                    if (match && match[1]) pageNum = parseInt(match[1], 10);
                }
                if (!isNaN(pageNum) && pageNum > maxPage) maxPage = pageNum;
            });
            totalSitePages = Math.max(1, maxPage);
        }
        console.log('Total site pages detected:', totalSitePages);

        function completeGalleryFetchAndProceed() {
            console.log('All gallery pages fetched. Total videos:', allVideoDataForSort.length);
            if (currentSortKeyBeingApplied.startsWith('deep:')) {
                initiateDeepSortProcess(currentSortKeyBeingApplied);
            } else {
                currentLoadingIndicator.text('Sorting...');
                applySorting(allVideoDataForSort, currentSortKeyBeingApplied);
                currentLoadingIndicator.remove(); currentLoadingIndicator = null;
                displayPage(currentContainerForDisplay, allVideoDataForSort, 1, VIDEOS_PER_PAGE_DISPLAY);
            }
        }

        if (totalSitePages <= 1) {
            completeGalleryFetchAndProceed();
            return;
        }

        let baseUrl = window.location.href.split('?')[0].split('#')[0];
        let pagesFetchedSuccessfully = 1;

        function fetchPage(page) {
            if (page > totalSitePages) {
                completeGalleryFetchAndProceed();
                return;
            }
            currentLoadingIndicator.text(`Loading gallery page ${page} of ${totalSitePages}...`);
            $.get(`${baseUrl}?page=${page}`, function(data) {
                let $pageContent = $(data);
                let $videosOnFetchedPage = $pageContent.find(VIDEO_ITEM_SELECTOR);
                $videosOnFetchedPage.each(function() {
                     const videoEntry = extractVideoData($(this));
                     allVideoDataForSort.push(videoEntry);
                });
                pagesFetchedSuccessfully++;
                fetchPage(page + 1);
            }).fail(function() {
                console.log('Failed to fetch gallery page:', page, ". Skipping.");
                fetchPage(page + 1);
            });
        }
        fetchPage(2);
    }


    function waitForVideos(callback) {
        let attempts = 0;
        const maxAttempts = 20;
        const interval = setInterval(function() {
            let $videos = $(VIDEO_ITEM_SELECTOR);
            if ($videos.length > 0) {
                clearInterval(interval);
                callback($videos);
            } else {
                attempts++;
                if (attempts >= maxAttempts) {
                    clearInterval(interval);
                    console.log('Max attempts reached. No videos found.');
                    callback($([]));
                }
            }
        }, 500);
    }

    // --- New UI Panel Logic ---
    let panelSelectedSortId = DEFAULT_SORT_ID;
    let panelSelectedSortDirection = DEFAULT_SORT_DIR; // 'asc', 'desc', or null for 'random'

    function updateSortPanelUI() {
        // Update dropdown selection
        $('#mvs-primary-sort-select').val(panelSelectedSortId);

        // Update direction buttons
        const $dirControls = $('#mvs-sort-direction-controls-global');
        $dirControls.find('.mvs-dir-btn').removeClass('mvs-active-dir');

        const selectedSortDef = SORT_DEFINITIONS.find(s => s.id === panelSelectedSortId);
        if (selectedSortDef && !selectedSortDef.noDirection) {
            $dirControls.show();
            if (panelSelectedSortDirection === 'asc') {
                $dirControls.find('.mvs-dir-asc').addClass('mvs-active-dir');
            } else if (panelSelectedSortDirection === 'desc') {
                $dirControls.find('.mvs-dir-desc').addClass('mvs-active-dir');
            }
        } else {
            $dirControls.hide(); // Hide direction buttons for random sort
        }

        // Update active state of sort items (if they were still individual items)
        // In this new design, individual sort items are not clickable for selection,
        // so this part is no longer directly relevant for selection highlighting.
        // However, if we want to visually indicate the selected item in the list,
        // we would need to iterate through them and apply a class.
        // For now, the dropdown is the primary selection indicator.
    }


    $(document).ready(function() {
        addGlobalStyle(newStyles);

        waitForVideos(function($initialVideos) {
            let $videoGalleryContainer = $initialVideos.first().parent();
             if ($initialVideos.parent('.thumbs, .media-list, .video-list, #discover-grid-wrapper div.row, .thumbs.uploads').length > 0) {
                 $videoGalleryContainer = $initialVideos.parent('.thumbs, .media-list, .video-list, #discover-grid-wrapper div.row, .thumbs.uploads').first();
            } else if ($initialVideos.closest('.thumbs, .media-list, .video-list, #discover-grid-wrapper div.row, .thumbs.uploads').length > 0) {
                 $videoGalleryContainer = $initialVideos.closest('.thumbs, .media-list, .video-list, #discover-grid-wrapper div.row, .thumbs.uploads').first();
            }

            if ($initialVideos.length === 0 || $videoGalleryContainer.length === 0) {
                console.log('No video gallery detected. Sorter UI not added.');
                return;
            }

            // --- Create Main Toggle Button ---
            const $sortToggleButton = $(`
                <button id="mvs-sort-toggle-btn" title="Sort Options">
                    ${SVG_ICONS.filter}
                </button>
            `);
            $('body').append($sortToggleButton);

            // --- Create Sort Panel (initially hidden) ---
            const $sortPanelContainer = $(`
                <div id="mvs-sort-panel-container">
                    <div class="mvs-panel-header">
                        <div class="mvs-panel-header-top">
                            <h3>Sort Videos</h3>
                            <button class="mvs-panel-close-btn" title="Close">${SVG_ICONS.close}</button>
                        </div>
                        <select id="mvs-primary-sort-select"></select>
                        <div id="mvs-sort-direction-controls-global" class="mvs-sort-direction-controls">
                            <button class="mvs-dir-btn mvs-dir-asc" title="Sort Ascending" data-dir="asc">${SVG_ICONS.arrowUp}</button>
                            <button class="mvs-dir-btn mvs-dir-desc" title="Sort Descending" data-dir="desc">${SVG_ICONS.arrowDown}</button>
                        </div>
                    </div>
                    <div class="mvs-panel-body"></div>
                    <div class="mvs-panel-footer">
                        <button class="mvs-panel-action-btn mvs-clear-btn">Clear Sort</button>
                        <button class="mvs-panel-action-btn mvs-apply-btn">Apply Sort</button>
                    </div>
                </div>
            `);
            const $panelBody = $sortPanelContainer.find('.mvs-panel-body');
            const $primarySortSelect = $sortPanelContainer.find('#mvs-primary-sort-select');

            // Populate primary sort dropdown
            const iconMap = {
                title: '📝', views: '👁️', duration: '⏱️', uploader: '👤',
                uploadDate: '📅', favorites: '⭐', comments: '💬', resolution: '📺',
                engagementComments: '📈', engagementFavorites: '💖', random: '🎲'
            };

            const groupedSortsForDropdown = SORT_DEFINITIONS.reduce((acc, sortDef) => {
                if (!sortDef.noDirection) { // Only add sortable items to dropdown
                    acc[sortDef.group] = acc[sortDef.group] || [];
                    acc[sortDef.group].push(sortDef);
                } else if (sortDef.id === 'random') { // Add random as a special case
                    acc[sortDef.group] = acc[sortDef.group] || [];
                    acc[sortDef.group].push(sortDef);
                }
                return acc;
            }, {});

            for (const groupName in groupedSortsForDropdown) {
                const $optgroup = $(`<optgroup label="${groupName}"></optgroup>`);
                groupedSortsForDropdown[groupName].forEach(sortDef => {
                    const icon = iconMap[sortDef.id] || '▫️'; // Default icon if not found
                    $optgroup.append(`<option value="${sortDef.id}">${icon} ${sortDef.label}</option>`);
                });
                $primarySortSelect.append($optgroup);
            }
            
            // Set initial selection for dropdown
            $primarySortSelect.val(DEFAULT_SORT_ID);

            // The detailed list of sort types in the panel body is removed as per user feedback.
            // $panelBody will remain empty or can be hidden via CSS.

            $('body').append($sortPanelContainer);

            // --- Event Handlers ---
            $sortToggleButton.on('click', () => {
                $sortPanelContainer.fadeToggle(200);
                $sortToggleButton.toggle(); // Hide toggle button when panel opens
                updateSortPanelUI(); // Update UI when panel opens
            });
            
            const hidePanel = () => {
                $sortPanelContainer.fadeOut(200, () => {
                    $sortToggleButton.show(); // Show toggle button when panel closes
                });
            };

            $sortPanelContainer.find('.mvs-panel-close-btn').on('click', hidePanel);

            $primarySortSelect.on('change', function() {
                panelSelectedSortId = $(this).val();
                const selectedSortDef = SORT_DEFINITIONS.find(s => s.id === panelSelectedSortId);
                if (selectedSortDef && !selectedSortDef.noDirection) {
                    panelSelectedSortDirection = selectedSortDef.defaultDir;
                } else {
                    panelSelectedSortDirection = null; // For random or non-directional sorts
                }
                updateSortPanelUI();
            });

            $('#mvs-sort-direction-controls-global .mvs-dir-btn').on('click', function() {
                panelSelectedSortDirection = $(this).data('dir');
                updateSortPanelUI();
            });

            $sortPanelContainer.find('.mvs-apply-btn').on('click', function() {
                const activeSortDef = SORT_DEFINITIONS.find(s => s.id === panelSelectedSortId);
                if (!activeSortDef) return;

                let sortKeyToApply;
                if (activeSortDef.noDirection) {
                    sortKeyToApply = activeSortDef.key;
                } else {
                    sortKeyToApply = activeSortDef.keys[panelSelectedSortDirection];
                }

                if (sortKeyToApply) {
                    prepareAndSortGallery($videoGalleryContainer, $(VIDEO_ITEM_SELECTOR), sortKeyToApply);
                }
                hidePanel(); // Hide panel after applying sort
            });

            $sortPanelContainer.find('.mvs-clear-btn').on('click', function() {
                panelSelectedSortId = DEFAULT_SORT_ID; // Revert to default
                panelSelectedSortDirection = DEFAULT_SORT_DIR;
                updateSortPanelUI();
                // For now, just resets panel. User must click "Apply Sort".
            });

            // Initialize panel UI
            updateSortPanelUI();
            console.log("Advanced Sorter Panel UI added.");
        });
    });
})(jQuery);