Grok Auto-Retry + Prompt Snippets + History + Favorites (v20.5 - Wait for Error Clear)

Fixed moderation detection with more robust selectors and flexible text matching

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Grok Auto-Retry + Prompt Snippets + History + Favorites (v20.5 - Wait for Error Clear)
// @namespace    http://tampermonkey.net/
// @version      27
// @description  Fixed moderation detection with more robust selectors and flexible text matching
// @author       You
// @license      MIT
// @match        https://grok.com/*
// @match        https://*.grok.com/*
// @match        https://grok.x.ai/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';

    // --- CONFIGURATION ---
    const TARGET_TEXTAREA_SELECTOR = 'textarea[aria-label="Make a video"]';
    const IMAGE_EDITOR_SELECTOR = 'textarea[aria-label="Type to edit image..."]';
    const IMAGE_PROMPT_SELECTOR = 'textarea[aria-label="Image prompt"]';
    const IMAGE_IMAGINE_SELECTOR = 'p[data-placeholder="Type to imagine"]';
    const RETRY_BUTTON_SELECTOR = 'button[aria-label="Make video"]';
    const IMAGE_EDITOR_BUTTON_SELECTOR = 'button[aria-label="Generate"]';
    const IMAGE_SUBMIT_BUTTON_SELECTOR = 'button[aria-label="Submit"]';

    // Multiple possible moderation text patterns (case-insensitive)
    const MODERATION_PATTERNS = [
        "content moderated",
        "try a different idea",
        "moderated",
        "content policy",
        "cannot generate",
        "unable to generate"
    ];

    const RETRY_DELAY_MS = 1500;
    const COOLDOWN_MS = 2500;
    const OBSERVER_THROTTLE_MS = 300;
    const MAX_HISTORY_ITEMS = 100;
    const DEBUG_MODE = true; // Set to false to disable console logs

    // --- DEFAULT SNIPPETS ---
    const DEFAULT_SNIPPETS = [
        {
            id: 'b1', label: 'Anime Stickers (Provocative)',
            text: 'Surrounding the central image: thick decorative border made of overlapping colorful anime-style stickers featuring nude anime girls with exaggerated proportions in various provocative poses. Each sticker has a white outline and slight drop shadow. The stickers completely frame all four edges of the image with some overlap into the main content.'
        },
        {
            id: 'b2', label: 'Anime Stickers (SFW)',
            text: 'Surrounding the central image: thick decorative border made of overlapping colorful anime-style stickers featuring anime girls with exaggerated proportions in various poses. Each sticker has a white outline and slight drop shadow. The stickers completely frame all four edges of the image with some overlap into the main content.'
        },
        { id: '1', label: 'Motion: Slow Mo', text: 'slow motion, high frame rate, smooth movement' },
        { id: '2', label: 'Style: Photorealistic', text: 'photorealistic, 8k resolution, highly detailed, unreal engine 5 render' },
        { id: '3', label: 'Lighting: Golden Hour', text: 'golden hour lighting, warm sun rays, lens flare, soft shadows' },
    ];

    // --- LOAD SAVED SETTINGS ---
    let maxRetries = GM_getValue('maxRetries', 5);
    let uiToggleKey = GM_getValue('uiToggleKey', 'h');
    let autoClickEnabled = GM_getValue('autoClickEnabled', true);
    let isUiVisible = GM_getValue('isUiVisible', true);
    let savedSnippets = GM_getValue('savedSnippets', DEFAULT_SNIPPETS);
    let videoPromptHistory = GM_getValue('videoPromptHistory', []);
    let imagePromptHistory = GM_getValue('imagePromptHistory', []);
    let videoFavorites = GM_getValue('videoFavorites', []);
    let imageFavorites = GM_getValue('imageFavorites', []);
    let panelSize = GM_getValue('panelSize', { width: '300px', height: '400px' });

    let isRetryEnabled = true;
    let limitReached = false;
    let currentRetryCount = 0;
    let lastTypedPrompt = "";
    let lastRetryTimestamp = 0;
    let lastGenerationTimestamp = 0;
    const GENERATION_COOLDOWN_MS = 3000;

    let observerThrottle = false;
    let moderationDetected = false;
    let processingModeration = false;
    let currentHistoryTab = 'video';
    let currentFavoritesTab = 'video';
    let currentEditingFavId = null;
    let lastModerationCheck = 0;
    let errorWaitInterval = null; // Store reference to polling interval

    // --- DEBUG LOGGER ---
    function debugLog(...args) {
        if (DEBUG_MODE) {
            console.log('[Grok Auto-Retry]', ...args);
        }
    }

    // --- STYLES ---
    GM_addStyle(`
        #grok-control-panel {
            position: fixed; bottom: 20px; right: 20px;
            width: ${panelSize.width}; height: ${panelSize.height};
            min-width: 280px; min-height: 250px; max-width: 90vw; max-height: 90vh;
            background: linear-gradient(145deg, #0a0a0a 0%, #1a1a1a 100%);
            border: 1px solid #2a2a2a;
            border-radius: 16px;
            padding: 15px;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            color: #e0e0e0;
            z-index: 99998;
            box-shadow: 0 8px 32px rgba(0,0,0,0.9), 0 0 0 1px rgba(255,255,255,0.05);
            display: flex;
            flex-direction: column;
            gap: 10px;
        }
        #grok-control-panel.hidden { display: none; }
        #grok-resize-handle {
            position: absolute; top: 0; left: 0; width: 15px; height: 15px;
            cursor: nwse-resize; z-index: 99999;
        }
        #grok-resize-handle::after {
            content: ''; position: absolute; top: 2px; left: 2px;
            border-top: 6px solid #3b82f6; border-right: 6px solid transparent;
            width: 0; height: 0; opacity: 0.7;
        }
        #grok-resize-handle:hover::after { opacity: 1; border-top-color: #60a5fa; }
        .grok-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-shrink: 0;
            margin-left: 10px;
            padding-bottom: 8px;
            border-bottom: 1px solid #2a2a2a;
        }
        .grok-title {
            font-weight: bold;
            font-size: 14px;
            color: #f0f0f0;
            text-shadow: 0 2px 4px rgba(0,0,0,0.5);
        }
        .grok-toggle-btn {
            background: linear-gradient(135deg, #10b981 0%, #059669 100%);
            border: none;
            color: white;
            padding: 6px 14px;
            border-radius: 20px;
            font-size: 11px;
            font-weight: bold;
            cursor: pointer;
            box-shadow: 0 2px 8px rgba(16, 185, 129, 0.4);
            transition: all 0.2s ease;
        }
        .grok-toggle-btn:hover {
            background: linear-gradient(135deg, #059669 0%, #047857 100%);
            box-shadow: 0 4px 12px rgba(16, 185, 129, 0.6);
        }
        .grok-toggle-btn.off {
            background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
            box-shadow: 0 2px 8px rgba(239, 68, 68, 0.4);
        }
        .grok-toggle-btn.off:hover {
            background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
            box-shadow: 0 4px 12px rgba(239, 68, 68, 0.6);
        }
        .grok-controls {
            display: flex;
            align-items: center;
            justify-content: space-between;
            font-size: 12px;
            color: #9ca3af;
            flex-shrink: 0;
            padding: 8px 0;
        }
        .grok-checkbox {
            display: flex;
            align-items: center;
            cursor: pointer;
            color: #d1d5db;
        }
        .grok-checkbox input {
            margin-right: 6px;
            cursor: pointer;
            accent-color: #3b82f6;
        }
        .grok-num-input {
            width: 40px;
            background: #1f1f1f;
            border: 1px solid #2a2a2a;
            color: #e0e0e0;
            border-radius: 6px;
            padding: 4px 6px;
            text-align: center;
            transition: all 0.2s ease;
        }
        .grok-num-input:focus {
            outline: none;
            border-color: #3b82f6;
            box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
        }
        .grok-prompt-label {
            font-size: 11px;
            font-weight: bold;
            color: #9ca3af;
            margin-bottom: -5px;
            flex-shrink: 0;
        }
        #grok-panel-prompt {
            width: 100%;
            flex-grow: 1;
            background: #0f0f0f;
            border: 1px solid #2a2a2a;
            border-radius: 8px;
            color: #e0e0e0;
            padding: 10px;
            font-size: 12px;
            font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
            resize: none;
            box-sizing: border-box;
            transition: all 0.2s ease;
        }
        #grok-panel-prompt:focus {
            border-color: #3b82f6;
            outline: none;
            box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
        }
        #grok-panel-prompt::placeholder {
            color: #4b5563;
        }
        .grok-btn-row {
            display: flex;
            gap: 8px;
            flex-shrink: 0;
        }
        .grok-action-btn {
            flex: 1;
            padding: 10px;
            border-radius: 8px;
            border: none;
            cursor: pointer;
            font-weight: 600;
            font-size: 12px;
            transition: all 0.2s ease;
            position: relative;
            overflow: hidden;
        }
        .grok-action-btn::before {
            content: '';
            position: absolute;
            top: 0;
            left: -100%;
            width: 100%;
            height: 100%;
            background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
            transition: left 0.5s;
        }
        .grok-action-btn:hover::before {
            left: 100%;
        }
        #btn-open-library {
            background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
            color: white;
            box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
        }
        #btn-open-library:hover {
            background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
            box-shadow: 0 4px 12px rgba(59, 130, 246, 0.5);
            transform: translateY(-1px);
        }
        #btn-open-favorites {
            background: linear-gradient(135deg, #ec4899 0%, #db2777 100%);
            color: white;
            box-shadow: 0 2px 8px rgba(236, 72, 153, 0.3);
        }
        #btn-open-favorites:hover {
            background: linear-gradient(135deg, #db2777 0%, #be185d 100%);
            box-shadow: 0 4px 12px rgba(236, 72, 153, 0.5);
            transform: translateY(-1px);
        }
        #btn-open-history {
            background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
            color: white;
            box-shadow: 0 2px 8px rgba(139, 92, 246, 0.3);
        }
        #btn-open-history:hover {
            background: linear-gradient(135deg, #7c3aed 0%, #6d28d9 100%);
            box-shadow: 0 4px 12px rgba(139, 92, 246, 0.5);
            transform: translateY(-1px);
        }
        #btn-generate {
            background: linear-gradient(135deg, #1f1f1f 0%, #2a2a2a 100%);
            color: #e0e0e0;
            border: 1px solid #3a3a3a;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
        }
        #btn-generate:hover {
            background: linear-gradient(135deg, #2a2a2a 0%, #353535 100%);
            border-color: #4a4a4a;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
            transform: translateY(-1px);
        }
        #grok-status {
            text-align: center;
            font-size: 11px;
            color: #10b981;
            padding-top: 8px;
            border-top: 1px solid #2a2a2a;
            flex-shrink: 0;
            font-weight: 500;
        }
        .status-error {
            color: #ef4444 !important;
        }
        .status-warning {
            color: #f59e0b !important;
        }

        /* Modal Styles */
        .grok-modal {
            position: fixed;
            right: 20px;
            width: 350px;
            height: 400px;
            background: linear-gradient(145deg, #0a0a0a 0%, #1a1a1a 100%);
            border: 1px solid #2a2a2a;
            border-radius: 16px;
            display: none;
            flex-direction: column;
            z-index: 99999;
            box-shadow: 0 8px 32px rgba(0,0,0,0.9), 0 0 0 1px rgba(255,255,255,0.05);
            font-family: -apple-system, BlinkMacSystemFont, sans-serif;
        }
        .grok-modal.active { display: flex; }
        .gl-header {
            padding: 12px 15px;
            background: linear-gradient(135deg, #1a1a1a 0%, #0f0f0f 100%);
            display: flex;
            justify-content: space-between;
            align-items: center;
            font-weight: bold;
            font-size: 13px;
            color: #f0f0f0;
            border-bottom: 1px solid #2a2a2a;
            border-radius: 16px 16px 0 0;
        }
        .gl-close {
            cursor: pointer;
            font-size: 20px;
            line-height: 1;
            color: #6b7280;
            transition: all 0.2s ease;
            width: 24px;
            height: 24px;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 6px;
        }
        .gl-close:hover {
            color: #f0f0f0;
            background: #2a2a2a;
        }

        /* History Tab Styles */
        .history-tabs {
            display: flex;
            background: #0f0f0f;
            border-bottom: 1px solid #2a2a2a;
        }
        .history-tab {
            flex: 1;
            padding: 10px;
            text-align: center;
            cursor: pointer;
            font-size: 11px;
            font-weight: 600;
            color: #6b7280;
            transition: all 0.2s ease;
            border-bottom: 2px solid transparent;
        }
        .history-tab:hover {
            color: #9ca3af;
            background: #1a1a1a;
        }
        .history-tab.active {
            color: #8b5cf6;
            border-bottom-color: #8b5cf6;
            background: #1a1a1a;
        }

        .gl-view-list {
            display: flex;
            flex-direction: column;
            height: 100%;
            overflow: hidden;
        }
        .gl-list-content {
            overflow-y: auto;
            padding: 12px;
            flex: 1;
            display: flex;
            flex-direction: column;
            gap: 8px;
        }
        .gl-list-content::-webkit-scrollbar {
            width: 8px;
        }
        .gl-list-content::-webkit-scrollbar-track {
            background: #0f0f0f;
        }
        .gl-list-content::-webkit-scrollbar-thumb {
            background: #2a2a2a;
            border-radius: 4px;
        }
        .gl-list-content::-webkit-scrollbar-thumb:hover {
            background: #3a3a3a;
        }
        .gl-item {
            background: linear-gradient(135deg, #1a1a1a 0%, #151515 100%);
            border: 1px solid #2a2a2a;
            padding: 10px;
            border-radius: 8px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            font-size: 12px;
            color: #e0e0e0;
            transition: all 0.2s ease;
        }
        .gl-item:hover {
            border-color: #3b82f6;
            background: linear-gradient(135deg, #1f1f1f 0%, #1a1a1a 100%);
            box-shadow: 0 2px 8px rgba(59, 130, 246, 0.2);
            transform: translateY(-1px);
        }
        .gl-item-text {
            cursor: pointer;
            flex: 1;
            margin-right: 10px;
        }
        .gl-item-text b {
            display: block;
            margin-bottom: 4px;
            color: #f0f0f0;
        }
        .gl-item-text span {
            color: #9ca3af;
            font-size: 10px;
            display: -webkit-box;
            -webkit-line-clamp: 2;
            -webkit-box-orient: vertical;
            overflow: hidden;
            line-height: 1.4;
        }
        .gl-item-actions {
            display: flex;
            gap: 6px;
        }
        .gl-icon-btn {
            background: #1f1f1f;
            border: 1px solid #2a2a2a;
            cursor: pointer;
            font-size: 14px;
            color: #9ca3af;
            padding: 6px;
            border-radius: 6px;
            transition: all 0.2s ease;
            width: 28px;
            height: 28px;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .gl-icon-btn:hover {
            color: #f0f0f0;
            background: #2a2a2a;
            border-color: #3a3a3a;
            transform: scale(1.1);
        }
        .gl-icon-btn.favorite {
            color: #ec4899;
        }
        .gl-icon-btn.favorite:hover {
            color: #db2777;
            transform: scale(1.2);
        }
        .gl-create-btn {
            margin: 12px;
            padding: 10px;
            background: linear-gradient(135deg, #10b981 0%, #059669 100%);
            color: white;
            text-align: center;
            border-radius: 8px;
            cursor: pointer;
            font-weight: 600;
            font-size: 12px;
            box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
            transition: all 0.2s ease;
        }
        .gl-create-btn:hover {
            background: linear-gradient(135deg, #059669 0%, #047857 100%);
            box-shadow: 0 4px 12px rgba(16, 185, 129, 0.5);
            transform: translateY(-1px);
        }
        .gl-view-editor {
            display: none;
            flex-direction: column;
            padding: 15px;
            height: 100%;
            gap: 10px;
        }
        .gl-view-editor.active { display: flex; }
        .gl-input, .gl-textarea {
            background: #0f0f0f;
            border: 1px solid #2a2a2a;
            color: #e0e0e0;
            padding: 10px;
            border-radius: 8px;
            font-size: 12px;
            width: 100%;
            box-sizing: border-box;
            transition: all 0.2s ease;
        }
        .gl-input:focus, .gl-textarea:focus {
            outline: none;
            border-color: #3b82f6;
            box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
        }
        .gl-textarea {
            flex-grow: 1;
            resize: none;
            font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
        }
        .gl-editor-buttons {
            display: flex;
            gap: 10px;
            margin-top: auto;
        }
        .gl-btn {
            flex: 1;
            padding: 10px;
            border-radius: 8px;
            border: none;
            cursor: pointer;
            font-weight: 600;
            color: white;
            font-size: 12px;
            transition: all 0.2s ease;
        }
        .gl-btn-save {
            background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
            box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
        }
        .gl-btn-save:hover {
            background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
            box-shadow: 0 4px 12px rgba(59, 130, 246, 0.5);
            transform: translateY(-1px);
        }
        .gl-btn-cancel {
            background: linear-gradient(135deg, #374151 0%, #1f2937 100%);
            box-shadow: 0 2px 8px rgba(55, 65, 81, 0.3);
        }
        .gl-btn-cancel:hover {
            background: linear-gradient(135deg, #4b5563 0%, #374151 100%);
            box-shadow: 0 4px 12px rgba(55, 65, 81, 0.5);
            transform: translateY(-1px);
        }

        /* History Specific Styles */
        .history-item-time {
            font-size: 9px;
            color: #6b7280;
            margin-top: 3px;
        }
        .history-clear-btn {
            margin: 12px;
            padding: 10px;
            background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
            color: white;
            text-align: center;
            border-radius: 8px;
            cursor: pointer;
            font-weight: 600;
            font-size: 12px;
            box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
            transition: all 0.2s ease;
        }
        .history-clear-btn:hover {
            background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
            box-shadow: 0 4px 12px rgba(239, 68, 68, 0.5);
            transform: translateY(-1px);
        }
    `);

    // --- DOM CREATION ---
    const panel = document.createElement('div');
    panel.id = 'grok-control-panel';
    if (!isUiVisible) panel.classList.add('hidden');
    panel.innerHTML = `
        <div id="grok-resize-handle" title="Drag to Resize"></div>
        <div class="grok-header">
            <span class="grok-title">Grok Tools v20.5</span>
            <button id="grok-toggle-btn" class="grok-toggle-btn">ON</button>
        </div>
        <div class="grok-controls">
            <label class="grok-checkbox">
                <input type="checkbox" id="grok-autoclick-cb" ${autoClickEnabled ? 'checked' : ''}> Auto-Retry
            </label>
            <div>
                Max: <input type="number" id="grok-retry-limit" value="${maxRetries}" class="grok-num-input" min="1">
            </div>
        </div>
        <div class="grok-prompt-label">Prompt Editor</div>
        <textarea id="grok-panel-prompt" placeholder="Type or paste prompt here..."></textarea>
        <div class="grok-btn-row">
            <button id="btn-open-library" class="grok-action-btn">Snippets</button>
            <button id="btn-open-favorites" class="grok-action-btn">❤️</button>
            <button id="btn-open-history" class="grok-action-btn">History</button>
            <button id="btn-generate" class="grok-action-btn">Generate</button>
        </div>
        <div id="grok-status">Ready</div>
        <div style="font-size:9px; color:#555; text-align:center;">Hide: Alt+${uiToggleKey.toUpperCase()}</div>
    `;
    document.body.appendChild(panel);

    // --- LIBRARY MODAL ---
    const modal = document.createElement('div');
    modal.id = 'grok-library-modal';
    modal.className = 'grok-modal';
    modal.innerHTML = `
        <div class="gl-header"><span>Snippets Library</span><span class="gl-close">&times;</span></div>
        <div class="gl-view-list" id="gl-view-list">
            <div class="gl-list-content" id="gl-list-container"></div>
            <div class="gl-create-btn" id="btn-create-snippet">Create New Snippet</div>
        </div>
        <div class="gl-view-editor" id="gl-view-editor">
            <label style="font-size:11px; color:#8b98a5;">Label</label>
            <input type="text" class="gl-input" id="gl-edit-label" placeholder="e.g. Cinematic Lighting">
            <label style="font-size:11px; color:#8b98a5;">Prompt Text</label>
            <textarea class="gl-textarea" id="gl-edit-text" placeholder="Content to append..."></textarea>
            <div class="gl-editor-buttons">
                <button class="gl-btn gl-btn-cancel" id="btn-edit-cancel">Cancel</button>
                <button class="gl-btn gl-btn-save" id="btn-edit-save">Save Snippet</button>
            </div>
        </div>
    `;
    document.body.appendChild(modal);

    // --- FAVORITES MODAL ---
    const favoritesModal = document.createElement('div');
    favoritesModal.id = 'grok-favorites-modal';
    favoritesModal.className = 'grok-modal';
    favoritesModal.innerHTML = `
        <div class="gl-header"><span>Favorites ❤️</span><span class="gl-close favorites-close">&times;</span></div>
        <div class="history-tabs">
            <div class="history-tab active" data-tab="video">🎥 Video</div>
            <div class="history-tab" data-tab="image">🖼️ Image</div>
        </div>
        <div class="gl-view-list" id="favorites-view-list">
            <div class="gl-list-content" id="favorites-list-container"></div>
        </div>
        <div class="gl-view-editor" id="favorites-view-viewer">
            <label style="font-size:11px; color:#8b98a5;">Name / Label</label>
            <input type="text" class="gl-input" id="fav-edit-label" placeholder="Favorite Name">
            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; margin-top:10px;">
                <label style="font-size:11px; color:#8b98a5;">Prompt Text</label>
                <span id="favorites-viewer-time" style="font-size:9px; color:#6b7280;"></span>
            </div>
            <textarea class="gl-textarea" id="favorites-viewer-text"></textarea>
            <div class="gl-editor-buttons">
                <button class="gl-btn gl-btn-cancel" id="btn-fav-viewer-back">Cancel</button>
                <button class="gl-btn gl-btn-save" id="btn-fav-viewer-save">Save Changes</button>
            </div>
        </div>
    `;
    document.body.appendChild(favoritesModal);

    // --- HISTORY MODAL ---
    const historyModal = document.createElement('div');
    historyModal.id = 'grok-history-modal';
    historyModal.className = 'grok-modal';
    historyModal.innerHTML = `
        <div class="gl-header"><span>Prompt History (100 max)</span><span class="gl-close history-close">&times;</span></div>
        <div class="history-tabs">
            <div class="history-tab active" data-tab="video">🎥 Video</div>
            <div class="history-tab" data-tab="image">🖼️ Image</div>
        </div>
        <div class="gl-view-list" id="history-view-list">
            <div class="gl-list-content" id="history-list-container"></div>
            <div class="history-clear-btn" id="btn-clear-history">Clear This Tab's History</div>
        </div>
        <div class="gl-view-editor history-viewer" id="history-view-viewer">
            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px;">
                <label style="font-size:11px; color:#9ca3af; font-weight: 600;">Full Prompt</label>
                <span id="history-viewer-time" style="font-size:9px; color:#6b7280;"></span>
            </div>
            <textarea class="gl-textarea" id="history-viewer-text" readonly></textarea>
            <div class="gl-editor-buttons">
                <button class="gl-btn gl-btn-cancel" id="btn-viewer-back">← Back</button>
                <button class="gl-btn gl-btn-save" id="btn-viewer-use">Use This Prompt</button>
            </div>
        </div>
    `;
    document.body.appendChild(historyModal);

    // --- RESIZE LOGIC ---
    const resizeHandle = document.getElementById('grok-resize-handle');
    let isResizing = false;
    let startX, startY, startWidth, startHeight;

    function updateModalPosition() {
        const pHeight = panel.offsetHeight;
        const bottom = (20 + pHeight + 10) + 'px';
        modal.style.bottom = bottom;
        historyModal.style.bottom = bottom;
        favoritesModal.style.bottom = bottom;
    }

    resizeHandle.addEventListener('mousedown', (e) => {
        isResizing = true;
        startX = e.clientX;
        startY = e.clientY;
        const rect = panel.getBoundingClientRect();
        startWidth = rect.width;
        startHeight = rect.height;
        e.preventDefault();
        document.body.style.cursor = 'nwse-resize';
    });

    document.addEventListener('mousemove', (e) => {
        if (!isResizing) return;
        const deltaX = startX - e.clientX;
        const deltaY = startY - e.clientY;
        const newWidth = Math.max(280, startWidth + deltaX);
        const newHeight = Math.max(250, startHeight + deltaY);
        panel.style.width = newWidth + 'px';
        panel.style.height = newHeight + 'px';
        updateModalPosition();
    });

    document.addEventListener('mouseup', () => {
        if (isResizing) {
            isResizing = false;
            document.body.style.cursor = '';
            const rect = panel.getBoundingClientRect();
            GM_setValue('panelSize', { width: rect.width + 'px', height: rect.height + 'px' });
            updateModalPosition();
        }
    });

    // --- REFS ---
    const toggleBtn = document.getElementById('grok-toggle-btn');
    const autoClickCb = document.getElementById('grok-autoclick-cb');
    const limitInput = document.getElementById('grok-retry-limit');
    const statusText = document.getElementById('grok-status');
    const promptBox = document.getElementById('grok-panel-prompt');
    const openLibBtn = document.getElementById('btn-open-library');
    const openFavoritesBtn = document.getElementById('btn-open-favorites');
    const openHistoryBtn = document.getElementById('btn-open-history');
    const generateBtn = document.getElementById('btn-generate');
    const modalEl = document.getElementById('grok-library-modal');
    const favoritesModalEl = document.getElementById('grok-favorites-modal');
    const historyModalEl = document.getElementById('grok-history-modal');
    const listContainer = document.getElementById('gl-list-container');
    const favoritesListContainer = document.getElementById('favorites-list-container');
    const historyListContainer = document.getElementById('history-list-container');
    const createBtn = document.getElementById('btn-create-snippet');
    const clearHistoryBtn = document.getElementById('btn-clear-history');
    const editLabel = document.getElementById('gl-edit-label');
    const editText = document.getElementById('gl-edit-text');
    let editingId = null;

    // --- HELPER FUNCTIONS ---
    function nativeValueSet(el, value) {
        if (!el) return;
        const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
        setter.call(el, value);
        el.dispatchEvent(new Event('input', { bubbles: true }));
    }

    function resetState(msg) {
        limitReached = false;
        currentRetryCount = 0;
        moderationDetected = false;
        processingModeration = false;
        if (errorWaitInterval) {
            clearInterval(errorWaitInterval);
            errorWaitInterval = null;
        }
        updateStatus(msg);
    }

    function updateStatus(msg, type) {
        statusText.textContent = msg;
        statusText.className = '';
        if (type === 'error') statusText.classList.add('status-error');
        if (type === 'warning') statusText.classList.add('status-warning');
    }

    // --- ROBUST MODERATION DETECTION ---
    function checkForModerationContent() {
        const now = Date.now();
        // Prevent checking too frequently
        if (now - lastModerationCheck < 200) return null;
        lastModerationCheck = now;

        // Method 1: Check toast notifications
        const toastSelectors = [
            'section[aria-label="Notifications alt+T"]',
            'section[aria-label*="Notification"]',
            '[role="alert"]',
            '[data-sonner-toast]',
            '.toast',
            '[class*="toast"]',
            '[class*="Toast"]'
        ];

        for (const selector of toastSelectors) {
            try {
                const containers = document.querySelectorAll(selector);
                for (const container of containers) {
                    const text = (container.textContent || container.innerText || '').toLowerCase();
                    for (const pattern of MODERATION_PATTERNS) {
                        if (text.includes(pattern.toLowerCase())) {
                            debugLog('Moderation detected via toast:', selector, text.substring(0, 100));
                            return { element: container, method: 'toast', text: text };
                        }
                    }
                }
            } catch (e) {}
        }

        // Method 2: Check error data types
        try {
            const errorElements = document.querySelectorAll('[data-type="error"], [data-state="error"], [class*="error"]');
            for (const el of errorElements) {
                const text = (el.textContent || el.innerText || '').toLowerCase();
                for (const pattern of MODERATION_PATTERNS) {
                    if (text.includes(pattern.toLowerCase())) {
                        debugLog('Moderation detected via error element:', text.substring(0, 100));
                        return { element: el, method: 'error-element', text: text };
                    }
                }
            }
        } catch (e) {}

        // Method 3: Scan all visible text elements
        try {
            const textElements = document.querySelectorAll('span, p, div, li');
            for (const el of textElements) {
                // Skip our own panel
                if (el.closest('#grok-control-panel') || el.closest('.grok-modal')) continue;

                // Only check elements with limited text content (avoid scanning large containers)
                const directText = el.childNodes.length === 1 && el.childNodes[0].nodeType === Node.TEXT_NODE;
                if (!directText && el.children.length > 0) continue;

                const text = (el.textContent || el.innerText || '').trim().toLowerCase();
                if (text.length < 10 || text.length > 200) continue;

                for (const pattern of MODERATION_PATTERNS) {
                    if (text.includes(pattern.toLowerCase())) {
                        debugLog('Moderation detected via text scan:', text);
                        return { element: el, method: 'text-scan', text: text };
                    }
                }
            }
        } catch (e) {}

        // Method 4: Check for specific Grok error styling
        try {
            const redTextElements = document.querySelectorAll('[class*="red"], [class*="danger"], [style*="color: red"], [style*="color:red"]');
            for (const el of redTextElements) {
                if (el.closest('#grok-control-panel')) continue;
                const text = (el.textContent || el.innerText || '').toLowerCase();
                for (const pattern of MODERATION_PATTERNS) {
                    if (text.includes(pattern.toLowerCase())) {
                        debugLog('Moderation detected via styled element:', text.substring(0, 100));
                        return { element: el, method: 'styled', text: text };
                    }
                }
            }
        } catch (e) {}

        return null;
    }

    function hideElement(element) {
        if (!element) return;
        try {
            element.style.display = 'none';
            element.style.visibility = 'hidden';
            element.style.opacity = '0';
            element.style.pointerEvents = 'none';
        } catch (e) {
            debugLog('Error hiding element:', e);
        }
    }

    function addToHistory(prompt, type) {
        if (!prompt || !prompt.trim()) return;

        const historyArray = type === 'image' ? imagePromptHistory : videoPromptHistory;
        const filtered = historyArray.filter(item => item.text !== prompt);

        filtered.unshift({
            id: Date.now().toString(),
            text: prompt,
            timestamp: Date.now(),
            type: type
        });

        const limited = filtered.slice(0, MAX_HISTORY_ITEMS);

        if (type === 'image') {
            imagePromptHistory = limited;
            GM_setValue('imagePromptHistory', imagePromptHistory);
        } else {
            videoPromptHistory = limited;
            GM_setValue('videoPromptHistory', videoPromptHistory);
        }
    }

    function addToFavorites(prompt, type) {
        if (!prompt || !prompt.trim()) return;

        const favArray = type === 'image' ? imageFavorites : videoFavorites;

        const exists = favArray.some(item => item.text === prompt);
        if (exists) {
            updateStatus(`Already in favorites!`, 'error');
            setTimeout(() => updateStatus('Ready'), 2000);
            return;
        }

        const newFav = {
            id: Date.now().toString(),
            text: prompt,
            label: prompt.length > 40 ? prompt.substring(0, 40) + '...' : prompt,
            timestamp: Date.now(),
            type: type
        };

        if (type === 'image') {
            imageFavorites.unshift(newFav);
            GM_setValue('imageFavorites', imageFavorites);
        } else {
            videoFavorites.unshift(newFav);
            GM_setValue('videoFavorites', videoFavorites);
        }

        updateStatus(`Added to favorites! ❤️`);
        setTimeout(() => updateStatus('Ready'), 2000);
    }

    function isInFavorites(prompt, type) {
        const favArray = type === 'image' ? imageFavorites : videoFavorites;
        return favArray.some(item => item.text === prompt);
    }

    function formatTimestamp(timestamp) {
        const date = new Date(timestamp);
        const now = new Date();
        const diff = now - date;

        if (diff < 60000) return 'Just now';
        if (diff < 3600000) {
            const mins = Math.floor(diff / 60000);
            return `${mins} minute${mins > 1 ? 's' : ''} ago`;
        }
        if (diff < 86400000) {
            const hours = Math.floor(diff / 3600000);
            return `${hours} hour${hours > 1 ? 's' : ''} ago`;
        }
        return date.toLocaleDateString();
    }

    function detectPromptType() {
        const videoBtn = document.querySelector(RETRY_BUTTON_SELECTOR);
        const videoTA = document.querySelector(TARGET_TEXTAREA_SELECTOR);

        if (videoBtn && !videoBtn.disabled) return 'video';
        if (videoTA && (document.activeElement === videoTA || videoTA.value.trim())) return 'video';
        if (document.querySelector(IMAGE_PROMPT_SELECTOR) ||
            document.querySelector(IMAGE_IMAGINE_SELECTOR) ||
            document.querySelector(IMAGE_EDITOR_SELECTOR)) {
            return 'image';
        }
        return 'video';
    }

    // --- PROMPT SYNC: Panel -> Grok ---
    let syncTimeout = null;
    let isUserTyping = false;

    promptBox.addEventListener('input', () => {
        clearTimeout(syncTimeout);
        syncTimeout = setTimeout(() => {
            const grokTA = document.querySelector(TARGET_TEXTAREA_SELECTOR) ||
                           document.querySelector(IMAGE_EDITOR_SELECTOR) ||
                           document.querySelector(IMAGE_PROMPT_SELECTOR);

            const imagineP = document.querySelector(IMAGE_IMAGINE_SELECTOR);

            if (!isUserTyping) {
                if (grokTA && document.activeElement !== grokTA) {
                    lastTypedPrompt = promptBox.value;
                    nativeValueSet(grokTA, lastTypedPrompt);
                    resetState("Ready");
                } else if (imagineP && document.activeElement !== imagineP) {
                    lastTypedPrompt = promptBox.value;
                    resetState("Ready");
                }
            }
        }, 100);
    });

    // --- PROMPT SYNC: Grok -> Panel ---
    let lastSyncedValue = '';
    let typingTimer = null;

    document.addEventListener('input', (e) => {
        if (e.target.matches(TARGET_TEXTAREA_SELECTOR) ||
            e.target.matches(IMAGE_EDITOR_SELECTOR) ||
            e.target.matches(IMAGE_PROMPT_SELECTOR)) {
            isUserTyping = true;
            clearTimeout(typingTimer);

            const val = e.target.value;
            if (val !== lastSyncedValue) {
                lastSyncedValue = val;
                promptBox.value = val;
                lastTypedPrompt = val;
                if (val.trim()) resetState("Ready");
            }

            typingTimer = setTimeout(() => {
                isUserTyping = false;
            }, 500);
        }
    }, { capture: true, passive: true });

    document.addEventListener('keydown', (e) => {
        const editor = document.querySelector('.tiptap.ProseMirror');
        const imagineP = document.querySelector(IMAGE_IMAGINE_SELECTOR);

        if ((editor && e.target.closest('.tiptap.ProseMirror')) ||
            (imagineP && document.activeElement === imagineP)) {
            isUserTyping = true;
            clearTimeout(typingTimer);
            typingTimer = setTimeout(() => {
                isUserTyping = false;

                let capturedText = '';
                if (editor && editor.textContent.trim()) {
                    capturedText = editor.textContent.trim();
                } else if (imagineP && (imagineP.textContent || imagineP.innerText || '').trim()) {
                    capturedText = (imagineP.textContent || imagineP.innerText || '').trim();
                }

                if (capturedText && capturedText !== promptBox.value) {
                    promptBox.value = capturedText;
                    lastTypedPrompt = capturedText;
                    resetState("Ready");
                }
            }, 500);
        }
    }, { capture: true, passive: true });

    let lastImagineValue = '';
    const imagineObserver = new MutationObserver((mutations) => {
        if (isUserTyping) {
            const editor = document.querySelector('.tiptap.ProseMirror');
            const imagineP = document.querySelector(IMAGE_IMAGINE_SELECTOR);

            let val = '';
            if (editor && editor.textContent.trim()) {
                val = editor.textContent.trim();
            } else if (imagineP) {
                val = (imagineP.textContent || imagineP.innerText || '').trim();
            }

            if (val && val !== lastImagineValue && val !== promptBox.value) {
                lastImagineValue = val;
                promptBox.value = val;
                lastTypedPrompt = val;

                if (imagineP && imagineP.classList.contains('is-empty') && val) {
                    imagineP.classList.remove('is-empty', 'is-editor-empty');
                }
            }
        }
    });

    const startImagineObserver = () => {
        const editor = document.querySelector('.tiptap.ProseMirror');
        const imagineP = document.querySelector(IMAGE_IMAGINE_SELECTOR);

        if (editor) {
            try {
                imagineObserver.observe(editor, { characterData: true, childList: true, subtree: true, attributes: false });
            } catch(e) {}
        }

        if (imagineP) {
            try {
                imagineObserver.observe(imagineP, { characterData: true, childList: true, subtree: true, attributes: false });
            } catch(e) {}
        }
    };
    startImagineObserver();

    document.addEventListener('input', (e) => {
        const target = e.target;
        if (target && target.closest && target.closest('.tiptap.ProseMirror')) {
            isUserTyping = true;
            clearTimeout(typingTimer);

            const editor = document.querySelector('.tiptap.ProseMirror');
            const val = editor ? (editor.textContent || editor.innerText || '').trim() : '';
            if (val && val !== lastImagineValue) {
                lastImagineValue = val;
                promptBox.value = val;
                lastTypedPrompt = val;
                resetState("Ready");
            }

            typingTimer = setTimeout(() => {
                isUserTyping = false;
            }, 500);
        }
    }, true);

    // --- MANUAL GENERATE ---
    generateBtn.addEventListener('click', () => {
        const grokTA = document.querySelector(TARGET_TEXTAREA_SELECTOR) ||
                       document.querySelector(IMAGE_EDITOR_SELECTOR) ||
                       document.querySelector(IMAGE_PROMPT_SELECTOR);
        const realBtn = document.querySelector(RETRY_BUTTON_SELECTOR) ||
                        document.querySelector(IMAGE_EDITOR_BUTTON_SELECTOR) ||
                        document.querySelector(IMAGE_SUBMIT_BUTTON_SELECTOR);
        const imagineP = document.querySelector(IMAGE_IMAGINE_SELECTOR);

        if (!grokTA && !imagineP) {
            updateStatus("Grok elements not found!", "error");
            return;
        }
        if (!realBtn) {
            updateStatus("Generate button not found!", "error");
            return;
        }

        const now = Date.now();
        if (now - lastGenerationTimestamp < GENERATION_COOLDOWN_MS) {
            const remaining = Math.ceil((GENERATION_COOLDOWN_MS - (now - lastGenerationTimestamp)) / 1000);
            updateStatus(`Cooldown: ${remaining}s remaining`, "error");
            return;
        }

        const promptToGenerate = promptBox.value.trim();
        if (promptToGenerate) {
            let promptType = 'image';
            if (realBtn.matches(RETRY_BUTTON_SELECTOR)) {
                promptType = 'video';
            } else if (realBtn.matches(IMAGE_SUBMIT_BUTTON_SELECTOR) ||
                       realBtn.matches(IMAGE_EDITOR_BUTTON_SELECTOR)) {
                promptType = 'image';
            } else {
                promptType = detectPromptType();
            }
            addToHistory(promptToGenerate, promptType);
        }

        if (grokTA) {
            nativeValueSet(grokTA, promptBox.value);
        } else if (imagineP) {
            imagineP.textContent = promptBox.value;
            if (imagineP.classList.contains('is-empty') && promptBox.value) {
                imagineP.classList.remove('is-empty');
            }
        }

        setTimeout(() => {
            if (!realBtn.disabled) {
                realBtn.click();
                lastGenerationTimestamp = Date.now();
                updateStatus("Generation Started...");
            } else {
                updateStatus("Grok button disabled/processing.", "error");
            }
        }, 50);
    });

    // --- CAPTURE IMAGE SUBMIT CLICKS ---
    let isAutoRetryClick = false;

    document.addEventListener('mousedown', (e) => {
        if (isAutoRetryClick) return;
        const submitBtn = e.target.closest('button[aria-label="Submit"]');
        if (submitBtn) {
            const editor = document.querySelector('.tiptap.ProseMirror');
            const imagineP = document.querySelector(IMAGE_IMAGINE_SELECTOR);
            const imageTA = document.querySelector(IMAGE_PROMPT_SELECTOR);
            let promptToCapture = '';

            if (editor && editor.textContent.trim().length > 0) {
                promptToCapture = editor.textContent.trim();
            } else if (imagineP) {
                const imagineText = (imagineP.textContent || imagineP.innerText || '').trim();
                if (imagineText) promptToCapture = imagineText;
            } else if (imageTA && imageTA.value.trim()) {
                promptToCapture = imageTA.value.trim();
            } else if (promptBox.value.trim()) {
                promptToCapture = promptBox.value.trim();
            } else if (lastTypedPrompt.trim()) {
                promptToCapture = lastTypedPrompt.trim();
            }

            if (promptToCapture && promptToCapture.length > 2) {
                addToHistory(promptToCapture, 'image');
                updateStatus("Image prompt captured!");
                setTimeout(() => { if (isRetryEnabled) updateStatus("Ready"); }, 2000);
            }
        }
    }, true);

    document.addEventListener('keydown', (e) => {
        if (e.key === 'Enter' && !e.shiftKey) {
            if (e.target.matches('textarea[aria-label="Make a video"]')) return;
            if (e.target.matches('textarea[aria-label="Image prompt"]')) {
                const val = e.target.value.trim();
                if (val && val.length > 2) {
                    addToHistory(val, 'image');
                    updateStatus("Image prompt captured!");
                    setTimeout(() => { if (isRetryEnabled) updateStatus("Ready"); }, 2000);
                }
            }
            else if (e.target.closest('.tiptap.ProseMirror')) {
                const editor = e.target.closest('.tiptap.ProseMirror');
                if (editor && editor.textContent.trim().length > 2) {
                    const val = editor.textContent.trim();
                    addToHistory(val, 'image');
                    updateStatus("Image prompt captured!");
                    setTimeout(() => { if (isRetryEnabled) updateStatus("Ready"); }, 2000);
                }
            }
            else if (e.target.matches(IMAGE_IMAGINE_SELECTOR)) {
                const imagineP = e.target;
                const val = (imagineP.textContent || imagineP.innerText || '').trim();
                if (val && val.length > 2) {
                    addToHistory(val, 'image');
                    updateStatus("Image prompt captured!");
                    setTimeout(() => { if (isRetryEnabled) updateStatus("Ready"); }, 2000);
                }
            }
        }
    }, true);

    // --- FAVORITES MODAL LOGIC ---
    const favoritesTabs = favoritesModal.querySelectorAll('.history-tab');
    favoritesTabs.forEach(tab => {
        tab.addEventListener('click', () => {
            favoritesTabs.forEach(t => t.classList.remove('active'));
            tab.classList.add('active');
            currentFavoritesTab = tab.dataset.tab;
            renderFavorites();
        });
    });

    function renderFavorites() {
        favoritesListContainer.innerHTML = '';
        const favArray = currentFavoritesTab === 'image' ? imageFavorites : videoFavorites;

        if (favArray.length === 0) {
            favoritesListContainer.innerHTML = `<div style="text-align:center; padding:20px; color:#555;">No ${currentFavoritesTab} favorites yet</div>`;
            return;
        }

        favArray.forEach(item => {
            const el = document.createElement('div');
            el.className = 'gl-item';
            const label = item.label || (item.text.length > 40 ? item.text.substring(0, 40) + '...' : item.text);

            el.innerHTML = `
                <div class="gl-item-text">
                    <b>${escapeHtml(label)}</b>
                    <span>${escapeHtml(item.text)}</span>
                    <div class="history-item-time">${formatTimestamp(item.timestamp)}</div>
                </div>
                <div class="gl-item-actions">
                    <button class="gl-icon-btn fav-btn-edit" title="Edit Name/Text">✎</button>
                    <button class="gl-icon-btn fav-btn-del" title="Remove">🗑</button>
                </div>`;

            el.querySelector('.gl-item-text').addEventListener('click', () => {
                promptBox.value = item.text;
                promptBox.dispatchEvent(new Event('input'));
                favoritesModalEl.classList.remove('active');
            });

            el.querySelector('.fav-btn-edit').addEventListener('click', (e) => {
                e.stopPropagation();
                editFavorite(item);
            });

            el.querySelector('.fav-btn-del').addEventListener('click', (e) => {
                e.stopPropagation();
                if (confirm(`Remove favorite "${label}"?`)) {
                    if (currentFavoritesTab === 'image') {
                        imageFavorites = imageFavorites.filter(h => h.id !== item.id);
                        GM_setValue('imageFavorites', imageFavorites);
                    } else {
                        videoFavorites = videoFavorites.filter(h => h.id !== item.id);
                        GM_setValue('videoFavorites', videoFavorites);
                    }
                    renderFavorites();
                }
            });
            favoritesListContainer.appendChild(el);
        });
    }

    function editFavorite(item) {
        document.getElementById('favorites-view-list').style.display = 'none';
        document.getElementById('favorites-view-viewer').classList.add('active');
        const label = item.label || (item.text.length > 40 ? item.text.substring(0, 40) + '...' : item.text);
        document.getElementById('fav-edit-label').value = label;
        document.getElementById('favorites-viewer-text').value = item.text;
        document.getElementById('favorites-viewer-time').textContent = formatTimestamp(item.timestamp);
        currentEditingFavId = item.id;
    }

    function closeFavoritesEditor() {
        document.getElementById('favorites-view-viewer').classList.remove('active');
        document.getElementById('favorites-view-list').style.display = 'flex';
        currentEditingFavId = null;
    }

    openFavoritesBtn.addEventListener('click', () => {
        favoritesModalEl.classList.add('active');
        updateModalPosition();
        renderFavorites();
    });

    favoritesModal.querySelector('.favorites-close').addEventListener('click', () => {
        favoritesModalEl.classList.remove('active');
        closeFavoritesEditor();
    });

    document.getElementById('btn-fav-viewer-back').addEventListener('click', closeFavoritesEditor);
    document.getElementById('btn-fav-viewer-save').addEventListener('click', () => {
        const newLabel = document.getElementById('fav-edit-label').value.trim() || "Untitled";
        const newText = document.getElementById('favorites-viewer-text').value.trim();
        if (!newText) { alert("Prompt text cannot be empty."); return; }
        if (!currentEditingFavId) return;

        let favArray = currentFavoritesTab === 'image' ? imageFavorites : videoFavorites;
        const idx = favArray.findIndex(f => f.id === currentEditingFavId);
        if (idx !== -1) {
            favArray[idx].label = newLabel;
            favArray[idx].text = newText;
            if (currentFavoritesTab === 'image') GM_setValue('imageFavorites', favArray);
            else GM_setValue('videoFavorites', favArray);
            renderFavorites();
            closeFavoritesEditor();
        }
    });

    // --- HISTORY TAB SWITCHING ---
    const historyTabs = historyModal.querySelectorAll('.history-tab');
    historyTabs.forEach(tab => {
        tab.addEventListener('click', () => {
            historyTabs.forEach(t => t.classList.remove('active'));
            tab.classList.add('active');
            currentHistoryTab = tab.dataset.tab;
            renderHistory();
        });
    });

    function renderHistory() {
        historyListContainer.innerHTML = '';
        const historyArray = currentHistoryTab === 'image' ? imagePromptHistory : videoPromptHistory;
        if (historyArray.length === 0) {
            historyListContainer.innerHTML = `<div style="text-align:center; padding:20px; color:#555;">No ${currentHistoryTab} history yet</div>`;
            return;
        }

        historyArray.forEach(item => {
            const el = document.createElement('div');
            el.className = 'gl-item history-item';
            const isFavorited = isInFavorites(item.text, item.type);

            el.innerHTML = `
                <div class="gl-item-text history-text">
                    <span class="history-preview">${escapeHtml(item.text)}</span>
                    <div class="history-item-time">${formatTimestamp(item.timestamp)}</div>
                </div>
                <div class="gl-item-actions">
                    <button class="gl-icon-btn history-btn-fav ${isFavorited ? 'favorite' : ''}" title="${isFavorited ? 'Remove from favorites' : 'Add to favorites'}">❤️</button>
                    <button class="gl-icon-btn history-btn-view" title="View Full">👁</button>
                    <button class="gl-icon-btn history-btn-del" title="Delete">🗑</button>
                </div>`;

            el.querySelector('.gl-item-text').addEventListener('click', () => {
                promptBox.value = item.text;
                promptBox.dispatchEvent(new Event('input'));
                historyModalEl.classList.remove('active');
            });

            el.querySelector('.history-btn-fav').addEventListener('click', (e) => {
                e.stopPropagation();
                if (isFavorited) {
                    if (item.type === 'image') {
                        imageFavorites = imageFavorites.filter(f => f.text !== item.text);
                        GM_setValue('imageFavorites', imageFavorites);
                    } else {
                        videoFavorites = videoFavorites.filter(f => f.text !== item.text);
                        GM_setValue('videoFavorites', videoFavorites);
                    }
                    updateStatus(`Removed from favorites`);
                } else {
                    addToFavorites(item.text, item.type);
                }
                renderHistory();
                setTimeout(() => updateStatus('Ready'), 2000);
            });

            el.querySelector('.history-btn-view').addEventListener('click', (e) => {
                e.stopPropagation();
                showHistoryViewer(item);
            });

            el.querySelector('.history-btn-del').addEventListener('click', (e) => {
                e.stopPropagation();
                if (currentHistoryTab === 'image') {
                    imagePromptHistory = imagePromptHistory.filter(h => h.id !== item.id);
                    GM_setValue('imagePromptHistory', imagePromptHistory);
                } else {
                    videoPromptHistory = videoPromptHistory.filter(h => h.id !== item.id);
                    GM_setValue('videoPromptHistory', videoPromptHistory);
                }
                renderHistory();
            });
            historyListContainer.appendChild(el);
        });
    }

    function showHistoryViewer(item) {
        document.getElementById('history-view-list').style.display = 'none';
        document.getElementById('history-view-viewer').classList.add('active');
        document.getElementById('history-viewer-text').value = item.text;
        document.getElementById('history-viewer-time').textContent = formatTimestamp(item.timestamp);
        historyModalEl.dataset.currentItemId = item.id;
    }

    function closeHistoryViewer() {
        document.getElementById('history-view-viewer').classList.remove('active');
        document.getElementById('history-view-list').style.display = 'flex';
    }

    openHistoryBtn.addEventListener('click', () => {
        historyModalEl.classList.add('active');
        updateModalPosition();
        renderHistory();
    });

    clearHistoryBtn.addEventListener('click', () => {
        const tabName = currentHistoryTab === 'image' ? 'image' : 'video';
        if (confirm(`Clear all ${tabName} prompt history?`)) {
            if (currentHistoryTab === 'image') {
                imagePromptHistory = [];
                GM_setValue('imagePromptHistory', []);
            } else {
                videoPromptHistory = [];
                GM_setValue('videoPromptHistory', []);
            }
            renderHistory();
        }
    });

    historyModal.querySelector('.history-close').addEventListener('click', () => {
        historyModalEl.classList.remove('active');
        closeHistoryViewer();
    });

    document.getElementById('btn-viewer-back').addEventListener('click', closeHistoryViewer);
    document.getElementById('btn-viewer-use').addEventListener('click', () => {
        const text = document.getElementById('history-viewer-text').value;
        promptBox.value = text;
        promptBox.dispatchEvent(new Event('input'));
        historyModalEl.classList.remove('active');
        closeHistoryViewer();
    });

    // --- SNIPPETS MODAL LOGIC ---
    function renderSnippets() {
        listContainer.innerHTML = '';
        savedSnippets.forEach(item => {
            const el = document.createElement('div');
            el.className = 'gl-item';
            el.innerHTML = `
                <div class="gl-item-text"><b>${escapeHtml(item.label)}</b><span>${escapeHtml(item.text)}</span></div>
                <div class="gl-item-actions">
                    <button class="gl-icon-btn gl-btn-edit">✎</button>
                    <button class="gl-icon-btn gl-btn-del">🗑</button>
                </div>`;
            el.querySelector('.gl-item-text').addEventListener('click', () => {
                const cur = promptBox.value;
                promptBox.value = cur + (cur && !cur.endsWith(' ') ? ' ' : '') + item.text;
                promptBox.dispatchEvent(new Event('input'));
                modalEl.classList.remove('active');
            });
            el.querySelector('.gl-btn-edit').addEventListener('click', (e) => { e.stopPropagation(); showEditor(item); });
            el.querySelector('.gl-btn-del').addEventListener('click', (e) => {
                e.stopPropagation();
                if (confirm(`Delete "${item.label}"?`)) {
                    savedSnippets = savedSnippets.filter(s => s.id !== item.id);
                    GM_setValue('savedSnippets', savedSnippets);
                    renderSnippets();
                }
            });
            listContainer.appendChild(el);
        });
    }

    function showEditor(item = null) {
        document.getElementById('gl-view-list').style.display = 'none';
        document.getElementById('gl-view-editor').classList.add('active');
        editingId = item ? item.id : null;
        editLabel.value = item ? item.label : '';
        editText.value = item ? item.text : '';
        editText.focus();
    }

    createBtn.addEventListener('click', () => showEditor(null));

    document.getElementById('btn-edit-save').addEventListener('click', () => {
        const label = editLabel.value.trim() || 'Untitled';
        const text = editText.value.trim();
        if (!text) return alert("Empty text");
        if (editingId) {
            const idx = savedSnippets.findIndex(s => s.id === editingId);
            if (idx > -1) {
                savedSnippets[idx].label = label;
                savedSnippets[idx].text = text;
            }
        } else {
            savedSnippets.push({ id: Date.now().toString(), label, text });
        }
        GM_setValue('savedSnippets', savedSnippets);
        document.getElementById('gl-view-editor').classList.remove('active');
        document.getElementById('gl-view-list').style.display = 'flex';
        renderSnippets();
    });

    document.getElementById('btn-edit-cancel').addEventListener('click', () => {
        document.getElementById('gl-view-editor').classList.remove('active');
        document.getElementById('gl-view-list').style.display = 'flex';
    });

    openLibBtn.addEventListener('click', () => {
        modalEl.classList.add('active');
        updateModalPosition();
        renderSnippets();
    });

    modal.querySelector('.gl-close').addEventListener('click', () => modalEl.classList.remove('active'));

    function escapeHtml(text) {
        return text ? text.replace(/&/g, "&amp;").replace(/</g, "&lt;") : '';
    }

    // --- TOGGLE BUTTON ---
    toggleBtn.addEventListener('click', () => {
        isRetryEnabled = !isRetryEnabled;
        toggleBtn.textContent = isRetryEnabled ? "ON" : "OFF";
        toggleBtn.classList.toggle('off', !isRetryEnabled);
        resetState(isRetryEnabled ? "Ready" : "Disabled");
        if (!isRetryEnabled) statusText.className = 'status-error';
    });

    autoClickCb.addEventListener('change', (e) => {
        autoClickEnabled = e.target.checked;
        GM_setValue('autoClickEnabled', autoClickEnabled);
    });

    limitInput.addEventListener('change', (e) => {
        maxRetries = parseInt(e.target.value);
        GM_setValue('maxRetries', maxRetries);
    });

    // --- MAIN MUTATION OBSERVER WITH IMPROVED DETECTION ---
    const observer = new MutationObserver(() => {
        if (observerThrottle || !isRetryEnabled || limitReached) return;

        observerThrottle = true;
        setTimeout(() => { observerThrottle = false; }, OBSERVER_THROTTLE_MS);

        // Check for moderation content using robust detection
        if (!processingModeration) {
            const moderationResult = checkForModerationContent();

            if (moderationResult && moderationResult.found !== false) {
                debugLog('Moderation detected!', moderationResult);
                moderationDetected = true;
                processingModeration = true;

                updateStatus(`Moderation detected! Waiting for message to clear...`, 'warning');

                // --- NEW LOGIC: WAIT FOR ERROR TO DISAPPEAR ---
                // We no longer force hide the element immediately.
                // We start polling to see when it's gone.
                waitForErrorDisappearance(moderationResult.element);
            }
        }

        startImagineObserver();

        // Video progress detection
        const progressEl = document.querySelector('.text-xs.font-semibold.w-\\[4ch\\].mb-\\[1px\\].tabular-nums');
        if (progressEl) {
            const txt = progressEl.textContent.trim();
            if (txt.includes('%')) {
                const val = parseInt(txt);
                if (!isNaN(val) && val >= 5 && val < 100) {
                    const videoTA = document.querySelector(TARGET_TEXTAREA_SELECTOR);
                    if (videoTA && videoTA.value.trim()) {
                        const prompt = videoTA.value.trim();
                        const existsInVideo = videoPromptHistory.some(item => item.text === prompt);
                        if (!existsInVideo) {
                            addToHistory(prompt, 'video');
                            updateStatus("Video prompt captured!");
                            setTimeout(() => { if (isRetryEnabled) updateStatus("Ready"); }, 2000);
                        }
                    }
                }
            }
        }
    });

    observer.observe(document.body, { childList: true, subtree: true, attributes: true, characterData: true });

    // --- WAIT FOR ERROR CLEARANCE FUNCTION ---
    function waitForErrorDisappearance(element) {
        if (errorWaitInterval) clearInterval(errorWaitInterval);

        let safetyCounter = 0;
        const POLL_MS = 500;
        const MAX_WAIT_MS = 10000; // 10 seconds max wait

        errorWaitInterval = setInterval(() => {
            safetyCounter += POLL_MS;

            // Check if element is still in DOM
            const isConnected = document.body.contains(element);
            // Check if element is visible (opacity/display)
            let isVisible = false;
            if (isConnected) {
                try {
                    const style = window.getComputedStyle(element);
                    isVisible = style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
                } catch(e) { isVisible = false; }
            }

            debugLog('Waiting for error clearance...', { isConnected, isVisible, safetyCounter });

            // If gone or timed out
            if (!isConnected || !isVisible || safetyCounter >= MAX_WAIT_MS) {
                clearInterval(errorWaitInterval);
                errorWaitInterval = null;

                if (safetyCounter >= MAX_WAIT_MS) {
                    debugLog('Error wait timed out, proceeding anyway.');
                } else {
                    debugLog('Error message cleared.');
                }

                updateStatus('Message cleared. Retrying...');
                const now = Date.now();
                handleRetry(now);
            }
        }, POLL_MS);
    }


    // --- RETRY HANDLER ---
    function handleRetry(now) {
        debugLog('handleRetry called', { currentRetryCount, maxRetries, autoClickEnabled });

        if (now - lastGenerationTimestamp < GENERATION_COOLDOWN_MS) {
            debugLog('Generation cooldown active');
            return;
        }

        lastRetryTimestamp = now;

        const grokTA = document.querySelector(TARGET_TEXTAREA_SELECTOR) ||
                       document.querySelector(IMAGE_EDITOR_SELECTOR) ||
                       document.querySelector(IMAGE_PROMPT_SELECTOR);
        const btn = document.querySelector(RETRY_BUTTON_SELECTOR) ||
                    document.querySelector(IMAGE_EDITOR_BUTTON_SELECTOR) ||
                    document.querySelector(IMAGE_SUBMIT_BUTTON_SELECTOR);
        const imagineP = document.querySelector(IMAGE_IMAGINE_SELECTOR);

        debugLog('Elements found:', { grokTA: !!grokTA, btn: !!btn, imagineP: !!imagineP, lastTypedPrompt });

        if ((grokTA || imagineP) && lastTypedPrompt) {
            // Re-fill the prompt
            if (grokTA) {
                nativeValueSet(grokTA, lastTypedPrompt);
                debugLog('Prompt refilled to textarea');
            } else if (imagineP) {
                imagineP.textContent = lastTypedPrompt;
                if (imagineP.classList.contains('is-empty') && lastTypedPrompt) {
                    imagineP.classList.remove('is-empty');
                }
                debugLog('Prompt refilled to imagineP');
            }

            if (autoClickEnabled && currentRetryCount >= maxRetries) {
                updateStatus(`Limit Reached (${maxRetries}/${maxRetries})`, "error");
                limitReached = true;
                processingModeration = false;
                moderationDetected = false;
                return;
            }

            if (autoClickEnabled && btn) {
                currentRetryCount++;
                updateStatus(`Retrying (${currentRetryCount}/${maxRetries})...`, 'warning');
                debugLog(`Scheduling retry ${currentRetryCount}/${maxRetries}`);

                isAutoRetryClick = true;
                setTimeout(() => {
                    if (!btn.disabled) {
                        debugLog('Clicking button...');
                        btn.click();
                        lastGenerationTimestamp = Date.now();
                        updateStatus(`Retry ${currentRetryCount}/${maxRetries} submitted`);
                    } else {
                        debugLog('Button is disabled');
                        updateStatus("Button disabled, waiting...", "error");
                    }
                    isAutoRetryClick = false;

                    // Reset processing state after a delay
                    setTimeout(() => {
                        processingModeration = false;
                        moderationDetected = false;
                    }, 2000);
                }, RETRY_DELAY_MS);
            } else {
                debugLog('Auto-click disabled or button not found');
                processingModeration = false;
                moderationDetected = false;
            }
        } else {
            debugLog('No textarea/prompt found or lastTypedPrompt empty');
            processingModeration = false;
            moderationDetected = false;
        }
    }

    // --- PERIODIC MODERATION CHECK (Backup) ---
    setInterval(() => {
        if (!isRetryEnabled || limitReached || processingModeration) return;

        const moderationResult = checkForModerationContent();
        if (moderationResult && moderationResult.found !== false) {
            debugLog('Periodic check found moderation');
            moderationDetected = true;
            processingModeration = true;
            updateStatus(`Moderation detected! Waiting...`, 'warning');
            waitForErrorDisappearance(moderationResult.element);
        }
    }, 1000);

    // --- KEYBOARD TOGGLE ---
    document.addEventListener('keydown', (e) => {
        if (e.altKey && e.key.toLowerCase() === uiToggleKey) {
            isUiVisible = !isUiVisible;
            GM_setValue('isUiVisible', isUiVisible);
            panel.classList.toggle('hidden', !isUiVisible);
            e.preventDefault();
        }
    });

    // --- CLEANUP ---
    window.addEventListener('beforeunload', () => {
        observer.disconnect();
        imagineObserver.disconnect();
        if (errorWaitInterval) clearInterval(errorWaitInterval);
    });

    // Initialize modal positions
    updateModalPosition();

    // Log startup
    debugLog('Grok Auto-Retry v20.5 initialized. Debug mode:', DEBUG_MODE);
    debugLog('Monitoring for patterns:', MODERATION_PATTERNS);

})();