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 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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;
            }
        }
    };

})();