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

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

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

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

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

你需要先安裝一款使用者腳本管理器擴展,比如 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';

    // 1. Define window.Config, window.State, window.Settings, window.DOM
    // 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,
        uiUpdateDebounceTimer: null // Added debounce timer for UI updates
    };

    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);
        }
    };

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


    // 2. Define window.Styles (consolidated)
    // styles.js
    // Minimal core CSS logic for AI Studio Advanced Control Suite. All main styles are in custom.css.

    window.Styles = {
        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;
            }
        `,
        addCoreStyles() {
            // Inject the core styles string defined above
            if (this.coreStyles) {
                 GM_addStyle(this.coreStyles);
            }
        },
        addPopupStyles() {
            // This function should now do nothing as custom.css handles it.
            // console.log("Popup styles handled by custom.css");
            return;
        }
    };


    // 3. Define window.InputLagFix
    // inputfix.js
    // Provides a modal to fix input lag and manage advanced input in AI Studio.

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


    // 4. Define window.Popup
    // 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) {}
            }
        }
    };


    // 5. Define window.ThemeManager
    // 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');
        }
    };


    // 6. Define window.UI
    // ui.js
    // 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();
            }
        }
    };


    // 7. Define window.Button
    // button.js
    // Provides the floating toggle button for chat visibility and options in AI Studio.

    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
            );
        }
    };


    // 8. Define window.ElementWatcher
    // watcher.js
    // Watches for DOM and settings changes to update UI and controls in AI Studio.

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


    // 9. Define window.App (ONCE)
    // app.js
    // Main application initialization and control logic for AI Studio Advanced Control Suite.

    window.App = {
        themeManagerInitialized: false,
        customStyleElement: null,
        async init() {
            console.log("Combined App Init Start");
            await window.Settings.load();

            // Inject Custom CSS FIRST
            try {
                const customCSSText = GM_getResourceText('CUSTOM_CSS');
                if (customCSSText) {
                    this.customStyleElement = GM_addStyle(customCSSText);
                    console.log("AC Script: Custom CSS injected.");
                } else { console.error("AC Script: Failed to load CUSTOM_CSS"); }
            } catch (e) { console.error("AC Script: Error injecting custom CSS:", e); }

            // Inject Core Styles
            if(window.Styles) window.Styles.addCoreStyles();

            // Register menu command
            if (window.Popup && typeof window.Popup.toggle === 'function') {
                GM_registerMenuCommand('Adv. Control Settings (AI Studio)', window.Popup.toggle);
            } else { console.warn("Popup.toggle not ready for menu command."); }

            // Initialize Theme Manager & Apply Saved Theme
            if (!this.themeManagerInitialized && window.ThemeManager) {
                 console.log("[App] Initializing ThemeManager...");
                 // Call loadThemes HERE
                 if(typeof window.ThemeManager.loadThemes === 'function') {
                     window.ThemeManager.loadThemes();
                 } else { console.error("ThemeManager.loadThemes missing!");}

                 this.themeManagerInitialized = true;
                 console.log("[App] ThemeManager initialized.");

                 const savedTheme = window.State.settings.activeTheme;
                 if (savedTheme && typeof window.ThemeManager.applyTheme === 'function') {
                     console.log(`[App] Applying saved theme: ${savedTheme}`);
                     try {
                         window.ThemeManager.applyTheme(savedTheme);
                         console.log(`[App] Applied saved theme '${savedTheme}'.`);
                     } catch (error) { console.error(`[App] Error applying saved theme '${savedTheme}':`, error); }
                 }
            } else if(!window.ThemeManager) { console.error("ThemeManager not found during App init"); }


            // Initialize UI parts and watcher
            this.initializeProgressively();
             console.log("Combined App Init End");
        },
        initializeProgressively() {
            console.log("Combined App Init Progressive Start");
             // Call UI methods
             if(window.UI) {
                 window.UI.applyChatVisibilityRules();
                 window.UI.applyLayoutRules();
             } else { console.error("window.UI not found for progressive init"); }

             // Start Watcher
             if (window.ElementWatcher) {
                 window.ElementWatcher.start();
             } else { console.error("window.ElementWatcher not found"); }
             console.log("Combined App Init Progressive End");
        }
    };

    // 10. Button Creation Logic
    function createToggleButton() {
        if (window.Button && typeof window.Button.create === 'function') {
             console.log("Calling Button.create");
            window.Button.create();
        } else { console.error("window.Button.create not found");}
    }

    // 11. Final Initialization Execution (AFTER everything is defined)
    if (document.readyState === 'loading') {
        window.addEventListener('DOMContentLoaded', () => {
             console.log("DOM Loaded - Creating Button & Initializing App");
             createToggleButton(); // Create button first
             if (window.App && window.App.init) window.App.init(); // Then init app
             else console.error("App not ready on DOMContentLoaded");
        });
    } else {
         console.log("DOM Ready - Creating Button & Initializing App");
         createToggleButton(); // Create button first
         if (window.App && window.App.init) window.App.init(); // Then init app
         else console.error("App not ready (DOM already loaded)");
    }

})(); // End of SINGLE main IIFE