Gray Swan Arena - Focus Mode

Clean UI with popup removal and focus mode for Gray Swan Arena with persistent state

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Gray Swan Arena - Focus Mode
// @namespace    http://tampermonkey.net/
// @version      2.1
// @license      MIT
// @description  Clean UI with popup removal and focus mode for Gray Swan Arena with persistent state
// @author       KarthiDreamr
// @match        https://app.grayswan.ai/*
// @grant        GM_addStyle
// @grant        GM.getValue
// @grant        GM.setValue
// @grant        GM_addValueChangeListener
// @grant        GM_removeValueChangeListener
// @homepage     https://github.com/yourusername/grayswan-focus-mode
// @supportURL   https://github.com/yourusername/grayswan-focus-mode/issues
// ==/UserScript==

(async function() {
    'use strict';

    // ========== CONFIGURATION ==========
    const CONFIG = {
        storageKey: 'gsa_focusMode',
        buttonPosition: { top: '14px', left: '14px' },
        animations: true,
        crossTabSync: true,
        autoApplyDelay: 100, // ms delay before applying clean mode
        popupCleanupInterval: 500, // ms interval for popup removal
        debugMode: false
    };

    // ========== UTILITY FUNCTIONS ==========
    function log(...args) {
        if (CONFIG.debugMode) {
            console.log('[GSA Focus Mode]', ...args);
        }
    }

    function debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    // ========== LOAD PERSISTED STATE ==========
    let isFocusMode = await GM.getValue(CONFIG.storageKey, false);
    log('Initial state loaded:', isFocusMode);

    // Apply focus mode immediately if saved (prevents flicker)
    if (isFocusMode) {
        setTimeout(() => {
            document.body.classList.add('gsa-focus-mode');
            log('Focus mode applied from storage');
        }, CONFIG.autoApplyDelay);
    }

    // ========== POPUP REMOVAL SYSTEM ==========
    function removePopupComponents() {
        /* =========================================================
           1.  REMOVE UI COMPONENT 1 - Success congratulations
           ---------------------------------------------------------*/
        document.querySelectorAll('p.mb-4.text-left.text-sm').forEach(p => {
            if (p.textContent.includes('Congratulations - you have successfully submitted a working break')) {
                const container = p.closest('div.flex.w-full.flex-col');
                if (container) {
                    container.remove();
                    log('Removed success congratulations popup');
                }
            }
        });

        /* =========================================================
           2.  REMOVE UI COMPONENT 2 - Prize dialog footer
           ---------------------------------------------------------*/
        document.querySelectorAll('div[data-slot="dialog-footer"]').forEach(footer => {
            const t = footer.textContent || '';
            if (t.includes('What prizes can I win?') && t.includes('Got it')) {
                footer.remove();
                log('Removed prize dialog footer');
            }
        });

        /* =========================================================
           3.  REMOVE UI COMPONENT 3 - Break failure dialog footer
           ---------------------------------------------------------*/
        document.querySelectorAll('div[data-slot="dialog-footer"]').forEach(footer => {
            const t = footer.textContent || '';
            if (t.includes('Why did my break fail?') && t.includes('Got it')) {
                footer.remove();
                log('Removed break failure dialog footer');
            }
        });

        /* =========================================================
           4.  REMOVE UI COMPONENT 4 - Dialog headers
           ---------------------------------------------------------*/
        document.querySelectorAll('div[data-slot="dialog-header"]').forEach(header => {
            const t = header.textContent || '';
            if (t.includes('Break Successful!') || t.includes('Break Rejected')) {
                header.remove();
                log('Removed dialog header');
            }
        });
    }

    // Start popup cleanup interval
    setInterval(removePopupComponents, CONFIG.popupCleanupInterval);
    log('Popup cleanup system started');

    // ========== ENHANCED CSS STYLES ==========
    GM_addStyle(`
        /* Hide header - but preserve lightbulb and its popup */
        .gsa-focus-mode .bg-background.fixed.top-0:not([aria-labelledby]):not([data-tooltip]):not([role="tooltip"]) {
            display: none !important;
        }

        /* Hide main sidebar - but NOT the documentation sidebar */
        .gsa-focus-mode #sidebar:not([aria-labelledby="behaviorDocDrawerTitle"]) {
            display: none !important;
        }

        .gsa-focus-mode .relative.hidden.h-full.md\\:block:not([aria-labelledby]) {
            display: none !important;
        }

        /* CRITICAL: Always show the lightbulb documentation sidebar */
        .gsa-focus-mode aside[aria-labelledby="behaviorDocDrawerTitle"],
        .gsa-focus-mode aside.fixed.top-0.right-0,
        .gsa-focus-mode aside.__web-inspector-hide-shortcut__ {
            display: flex !important;
            visibility: visible !important;
            opacity: 1 !important;
            pointer-events: auto !important;
            z-index: 9999 !important;
        }

        /* EXCEPTION: Always preserve tooltip/popup/dropdown elements */
        .gsa-focus-mode [data-tooltip],
        .gsa-focus-mode [role="tooltip"],
        .gsa-focus-mode [data-popover],
        .gsa-focus-mode [data-dropdown],
        .gsa-focus-mode [data-slot="tooltip"],
        .gsa-focus-mode [data-slot="popover"],
        .gsa-focus-mode [data-slot="dropdown-menu"],
        .gsa-focus-mode .tooltip,
        .gsa-focus-mode .popover,
        .gsa-focus-mode .dropdown-menu {
            display: block !important;
            visibility: visible !important;
            opacity: 1 !important;
            pointer-events: auto !important;
        }

        /* EXCEPTION: Lightbulb button and any trigger elements */
        .gsa-focus-mode button[data-tooltip-trigger],
        .gsa-focus-mode button:has(svg.lucide-lightbulb),
        .gsa-focus-mode [id*="bits-"]:not(#gsa-focus-toggle):not(#gsa-status-indicator) {
            display: flex !important;
            visibility: visible !important;
        }

        /* Hide breadcrumb navigation */
        .gsa-focus-mode nav[data-slot="breadcrumb"] {
            display: none !important;
        }

        /* Hide top tab navigation */
        .gsa-focus-mode .scrollbar-hide.-mb-1.flex.max-w-screen {
            display: none !important;
        }

        /* Hide mobile menu button */
        .gsa-focus-mode .md\\:hidden button[data-slot="sheet-trigger"] {
            display: none !important;
        }

        /* ENHANCED: Remove all padding and margins from main containers */
        .gsa-focus-mode .flex.flex-1.flex-col.overflow-hidden.pt-\\[4\\.5rem\\] {
            padding-top: 0 !important;
            padding-left: 0 !important;
            padding-right: 0 !important;
        }

        .gsa-focus-mode .relative.mx-auto.flex.h-full.w-full.flex-1 {
            padding: 0 !important;
            margin: 0 !important;
            max-width: none !important;
        }

        .gsa-focus-mode .flex.md\\:h-full.md\\:w-full.md\\:gap-4 {
            gap: 0 !important;
            padding: 0 !important;
        }

        /* ENHANCED: Make chat area completely full screen */
        .gsa-focus-mode .relative.flex.h-full.w-full.flex-col.gap-3 {
            max-width: none !important;
            margin: 0 !important;
            padding: 8px !important;
            gap: 8px !important;
        }

        /* ENHANCED: Remove padding from chat container */
        .gsa-focus-mode .dark\\:border-border.relative.flex.min-h-0.flex-1.flex-col.overflow-hidden {
            margin: 0 !important;
        }

        /* ENHANCED: Optimize chat content area spacing */
        .gsa-focus-mode .flex.h-full.min-h-0.flex-1.flex-col.overflow-auto {
            padding: 12px !important;
        }

        /* ENHANCED: Remove excessive margins from chat messages area */
        .gsa-focus-mode .mx-auto.flex.h-full.w-full.max-w-prose.flex-col.items-center.justify-center.space-y-2 {
            max-width: none !important;
            margin: 0 !important;
            padding: 0 !important;
        }

        /* ENHANCED: Optimize input area spacing */
        .gsa-focus-mode .relative.mx-auto.w-full.max-w-\\[75ch\\].shrink-0 {
            max-width: none !important;
            margin: 0 !important;
            padding: 8px !important;
        }

        /* ENHANCED: Remove body margins in focus mode */
        .gsa-focus-mode body {
            margin: 0 !important;
            padding: 0 !important;
        }

        /* ENHANCED: Make main content container full height */
        .gsa-focus-mode .flex.min-h-screen.w-full.flex-col.overflow-hidden {
            min-height: 100vh !important;
        }

        /* ========== ENHANCED TOGGLE BUTTON STYLES ========== */
        #gsa-focus-toggle {
            position: fixed;
            top: ${CONFIG.buttonPosition.top};
            left: ${CONFIG.buttonPosition.left};
            z-index: 10000;
            background: #1a1a1a;
            color: #e5e5e5;
            border: 1px solid #333;
            border-radius: 20px;
            padding: 6px 12px;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', monospace;
            font-size: 12px;
            font-weight: 500;
            cursor: pointer;
            box-shadow: 0 2px 8px rgba(0,0,0,0.2);
            transition: all 0.2s ease;
            backdrop-filter: blur(8px);
            opacity: 0.8;
            user-select: none;
            display: flex;
            align-items: center;
            gap: 4px;
        }

        #gsa-focus-toggle:hover {
            background: #2a2a2a;
            border-color: #555;
            opacity: 1;
            transform: translateY(-1px);
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
        }

        #gsa-focus-toggle.active {
            background: #1a1a1a;
            border-color: #30363d;
            color: #58a6ff;
        }

        #gsa-focus-toggle.active:hover {
            background: #2a2a2a;
            border-color: #58a6ff;
        }

        /* ========== STATUS INDICATOR ========== */
        #gsa-status-indicator {
            position: fixed;
            top: ${CONFIG.buttonPosition.top};
            left: calc(${CONFIG.buttonPosition.left} + 120px);
            z-index: 9999;
            background: rgba(0, 0, 0, 0.8);
            color: #fff;
            padding: 4px 8px;
            border-radius: 12px;
            font-size: 10px;
            font-family: monospace;
            opacity: 0;
            transition: opacity 0.3s ease;
            pointer-events: none;
            backdrop-filter: blur(4px);
        }

        #gsa-status-indicator.show {
            opacity: 1;
        }

        #gsa-status-indicator.success {
            background: rgba(34, 197, 94, 0.8);
        }

        #gsa-status-indicator.sync {
            background: rgba(59, 130, 246, 0.8);
        }

        /* ========== ANIMATIONS ========== */
        ${CONFIG.animations ? `
        .gsa-focus-mode {
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        }

        .gsa-focus-mode * {
            transition: all 0.2s ease;
        }
        ` : ''}

        /* ========== RESPONSIVE IMPROVEMENTS ========== */
        @media (max-width: 768px) {
            #gsa-focus-toggle {
                top: 10px;
                left: 10px;
                padding: 5px 10px;
                font-size: 11px;
            }

            #gsa-status-indicator {
                top: 10px;
                left: 100px;
            }
        }
    `);

    // ========== CORE FUNCTIONALITY ==========
    let toggleButton;
    let statusIndicator;
    let changeListener;

    // Create UI elements
    function createUI() {
        // Toggle button
        toggleButton = document.createElement('button');
        toggleButton.id = 'gsa-focus-toggle';
        updateButtonText();
        toggleButton.title = 'Toggle focus mode (F8 or Ctrl+Shift+F)';

        // Status indicator
        statusIndicator = document.createElement('div');
        statusIndicator.id = 'gsa-status-indicator';
        statusIndicator.textContent = 'Ready';

        document.body.appendChild(toggleButton);
        document.body.appendChild(statusIndicator);

        log('UI elements created');
    }

    // Update button text and state
    function updateButtonText() {
        if (!toggleButton) return;

        if (isFocusMode) {
            toggleButton.classList.add('active');
            toggleButton.innerHTML = '↩️ <span style="font-size: 10px;">Exit</span>';
        } else {
            toggleButton.classList.remove('active');
            toggleButton.innerHTML = '🎯 <span style="font-size: 10px;">Focus</span>';
        }
    }

    // Show status message
    function showStatus(message, type = 'success', duration = 2000) {
        if (!statusIndicator) {
            log('Status indicator not ready, skipping message:', message);
            return;
        }

        statusIndicator.textContent = message;
        statusIndicator.className = `show ${type}`;

        setTimeout(() => {
            if (statusIndicator) {
                statusIndicator.classList.remove('show');
                statusIndicator.classList.remove(type);
            }
        }, duration);
    }

    // Enhanced toggle function
    async function toggleFocusMode(source = 'manual') {
        const wasActive = isFocusMode;
        isFocusMode = document.body.classList.toggle('gsa-focus-mode');

        // Update UI
        updateButtonText();

        // Save state
        try {
            await GM.setValue(CONFIG.storageKey, isFocusMode);
            log(`Mode toggled to: ${isFocusMode ? 'ON' : 'OFF'} (source: ${source})`);

            if (source === 'manual') {
                showStatus(
                    isFocusMode ? 'Focus Mode ON' : 'Focus Mode OFF',
                    'success'
                );
            } else if (source === 'sync') {
                showStatus('Synced from other tab', 'sync', 1500);
            }
        } catch (error) {
            console.error('[GSA Focus Mode] Failed to save state:', error);
            showStatus('Save failed', 'error');
        }
    }

    // Debounced toggle for performance
    const debouncedToggle = debounce(toggleFocusMode, 100);

    // ========== CROSS-TAB SYNCHRONIZATION ==========
    function setupCrossTabSync() {
        if (!CONFIG.crossTabSync) return;

        changeListener = GM_addValueChangeListener(
            CONFIG.storageKey,
            (name, oldVal, newVal, remote) => {
                if (!remote) return; // Ignore changes from this tab

                log('Received remote state change:', newVal);
                isFocusMode = !!newVal;

                // Apply state change
                document.body.classList.toggle('gsa-focus-mode', isFocusMode);
                updateButtonText();

                // Show sync notification
                showStatus('Synced from other tab', 'sync', 1500);
            }
        );

        log('Cross-tab synchronization enabled');
    }

    // ========== KEYBOARD SHORTCUTS ==========
    function setupKeyboardShortcuts() {
        document.addEventListener('keydown', function(e) {
            // F8 shortcut
            if (e.key === 'F8') {
                e.preventDefault();
                debouncedToggle('keyboard');
                return;
            }

            // Ctrl+Shift+F shortcut
            if (e.ctrlKey && e.shiftKey && e.key === 'F') {
                e.preventDefault();
                debouncedToggle('keyboard');
                return;
            }

            // Escape to exit focus mode
            if (e.key === 'Escape' && isFocusMode) {
                debouncedToggle('keyboard');
                return;
            }
        });

        log('Keyboard shortcuts registered: F8, Ctrl+Shift+F, Escape');
    }

    // ========== AUTO-DETECTION AND ADAPTATION ==========
    function adaptToPageChanges() {
        // Observer for dynamic content changes
        const observer = new MutationObserver(function(mutations) {
            mutations.forEach(function(mutation) {
                if (mutation.type === 'childList' && isFocusMode) {
                    // Re-apply focus mode classes if needed
                    setTimeout(() => {
                        if (!document.body.classList.contains('gsa-focus-mode')) {
                            document.body.classList.add('gsa-focus-mode');
                            log('Focus mode re-applied after DOM change');
                        }
                    }, 50);
                }
            });
        });

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

        log('DOM observer started for dynamic content adaptation');
    }

    // ========== INITIALIZATION ==========
    function initialize() {
        createUI();
        setupCrossTabSync();
        setupKeyboardShortcuts();
        adaptToPageChanges();

        // Button click event
        toggleButton.addEventListener('click', () => debouncedToggle('manual'));

        // Apply saved state with proper timing
        if (isFocusMode) {
            updateButtonText();
            // Delay the status message to ensure UI is ready
            setTimeout(() => showStatus('Focus Mode restored', 'success', 1500), 100);
        }

        log('Script fully initialized');
        
        // Delay the loaded message to ensure statusIndicator is fully ready
        setTimeout(() => {
            showStatus('GSA Focus Mode loaded', 'success', 2000);
        }, 200);
    }


    // ========== CLEANUP ==========
    window.addEventListener('beforeunload', function() {
        if (changeListener && CONFIG.crossTabSync) {
            GM_removeValueChangeListener(changeListener);
            log('Cleanup completed');
        }
    });

    // ========== START ==========
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initialize);
    } else {
        // DOM already loaded
        setTimeout(initialize, 100);
    }

    // Fallback initialization
    window.addEventListener('load', function() {
        if (!toggleButton) {
            log('Fallback initialization triggered');
            initialize();
        }
    });

    log('Gray Swan Arena Focus Mode script loaded successfully');
})();