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

Fixed moderation detection with more robust selectors and flexible text matching

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

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

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

이 스크립트를 설치하려면 Tampermonkey 또는 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);

})();