Eye in the Cloud - A Google AI Studio Focused Experience

Get focused by hiding the clutter, hide chat history, lag free text box, VIBE Mode, and themes!

目前为 2025-05-04 提交的版本,查看 最新版本

// ==UserScript==
// @name         Eye in the Cloud - A Google AI Studio Focused Experience
// @namespace    https://github.com/soitgoes-again/eyeinthecloud
// @version      0.369
// @description  Get focused by hiding the clutter, hide chat history, lag free text box, VIBE Mode, and themes!
// @author       so it goes...again
// @match        https://aistudio.google.com/*
// @resource     CUSTOM_CSS https://raw.githubusercontent.com/soitgoes-again/eyeinthecloud/main/css/custom.css
// @resource     DOS_THEME_CSS https://raw.githubusercontent.com/soitgoes-again/eyeinthecloud/main/css/theme.dos.css
// @resource     NATURE_THEME_CSS https://raw.githubusercontent.com/soitgoes-again/eyeinthecloud/main/css/theme.nature.css
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_getResourceText
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // Styles Module (Placeholder - implementation in eyeinthecloud.styles.js)
    // ===================================================
    window.Styles = {
        addCoreStyles() {
            if (window.coreStyles) {
                GM_addStyle(window.coreStyles);
            }
        },
        addPopupStyles() {
            console.log("AC Script: Attempting to add popup styles. Already added?", window.eyeinthecloudRemainingStylesAdded); // <-- Add log
            if (window.eyeinthecloudRemainingStylesAdded) return;
            window.eyeinthecloudRemainingStylesAdded = true;
            if (window.popupStyles && typeof window.popupStyles === 'function') {
                console.log("AC Script: Injecting dynamic popup styles now."); // <-- Add log
                GM_addStyle(window.popupStyles(window.Config));
            } else {
                console.warn("AC Script: window.popupStyles not found or not a function."); // <-- Add log
            }
        }
    };

    // Initialize the application
    if (window.App) {
        window.App.init();
    }

})();

(function() {
    'use strict';

    let baseStylesInjected = false;

    // Application Initialization
    // ===================================================
    window.App = {
        themeManagerInitialized: false, // Track theme init
        customStyleElement: null, // To store the custom style element
        async init() {
            await window.Settings.load();

            // Inject Custom CSS FIRST
            if (!baseStylesInjected) {
                try {
                    const customCSSText = GM_getResourceText('CUSTOM_CSS');
                    if (customCSSText) {
                        this.customStyleElement = GM_addStyle(customCSSText);
                        baseStylesInjected = true;
                    } else {
                        // Handle failure to load CUSTOM_CSS resource
                    }
                } catch (e) {
                    // Handle error injecting base styles
                }
            }

            window.Styles.addCoreStyles();
            // Register menu command only if Popup.toggle is available
            if (window.Popup && typeof window.Popup.toggle === 'function') {
                GM_registerMenuCommand('Adv. Control Settings (AI Studio)', window.Popup.toggle);
            }
            
            // Initialize Theme Manager if available
            if (!this.themeManagerInitialized && window.ThemeManager) {
                window.ThemeManager.loadThemes();
                
                this.themeManagerInitialized = true; // Mark as initialized HERE
                
                // --- *** APPLY SAVED THEME ON LOAD *** ---
                const savedTheme = window.State.settings.activeTheme; // Get loaded theme pref
                
                if (savedTheme && typeof window.ThemeManager.applyTheme === 'function') {
                    try {
                        // Apply the saved theme
                        window.ThemeManager.applyTheme(savedTheme);
                        // Note: applyTheme now handles saving this state again via Settings.update,
                        // which is slightly redundant on load but harmless.
                    } catch (error) {
                        // Handle error applying saved theme
                        // Optionally clear the bad setting if apply fails
                        // window.Settings.update('activeTheme', null);
                    }
                } else if (savedTheme) {
                    // Handle inability to apply saved theme
                }
                // --- *** END OF APPLY SAVED THEME *** ---
            }
            
            this.initializeProgressively();
        },
        initializeProgressively() {
            // Only initialize other modules, not the button
            const chatContainer = document.querySelector(window.Config.selectors.chatContainer);
            if (chatContainer) {
                window.UI.applyChatVisibilityRules();
            }
            const layoutContainer = document.querySelector(window.Config.selectors.overallLayout);
            if (layoutContainer) {
                window.UI.applyLayoutRules();
            }
            if (window.ElementWatcher) window.ElementWatcher.start(); 
        }
    };

    // --- SINGLE Point of Button Creation ---
    function createToggleButton() {
        if (window.Button && typeof window.Button.create === 'function') {
            window.Button.create();
        }
    }
    // Create the toggle button immediately or on DOMContentLoaded
    if (document.readyState === 'loading') {
        window.addEventListener('DOMContentLoaded', createToggleButton);
    } else {
        createToggleButton();
    }

    // --- Initialize the App ---
    if (window.App) {
        window.App.init();
    }

})();
// button.js
// Provides the floating toggle button for chat visibility and options in AI Studio.

(function() {
    'use strict';

    // Toggle Button Module
    // ===================================================
    window.Button = {
        create() {
            if (document.getElementById(window.Config.ids.scriptButton)) {
                window.State.scriptToggleButton = document.getElementById(window.Config.ids.scriptButton);
                this.updateAppearance();
                return;
            }
            // Create the floating button and append to body
            window.State.scriptToggleButton = document.createElement('button');
            window.State.scriptToggleButton.id = window.Config.ids.scriptButton;
            window.State.scriptToggleButton.className = 'mdc-icon-button mat-mdc-icon-button mat-unthemed mat-mdc-button-base gmat-mdc-button advanced-control-button';
            // Remove inline margin/order styles for floating
            window.State.scriptToggleButton.removeAttribute('style');
            const spanRipple = document.createElement('span');
            spanRipple.className = 'mat-mdc-button-persistent-ripple mdc-icon-button__ripple';
            window.State.scriptToggleButton.appendChild(spanRipple);
            const icon = document.createElement('span');
            icon.className = 'material-symbols-outlined notranslate';
            icon.setAttribute('aria-hidden', 'true');
            window.State.scriptToggleButton.appendChild(icon);
            const focusIndicator = document.createElement('span');
            focusIndicator.className = 'mat-focus-indicator';
            window.State.scriptToggleButton.appendChild(focusIndicator);
            const touchTarget = document.createElement('span');
            touchTarget.className = 'mat-mdc-button-touch-target';
            window.State.scriptToggleButton.appendChild(touchTarget);
            window.State.scriptToggleButton.addEventListener('click', window.Popup.toggle);
            document.body.appendChild(window.State.scriptToggleButton);
            this.updateAppearance();
        },
        updateAppearance() {
            if (!window.State.scriptToggleButton) return;
            const iconSpan = window.State.scriptToggleButton.querySelector('.material-symbols-outlined');
            if (iconSpan) {
                iconSpan.textContent = window.State.isCurrentlyHidden ? window.Config.icons.hidden : window.Config.icons.visible;
            }
            const tooltipText = window.State.isCurrentlyHidden ?
                'Chat history hidden (Click for options)' :
                'Chat history visible (Click for options)';
            window.State.scriptToggleButton.setAttribute('aria-label', tooltipText);
            window.State.scriptToggleButton.setAttribute('mattooltip', tooltipText);
            // Reregister command in case text changed
            GM_registerMenuCommand(
                window.State.isCurrentlyHidden ?
                'Show All History (via settings)' :
                'Hide History (via settings)',
                window.Popup.toggle
            );
        }
    };

})();
// dom.js
// DOM utility functions for creating and managing elements in AI Studio Advanced Control Suite.

window.DOM = {
    /**
     * Create an element with attributes and children
     */
    createElement(tag, attributes = {}, children = []) {
        const element = document.createElement(tag);
        // Apply attributes
        for (const [key, value] of Object.entries(attributes)) {
            if (key === 'className') {
                element.className = value;
            } else if (key === 'textContent') {
                element.textContent = value;
            } else if (key === 'events') {
                for (const [event, handler] of Object.entries(value)) {
                    element.addEventListener(event, handler);
                }
            } else {
                element.setAttribute(key, value);
            }
        }
        // Append children
        if (!Array.isArray(children)) children = [children];
        children.filter(child => child).forEach(child => {
            if (typeof child === 'string') {
                element.appendChild(document.createTextNode(child));
            } else {
                element.appendChild(child);
            }
        });
        return element;
    },
    /**
     * Create a toggle switch with label
     */
    createToggle(id, labelText, checked, onChange) {
        const container = this.createElement('div', { className: 'toggle-setting' });
        const label = this.createElement('label', { 
            className: 'toggle-label',
            htmlFor: id,
            textContent: labelText
        });
        const toggle = this.createElement('input', {
            type: 'checkbox',
            className: 'basic-slide-toggle',
            id: id,
            checked: checked,
            events: { change: (e) => onChange(e.target.checked) }
        });
        container.appendChild(label);
        container.appendChild(toggle);
        return container;
    }
};
// inputfix.js
// Provides a modal to fix input lag and manage advanced input in AI Studio.

(function() {
    'use strict';

    window.InputLagFix = {
        modalElement: null,
        modalTextarea: null,
        modalContent: null, // Added reference for opacity
        triggerButton: null,
        persistentModalText: '', // Store text here
        isInitialized: false,

        init() {
            // This ensures modal is created once, trigger button is attempted when needed
            if (!this.isInitialized) {
                this.createModal(); // Create modal structure once
                this.isInitialized = true;
            }
            this.createTriggerButton(); // Attempt to create/find button
        },

        createTriggerButton() {
            const buttonId = 'adv-modal-trigger-btn';
            const targetContainerSelector = '.prompt-input-wrapper-container';

            // Check if button already exists in the DOM
            const existingButton = document.getElementById(buttonId);
            if (existingButton && document.body.contains(existingButton)) {
                this.triggerButton = existingButton; // Update reference if needed
                // Ensure listener is attached (prevents issues if script reloads)
                existingButton.removeEventListener('click', this.showModal); // Remove potential old listener
                existingButton.addEventListener('click', () => this.showModal());
                return; // Already exists
            }

            // If we have a reference but it's detached, clear it
            if (this.triggerButton && !document.body.contains(this.triggerButton)) {
                this.triggerButton = null;
            }

            // Create the button only if necessary
            if (!this.triggerButton) {
                const parentContainer = document.querySelector(targetContainerSelector);
                if (!parentContainer) {
                    return; // Cannot append yet
                }

                const button = document.createElement('button');
                button.id = buttonId;
                // Keep existing classes for Material styling, add new class for default hiding
                button.className = 'mdc-icon-button mat-mdc-icon-button mat-unthemed mat-mdc-button-base gmat-mdc-button adv-modal-trigger eic-hidden-by-default';
                button.setAttribute('mat-icon-button', '');
                button.setAttribute('aria-label', 'Open Advanced Input');
                button.setAttribute('mattooltip', 'Open Advanced Input');

                const iconSpan = document.createElement('span');
                iconSpan.className = 'material-symbols-outlined notranslate';
                iconSpan.textContent = 'chat_bubble';
                button.appendChild(iconSpan);

                // Add ripple/focus/touch elements
                const spanRipple = document.createElement('span');
                spanRipple.className = 'mat-mdc-button-persistent-ripple mdc-icon-button__ripple';
                button.appendChild(spanRipple);
                const focusIndicator = document.createElement('span');
                focusIndicator.className = 'mat-focus-indicator';
                button.appendChild(focusIndicator);
                const touchTarget = document.createElement('span');
                touchTarget.className = 'mat-mdc-button-touch-target';
                button.appendChild(touchTarget);

                // Add event listener only once during creation
                button.addEventListener('click', () => this.showModal());

                // Create a simple wrapper
                const buttonWrapper = document.createElement('div');
                buttonWrapper.className = 'button-wrapper'; // Match existing structure
                buttonWrapper.appendChild(button);

                // Append the wrapper simply to the end of the parent container
                parentContainer.appendChild(buttonWrapper);

                this.triggerButton = button; // Store reference
            }
        },

        createModal() {
            if (document.getElementById('adv-input-modal-overlay')) {
                 this.modalElement = document.getElementById('adv-input-modal-overlay');
                 this.modalContent = document.getElementById('adv-input-modal-content');
                 this.modalTextarea = document.getElementById('adv-input-modal-textarea');
                 return; // Already exists
            }

            // Outer Overlay - Let CSS handle positioning and visibility
            this.modalElement = document.createElement('div');
            this.modalElement.id = 'adv-input-modal-overlay';
            
            // Close modal if clicking overlay background
            this.modalElement.addEventListener('click', (event) => {
                if (event.target === this.modalElement) {
                   this.handleCancel();
                }
            });

            // Inner Content Container - Minimal inline styles
            this.modalContent = document.createElement('div');
            this.modalContent.id = 'adv-input-modal-content';
            Object.assign(this.modalContent.style, {
                width: '80%',
                height: '80%',
                maxWidth: '1000px',
                maxHeight: '700px',
                borderRadius: '8px',
                display: 'flex',
                flexDirection: 'column',
                padding: '20px'
            });

            // Textarea - Only layout-related styles
            this.modalTextarea = document.createElement('textarea');
            this.modalTextarea.id = 'adv-input-modal-textarea';
            Object.assign(this.modalTextarea.style, {
                flexGrow: '1',
                width: 'calc(100% - 20px)',
                borderRadius: '4px',
                marginBottom: '15px',
                padding: '10px',
                fontSize: '1rem',
                resize: 'none',
                outline: 'none'
            });
            // Prevent clicks inside textarea from closing modal
            this.modalTextarea.addEventListener('click', (event) => event.stopPropagation());

            // Button Container
            const buttonContainer = document.createElement('div');
            buttonContainer.className = 'adv-modal-buttons';
            Object.assign(buttonContainer.style, {
                display: 'flex',
                justifyContent: 'flex-end',
                gap: '10px',
                marginTop: 'auto' // Push buttons to bottom
            });
            // Prevent clicks inside button area from closing modal
            buttonContainer.addEventListener('click', (event) => event.stopPropagation());

            // Helper to create styled buttons
            const createModalButton = (text, onClick) => {
                const button = document.createElement('button');
                button.textContent = text;
                Object.assign(button.style, {
                    padding: '8px 16px',
                    borderRadius: '4px',
                    cursor: 'pointer',
                    fontSize: '0.9rem'
                });
                button.addEventListener('click', onClick);
                return button;
            };

            // Create Buttons
            const cancelButton = createModalButton('Cancel', this.handleCancel.bind(this));
            const addButton = createModalButton('Add to Input', this.handleAdd.bind(this));
            const sendButton = createModalButton('Send', this.handleSend.bind(this));
            // No Object.assign for sendButton color/background/border
            // No mouseover/mouseout listeners for any button

            // Append elements
            buttonContainer.appendChild(cancelButton);
            buttonContainer.appendChild(addButton);
            buttonContainer.appendChild(sendButton);

            this.modalContent.appendChild(this.modalTextarea);
            this.modalContent.appendChild(buttonContainer);

            this.modalElement.appendChild(this.modalContent);
            document.body.appendChild(this.modalElement);
        },

        showModal() {
            if (!this.modalElement) this.createModal(); // Ensure it exists
            if (!this.modalElement) return; // Bail if creation failed

            this.modalTextarea.value = this.persistentModalText;
            this.modalElement.classList.add('visible');
            this.modalTextarea.focus();
            // Add keydown listener for Escape key
            document.addEventListener('keydown', this.handleEscKey);
        },

        hideModal() {
            if (this.modalElement) {
                this.modalElement.classList.remove('visible');
            }
             // Remove keydown listener
             document.removeEventListener('keydown', this.handleEscKey);
        },

        // Bind 'this' correctly or use arrow function
        handleEscKey: (event) => {
            if (event.key === 'Escape') {
                 // Check if 'this' refers to InputLagFix object
                 if (window.InputLagFix && window.InputLagFix.modalElement?.classList.contains('visible')) { // New check
                      window.InputLagFix.handleCancel();
                 }
            }
        },

        handleCancel() {
             if (!this.modalTextarea) return;
            this.persistentModalText = this.modalTextarea.value; // Save text
            this.hideModal();
        },

        handleAdd() {
             if (!this.modalTextarea) return;
            const realInput = document.querySelector(window.Config.selectors.chatInput);
            if (!realInput) {
                this.hideModal();
                return;
            }

            const textToAdd = this.modalTextarea.value;
            this.persistentModalText = textToAdd; // Save text

            // Append text, adding a newline if real input already has content
            realInput.value += (realInput.value.trim() ? '\n' : '') + textToAdd;

            // Dispatch events
            realInput.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
            realInput.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));

            // Optional focus/blur might help some frameworks update
            // realInput.focus();
            // realInput.blur();

            this.hideModal();
        },

        handleSend() {
             if (!this.modalTextarea) return;
            const realInput = document.querySelector(window.Config.selectors.chatInput);
            const realRunButton = document.querySelector(window.Config.selectors.runButton);

            if (!realInput || !realRunButton) {
                 this.hideModal(); // Hide modal even if elements aren't found
                return;
            }

            const textToSend = this.modalTextarea.value;
            if (!textToSend.trim()) {
                this.handleCancel(); // Treat empty send as cancel
                return;
            }

            // Append text
            realInput.value += (realInput.value.trim() ? '\n' : '') + textToSend;

            // Dispatch events
            realInput.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
            realInput.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));

            // Click the real button after a short delay
            setTimeout(() => {
                if (realRunButton && !realRunButton.disabled) {
                    realRunButton.click();
                    this.persistentModalText = ''; // Clear persistent text on successful send
                    if(this.modalTextarea) this.modalTextarea.value = ''; // Clear textarea visually
                } else {
                    this.persistentModalText = textToSend; // Keep text if send failed
                }
                this.hideModal(); // Hide modal after attempt
            }, 150); // 150ms delay
        }
    };

     // Attempt initial setup if input area might already exist
     if (document.readyState === 'complete' || document.readyState === 'interactive') {
         window.InputLagFix.init();
     } else {
         window.addEventListener('DOMContentLoaded', () => window.InputLagFix.init());
     }

})();
// popup.js
// Popup dialog and settings UI for AI Studio Advanced Control Suite.

window.Popup = {
    /**
     * Create the settings popup
     */
    create() {
        if (document.getElementById(Config.ids.popup)) {
            State.popupElement = document.getElementById(Config.ids.popup);
            return;
        }
        // Create the popup element
        State.popupElement = window.DOM.createElement('div', { id: Config.ids.popup });
        // Build the popup header
        const headerDiv = window.DOM.createElement('div', { className: 'popup-header' });
        // --- Editable Title Display ---
        const titleDisplay = window.DOM.createElement('div', {
            id: 'popup-editable-title',
            className: 'popup-title popup-editable-title',
            textContent: State.settings.headingText || 'Eye in the Cloud',
            title: 'Click to edit title',
            tabindex: '0',
            style: 'cursor: text;',
            events: {
                click: (e) => window.Popup.enterEditTitleMode(e.target),
                focus: (e) => window.Popup.enterEditTitleMode(e.target),
                mousedown: (e) => { if (e.detail > 1) e.preventDefault(); }
            }
        });
        const closeButton = window.DOM.createElement('button', 
            { className: 'close-popup-button', events: { click: this.hide } },
            [window.DOM.createElement('span', { className: 'material-symbols-outlined notranslate', textContent: 'close' })]
        );
        headerDiv.appendChild(titleDisplay);
        headerDiv.appendChild(closeButton);
        State.popupElement.appendChild(headerDiv);
        // Build the popup content
        const contentDiv = window.DOM.createElement('div', { className: 'popup-content' });
        // --- Section 1: VIBE Mode Button ---
        const vibeSection = window.DOM.createElement('div', { className: 'popup-section vibe-section' });
        const vibeButton = window.DOM.createElement('button', {
            id: 'vibe-mode-toggle',
            className: 'vibe-button',
            events: {
                click: this.toggleVibeMode
            }
        }, [
            window.DOM.createElement('span', {
                className: 'material-symbols-outlined notranslate',
                textContent: 'bolt'
            }),
            'VIBE'
        ]);
        vibeSection.appendChild(vibeButton);
        contentDiv.appendChild(vibeSection); // Add VIBE section first
        // --- Section 2: History Settings ---
        const historyFieldset = window.DOM.createElement('fieldset', { className: 'popup-section' });
        const historyLegend = window.DOM.createElement('legend', { textContent: 'History' });
        historyFieldset.appendChild(historyLegend);
        // Add Show All toggle (inverted logic for limitHistory)
        historyFieldset.appendChild(
            window.DOM.createToggle(
                'show-all-history-toggle',
                'Show All',
                !State.settings.limitHistory,
                checked => Settings.update('limitHistory', !checked)
            )
        );
        // Turns slider
        const sliderContainer = window.DOM.createElement('div', { className: 'slider-container' });
        const sliderLabel = window.DOM.createElement('label', { htmlFor: 'num-turns-slider' });
        sliderLabel.appendChild(window.DOM.createElement('span', { textContent: 'Currently Showing: ' }));
        sliderLabel.appendChild(window.DOM.createElement('span', { id: 'num-turns-value', textContent: State.settings.limitHistory ? State.settings.numTurnsToShow : 'All' }));
        const slider = window.DOM.createElement('input', {
            id: 'num-turns-slider',
            type: 'range',
            min: '1',
            max: '10', // Will be updated dynamically
            value: State.settings.numTurnsToShow,
            events: {
                input: (e) => {
                    const sliderElement = e.target;
                    const value = parseInt(sliderElement.value);
                    const min = parseInt(sliderElement.min);
                    const max = parseInt(sliderElement.max);

                    // --- *** START: Added code for track fill *** ---
                    // Calculate percentage for CSS variable
                    const percentage = ((value - min) / (max - min)) * 100;
                    sliderElement.style.setProperty('--_slider-fill-percent', `${percentage}%`);
                    // --- *** END: Added code for track fill *** ---

                    // Original logic to update settings and display
                    if (State.settings.limitHistory) {
                        document.getElementById('num-turns-value').textContent = value;
                        Settings.update('numTurnsToShow', value);
                    }
                },
                change: (e) => {
                    const sliderElement = e.target;
                    const value = parseInt(sliderElement.value);
                    const min = parseInt(sliderElement.min);
                    const max = parseInt(sliderElement.max);
                    const percentage = ((value - min) / (max - min)) * 100;
                    sliderElement.style.setProperty('--_slider-fill-percent', `${percentage}%`);
                }
            }
        });

        // --- *** ADD Initial Setting of CSS variable *** ---
        const initialValue = parseInt(slider.value);
        const initialMin = parseInt(slider.min);
        const initialMax = parseInt(slider.max);
        const initialPercentage = ((initialValue - initialMin) / (initialMax - initialMin)) * 100;
        slider.style.setProperty('--_slider-fill-percent', `${initialPercentage}%`);
        // --- *** END Initial Setting *** ---

        sliderContainer.appendChild(sliderLabel);
        sliderContainer.appendChild(slider);
        historyFieldset.appendChild(sliderContainer);
        contentDiv.appendChild(historyFieldset);
        // --- Section 3: UI Settings ---
        const uiFieldset = window.DOM.createElement('fieldset', { className: 'popup-section' });
        uiFieldset.appendChild(window.DOM.createElement('legend', { textContent: 'Hide' }));
        // Add toggle settings using our helper function
        uiFieldset.appendChild(
            window.DOM.createToggle('hide-sidebars-toggle', 'Sidebars', State.settings.hideSidebars, 
                checked => Settings.update('hideSidebars', checked))
        );
        uiFieldset.appendChild(
            window.DOM.createToggle('hide-header-toggle', 'Header', State.settings.hideHeader,
                checked => Settings.update('hideHeader', checked))
        );
        uiFieldset.appendChild(
            window.DOM.createToggle('hide-toolbar-toggle', 'Toolbar', State.settings.hideToolbar,
                checked => Settings.update('hideToolbar', checked))
        );
        // Add toggle for hide prompt chips (was showPromptChips)
        uiFieldset.appendChild(
            window.DOM.createToggle('hide-prompt-chips-toggle', 'Prompt Chips', State.settings.hidePromptChips,
                checked => Settings.update('hidePromptChips', checked))
        );
        // Add toggle for hide feedback buttons
        uiFieldset.appendChild(
            window.DOM.createToggle('hide-feedback-buttons-toggle', 'Feedback Buttons', State.settings.hideFeedbackButtons,
                checked => Settings.update('hideFeedbackButtons', checked))
        );
        contentDiv.appendChild(uiFieldset);
        // --- Section: Themes (moved here after UI Settings) ---
        const themeSection = window.DOM.createElement('fieldset', { id: 'theme-selector-section', className: 'popup-section theme-section' });
        themeSection.appendChild(window.DOM.createElement('legend', { textContent: 'Themes' }));
        const themeButtonsContainer = window.DOM.createElement('div', { className: 'theme-buttons-container'});
        // DOS Theme Button
        const dosButton = window.DOM.createElement('button', {
            id: 'theme-btn-dos',
            className: 'theme-select-button',
            title: 'DOS Terminal Theme',
            events: { click: () => {
                window.Popup.handleThemeButtonClick('dos');
            }}
        }, [window.DOM.createElement('span', {className: 'material-symbols-outlined notranslate', textContent: 'code'})]);
        // Nature Theme Button
        const natureButton = window.DOM.createElement('button', {
            id: 'theme-btn-nature',
            className: 'theme-select-button',
            title: 'Light Nature Theme',
            events: { click: () => {
                window.Popup.handleThemeButtonClick('nature');
            }}
        }, [window.DOM.createElement('span', {className: 'material-symbols-outlined notranslate', textContent: 'eco'})]); // or 'grass'
        themeButtonsContainer.appendChild(dosButton);
        themeButtonsContainer.appendChild(natureButton);
        themeSection.appendChild(themeButtonsContainer);
        contentDiv.appendChild(themeSection);

        State.popupElement.appendChild(contentDiv);
        // Add popup to document body
        document.body.appendChild(State.popupElement);
    },
    /**
     * Switches the title display element to an input field for editing.
     */
    enterEditTitleMode(displayElement) {
        if (!displayElement || displayElement.tagName === 'INPUT') return;
        const currentText = displayElement.textContent;
        const headerDiv = displayElement.parentNode;
        const closeButton = headerDiv.querySelector('.close-popup-button');
        // Create the input element
        const inputField = window.DOM.createElement('input', {
            type: 'text',
            id: 'popup-title-input',
            className: 'popup-title popup-title-input',
            value: currentText,
            'data-original-value': currentText,
            style: `width: ${headerDiv.offsetWidth - closeButton.offsetWidth - 40}px; background: transparent; border: none; border-bottom: 1px solid var(--eic-popup-accent); outline: none; color: inherit; font-size: inherit; font-weight: inherit; padding: 0; margin: 0;`,
            events: {
                blur: (e) => window.Popup.exitEditTitleMode(e.target),
                keydown: (e) => {
                    if (e.key === 'Enter') {
                        e.preventDefault();
                        window.Popup.exitEditTitleMode(e.target, true);
                    } else if (e.key === 'Escape') {
                        window.Popup.exitEditTitleMode(e.target, false);
                    }
                }
            }
        });
        headerDiv.replaceChild(inputField, displayElement);
        inputField.focus();
        inputField.select();
    },
    /**
     * Switches the input field back to a display element, saving if requested.
     */
    exitEditTitleMode(inputField, shouldSave = true) {
        if (!inputField || inputField.tagName !== 'INPUT') return;
        const headerDiv = inputField.parentNode;
        const closeButton = headerDiv.querySelector('.close-popup-button');
        const newValue = inputField.value.trim();
        const originalValue = inputField.getAttribute('data-original-value');
        let finalValue = originalValue;
        if (shouldSave) {
            if (newValue && newValue !== originalValue) {
                Settings.update('headingText', newValue);
                finalValue = newValue;
            } else {
                finalValue = originalValue;
            }
        } else {
            finalValue = originalValue;
        }
        if (!finalValue) {
            finalValue = 'Eye in the Cloud';
            if (shouldSave && State.settings.headingText !== finalValue) {
                Settings.update('headingText', finalValue);
            }
        }
        const titleDisplay = window.DOM.createElement('div', {
            id: 'popup-editable-title',
            className: 'popup-title popup-editable-title',
            textContent: finalValue,
            title: 'Click to edit title',
            tabindex: '0',
            style: 'cursor: text;',
            events: {
                click: (e) => window.Popup.enterEditTitleMode(e.target),
                focus: (e) => window.Popup.enterEditTitleMode(e.target),
                mousedown: (e) => { if (e.detail > 1) e.preventDefault(); }
            }
        });
        if (headerDiv && inputField) {
            headerDiv.replaceChild(titleDisplay, inputField);
        }
    },
    /**
     * Show the popup dialog
     */
    show() {
        if (!State.popupElement) {
            this.create();
        }
        
        // Remove call to Styles.addPopupStyles() - rely only on custom.css
        
        this.updateUIState();
        const blurOverlay = document.createElement('div');
        blurOverlay.id = 'adv-controls-blur-overlay';
        blurOverlay.style.position = 'fixed';
        blurOverlay.style.top = '0';
        blurOverlay.style.left = '0';
        blurOverlay.style.width = '100%';
        blurOverlay.style.height = '100%';
        blurOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
        blurOverlay.style.zIndex = '9998';
        blurOverlay.style.opacity = '0';
        blurOverlay.addEventListener('click', this.hide);
        document.body.appendChild(blurOverlay);
        // Trigger the fade-in using requestAnimationFrame
        requestAnimationFrame(() => {
            blurOverlay.style.opacity = '1';
        });
        State.popupElement.classList.add('visible');
        const accentColor = getComputedStyle(document.documentElement).getPropertyValue('--eic-popup-accent') || '#8ab4f8';
        document.documentElement.style.setProperty('--eic-popup-accent', accentColor);
        setTimeout(() => {
            document.addEventListener('click', this.handleOutsideClick);
        }, 10);
    },
    /**
     * Hide the popup dialog
     */
    hide() {
        if (!State.popupElement) return;
        State.popupElement.classList.remove('visible');
        const blurOverlay = document.getElementById('adv-controls-blur-overlay');
        if (blurOverlay) blurOverlay.remove();
        document.removeEventListener('click', window.Popup.handleOutsideClick);
    },
    /**
     * Handle clicks outside the popup
     */
    handleOutsideClick(e) {
        if (State.popupElement && 
            !State.popupElement.contains(e.target) && 
            e.target.id !== Config.ids.scriptButton) {
            window.Popup.hide();
        }
    },
    /**
     * Toggle popup visibility
     */
    toggle(event) {
        if (event) event.stopPropagation();
        
        // Remove call to Styles.addPopupStyles() here too
        
        if (State.popupElement?.classList.contains('visible')) {
            window.Popup.hide();
        } else {
            window.Popup.show();
        }
    },
    /**
     * Toggle VIBE mode on/off
     */
    toggleVibeMode() {
        if (State.isVibeModeActive) {
            // --- Deactivate VIBE mode ---
            State.isVibeModeActive = false;
            if (State.preVibeSettings) {
                // Restore previous settings using batchUpdate
                Settings.batchUpdate(State.preVibeSettings);
                State.preVibeSettings = null; // Clear saved state
            }
        } else {
            // --- Activate VIBE mode ---
            State.isVibeModeActive = true;
            // Deep copy current settings to save them
            // Using JSON parse/stringify for a simple deep clone suitable here
            State.preVibeSettings = JSON.parse(JSON.stringify(State.settings));
            // Define VIBE settings
            const vibeSettings = {
                limitHistory: true,
                numTurnsToShow: 1,
                hideSidebars: true,
                hideHeader: true,
                hideToolbar: true,
                hidePromptChips: true,
                hideFeedbackButtons: true
            };
            Settings.batchUpdate(vibeSettings); // Apply VIBE settings
        }
        // Update the popup UI immediately to reflect the change
        Popup.updateUIState();
    },
    /**
     * Handle theme button click
     */
    handleThemeButtonClick(themeName) {
        if (State.isVibeModeActive) return; // Don't change theme if VIBE is on
        if (State.activeTheme === themeName) {
            ThemeManager.removeActiveTheme(); // Toggle off
        } else {
            ThemeManager.applyTheme(themeName); // Activate new theme
        }
        // No need to call updateUIState here, apply/remove Theme will do it.
    },
    /**
     * Update UI elements in the popup to match current settings
     */
    updateUIState() {
        if (!State.popupElement) return;
        // --- Update editable title display if not editing ---
        const titleDisplay = State.popupElement.querySelector('#popup-editable-title');
        const titleInput = State.popupElement.querySelector('#popup-title-input');
        if (titleDisplay && !titleInput && titleDisplay.textContent !== State.settings.headingText) {
            titleDisplay.textContent = State.settings.headingText || 'Eye in the Cloud';
        }

        // --- Update VIBE button and section state ---
        const vibeButton = State.popupElement.querySelector('#vibe-mode-toggle');
        const sectionsToDisable = State.popupElement.querySelectorAll('.popup-content .popup-section:not(.vibe-section)'); // Select all sections except vibe
        if (vibeButton) {
            vibeButton.classList.toggle('active', State.isVibeModeActive);
        }
        sectionsToDisable.forEach(section => section.classList.toggle('disabled-by-vibe', State.isVibeModeActive));

        // --- Update Slider Max Value (Crucial: Do this BEFORE setting slider value/disabled state) ---
        const turnsSlider = State.popupElement?.querySelector('#num-turns-slider');
        if (turnsSlider) {
            let maxExchanges = 1; // Default to 1 if no turns found
            try {
                 const chatContainer = document.querySelector(window.Config.selectors.chatContainer);
                 if (chatContainer) {
                     const aiTurns = chatContainer.querySelectorAll(window.Config.selectors.aiTurn);
                     // Set max to at least 1, even if there are 0 AI turns, to avoid range errors.
                     maxExchanges = Math.max(1, aiTurns.length);
                 }
            } catch (error) {}
            // Only update if the max value is actually different
            if (parseInt(turnsSlider.max) !== maxExchanges) {
                turnsSlider.max = maxExchanges;
            }
        }

        // --- History Section Update ---
        const showAllToggle = State.popupElement?.querySelector('#show-all-history-toggle');
        // Note: turnsSlider is already defined and checked above
        const turnsValueDisplay = State.popupElement?.querySelector('#num-turns-value');
        const userWantsLimit = State.settings.limitHistory; // What the user explicitly set
        const isEffectivelyLimited = State.isVibeModeActive || userWantsLimit; // Is history actually limited?

        if (showAllToggle && turnsSlider && turnsValueDisplay) {
            showAllToggle.checked = !userWantsLimit; // Toggle reflects user's choice
            showAllToggle.disabled = State.isVibeModeActive; // Disable toggle in Vibe

            // Determine slider state and display text
            if (State.isVibeModeActive) {
                turnsSlider.disabled = true;
                turnsSlider.parentElement.style.opacity = '0.5';
                turnsValueDisplay.textContent = '1 (VIBE)';
                // Ensure slider visually shows 1, though disabled
                turnsSlider.value = 1;
            } else if (userWantsLimit) { // Vibe OFF, User wants limit ON
                turnsSlider.disabled = false;
                turnsSlider.parentElement.style.opacity = '1';
                // Ensure the current value doesn't exceed the calculated max
                let currentVal = State.settings.numTurnsToShow;
                let currentMax = parseInt(turnsSlider.max); // Use the max we just set
                if (currentVal > currentMax) {
                    currentVal = currentMax; // Cap the value if needed
                }
                turnsSlider.value = currentVal;
                turnsValueDisplay.textContent = State.settings.numTurnsToShow;
            } else { // Vibe OFF, User wants Show All
                turnsSlider.disabled = true;
                turnsSlider.parentElement.style.opacity = '0.5';
                turnsValueDisplay.textContent = 'All';
            }
        }

        const sidebarsToggle = State.popupElement.querySelector('#hide-sidebars-toggle');
        if (sidebarsToggle) {
            sidebarsToggle.checked = State.settings.hideSidebars;
            sidebarsToggle.disabled = State.isVibeModeActive;
        }
        const headerToggle = State.popupElement.querySelector('#hide-header-toggle');
        if (headerToggle) {
            headerToggle.checked = State.settings.hideHeader;
            headerToggle.disabled = State.isVibeModeActive;
        }
        const toolbarToggle = State.popupElement.querySelector('#hide-toolbar-toggle');
        if (toolbarToggle) {
            toolbarToggle.checked = State.settings.hideToolbar;
            toolbarToggle.disabled = State.isVibeModeActive;
        }
        const promptChipsToggle = State.popupElement.querySelector('#hide-prompt-chips-toggle');
        if (promptChipsToggle) {
            promptChipsToggle.checked = State.settings.hidePromptChips;
            promptChipsToggle.disabled = State.isVibeModeActive;
        }
        const feedbackButtonsToggle = State.popupElement.querySelector('#hide-feedback-buttons-toggle');
        if (feedbackButtonsToggle) {
            feedbackButtonsToggle.checked = State.settings.hideFeedbackButtons;
            feedbackButtonsToggle.disabled = State.isVibeModeActive;
        }

        // Also disable theme section when Vibe is active
        const themeSection = State.popupElement?.querySelector('#theme-selector-section');
        if (themeSection) {
             themeSection.classList.toggle('disabled-by-vibe', State.isVibeModeActive);
        }
        // --- Update Theme Button States ---
        const dosBtn = State.popupElement?.querySelector('#theme-btn-dos');
        const natureBtn = State.popupElement?.querySelector('#theme-btn-nature');
        if (dosBtn) dosBtn.classList.toggle('active', State.activeTheme === 'dos');
        if (natureBtn) natureBtn.classList.toggle('active', State.activeTheme === 'nature');

        // --- Update Slider Track Fill ---
        if (turnsSlider) {
            try {
                const currentValue = parseInt(turnsSlider.value);
                const currentMin = parseInt(turnsSlider.min);
                const currentMax = parseInt(turnsSlider.max);
                const range = currentMax - currentMin;
                const currentPercentage = (range > 0) ? (((currentValue - currentMin) / range) * 100) : 0;
                turnsSlider.style.setProperty('--_slider-fill-percent', `${currentPercentage}%`);
            } catch (err) {}
        }
    }
};
// shared.js
// Shared configuration, state, and settings logic for AI Studio Advanced Control Suite.

window.Config = {
    selectors: {
        leftSidebar: 'ms-navbar',
        rightSidebar: 'ms-right-side-panel',
        header: 'ms-header-root',
        toolbar: 'ms-toolbar',
        chatInput: 'textarea[aria-label="Type something"]',
        runButton: 'button.run-button[aria-label="Run"]',
        overallLayout: 'body > app-root > ms-app > div',
        chatContainer: 'ms-autoscroll-container',
        userTurn: 'ms-chat-turn:has([data-turn-role="User"])',
        aiTurn: 'ms-chat-turn:has([data-turn-role="Model"])',
        buttonContainer: 'div.right-side'
    },
    ids: {
        scriptButton: 'advanced-control-toggle-button',
        popup: 'advanced-control-popup',
        fakeInput: 'advanced-control-fake-input',
        fakeRunButton: 'advanced-control-fake-run-button'
    },
    classes: {
        layoutHide: 'adv-controls-hide-ui'
    },
    settingsKey: 'aiStudioAdvancedControlSettings_v4',
    defaultSettings: {
        limitHistory: false,
        numTurnsToShow: 2,
        hideSidebars: false,
        hideHeader: false,
        hideToolbar: false,
        headingText: 'Eye in the Cloud',
        showPromptChips: false,
        hidePromptChips: false,
        hideFeedbackButtons: false,
        activeTheme: null  // Add activeTheme setting with null default (no theme)
        // Note: isVibeModeActive and preVibeSettings are NOT persisted intentionally.
        // Vibe mode is transient and should reset on page load/script reload.
    },
    icons: {
        visible: 'visibility',
        hidden: 'visibility_off'
    }
};

window.State = {
    settings: { ...window.Config.defaultSettings },
    isVibeModeActive: false, // New state for VIBE mode
    activeTheme: null, // 'dos', 'nature', or null
    themeCSS: {}, // Store loaded theme CSS strings { dos: "...", nature: "..." }
    preVibeSettings: null,   // New state to store settings before VIBE mode
    isCurrentlyHidden: false,
    scriptToggleButton: null,
    popupElement: null,
    chatObserver: null,
    debounceTimer: null,
    realChatInput: null,
    realRunButton: null,
    fakeChatInput: null
};

window.Settings = {
    async load() {
        const storedSettings = await GM_getValue(window.Config.settingsKey, window.Config.defaultSettings);
        window.State.settings = { ...window.Config.defaultSettings, ...storedSettings };
        window.State.isCurrentlyHidden = false;
    },
    async save() {
        // Save all settings, not just a subset
        await GM_setValue(window.Config.settingsKey, { ...window.State.settings });
    },
    update(key, value) {
        if (window.State.settings[key] === value) return;
        window.State.settings[key] = value;

        let needsChatRules = false;
        let needsLayoutRules = false;

        // Determine necessary updates based on the changed key
        if (key === 'numTurnsToShow' || key === 'limitHistory') {
            needsChatRules = true;
        } else if (key === 'hideSidebars' || key === 'hideHeader' || key === 'hideToolbar') {
            needsLayoutRules = true;
        }
        // No specific flags needed for headingText, hidePromptChips, hideFeedbackButtons as they are called directly below

        this.save(); // Save the updated settings

        // Apply necessary UI updates immediately
        // Debounce UI updates slightly if multiple settings change rapidly (like in Vibe mode restore)
        clearTimeout(window.State.uiUpdateDebounceTimer);
        window.State.uiUpdateDebounceTimer = setTimeout(() => {
            if (needsLayoutRules && window.UI) {
                window.UI.applyLayoutRules();
            }
            if (needsChatRules && window.UI) {
                window.UI.applyChatVisibilityRules(); // No need for extra delay here now
            }
            // --- Direct UI updates for specific settings ---
            if (key === 'headingText') {
                window.UI?.updateHeadingText();
            }
            if (key === 'hidePromptChips') {
                window.UI?.updatePromptChipsVisibility();
            }
            if (key === 'hideFeedbackButtons') {
                window.UI?.updateTurnFooterVisibility();
            }
            // Update popup UI if it's open
            if (window.State.popupElement?.classList.contains('visible') && window.Popup) {
                window.Popup.updateUIState();
            }
        }, 50); // Apply a small debounce
    },
    batchUpdate(settingsToUpdate) {
        let needsChatRules = false;
        let needsLayoutRules = false;
        let updated = false;

        for (const key in settingsToUpdate) {
            if (window.State.settings.hasOwnProperty(key) && window.State.settings[key] !== settingsToUpdate[key]) {
                window.State.settings[key] = settingsToUpdate[key];
                updated = true;
                if (key === 'numTurnsToShow' || key === 'limitHistory') {
                    needsChatRules = true;
                } else if (key === 'hideSidebars' || key === 'hideHeader' || key === 'hideToolbar') {
                    needsLayoutRules = true;
                }
                // Check other keys if they have direct UI updates needed within the batch logic if necessary
            }
        }

        if (!updated) return;

        this.save(); // Save the updated settings

        // Apply necessary UI updates immediately
        clearTimeout(window.State.uiUpdateDebounceTimer);
        window.State.uiUpdateDebounceTimer = setTimeout(() => {
            if (needsLayoutRules && window.UI) {
                window.UI.applyLayoutRules();
            }
            if (needsChatRules && window.UI) {
                window.UI.applyChatVisibilityRules();
            }
            // --- Direct UI updates for specific settings ---
            if (settingsToUpdate.hasOwnProperty('headingText')) {
                window.UI?.updateHeadingText();
            }
            if (settingsToUpdate.hasOwnProperty('hidePromptChips')) {
                window.UI?.updatePromptChipsVisibility();
            }
            if (settingsToUpdate.hasOwnProperty('hideFeedbackButtons')) {
                window.UI?.updateTurnFooterVisibility();
            }
            // Update popup UI if it's open
            if (window.State.popupElement?.classList.contains('visible') && window.Popup) {
                window.Popup.updateUIState();
            }
        }, 50);
    }
};
// styles.js
// Minimal core CSS logic for AI Studio Advanced Control Suite. All main styles are in custom.css.

window.coreStyles = `
    /* Basic UI hiding classes - essential structure only */
    .adv-controls-hide-ui-sidebars ms-navbar,
    .adv-controls-hide-ui-sidebars ms-right-side-panel {
        display: none !important;
    }
    .adv-controls-hide-ui-header ms-header-root {
        display: none !important;
    }
    .adv-controls-hide-ui-toolbar ms-toolbar {
        display: none !important;
    }
`;

// No longer inject any popup styles from here - custom.css handles everything
window.popupStyles = function(Config) {
    // Return empty string - all styling comes from custom.css now
    return '';
};
// thememanager.js
// Theme management logic for Eye in the Cloud (AI Studio Advanced Control Suite).

// --- Embedded Theme CSS ---
const dosThemeCSS = `
/* == Theme: DOS Green Terminal == */
body.theme-dos-applied {
  /* --- Core Palette --- */
  --mdc-theme-primary: #00ff00; /* Bright Green */
  --mdc-theme-on-primary: #000000; /* Black text on green */

  --mdc-theme-background: #000000; /* Black background */
  --mdc-theme-on-background: #00ff00; /* Green text on black */

  --mdc-theme-surface: #111111; /* Very dark grey for surfaces */
  --mdc-theme-on-surface: #00ff00; /* Green text on surfaces */

  --mdc-theme-surface-variant: #222222; /* Slightly lighter dark grey */
  --mdc-theme-on-surface-variant: #00cc00; /* Slightly dimmer green */

  --mdc-theme-outline: #008000; /* Darker green for borders/outlines */
  --mdc-theme-outline-variant: #005000; /* Even darker green */

  --mdc-theme-error: #ff0000; /* Standard red for errors */
  --mdc-theme-on-error: #000000; /* Black text on red */

  /* --- Typography --- */
  --mdc-typography-font-family: 'Courier New', Courier, monospace;
  font-family: 'Courier New', Courier, monospace !important;

  /* --- Shape (Optional) --- */
  --mdc-shape-small-component-radius: 0px;
  --mdc-shape-medium-component-radius: 0px;
  --mdc-shape-large-component-radius: 0px;
}
body.theme-dos-applied ms-code-block {
  background-color: #1a1a1a !important;
  border: 1px solid #005000 !important;
}
body.theme-dos-applied ms-code-block code {
  color: #00ff00 !important;
}
body.theme-dos-applied .material-symbols-outlined {
    color: var(--mdc-theme-on-surface);
}
body.theme-dos-applied button .material-symbols-outlined {
    color: inherit;
}
`;

const natureThemeCSS = `
/* == Theme: Light Nature == */
body.theme-nature-applied {
  --mdc-theme-primary: #4caf50;
  --mdc-theme-on-primary: #ffffff;
  --mdc-theme-background: #f5f5f5;
  --mdc-theme-on-background: #444444;
  --mdc-theme-surface: #ffffff;
  --mdc-theme-on-surface: #333333;
  --mdc-theme-surface-variant: #e0e0e0;
  --mdc-theme-on-surface-variant: #555555;
  --mdc-theme-outline: #bdbdbd;
  --mdc-theme-outline-variant: #cccccc;
  --mdc-theme-error: #d32f2f;
  --mdc-theme-on-error: #ffffff;
  --mdc-typography-font-family: 'Roboto', 'Helvetica Neue', sans-serif;
  font-family: 'Roboto', 'Helvetica Neue', sans-serif !important;
  --mdc-shape-small-component-radius: 6px;
  --mdc-shape-medium-component-radius: 12px;
  --mdc-shape-large-component-radius: 16px;
}
body.theme-nature-applied .material-symbols-outlined {
    color: var(--mdc-theme-on-surface);
}
body.theme-nature-applied button .material-symbols-outlined {
    color: inherit;
}
body.theme-nature-applied .mdc-button--raised .mdc-button__icon,
body.theme-nature-applied .mat-mdc-raised-button .mat-icon {
    color: var(--mdc-theme-on-primary);
}
`;
// --- End Embedded CSS ---

window.ThemeManager = {
    styleElements: {},
    loadThemes() {
        // Ensure resource names are mapped for theme switching
        window.State.themeResourceNames = {
            'dos': 'DOS_THEME_CSS',
            'nature': 'NATURE_THEME_CSS'
        };
    },
    applyTheme(themeName) {
        // --- Re-enable this function ---
        if (!window.State.themeResourceNames) {
            this.loadThemes();
        }
        const resourceName = window.State.themeResourceNames[themeName];
        if (!resourceName) {
            return;
        }

        this.removeActiveThemeClasses();

        // Inject Theme Override CSS if not already present or re-enable it
        if (!this.styleElements[themeName]) {
            const cssText = GM_getResourceText(resourceName);
            if (cssText) {
                // IMPORTANT: Theme CSS should ONLY contain variable overrides now
                this.styleElements[themeName] = GM_addStyle(cssText);
            } else {
                return;
            }
        } else {
            this.styleElements[themeName].disabled = false; // Re-enable if previously disabled
        }

        // Ensure other theme stylesheets are disabled
        for (const name in this.styleElements) {
            if (name !== themeName && this.styleElements[name]) {
                this.styleElements[name].disabled = true;
            }
        }

        // Apply theme class ONLY to body, like the old version
        document.body.classList.add(`theme-${themeName}-applied`);

        window.State.activeTheme = themeName;
        window.Settings.update('activeTheme', themeName); // Use Settings.update to handle saving

        // Update Popup UI if visible
        if (window.State.popupElement?.classList.contains('visible') && window.Popup) {
            window.Popup.updateUIState();
        }
    },
    removeActiveTheme() {
        // --- Re-enable this function ---
        if (!window.State.activeTheme) {
            return;
        }
        const currentTheme = window.State.activeTheme;
        this.removeActiveThemeClasses();
        // Disable the theme override stylesheet
        if (this.styleElements[currentTheme]) {
            this.styleElements[currentTheme].disabled = true;
        }
        window.State.activeTheme = null;
        window.Settings.update('activeTheme', null); // Use Settings.update to handle saving
        // Update Popup UI if visible
        if (window.State.popupElement?.classList.contains('visible') && window.Popup) {
            window.Popup.updateUIState();
        }
    },
    removeActiveThemeClasses() {
        // Ensure class is removed ONLY from body if that's where applyTheme adds it
        document.body.classList.remove('theme-dos-applied', 'theme-nature-applied');
    }
};

// Ensure theme resource mapping is set on load
window.ThemeManager.loadThemes();
// ==UserScript==
// @name         AI Studio - UI Module
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  UI Control Module for AI Studio Advanced Control Suite
// @author       You & Gemini
// @match        https://aistudio.google.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // UI Control Module
    // ===================================================
    window.UI = {
        applyChatVisibilityRules() {
            const chatContainer = document.querySelector(window.Config.selectors.chatContainer);
            if (!chatContainer) {
                return; // Exit if container not found
            }
            const allUserTurns = Array.from(chatContainer.querySelectorAll(window.Config.selectors.userTurn));
            const allAiTurns = Array.from(chatContainer.querySelectorAll(window.Config.selectors.aiTurn));
            const allTurns = Array.from(chatContainer.querySelectorAll(
                `${window.Config.selectors.userTurn}, ${window.Config.selectors.aiTurn}`
            ));
            let turnsToShow = [];
            let localDidHideSomething = false;
            const setDisplay = (element, visible) => {
                const targetDisplay = visible ? '' : 'none';
                if (element.style.display !== targetDisplay) {
                    element.style.display = targetDisplay;
                }
            };
            const limitEnabled = window.State.settings.limitHistory;
            const numExchangesToShow = window.State.settings.numTurnsToShow;

            if (!limitEnabled) {
                allTurns.forEach(turn => setDisplay(turn, true));
                localDidHideSomething = false;
            } else {
                if (numExchangesToShow <= 0) {
                    allTurns.forEach(turn => setDisplay(turn, true));
                    localDidHideSomething = false;
                } else {
                    // Robust: Show last N AI turns and their preceding user turns
                    const aiTurns = Array.from(chatContainer.querySelectorAll(window.Config.selectors.aiTurn));
                    const recentAiTurns = aiTurns.slice(-numExchangesToShow);
                    const turnElementsSet = new Set();
                    recentAiTurns.forEach(aiTurn => {
                        turnElementsSet.add(aiTurn);
                        // Find the immediately preceding user turn, if any
                        let previousElement = aiTurn.previousElementSibling;
                        while(previousElement && !previousElement.matches(window.Config.selectors.userTurn) && !previousElement.matches(window.Config.selectors.aiTurn)) {
                            previousElement = previousElement.previousElementSibling;
                        }
                        if (previousElement && previousElement.matches(window.Config.selectors.userTurn)) {
                            turnElementsSet.add(previousElement);
                        }
                    });
                    // Edge case: No AI turns, but user turns exist
                    if (aiTurns.length === 0 && numExchangesToShow >= 1) {
                        const userTurns = Array.from(chatContainer.querySelectorAll(window.Config.selectors.userTurn));
                        if (userTurns.length > 0) {
                            turnElementsSet.add(userTurns[userTurns.length - 1]);
                        }
                    }
                    allTurns.forEach(turn => {
                        const shouldBeVisible = turnElementsSet.has(turn);
                        setDisplay(turn, shouldBeVisible);
                        if (!shouldBeVisible) localDidHideSomething = true;
                    });
                }
            }
            if (window.State.isCurrentlyHidden !== localDidHideSomething) {
                window.State.isCurrentlyHidden = localDidHideSomething;
                if (window.Button && typeof window.Button.updateAppearance === 'function') {
                    window.Button.updateAppearance();
                }
            }
        },
        updateHeadingText() {
            const heading = document.querySelector('h1.gradient-text');
            if (heading && window.State?.settings) {
                heading.textContent = window.State.settings.headingText;
            }
        },
        updatePromptChipsVisibility() {
            const chips = document.querySelector('.chips-container');
            if (chips && window.State?.settings) {
                chips.style.display = window.State.settings.hidePromptChips ? 'none' : '';
            }
        },
        updateInputPlaceholder() {
            const overlay = document.querySelector('.placeholder-overlay');
            if (overlay) {
                overlay.textContent = 'If I tried to write a million words a day...';
            }
        },
        updateTurnFooterVisibility() {
            if (!window.State?.settings) return;

            const footers = document.querySelectorAll('.turn-footer');
            if (footers.length === 0) {
                return;
            }
            const shouldHide = window.State.settings.hideFeedbackButtons;
            footers.forEach(footer => {
                footer.style.display = shouldHide ? 'none' : '';
            });
        },
        applyLayoutRules() {
            const layoutContainer = document.querySelector(window.Config.selectors.overallLayout);
            if (!layoutContainer || !window.State?.settings) {
                return;
            }
            const shouldHideSidebars = window.State.settings.hideSidebars;
            const shouldHideHeader = window.State.settings.hideHeader;
            const shouldHideToolbar = window.State.settings.hideToolbar;
            layoutContainer.classList.toggle(`${window.Config.classes.layoutHide}-sidebars`, shouldHideSidebars);
            layoutContainer.classList.toggle(`${window.Config.classes.layoutHide}-header`, shouldHideHeader);
            layoutContainer.classList.toggle(`${window.Config.classes.layoutHide}-toolbar`, shouldHideToolbar);
            if (window.State.popupElement?.style.display === 'block' && window.Popup) {
                window.Popup.updateUIState();
            }
        }
    };

})();
// watcher.js
// Watches for DOM and settings changes to update UI and controls in AI Studio.

(function() {
    'use strict';

    // Element Watcher Module
    // ===================================================
    window.ElementWatcher = {
        observer: null,
        debounceTimer: null,

        // Map logical UI areas to their corresponding update functions
        uiUpdateFunctions: {
            layout: () => window.UI?.applyLayoutRules(), // Covers sidebars, header, toolbar, input fix
            heading: () => window.UI?.updateHeadingText(),
            promptChips: () => window.UI?.updatePromptChipsVisibility(),
            turnFooters: () => window.UI?.updateTurnFooterVisibility(),
            placeholder: () => window.UI?.updateInputPlaceholder(),
        },

        // Debounced function to handle DOM changes
        handleDomChange() {
            if (!window.UI || !window.State?.settings) return;

            // Ensure the InputLagFix button/modal logic runs if elements appear
            if (window.InputLagFix && typeof window.InputLagFix.init === 'function') {
                window.InputLagFix.init();
            }

            // --- START: Input Lag Fix Button Visibility Control ---
            try {
                const triggerButton = document.getElementById('adv-modal-trigger-btn');
                if (triggerButton) {
                    // Check if the zero-state wrapper exists OR if there are no chat turns yet
                    const isZeroState = !!document.querySelector('.zero-state-wrapper');
                    const hasChatTurns = !!document.querySelector('ms-chat-turn'); // Check if any chat turns exist

                    // Determine if the button should be visible
                    const shouldBeVisible = !isZeroState && hasChatTurns;

                    // Toggle the visibility class based on the state
                    triggerButton.classList.toggle('eic-visible', shouldBeVisible);

                    // Optional cleanup of the default hidden class once visibility is managed
                    if (shouldBeVisible) {
                        triggerButton.classList.remove('eic-hidden-by-default');
                    }

                    // --- Ensure our icon is first in the button container ---
                    // Find the wrapper and its parent container
                    const buttonWrapper = triggerButton.closest('.button-wrapper');
                    const parentContainer = buttonWrapper?.parentElement;
                    if (buttonWrapper && parentContainer && parentContainer.children[0] !== buttonWrapper) {
                        parentContainer.insertBefore(buttonWrapper, parentContainer.firstChild);
                    }
                } else {
                    // If button isn't found, InputLagFix.init() should try to create it on next run
                    // This check prevents errors if InputLagFix hasn't loaded yet
                    if (window.InputLagFix && typeof window.InputLagFix.init === 'function') {
                        window.InputLagFix.init();
                    }
                }
            } catch (error) {}
            // --- END: Input Lag Fix Button Visibility Control ---

            // Always call all UI update functions on DOM change
            window.UI.applyLayoutRules();
            window.UI.updateHeadingText();
            window.UI.updatePromptChipsVisibility();
            window.UI.updateTurnFooterVisibility();
            window.UI.updateInputPlaceholder();

            // --- START: Disclaimer Text Modification ---
            try {
                const disclaimerSpan = document.querySelector('.disclaimer-container span.disclaimer');
                if (disclaimerSpan) {
                    const newDisclaimerText = "This reality is for testing only. No production use.";
                    // Only update if the text is different to avoid unnecessary changes
                    if (disclaimerSpan.textContent.trim() !== newDisclaimerText) {
                        disclaimerSpan.textContent = newDisclaimerText;
                    }
                }
            } catch (error) {}
            // --- END: Disclaimer Text Modification ---

            if (window.UI) {
                window.UI.applyChatVisibilityRules();
            }
            // Update slider max if popup is open
            // Use classList.contains for reliability, as display might be handled by transitions
            if (window.State.popupElement?.classList.contains('visible')) {
                try {
                    const chatContainer = document.querySelector(window.Config.selectors.chatContainer);
                    if (chatContainer) {
                        const aiTurns = chatContainer.querySelectorAll(window.Config.selectors.aiTurn);
                        const maxExchanges = aiTurns.length > 0 ? aiTurns.length : 1;
                        const slider = window.State.popupElement.querySelector('#num-turns-slider');
                        const valueDisplay = window.State.popupElement.querySelector('#num-turns-value');
                        if (slider && valueDisplay) {
                            if (parseInt(slider.max) !== maxExchanges) {
                                slider.max = maxExchanges;
                            }
                            let currentValue = parseInt(slider.value);
                            if (currentValue > maxExchanges) {
                                slider.value = maxExchanges;
                                // Only update value display if NOT in VIBE mode and limiting is ON
                                if (!window.State.isVibeModeActive && window.State.settings.limitHistory) {
                                    valueDisplay.textContent = maxExchanges;
                                }
                                // Update the actual setting if it was capped
                                if (window.State.settings.numTurnsToShow !== maxExchanges) {
                                    // Use setTimeout to avoid potential conflicts if called during another update cycle
                                    setTimeout(() => Settings.update('numTurnsToShow', maxExchanges), 0);
                                }
                            }
                        }
                    }
                } catch (error) {}
            }
        },

        start() {
            if (this.observer) return; // Already started
            if (!window.UI || !window.State?.settings) {
                // Retry starting after a short delay if UI/State aren't ready
                setTimeout(() => this.start(), 500);
                return;
            }

            // --- Setup Mutation Observer ---
            this.observer = new MutationObserver(() => {
                // Debounce the handler
                clearTimeout(this.debounceTimer);
                // Use a reasonable debounce time (e.g., 150-250ms)
                this.debounceTimer = setTimeout(() => this.handleDomChange(), 200);
            });

            // Observe the body for subtree and child list changes
            // Important: Start observing *before* the initial call to handleDomChange
            this.observer.observe(document.body, { childList: true, subtree: true });

            // --- Initial UI Application ---
            // Call handler once shortly after starting observer to catch initial state
            // This ensures elements potentially added *during* script load are handled.
            setTimeout(() => this.handleDomChange(), 50); // Small delay after observer starts

        },

        stop() {
            if (this.observer) {
                this.observer.disconnect();
                this.observer = null;
            }
            if (this.debounceTimer) {
                clearTimeout(this.debounceTimer);
                this.debounceTimer = null;
            }
        }
    };

})();