AI Floating Bubble Pro

A beautifully designed, highly customizable floating AI bubble with comprehensive AI bot list. Opens AI sites in a sidebar-style window.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         AI Floating Bubble Pro
// @version      3.0
// @description  A beautifully designed, highly customizable floating AI bubble with comprehensive AI bot list. Opens AI sites in a sidebar-style window.
// @author       Mayukhjit Chakraborty
// @match        *://*/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @license      MIT
// @namespace    http://tampermonkey.net/
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // Skip on sidebar windows and iframes
    const urlParams = new URLSearchParams(window.location.search);
    if (urlParams.has('ai_sidebar_window') || window.top !== window.self) return;

    // Prevent double-injection (some environments/users may load the script twice)
    if (window.__AI_FLOATING_BUBBLE_PRO__) return;
    if (document.getElementById('aiBubbleContainer')) return;
    window.__AI_FLOATING_BUBBLE_PRO__ = true;

    /**
     * Default configuration
     */
    const DEFAULT_CONFIG = {
        theme: 'auto',
        accentColor: '#6366f1',
        bubbleSize: 56,
        bubbleOpacity: 1,
        menuAnimationSpeed: 0.25,
        sidebarWidth: 420,
        sidebarHeightPercent: 0.92,
        openMode: 'window', // 'window' | 'tab'
        openPromptManagerOnAISites: true,
        fontPreset: 'inter', // 'system' | 'inter' | 'poppins' | 'jetbrains-mono'
        motionPreset: 'lively', // 'subtle' | 'lively'
        enableSounds: false,
        enableTooltips: true,
        compactMode: false,
        showLoginBadges: true,
        snapToCorners: true,
        favorites: [],
        hiddenSites: [],
        position: { left: null, top: null, right: 20, bottom: 20 },
        isHidden: false,
        // Defaults chosen to avoid common browser/app shortcuts
        hotkey: { ctrl: true, shift: true, key: 'Y' },
        // Recovery-friendly: avoids Alt (often intercepted)
        unhideHotkey: { ctrl: true, shift: true, key: 'U' }
    };

    /**
     * AI Sites Database with icons
     */
    const AI_SITES = [
        { id: 'chatgpt', name: "ChatGPT", url: "https://chat.openai.com/", loginNeeded: false, faIcon: "fa-brands fa-openai", color: "#10a37f" },
        { id: 'gemini', name: "Gemini", url: "https://gemini.google.com/", loginNeeded: true, faIcon: "fa-brands fa-google", color: "#4285f4" },
        { id: 'copilot', name: "Copilot", url: "https://copilot.microsoft.com/", loginNeeded: false, faIcon: "fa-brands fa-microsoft", color: "#0078d4" },
        { id: 'meta', name: "Meta AI", url: "https://www.meta.ai/", loginNeeded: false, faIcon: "fa-brands fa-meta", color: "#0866ff" },
        { id: 'perplexity', name: "Perplexity", url: "https://www.perplexity.ai/", loginNeeded: false, faIcon: "fa-solid fa-magnifying-glass", color: "#20808d" },
        { id: 'poe', name: "Poe", url: "https://poe.com/", loginNeeded: true, faIcon: "fa-solid fa-comment-dots", color: "#5a4fcf" },
        { id: 'grok', name: "Grok", url: "https://x.com/i/grok", loginNeeded: true, faIcon: "fa-brands fa-x-twitter", color: "#1da1f2" },
        { id: 'claude', name: "Claude", url: "https://claude.ai/", loginNeeded: true, faIcon: "fa-solid fa-brain", color: "#cc785c" },
        { id: 'qwen', name: "Qwen", url: "https://chat.qwen.ai/", loginNeeded: false, faIcon: "fa-solid fa-globe", color: "#615dfa" },
        { id: 'deepseek', name: "Deepseek", url: "https://chat.deepseek.com/", loginNeeded: true, faIcon: "fa-solid fa-water", color: "#0066ff" },
        { id: 'lmarena', name: "LMArena", url: "https://lmarena.ai/", loginNeeded: false, faIcon: "fa-solid fa-trophy", color: "#f59e0b" },
        { id: 'z', name: "Z", url: "https://chat.z.ai/", loginNeeded: false, faIcon: "fa-solid fa-bolt", color: "#8b5cf6" }
    ];

    const PROMPT_STORAGE_KEY = 'aiBubblePrompts';
    const PROMPT_LIBRARY_VERSION = 2;

    const DEFAULT_PROMPTS = [
        {
            name: 'Summarize with action items',
            text: 'Summarize the following content in bullet points, then list clear action items.\n\n{{content}}',
            tags: ['summary', 'productivity'],
            bots: 'all'
        },
        {
            text: 'Generate a prompt library as STRICT JSON (no Markdown, no comments).\n\nOutput format: either an array of prompt objects, OR {"prompts": [ ... ]}.\n\nEach prompt object must include:\n- name (string)\n- text (string)\n- tags (array of strings) OR tags (comma-separated string)\n- bots: either "all" OR an array of bot ids\n\nBot ids allowed: chatgpt, gemini, copilot, meta, perplexity, poe, grok, claude, qwen, deepseek, lmarena, z\n\nRequirements:\n- Return valid JSON only\n- Include {{count}} prompts\n- Include placeholders like {{content}} where useful\n- Use short, descriptive tags\n\nTheme for the prompts:\n{{theme}}',
            text: 'You are a senior engineer. Ask clarifying questions first, then propose a minimal reproduction, likely root cause, and a fix.\n\nContext:\n{{context}}\n\nCode/logs:\n{{code_or_logs}}',
            tags: ['debug', 'code'],
            bots: 'all'
        },
        {
            name: 'Write a professional email',
            text: 'Write a concise professional email. Tone: {{tone}}. Audience: {{audience}}. Goal: {{goal}}.\n\nKey points:\n{{points}}',
            tags: ['writing', 'email'],
            bots: 'all'
        },
        {
            name: 'Convert notes into a plan',
            text: 'Turn these notes into a clear plan with milestones, risks, and next steps:\n\n{{notes}}',
            tags: ['planning', 'productivity'],
            bots: 'all'
        },
        {
            name: 'Rewrite for clarity (keep meaning)',
            text: 'Rewrite the text to be clearer and more concise while keeping the original meaning. Keep formatting if present.\n\n{{text}}',
            tags: ['writing', 'rewrite'],
            bots: 'all'
        },
        {
            name: 'Tone shift (friendly/professional)',
            text: 'Rewrite this in a {{tone}} tone. Keep it brief and natural.\n\n{{text}}',
            tags: ['writing'],
            bots: 'all'
        },
        {
            name: 'Brainstorm ideas (with constraints)',
            text: 'Brainstorm {{n}} ideas. Constraints: {{constraints}}. For each idea: short description + why it fits + next step.\n\nContext:\n{{context}}',
            tags: ['brainstorm', 'productivity'],
            bots: 'all'
        },
        {
            name: 'Explain like I know basics',
            text: 'Explain this assuming I know the basics but want the key insights, common pitfalls, and a simple example.\n\nTopic:\n{{topic}}',
            tags: ['learning'],
            bots: 'all'
        },
        {
            name: 'Code review checklist',
            text: 'Review this code like a senior engineer. Focus on correctness, readability, edge cases, performance, and maintainability. Provide actionable suggestions and point out risks.\n\nCode:\n{{code}}',
            tags: ['code', 'review'],
            bots: 'all'
        },
        {
            name: 'Refactor plan (safe steps)',
            text: 'Propose a refactor plan with safe incremental steps. Include: goal, current smells, step-by-step plan, risks, and how to validate each step.\n\nContext:\n{{context}}\n\nCode:\n{{code}}',
            tags: ['code', 'refactor'],
            bots: 'all'
        },
        {
            name: 'Write unit tests',
            text: 'Write unit tests for this. Use the existing project patterns if implied. Cover edge cases and failure paths.\n\nCode:\n{{code}}\n\nNotes:\n{{notes}}',
            tags: ['code', 'tests'],
            bots: 'all'
        },
        {
            name: 'Security review (practical)',
            text: 'Do a practical security review: identify likely vulnerabilities, risky assumptions, and suggested mitigations. Keep it grounded and actionable.\n\nSystem description:\n{{system}}\n\nCode/config:\n{{code_or_config}}',
            tags: ['security', 'review'],
            bots: 'all'
        },
        {
            name: 'SQL query helper',
            text: 'Help write a SQL query for {{dialect}}. Provide the query, explain it, and list indices/optimizations if relevant.\n\nSchema:\n{{schema}}\n\nGoal:\n{{goal}}\n\nExample rows (optional):\n{{examples}}',
            tags: ['sql', 'data'],
            bots: 'all'
        },
        {
            name: 'Meeting notes to summary',
            text: 'Summarize these meeting notes into: decisions, action items (owner + due date if present), open questions, and follow-ups.\n\n{{notes}}',
            tags: ['summary', 'meetings'],
            bots: 'all'
        },
        {
            name: 'Compare options (decision matrix)',
            text: 'Compare these options using a simple decision matrix. Include criteria, weights (if provided), pros/cons, and a recommendation.\n\nOptions:\n{{options}}\n\nCriteria (optional):\n{{criteria}}\n\nConstraints:\n{{constraints}}',
            tags: ['planning', 'decision'],
            bots: 'all'
        },
        {
            name: 'Translate (preserve meaning)',
            text: 'Translate this to {{language}}. Preserve meaning, tone, and formatting.\n\n{{text}}',
            tags: ['translation', 'writing'],
            bots: 'all'
        },
        {
            name: 'Create a checklist',
            text: 'Create a clear checklist with grouped sections. Include prerequisites and acceptance criteria.\n\nTask:\n{{task}}\n\nContext:\n{{context}}',
            tags: ['productivity', 'checklist'],
            bots: 'all'
        },
        {
            name: 'Generate prompts JSON (import format)',
            text: 'Generate a prompt library as STRICT JSON (no Markdown, no comments).\n\nOutput format: either an array of prompt objects, OR {"prompts": [ ... ]}.\n\nEach prompt object must include:\n- name (string)\n- text (string)\n- tags (array of strings) OR tags (comma-separated string)\n- bots: either "all" OR an array of bot ids\n\nBot ids allowed: chatgpt, gemini, copilot, perplexity, poe, grok, claude, qwen, deepseek, lmarena, z\n\nRequirements:\n- Return valid JSON only\n- Include {{count}} prompts\n- Include placeholders like {{content}} where useful\n- Use short, descriptive tags\n\nTheme for the prompts:\n{{theme}}',
            tags: ['prompts', 'json'],
            bots: 'all'
        }
    ];

    class PromptLibrary {
        constructor() {
            this._loadedVersion = 0;
            this.prompts = this._load();

            if (!this.prompts || this.prompts.length === 0) {
                this.prompts = this._seedDefaults();
                this._save();
            } else if ((this._loadedVersion || 0) < PROMPT_LIBRARY_VERSION) {
                this._migrateFrom(this._loadedVersion || 0);
            }
        }

        _getRaw() {
            try {
                return typeof GM_getValue !== 'undefined'
                    ? GM_getValue(PROMPT_STORAGE_KEY, null)
                    : localStorage.getItem(PROMPT_STORAGE_KEY);
            } catch (e) {
                return null;
            }
        }

        _setRaw(value) {
            try {
                if (typeof GM_setValue !== 'undefined') {
                    GM_setValue(PROMPT_STORAGE_KEY, value);
                } else {
                    localStorage.setItem(PROMPT_STORAGE_KEY, JSON.stringify(value));
                }
            } catch (e) {
                console.warn('Failed to save prompts:', e);
            }
        }

        _load() {
            try {
                const saved = this._getRaw();
                if (!saved) return [];
                const parsed = typeof saved === 'string' ? JSON.parse(saved) : saved;
                if (Array.isArray(parsed)) return parsed.map(p => this._normalizePrompt(p)).filter(Boolean);
                if (parsed && Array.isArray(parsed.prompts)) {
                    this._loadedVersion = parsed.version || 0;
                    return parsed.prompts.map(p => this._normalizePrompt(p)).filter(Boolean);
                }
                return [];
            } catch (e) {
                console.warn('Failed to load prompts:', e);
                return [];
            }
        }

        _migrateFrom(fromVersion) {
            // v2: add new DEFAULT_PROMPTS (by name) without overwriting user prompts
            const existingByName = new Set(this.prompts.map(p => (p.name || '').toLowerCase()));
            const toAdd = DEFAULT_PROMPTS
                .map(p => this._normalizePrompt(p))
                .filter(Boolean)
                .filter(p => !existingByName.has((p.name || '').toLowerCase()));

            if (toAdd.length > 0) {
                this.prompts.push(...toAdd);
            }

            this._save();
        }

        _save() {
            this._setRaw({ version: PROMPT_LIBRARY_VERSION, prompts: this.prompts });
        }

        _seedDefaults() {
            return DEFAULT_PROMPTS.map(p => this._normalizePrompt(p));
        }

        _uuid() {
            try {
                return (crypto && crypto.randomUUID) ? crypto.randomUUID() : `p_${Date.now()}_${Math.random().toString(16).slice(2)}`;
            } catch {
                return `p_${Date.now()}_${Math.random().toString(16).slice(2)}`;
            }
        }

        _normalizePrompt(p) {
            if (!p) return null;
            const name = (p.name || '').toString().trim();
            const text = (p.text || p.prompt || '').toString();
            if (!name || !text) return null;

            const tags = Array.isArray(p.tags)
                ? p.tags.map(t => (t || '').toString().trim()).filter(Boolean)
                : (typeof p.tags === 'string'
                    ? p.tags.split(',').map(t => t.trim()).filter(Boolean)
                    : []);

            let bots = p.bots;
            if (!bots) bots = 'all';
            if (bots !== 'all' && !Array.isArray(bots)) bots = 'all';

            const now = new Date().toISOString();
            return {
                id: (p.id || '').toString().trim() || this._uuid(),
                name,
                text,
                tags,
                bots,
                createdAt: p.createdAt || now,
                updatedAt: p.updatedAt || now
            };
        }

        list() {
            return [...this.prompts].sort((a, b) => (a.name || '').localeCompare(b.name || ''));
        }

        upsert(prompt) {
            const normalized = this._normalizePrompt(prompt);
            if (!normalized) return null;
            const idx = this.prompts.findIndex(p => p.id === normalized.id);
            const now = new Date().toISOString();
            if (idx >= 0) {
                this.prompts[idx] = { ...this.prompts[idx], ...normalized, updatedAt: now };
            } else {
                this.prompts.push({ ...normalized, createdAt: now, updatedAt: now });
            }
            this._save();
            return normalized;
        }

        remove(id) {
            const before = this.prompts.length;
            this.prompts = this.prompts.filter(p => p.id !== id);
            if (this.prompts.length !== before) this._save();
        }

        importFromJson(jsonText) {
            const parsed = JSON.parse(jsonText);
            const items = Array.isArray(parsed) ? parsed : (parsed && Array.isArray(parsed.prompts) ? parsed.prompts : []);
            if (!Array.isArray(items)) return { imported: 0, skipped: 0 };

            let imported = 0;
            let skipped = 0;

            items.forEach((raw) => {
                const normalized = this._normalizePrompt(raw);
                if (!normalized) {
                    skipped++;
                    return;
                }

                const byId = this.prompts.findIndex(p => p.id === normalized.id);
                const byName = this.prompts.findIndex(p => p.name.toLowerCase() === normalized.name.toLowerCase());
                const idx = byId >= 0 ? byId : byName;

                if (idx >= 0) {
                    this.prompts[idx] = { ...this.prompts[idx], ...normalized, updatedAt: new Date().toISOString() };
                } else {
                    this.prompts.push(normalized);
                }
                imported++;
            });

            this._save();
            return { imported, skipped };
        }
    }

    /**
     * SVG Icons
     */
    const ICONS = {
        bubble: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M12 2a4 4 0 0 1 4 4v1a4 4 0 0 1-8 0V6a4 4 0 0 1 4-4z"/>
            <path d="M8 14h8"/>
            <path d="M8 18h8"/>
            <rect x="6" y="10" width="12" height="12" rx="2"/>
            <circle cx="9" cy="22" r="1"/>
            <circle cx="15" cy="22" r="1"/>
        </svg>`,
        sparkle: `<svg viewBox="0 0 24 24" fill="currentColor">
            <path d="M12 0L14.59 8.41L23 11L14.59 13.59L12 22L9.41 13.59L1 11L9.41 8.41L12 0Z"/>
            <path d="M5 2L5.7 4.3L8 5L5.7 5.7L5 8L4.3 5.7L2 5L4.3 4.3L5 2Z" opacity="0.6"/>
            <path d="M19 14L19.54 15.46L21 16L19.54 16.54L19 18L18.46 16.54L17 16L18.46 15.46L19 14Z" opacity="0.6"/>
        </svg>`,
        settings: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <circle cx="12" cy="12" r="3"/>
            <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
        </svg>`,
        close: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <line x1="18" y1="6" x2="6" y2="18"/>
            <line x1="6" y1="6" x2="18" y2="18"/>
        </svg>`,
        star: `<svg viewBox="0 0 24 24" fill="currentColor">
            <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
        </svg>`,
        starOutline: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
        </svg>`,
        hide: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/>
            <line x1="1" y1="1" x2="23" y2="23"/>
        </svg>`,
        resize: `<svg viewBox="0 0 24 24" fill="currentColor">
            <path d="M22 22H20V20H22V22ZM22 18H20V16H22V18ZM18 22H16V20H18V22ZM22 14H20V12H22V14ZM18 18H16V16H18V18ZM14 22H12V20H14V22Z"/>
        </svg>`,
        search: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <circle cx="11" cy="11" r="8"/>
            <path d="m21 21-4.35-4.35"/>
        </svg>`,
        login: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/>
            <polyline points="10 17 15 12 10 7"/>
            <line x1="15" y1="12" x2="3" y2="12"/>
        </svg>`,
        externalLink: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
            <polyline points="15 3 21 3 21 9"/>
            <line x1="10" y1="14" x2="21" y2="3"/>
        </svg>`,
        palette: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <circle cx="13.5" cy="6.5" r="0.5" fill="currentColor"/>
            <circle cx="17.5" cy="10.5" r="0.5" fill="currentColor"/>
            <circle cx="8.5" cy="7.5" r="0.5" fill="currentColor"/>
            <circle cx="6.5" cy="12.5" r="0.5" fill="currentColor"/>
            <path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.555C21.965 6.012 17.461 2 12 2z"/>
        </svg>`,
        moon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
        </svg>`,
        sun: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <circle cx="12" cy="12" r="5"/>
            <line x1="12" y1="1" x2="12" y2="3"/>
            <line x1="12" y1="21" x2="12" y2="23"/>
            <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
            <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
            <line x1="1" y1="12" x2="3" y2="12"/>
            <line x1="21" y1="12" x2="23" y2="12"/>
            <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
            <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
        </svg>`,
        monitor: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
            <line x1="8" y1="21" x2="16" y2="21"/>
            <line x1="12" y1="17" x2="12" y2="21"/>
        </svg>`,
        keyboard: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <rect x="2" y="4" width="20" height="16" rx="2" ry="2"/>
            <path d="M6 8h.001"/>
            <path d="M10 8h.001"/>
            <path d="M14 8h.001"/>
            <path d="M18 8h.001"/>
            <path d="M8 12h.001"/>
            <path d="M12 12h.001"/>
            <path d="M16 12h.001"/>
            <path d="M7 16h10"/>
        </svg>`,
        sliders: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <line x1="4" y1="21" x2="4" y2="14"/>
            <line x1="4" y1="10" x2="4" y2="3"/>
            <line x1="12" y1="21" x2="12" y2="12"/>
            <line x1="12" y1="8" x2="12" y2="3"/>
            <line x1="20" y1="21" x2="20" y2="16"/>
            <line x1="20" y1="12" x2="20" y2="3"/>
            <line x1="1" y1="14" x2="7" y2="14"/>
            <line x1="9" y1="8" x2="15" y2="8"/>
            <line x1="17" y1="16" x2="23" y2="16"/>
        </svg>`,
        reset: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
            <path d="M3 3v5h5"/>
        </svg>`
    };

    /**
     * Configuration Manager
     */
    class ConfigManager {
        constructor() {
            this.config = this._loadConfig();
        }

        _loadConfig() {
            try {
                const saved = typeof GM_getValue !== 'undefined'
                    ? GM_getValue('aiBubbleConfig', null)
                    : localStorage.getItem('aiBubbleConfig');

                if (saved) {
                    const parsed = typeof saved === 'string' ? JSON.parse(saved) : saved;
                    return { ...DEFAULT_CONFIG, ...parsed };
                }
            } catch (e) {
                console.warn('Failed to load config:', e);
            }
            return { ...DEFAULT_CONFIG };
        }

        save() {
            try {
                if (typeof GM_setValue !== 'undefined') {
                    GM_setValue('aiBubbleConfig', this.config);
                } else {
                    localStorage.setItem('aiBubbleConfig', JSON.stringify(this.config));
                }
            } catch (e) {
                console.warn('Failed to save config:', e);
            }
        }

        get(key) {
            return this.config[key];
        }

        set(key, value) {
            this.config[key] = value;
            this.save();
        }

        reset() {
            this.config = { ...DEFAULT_CONFIG };
            this.save();
        }
    }

    /**
     * Theme Manager
     */
    class ThemeManager {
        constructor(configManager) {
            this.configManager = configManager;
            this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
            this._setupMediaQueryListener();
        }

        _setupMediaQueryListener() {
            this.mediaQuery.addEventListener('change', () => {
                if (this.configManager.get('theme') === 'auto') {
                    this._applyTheme();
                }
            });
        }

        _applyTheme() {
            const theme = this.configManager.get('theme');
            const isDark = theme === 'dark' || (theme === 'auto' && this.mediaQuery.matches);
            document.documentElement.setAttribute('data-ai-bubble-theme', isDark ? 'dark' : 'light');
        }

        getCurrentTheme() {
            const theme = this.configManager.get('theme');
            if (theme === 'auto') {
                return this.mediaQuery.matches ? 'dark' : 'light';
            }
            return theme;
        }

        setTheme(theme) {
            this.configManager.set('theme', theme);
            this._applyTheme();
        }

        init() {
            this._applyTheme();
        }
    }

    /**
     * Sound Manager
     */
    class SoundManager {
        constructor(configManager) {
            this.configManager = configManager;
            this.audioContext = null;
        }

        _getContext() {
            if (!this.audioContext) {
                this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
            }
            return this.audioContext;
        }

        play(type = 'click') {
            if (!this.configManager.get('enableSounds')) return;

            try {
                const ctx = this._getContext();
                const oscillator = ctx.createOscillator();
                const gainNode = ctx.createGain();

                oscillator.connect(gainNode);
                gainNode.connect(ctx.destination);

                const sounds = {
                    click: { freq: 600, duration: 0.05, type: 'sine' },
                    open: { freq: 800, duration: 0.1, type: 'sine' },
                    close: { freq: 400, duration: 0.08, type: 'sine' }
                };

                const sound = sounds[type] || sounds.click;
                oscillator.type = sound.type;
                oscillator.frequency.setValueAtTime(sound.freq, ctx.currentTime);
                gainNode.gain.setValueAtTime(0.1, ctx.currentTime);
                gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + sound.duration);

                oscillator.start(ctx.currentTime);
                oscillator.stop(ctx.currentTime + sound.duration);
            } catch (e) {
                // Silently fail if audio doesn't work
            }
        }
    }

    /**
     * Main Application Class
     */
    class AIFloatingBubble {
        constructor() {
            this.configManager = new ConfigManager();
            this.themeManager = new ThemeManager(this.configManager);
            this.soundManager = new SoundManager(this.configManager);
            this.promptLibrary = new PromptLibrary();

            this.elements = {};
            this.state = {
                isDragging: false,
                isResizing: false,
                isSidebarResizing: false,
                isMenuOpen: false,
                isSettingsOpen: false,
                isPromptsOpen: false,
                searchQuery: '',
                promptSearchQuery: '',
                promptTagFilter: '',
                lastFavoriteToggle: null,
                suppressClickUntil: 0,
                offsetX: 0,
                offsetY: 0,
                startSize: 0,
                startX: 0,
                startY: 0
            };

            this.hideTimeout = null;
            this.rafId = null;
            this._lastEscapeAt = 0;

            this._init();
        }

        _init() {
            this.themeManager.init();
            this._injectExternalAssets();
            this._injectStyles();
            this._createElements();
            this._loadState();
            this._setupEventListeners();
            this._updateBubbleSize();
            this._updateAccentColor();
            this._applyTypography();
            this._applyMotionPreset();

            const currentBotId = this._getCurrentBotId();
            if (currentBotId && this.configManager.get('openPromptManagerOnAISites')) {
                setTimeout(() => {
                    if (!this.state.isMenuOpen && !this.state.isSettingsOpen && !this.state.isPromptsOpen) {
                        this._openPrompts();
                    }
                }, 350);
            }

            // On supported AI sites, inject an in-chat button to open Prompt Manager
            if (currentBotId) {
                this._setupChatIntegration();
            }
        }

        _setupChatIntegration() {
            const inject = () => {
                const input = this._findChatInput();
                if (!input) return;
                this._injectPromptButtonIntoChat(input);
            };

            inject();

            // SPAs often re-render the composer
            this._chatObserver?.disconnect?.();
            this._chatObserver = new MutationObserver(() => inject());
            this._chatObserver.observe(document.documentElement, { childList: true, subtree: true });
        }

        _injectPromptButtonIntoChat(inputEl) {
            try {
                if (!inputEl) return;
                if (inputEl.dataset.aiBubblePromptBtnAttached) return;

                const rect = inputEl.getBoundingClientRect?.();
                if (!rect || rect.width < 240 || rect.height < 24) return;

                // Choose a host container (something stable around the composer)
                let host = inputEl.closest('[role="textbox"], textarea, .composer, .chat-input, form') || inputEl.parentElement;
                if (!host) return;

                const style = window.getComputedStyle(host);
                if (style.position === 'static') host.style.position = 'relative';

                const btn = document.createElement('button');
                btn.type = 'button';
                btn.className = 'ai-chat-prompts-btn';
                btn.setAttribute('aria-label', 'Open Prompt Manager');
                btn.innerHTML = this._fa('fa-solid fa-book');
                btn.addEventListener('click', (e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    this.soundManager.play('click');
                    this._openPrompts();
                });

                host.appendChild(btn);
                inputEl.dataset.aiBubblePromptBtnAttached = '1';
            } catch {
                // ignore
            }
        }

        _isProbablyChatInput(el) {
            if (!el) return false;

            const tag = (el.tagName || '').toUpperCase();
            if (tag === 'INPUT') return false;

            if (tag !== 'TEXTAREA') {
                const ce = el.getAttribute?.('contenteditable');
                if (ce !== 'true') return false;
                const role = (el.getAttribute?.('role') || '').toLowerCase();
                if (role && role !== 'textbox') return false;
            }

            const rect = el.getBoundingClientRect?.();
            if (!rect || rect.width < 240 || rect.height < 24) return false;
            if (rect.bottom < window.innerHeight * 0.55) return false;

            const ph = (el.getAttribute?.('placeholder') || '').toLowerCase();
            const aria = (el.getAttribute?.('aria-label') || '').toLowerCase();
            const id = (el.id || '').toLowerCase();
            const name = (el.getAttribute?.('name') || '').toLowerCase();
            const combined = `${ph} ${aria} ${id} ${name}`;

            const rejectWords = ['email', 'e-mail', 'username', 'password', 'sign in', 'log in', 'login', 'search'];
            if (rejectWords.some(w => combined.includes(w))) return false;

            const form = el.closest?.('form');
            if (form) {
                const formText = `${form.getAttribute?.('action') || ''} ${form.className || ''} ${form.id || ''}`.toLowerCase();
                if (formText.includes('login') || formText.includes('signin') || formText.includes('auth')) return false;
                if (form.querySelector?.('input[type="password"]')) return false;
            }

            return true;
        }

        _injectExternalAssets() {
            // Font Awesome (best-effort; may be blocked by CSP on some sites)
            const faId = 'aiBubbleFontAwesome';
            if (!document.getElementById(faId)) {
                const link = document.createElement('link');
                link.id = faId;
                link.rel = 'stylesheet';
                const cdns = [
                    'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css',
                    'https://cdn.jsdelivr.net/npm/@fortawesome/[email protected]/css/all.min.css'
                ];
                link.href = cdns[0];
                link.onerror = () => {
                    if (link.dataset.fallbackTried) return;
                    link.dataset.fallbackTried = '1';
                    link.href = cdns[1];
                };
                document.head.appendChild(link);
            }
        }

        _injectStyles() {
            const accentColor = this.configManager.get('accentColor');
            GM_addStyle(`
                /* CSS Variables & Theme Support */
                :root {
                    --ai-bubble-accent: ${accentColor};
                    --ai-bubble-accent-rgb: ${this._hexToRgb(accentColor)};
                }

                [data-ai-bubble-theme="light"] {
                    --ai-bubble-bg: rgba(255, 255, 255, 0.95);
                    --ai-bubble-bg-solid: #ffffff;
                    --ai-bubble-text: #1a1a2e;
                    --ai-bubble-text-secondary: #64748b;
                    --ai-bubble-border: rgba(0, 0, 0, 0.08);
                    --ai-bubble-hover: rgba(99, 102, 241, 0.08);
                    --ai-bubble-shadow: 0 20px 60px rgba(0, 0, 0, 0.15), 0 8px 25px rgba(0, 0, 0, 0.1);
                    --ai-bubble-shadow-sm: 0 4px 15px rgba(0, 0, 0, 0.1);
                    --ai-bubble-input-bg: #f8fafc;
                }

                [data-ai-bubble-theme="dark"] {
                    --ai-bubble-bg: rgba(30, 30, 46, 0.95);
                    --ai-bubble-bg-solid: #1e1e2e;
                    --ai-bubble-text: #e2e8f0;
                    --ai-bubble-text-secondary: #94a3b8;
                    --ai-bubble-border: rgba(255, 255, 255, 0.1);
                    --ai-bubble-hover: rgba(99, 102, 241, 0.15);
                    --ai-bubble-shadow: 0 20px 60px rgba(0, 0, 0, 0.4), 0 8px 25px rgba(0, 0, 0, 0.3);
                    --ai-bubble-shadow-sm: 0 4px 15px rgba(0, 0, 0, 0.3);
                    --ai-bubble-input-bg: rgba(0, 0, 0, 0.2);
                }

                /* Animations */
                @keyframes aiBubblePulse {
                    0%, 100% { box-shadow: 0 0 0 0 rgba(var(--ai-bubble-accent-rgb), 0.4); }
                    50% { box-shadow: 0 0 0 8px rgba(var(--ai-bubble-accent-rgb), 0); }
                }

                @keyframes aiBubbleFloat {
                    0%, 100% { transform: translateY(0); }
                    50% { transform: translateY(-3px); }
                }

                @keyframes aiBubblePop {
                    0% { transform: scale(1); }
                    40% { transform: scale(1.15); }
                    100% { transform: scale(1); }
                }

                @keyframes aiBubbleSplash {
                    0% { transform: scale(0.2); opacity: 0.0; }
                    20% { opacity: 0.65; }
                    100% { transform: scale(2.8); opacity: 0; }
                }

                @keyframes aiBubbleWiggle {
                    0%, 100% { transform: translateY(0) rotate(0deg); }
                    35% { transform: translateY(-4px) rotate(-2deg); }
                    70% { transform: translateY(-2px) rotate(2deg); }
                }

                @keyframes aiBubbleSparkle {
                    0%, 100% { opacity: 1; transform: scale(1) rotate(0deg); }
                    50% { opacity: 0.8; transform: scale(0.95) rotate(5deg); }
                }

                @keyframes aiFavoriteStar {
                    0% { transform: scale(0.85) rotate(-8deg); }
                    45% { transform: scale(1.25) rotate(6deg); }
                    100% { transform: scale(1) rotate(0deg); }
                }

                @keyframes aiBubbleMenuSlideIn {
                    from {
                        opacity: 0;
                        transform: scale(0.9) translateY(10px);
                    }
                    to {
                        opacity: 1;
                        transform: scale(1) translateY(0);
                    }
                }

                @keyframes aiBubbleMenuSlideOut {
                    from {
                        opacity: 1;
                        transform: scale(1) translateY(0);
                    }
                    to {
                        opacity: 0;
                        transform: scale(0.9) translateY(10px);
                    }
                }

                @keyframes aiBubbleItemSlideIn {
                    from {
                        opacity: 0;
                        transform: translateX(-10px);
                    }
                    to {
                        opacity: 1;
                        transform: translateX(0);
                    }
                }

                @keyframes aiBubbleSettingsSlide {
                    from {
                        opacity: 0;
                        transform: translateX(20px);
                    }
                    to {
                        opacity: 1;
                        transform: translateX(0);
                    }
                }

                @keyframes aiBubbleRipple {
                    to {
                        transform: scale(4);
                        opacity: 0;
                    }
                }

                @keyframes aiBubbleShake {
                    0%, 100% { transform: translateX(0); }
                    25% { transform: translateX(-2px); }
                    75% { transform: translateX(2px); }
                }

                /* Main Container */
                #aiBubbleContainer {
                    position: fixed;
                    z-index: 2147483647;
                    font-family: var(--ai-bubble-font, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif);
                    transition: opacity 0.3s ease;
                }

                /* Ensure typography applies to all controls */
                #aiBubbleContainer button,
                #aiBubbleContainer input,
                #aiBubbleContainer select,
                #aiBubbleContainer textarea {
                    font-family: inherit;
                }

                #aiBubbleModalOverlay,
                #aiBubbleModalOverlay button,
                #aiBubbleModalOverlay input,
                #aiBubbleModalOverlay select,
                #aiBubbleModalOverlay textarea {
                    font-family: var(--ai-bubble-font, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif);
                }

                /* In-chat prompts button (on supported AI sites) */
                .ai-chat-prompts-btn {
                    position: absolute;
                    right: 10px;
                    bottom: 10px;
                    width: 28px;
                    height: 28px;
                    border-radius: 8px;
                    border: 1px solid rgba(255,255,255,0.18);
                    background: color-mix(in srgb, var(--ai-bubble-accent) 85%, transparent);
                    color: white;
                    cursor: pointer;
                    display: inline-flex;
                    align-items: center;
                    justify-content: center;
                    opacity: 0.9;
                    transition: transform 0.15s ease, opacity 0.15s ease;
                    z-index: 10;
                }

                .ai-chat-prompts-btn:hover {
                    opacity: 1;
                    transform: scale(1.06);
                }

                .ai-chat-prompts-btn i {
                    font-size: 14px;
                }

                #aiBubbleContainer i.fa-solid,
                #aiBubbleContainer i.fa-regular,
                #aiBubbleContainer i.fa-brands {
                    line-height: 1;
                }

                #aiBubbleContainer i.fa-solid,
                #aiBubbleContainer i.fa-regular,
                #aiBubbleContainer i.fa-brands {
                    display: inline-flex;
                    align-items: center;
                    justify-content: center;
                }

                #aiBubbleContainer.hidden {
                    opacity: 0;
                    pointer-events: none;
                }

                #aiBubbleContainer.dragging {
                    cursor: grabbing !important;
                }

                #aiBubbleContainer.dragging * {
                    cursor: grabbing !important;
                }

                /* Main Bubble Button */
                #aiBubbleButton {
                    width: var(--bubble-size, 56px);
                    height: var(--bubble-size, 56px);
                    border-radius: 50%;
                    background: linear-gradient(135deg, var(--ai-bubble-accent) 0%, color-mix(in srgb, var(--ai-bubble-accent) 70%, #8b5cf6) 100%);
                    border: none;
                    cursor: grab;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    box-shadow: var(--ai-bubble-shadow-sm), 0 0 20px rgba(var(--ai-bubble-accent-rgb), 0.3);
                    transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.3s ease;
                    position: relative;
                    overflow: hidden;
                    animation: aiBubbleWiggle var(--ai-bubble-wiggle-duration, 3.1s) ease-in-out infinite;
                    opacity: var(--bubble-opacity, 1);
                }

                #aiBubbleButton.popping {
                    animation: aiBubblePop 0.38s cubic-bezier(0.34, 1.56, 0.64, 1) both;
                }

                #aiBubbleButton .splash {
                    position: absolute;
                    inset: -6px;
                    border-radius: 999px;
                    border: 2px solid rgba(255,255,255,0.45);
                    opacity: 0;
                    pointer-events: none;
                }

                #aiBubbleButton.splashing .splash {
                    animation: aiBubbleSplash 0.65s ease-out;
                }

                #aiBubbleButton::before {
                    content: '';
                    position: absolute;
                    inset: 0;
                    border-radius: 50%;
                    background: linear-gradient(135deg, rgba(255,255,255,0.3) 0%, transparent 50%);
                    pointer-events: none;
                }

                #aiBubbleButton:hover {
                    transform: scale(1.1);
                    box-shadow: var(--ai-bubble-shadow), 0 0 30px rgba(var(--ai-bubble-accent-rgb), 0.4);
                    animation: none;
                }

                #aiBubbleButton:active {
                    transform: scale(0.95);
                    cursor: grabbing;
                }

                #aiBubbleButton .bubble-icon {
                    width: 60%;
                    height: 60%;
                    color: white;
                    filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
                    animation: aiBubbleSparkle 2s ease-in-out infinite;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                }

                #aiBubbleButton .bubble-icon i {
                    font-size: var(--ai-bubble-main-icon-size, 22px);
                }

                /* Ripple Effect */
                .ai-ripple {
                    position: absolute;
                    border-radius: 50%;
                    background: rgba(255, 255, 255, 0.4);
                    transform: scale(0);
                    animation: aiBubbleRipple 0.6s linear;
                    pointer-events: none;
                }

                /* Resize Handle */
                #aiBubbleResize {
                    position: absolute;
                    bottom: -4px;
                    right: -4px;
                    width: var(--ai-bubble-resize-size, 16px);
                    height: var(--ai-bubble-resize-size, 16px);
                    cursor: se-resize;
                    opacity: 0;
                    transition: opacity 0.2s ease;
                    color: white;
                    filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));
                }

                #aiBubbleResize svg {
                    width: 100%;
                    height: 100%;
                    display: block;
                }

                #aiBubbleContainer:hover #aiBubbleResize {
                    opacity: 0.7;
                }

                #aiBubbleResize:hover {
                    opacity: 1 !important;
                }

                /* Sidebar Window Resize Handle (appears only when popup is open) */
                #aiSidebarResize {
                    position: absolute;
                    top: auto;
                    right: auto;
                    bottom: auto;
                    left: auto;
                    width: var(--ai-bubble-resize-size, 16px);
                    height: var(--ai-bubble-resize-size, 16px);
                    cursor: nwse-resize;
                    opacity: 0;
                    transition: opacity 0.2s ease, transform 0.15s ease;
                    color: white;
                    filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));
                    pointer-events: none;
                }

                #aiSidebarResize svg {
                    width: 100%;
                    height: 100%;
                    display: block;
                }

                #aiBubbleContainer.sidebar-open:hover #aiSidebarResize {
                    opacity: 0.7;
                    pointer-events: auto;
                }

                #aiBubbleContainer.sidebar-open #aiSidebarResize:hover {
                    opacity: 1 !important;
                    transform: scale(1.06);
                }

                /* Corner-aware placement + cursor */
                #aiBubbleContainer.sidebar-resize-br #aiSidebarResize {
                    right: -4px;
                    bottom: -4px;
                    cursor: se-resize;
                }
                #aiBubbleContainer.sidebar-resize-bl #aiSidebarResize {
                    left: -4px;
                    bottom: -4px;
                    cursor: sw-resize;
                }
                #aiBubbleContainer.sidebar-resize-tr #aiSidebarResize {
                    right: -4px;
                    top: -4px;
                    cursor: ne-resize;
                }
                #aiBubbleContainer.sidebar-resize-tl #aiSidebarResize {
                    left: -4px;
                    top: -4px;
                    cursor: nw-resize;
                }

                #aiBubbleContainer.sidebar-resizing,
                #aiBubbleContainer.sidebar-resizing * {
                    cursor: var(--ai-sidebar-resize-cursor, nwse-resize) !important;
                }

                /* Menu Container */
                #aiBubbleMenu {
                    position: absolute;
                    bottom: calc(var(--bubble-size, 56px) + 12px);
                    right: 0;
                    min-width: 280px;
                    max-width: 340px;
                    background: var(--ai-bubble-bg);
                    backdrop-filter: blur(20px);
                    -webkit-backdrop-filter: blur(20px);
                    border-radius: 16px;
                    border: 1px solid var(--ai-bubble-border);
                    box-shadow: var(--ai-bubble-shadow);
                    overflow: hidden;
                    opacity: 0;
                    pointer-events: none;
                    transform: scale(0.9) translateY(10px);
                    transform-origin: bottom right;
                    transition: none;
                }

                #aiBubbleMenu.visible {
                    opacity: 1;
                    pointer-events: auto;
                    transform: scale(1) translateY(0);
                    animation: aiBubbleMenuSlideIn 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
                }

                #aiBubbleMenu.closing {
                    animation: aiBubbleMenuSlideOut 0.2s ease-out forwards;
                }

                /* Search Container */
                .ai-menu-search {
                    padding: 12px;
                    border-bottom: 1px solid var(--ai-bubble-border);
                }

                .ai-search-wrapper {
                    position: relative;
                    display: flex;
                    align-items: center;
                }

                .ai-search-icon {
                    position: absolute;
                    left: 12px;
                    width: 16px;
                    height: 16px;
                    color: var(--ai-bubble-text-secondary);
                    pointer-events: none;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                }

                .ai-search-icon i {
                    font-size: 14px;
                }

                .ai-search-input {
                    width: 100%;
                    padding: 10px 12px 10px 38px;
                    border: 1px solid var(--ai-bubble-border);
                    border-radius: 10px;
                    background: var(--ai-bubble-input-bg);
                    color: var(--ai-bubble-text);
                    font-size: 14px;
                    font-family: inherit;
                    outline: none;
                    transition: border-color 0.2s ease, box-shadow 0.2s ease;
                }

                .ai-search-input::placeholder {
                    color: var(--ai-bubble-text-secondary);
                }

                .ai-search-input:focus {
                    border-color: var(--ai-bubble-accent);
                    box-shadow: 0 0 0 3px rgba(var(--ai-bubble-accent-rgb), 0.15);
                }

                /* Menu Items List */
                .ai-menu-list {
                    max-height: 320px;
                    overflow-y: auto;
                    overflow-x: hidden;
                    padding: 8px;
                    scrollbar-width: thin;
                    scrollbar-color: var(--ai-bubble-border) transparent;
                }

                .ai-menu-list::-webkit-scrollbar {
                    width: 6px;
                }

                .ai-menu-list::-webkit-scrollbar-track {
                    background: transparent;
                }

                .ai-menu-list::-webkit-scrollbar-thumb {
                    background: var(--ai-bubble-border);
                    border-radius: 3px;
                }

                /* Menu Item */
                .ai-menu-item {
                    display: flex;
                    align-items: center;
                    gap: 12px;
                    padding: 12px;
                    border-radius: 10px;
                    cursor: pointer;
                    transition: all 0.2s ease;
                    position: relative;
                    overflow: hidden;
                    background: transparent;
                    border: none;
                    width: 100%;
                    text-align: left;
                    color: var(--ai-bubble-text);
                    font-size: 14px;
                    font-family: inherit;
                }

                .ai-menu-item:hover {
                    background: var(--ai-bubble-hover);
                }

                .ai-menu-item:focus {
                    outline: none;
                    background: var(--ai-bubble-hover);
                    box-shadow: inset 0 0 0 2px var(--ai-bubble-accent);
                }

                .ai-menu-item.visible {
                    animation: aiBubbleItemSlideIn 0.2s ease-out forwards;
                }

                .ai-menu-item-icon {
                    width: 36px;
                    height: 36px;
                    border-radius: 10px;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    flex-shrink: 0;
                    transition: transform 0.2s ease;
                    overflow: hidden;
                }

                .ai-menu-item-icon .ai-bot-favicon {
                    width: 22px;
                    height: 22px;
                    border-radius: 6px;
                    display: block;
                }

                .ai-menu-item-icon .ai-bot-fallback {
                    width: 22px;
                    height: 22px;
                    display: none;
                    align-items: center;
                    justify-content: center;
                    color: currentColor;
                }

                .ai-menu-item-icon .ai-bot-fallback i {
                    font-size: 16px;
                }

                .ai-menu-item:hover .ai-menu-item-icon {
                    transform: scale(1.1);
                }

                .ai-menu-item-content {
                    flex: 1;
                    min-width: 0;
                }

                .ai-menu-item-name {
                    font-weight: 500;
                    color: var(--ai-bubble-text);
                    white-space: nowrap;
                    overflow: hidden;
                    text-overflow: ellipsis;
                }

                .ai-menu-item-badge {
                    font-size: 11px;
                    color: var(--ai-bubble-text-secondary);
                    display: flex;
                    align-items: center;
                    gap: 4px;
                    margin-top: 2px;
                }

                .ai-menu-item-badge svg,
                .ai-menu-item-badge i {
                    width: 12px;
                    height: 12px;
                }

                .ai-menu-item-actions {
                    display: flex;
                    align-items: center;
                    gap: 4px;
                    opacity: 0;
                    transition: opacity 0.2s ease;
                }

                .ai-menu-item-actions i {
                    display: inline-flex;
                    align-items: center;
                    justify-content: center;
                }

                .ai-menu-item:hover .ai-menu-item-actions {
                    opacity: 1;
                }

                .ai-menu-item-fav {
                    width: 28px;
                    height: 28px;
                    border-radius: 6px;
                    border: none;
                    background: transparent;
                    cursor: pointer;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    color: var(--ai-bubble-text-secondary);
                    transition: all 0.2s ease;
                    padding: 0;
                }

                .ai-menu-item-fav i {
                    transition: transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1), color 0.2s ease;
                    transform-origin: center;
                    will-change: transform;
                }

                .ai-menu-item-fav.star-anim i {
                    animation: aiFavoriteStar 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both;
                }

                .ai-menu-item-fav:hover {
                    background: rgba(var(--ai-bubble-accent-rgb), 0.15);
                    color: var(--ai-bubble-accent);
                }

                .ai-menu-item-fav.active {
                    color: #f59e0b;
                }

                .ai-menu-item-fav svg,
                .ai-menu-item-fav i {
                    width: 16px;
                    height: 16px;
                }

                .ai-menu-item-fav i { font-size: 16px; }

                .ai-menu-item-open {
                    width: 28px;
                    height: 28px;
                    border-radius: 6px;
                    border: none;
                    background: var(--ai-bubble-accent);
                    cursor: pointer;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    color: white;
                    transition: all 0.2s ease;
                    padding: 0;
                }

                .ai-menu-item-open.secondary {
                    background: transparent;
                    border: 1px solid var(--ai-bubble-border);
                    color: var(--ai-bubble-text);
                }

                .ai-menu-item-open:hover {
                    transform: scale(1.1);
                }

                .ai-menu-item-open svg,
                .ai-menu-item-open i {
                    width: 14px;
                    height: 14px;
                }

                .ai-menu-item-open i { font-size: 14px; }

                /* Menu Section Divider */
                .ai-menu-divider {
                    height: 1px;
                    background: var(--ai-bubble-border);
                    margin: 8px 12px;
                }

                .ai-menu-section-title {
                    padding: 8px 12px 4px;
                    font-size: 11px;
                    font-weight: 600;
                    text-transform: uppercase;
                    letter-spacing: 0.5px;
                    color: var(--ai-bubble-text-secondary);
                }

                /* Menu Footer */
                .ai-menu-footer {
                    padding: 12px;
                    border-top: 1px solid var(--ai-bubble-border);
                    display: flex;
                    gap: 8px;
                }

                .ai-menu-footer-btn {
                    flex: 1;
                    padding: 10px;
                    border-radius: 10px;
                    border: 1px solid var(--ai-bubble-border);
                    background: transparent;
                    color: var(--ai-bubble-text);
                    font-size: 13px;
                    font-weight: 500;
                    cursor: pointer;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    gap: 6px;
                    transition: all 0.2s ease;
                    font-family: inherit;
                }

                .ai-menu-footer-btn:hover {
                    background: var(--ai-bubble-hover);
                    border-color: var(--ai-bubble-accent);
                }

                .ai-menu-footer-btn svg {
                    width: 16px;
                    height: 16px;
                }

                .ai-menu-footer-btn i {
                    width: 16px;
                    height: 16px;
                    font-size: 16px;
                    display: inline-flex;
                    align-items: center;
                    justify-content: center;
                }

                .ai-menu-footer-btn.danger {
                    color: #ef4444;
                }

                .ai-menu-footer-btn.danger:hover {
                    background: rgba(239, 68, 68, 0.1);
                    border-color: #ef4444;
                }

                /* No Results */
                .ai-menu-empty {
                    padding: 32px 16px;
                    text-align: center;
                    color: var(--ai-bubble-text-secondary);
                    font-size: 14px;
                }

                .ai-menu-empty-icon {
                    font-size: 32px;
                    margin-bottom: 8px;
                    opacity: 0.5;
                }

                /* Settings Panel */
                #aiBubbleSettings {
                    position: absolute;
                    bottom: calc(var(--bubble-size, 56px) + 12px);
                    right: 0;
                    width: 340px;
                    background: var(--ai-bubble-bg);
                    backdrop-filter: blur(20px);
                    -webkit-backdrop-filter: blur(20px);
                    border-radius: 16px;
                    border: 1px solid var(--ai-bubble-border);
                    box-shadow: var(--ai-bubble-shadow);
                    overflow: hidden;
                    opacity: 0;
                    pointer-events: none;
                    transform: translateX(20px);
                    transform-origin: bottom right;
                }

                #aiBubbleSettings.visible {
                    opacity: 1;
                    pointer-events: auto;
                    animation: aiBubbleSettingsSlide 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
                }

                .ai-settings-header {
                    padding: 16px;
                    border-bottom: 1px solid var(--ai-bubble-border);
                    display: flex;
                    align-items: center;
                    justify-content: space-between;
                    gap: 10px;
                }

                .ai-settings-title {
                    font-size: 16px;
                    font-weight: 600;
                    color: var(--ai-bubble-text);
                    display: flex;
                    align-items: center;
                    gap: 8px;
                    flex: 1;
                }

                .ai-settings-title svg {
                    width: 20px;
                    height: 20px;
                    color: var(--ai-bubble-accent);
                }

                .ai-settings-close {
                    width: 32px;
                    height: 32px;
                    border-radius: 8px;
                    border: none;
                    background: transparent;
                    cursor: pointer;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    color: var(--ai-bubble-text-secondary);
                    transition: all 0.2s ease;
                }

                .ai-settings-back {
                    width: 32px;
                    height: 32px;
                    border-radius: 8px;
                    border: none;
                    background: transparent;
                    cursor: pointer;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    color: var(--ai-bubble-text-secondary);
                    transition: all 0.2s ease;
                    flex: 0 0 auto;
                }

                .ai-settings-close:hover {
                    background: var(--ai-bubble-hover);
                    color: var(--ai-bubble-text);
                }

                .ai-settings-back:hover {
                    background: var(--ai-bubble-hover);
                    color: var(--ai-bubble-text);
                }

                .ai-settings-close svg {
                    width: 18px;
                    height: 18px;
                }

                .ai-settings-back svg,
                .ai-settings-back i {
                    width: 18px;
                    height: 18px;
                }

                .ai-settings-content {
                    padding: 16px;
                    max-height: 400px;
                    overflow-y: auto;
                }

                .ai-settings-section {
                    margin-bottom: 20px;
                }

                .ai-settings-section:last-child {
                    margin-bottom: 0;
                }

                .ai-settings-section-title {
                    font-size: 12px;
                    font-weight: 600;
                    text-transform: uppercase;
                    letter-spacing: 0.5px;
                    color: var(--ai-bubble-text-secondary);
                    margin-bottom: 12px;
                    display: flex;
                    align-items: center;
                    gap: 6px;
                }

                .ai-settings-section-title svg {
                    width: 14px;
                    height: 14px;
                }

                /* Setting Row */
                .ai-setting-row {
                    display: flex;
                    align-items: center;
                    justify-content: space-between;
                    padding: 10px 0;
                }

                .ai-setting-row + .ai-setting-row {
                    border-top: 1px solid var(--ai-bubble-border);
                }

                .ai-setting-label {
                    font-size: 14px;
                    color: var(--ai-bubble-text);
                }

                .ai-setting-desc {
                    font-size: 12px;
                    color: var(--ai-bubble-text-secondary);
                    margin-top: 2px;
                }

                /* Toggle Switch */
                .ai-toggle {
                    position: relative;
                    width: 44px;
                    height: 24px;
                    background: var(--ai-bubble-border);
                    border-radius: 12px;
                    cursor: pointer;
                    transition: background 0.2s ease;
                    flex-shrink: 0;
                }

                .ai-toggle.active {
                    background: var(--ai-bubble-accent);
                }

                .ai-toggle::after {
                    content: '';
                    position: absolute;
                    top: 2px;
                    left: 2px;
                    width: 20px;
                    height: 20px;
                    background: white;
                    border-radius: 50%;
                    transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
                    box-shadow: 0 2px 4px rgba(0,0,0,0.2);
                }

                .ai-toggle.active::after {
                    transform: translateX(20px);
                }

                /* Theme Selector */
                .ai-theme-selector {
                    display: flex;
                    gap: 8px;
                }

                .ai-theme-btn {
                    width: 36px;
                    height: 36px;
                    border-radius: 10px;
                    border: 2px solid var(--ai-bubble-border);
                    background: var(--ai-bubble-input-bg);
                    cursor: pointer;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    color: var(--ai-bubble-text-secondary);
                    transition: all 0.2s ease;
                }

                .ai-theme-btn:hover {
                    border-color: var(--ai-bubble-accent);
                }

                .ai-theme-btn.active {
                    border-color: var(--ai-bubble-accent);
                    background: rgba(var(--ai-bubble-accent-rgb), 0.15);
                    color: var(--ai-bubble-accent);
                }

                .ai-theme-btn svg,
                .ai-theme-btn i {
                    width: 18px;
                    height: 18px;
                }

                .ai-select {
                    padding: 10px 12px;
                    border: 1px solid var(--ai-bubble-border);
                    border-radius: 10px;
                    background: var(--ai-bubble-input-bg);
                    color: var(--ai-bubble-text);
                    font-size: 13px;
                    font-family: inherit;
                    outline: none;
                    min-width: 140px;
                }

                .ai-select:focus {
                    border-color: var(--ai-bubble-accent);
                    box-shadow: 0 0 0 3px rgba(var(--ai-bubble-accent-rgb), 0.15);
                }

                /* Color Picker */
                .ai-color-picker {
                    display: flex;
                    gap: 6px;
                    flex-wrap: wrap;
                }

                .ai-color-swatch {
                    width: 28px;
                    height: 28px;
                    border-radius: 8px;
                    border: 2px solid transparent;
                    cursor: pointer;
                    transition: all 0.2s ease;
                }

                .ai-color-swatch:hover {
                    transform: scale(1.1);
                }

                .ai-color-swatch.active {
                    border-color: var(--ai-bubble-text);
                    box-shadow: 0 0 0 2px var(--ai-bubble-bg-solid);
                }

                /* Slider */
                .ai-slider-container {
                    flex: 1;
                    max-width: 120px;
                }

                .ai-slider {
                    width: 100%;
                    height: 6px;
                    border-radius: 3px;
                    background: var(--ai-bubble-border);
                    -webkit-appearance: none;
                    appearance: none;
                    cursor: pointer;
                }

                .ai-slider::-webkit-slider-thumb {
                    -webkit-appearance: none;
                    width: 18px;
                    height: 18px;
                    border-radius: 50%;
                    background: var(--ai-bubble-accent);
                    cursor: pointer;
                    box-shadow: 0 2px 6px rgba(0,0,0,0.2);
                    transition: transform 0.2s ease;
                }

                .ai-slider::-webkit-slider-thumb:hover {
                    transform: scale(1.1);
                }

                .ai-slider::-moz-range-thumb {
                    width: 18px;
                    height: 18px;
                    border-radius: 50%;
                    background: var(--ai-bubble-accent);
                    cursor: pointer;
                    border: none;
                    box-shadow: 0 2px 6px rgba(0,0,0,0.2);
                }

                .ai-slider-value {
                    font-size: 12px;
                    color: var(--ai-bubble-text-secondary);
                    text-align: right;
                    margin-top: 4px;
                }

                /* Hotkey Display */
                .ai-hotkey-display {
                    display: flex;
                    gap: 4px;
                    align-items: center;
                }

                .ai-hotkey-row {
                    display: flex;
                    align-items: center;
                    gap: 8px;
                }

                /* Tooltip */
                #aiBubbleTooltip {
                    position: fixed;
                    z-index: 2147483649;
                    padding: 6px 9px;
                    border-radius: 10px;
                    border: 1px solid var(--ai-bubble-border);
                    background: var(--ai-bubble-bg);
                    color: var(--ai-bubble-text);
                    box-shadow: var(--ai-bubble-shadow-sm);
                    font-size: 12px;
                    line-height: 1.2;
                    pointer-events: none;
                    opacity: 0;
                    transform: translateY(6px);
                    transition: opacity 0.12s ease, transform 0.12s ease;
                    max-width: 260px;
                    white-space: nowrap;
                    overflow: hidden;
                    text-overflow: ellipsis;
                }

                #aiBubbleTooltip.visible {
                    opacity: 1;
                    transform: translateY(0);
                }

                .ai-hotkey-key {
                    padding: 4px 8px;
                    background: var(--ai-bubble-input-bg);
                    border: 1px solid var(--ai-bubble-border);
                    border-radius: 6px;
                    font-size: 12px;
                    font-weight: 500;
                    color: var(--ai-bubble-text);
                    font-family: monospace;
                }

                /* Settings Footer */
                .ai-settings-footer {
                    padding: 12px 16px;
                    border-top: 1px solid var(--ai-bubble-border);
                    display: flex;
                    gap: 8px;
                }

                .ai-settings-footer-btn {
                    flex: 1;
                    padding: 10px;
                    border-radius: 10px;
                    border: none;
                    font-size: 13px;
                    font-weight: 500;
                    cursor: pointer;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    gap: 6px;
                    transition: all 0.2s ease;
                    font-family: inherit;
                }

                .ai-settings-footer-btn.primary {
                    background: var(--ai-bubble-accent);
                    color: white;
                }

                .ai-settings-footer-btn.primary:hover {
                    filter: brightness(1.1);
                }

                .ai-settings-footer-btn.secondary {
                    background: transparent;
                    border: 1px solid var(--ai-bubble-border);
                    color: var(--ai-bubble-text);
                }

                .ai-settings-footer-btn.secondary:hover {
                    background: var(--ai-bubble-hover);
                }

                .ai-settings-footer-btn svg {
                    width: 16px;
                    height: 16px;
                }

                .ai-settings-footer-btn i {
                    width: 16px;
                    height: 16px;
                }

                .ai-credit {
                    font-size: 11px;
                    color: var(--ai-bubble-text-secondary);
                    display: flex;
                    align-items: center;
                    justify-content: flex-end;
                    flex: 1;
                    white-space: nowrap;
                }

                /* Prompt Manager Panel */
                #aiBubblePrompts {
                    position: absolute;
                    bottom: calc(var(--bubble-size, 56px) + 12px);
                    right: 0;
                    width: 380px;
                    background: var(--ai-bubble-bg);
                    backdrop-filter: blur(20px);
                    -webkit-backdrop-filter: blur(20px);
                    border-radius: 16px;
                    border: 1px solid var(--ai-bubble-border);
                    box-shadow: var(--ai-bubble-shadow);
                    overflow: hidden;
                    opacity: 0;
                    pointer-events: none;
                    transform: translateX(20px);
                    transform-origin: bottom right;
                }

                #aiBubblePrompts.visible {
                    opacity: 1;
                    pointer-events: auto;
                    animation: aiBubbleSettingsSlide 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
                }

                .ai-prompts-header {
                    padding: 16px;
                    border-bottom: 1px solid var(--ai-bubble-border);
                    display: flex;
                    align-items: center;
                    justify-content: space-between;
                }

                .ai-prompts-title {
                    font-size: 16px;
                    font-weight: 600;
                    color: var(--ai-bubble-text);
                    display: flex;
                    align-items: center;
                    gap: 8px;
                }

                .ai-prompts-title i {
                    color: var(--ai-bubble-accent);
                }

                .ai-prompts-close {
                    width: 32px;
                    height: 32px;
                    border-radius: 8px;
                    border: none;
                    background: transparent;
                    cursor: pointer;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    color: var(--ai-bubble-text-secondary);
                    transition: all 0.2s ease;
                }

                .ai-prompts-back {
                    width: 32px;
                    height: 32px;
                    border-radius: 8px;
                    border: none;
                    background: transparent;
                    cursor: pointer;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    color: var(--ai-bubble-text-secondary);
                    transition: all 0.2s ease;
                }

                .ai-prompts-close:hover {
                    background: var(--ai-bubble-hover);
                    color: var(--ai-bubble-text);
                }

                .ai-prompts-back:hover {
                    background: var(--ai-bubble-hover);
                    color: var(--ai-bubble-text);
                }

                .ai-prompts-content {
                    padding: 12px 16px 16px;
                    max-height: 480px;
                    overflow-y: auto;
                }

                .ai-prompts-toolbar {
                    display: flex;
                    gap: 8px;
                    align-items: center;
                    margin-bottom: 12px;
                }

                .ai-prompts-toolbar .ai-search-input {
                    flex: 1;
                }

                .ai-tag-filter {
                    width: 120px;
                    padding: 10px 10px;
                    border: 1px solid var(--ai-bubble-border);
                    border-radius: 10px;
                    background: var(--ai-bubble-input-bg);
                    color: var(--ai-bubble-text);
                    font-size: 13px;
                    font-family: inherit;
                    outline: none;
                }

                .ai-prompts-list {
                    display: flex;
                    flex-direction: column;
                    gap: 8px;
                }

                .ai-prompt-card {
                    border: 1px solid var(--ai-bubble-border);
                    border-radius: 12px;
                    padding: 12px;
                    background: color-mix(in srgb, var(--ai-bubble-bg-solid) 50%, transparent);
                    transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease;
                }

                .ai-prompt-card:hover {
                    transform: translateY(-1px);
                    border-color: rgba(var(--ai-bubble-accent-rgb), 0.35);
                    background: color-mix(in srgb, var(--ai-bubble-bg-solid) 70%, transparent);
                }

                .ai-prompt-top {
                    display: flex;
                    align-items: flex-start;
                    justify-content: space-between;
                    gap: 10px;
                }

                .ai-prompt-name {
                    font-weight: 600;
                    font-size: 14px;
                    color: var(--ai-bubble-text);
                    margin-bottom: 4px;
                }

                .ai-prompt-meta {
                    font-size: 12px;
                    color: var(--ai-bubble-text-secondary);
                }

                .ai-tag-row {
                    margin-top: 8px;
                    display: flex;
                    flex-wrap: wrap;
                    gap: 6px;
                }

                .ai-tag {
                    font-size: 11px;
                    padding: 3px 8px;
                    border-radius: 999px;
                    border: 1px solid var(--ai-bubble-border);
                    background: rgba(var(--ai-bubble-accent-rgb), 0.08);
                    color: var(--ai-bubble-text);
                }

                .ai-prompt-actions {
                    display: flex;
                    gap: 6px;
                }

                .ai-mini-btn {
                    width: 30px;
                    height: 30px;
                    border-radius: 8px;
                    border: 1px solid var(--ai-bubble-border);
                    background: transparent;
                    cursor: pointer;
                    color: var(--ai-bubble-text-secondary);
                    display: inline-flex;
                    align-items: center;
                    justify-content: center;
                    transition: all 0.2s ease;
                }

                .ai-mini-btn:hover {
                    background: var(--ai-bubble-hover);
                    border-color: rgba(var(--ai-bubble-accent-rgb), 0.35);
                    color: var(--ai-bubble-text);
                }

                .ai-mini-btn.primary {
                    background: var(--ai-bubble-accent);
                    border-color: var(--ai-bubble-accent);
                    color: white;
                }

                .ai-mini-btn.primary:hover {
                    filter: brightness(1.08);
                }

                .ai-mini-btn.danger:hover {
                    background: rgba(239, 68, 68, 0.12);
                    border-color: rgba(239, 68, 68, 0.35);
                    color: #ef4444;
                }

                .ai-prompts-footer {
                    padding: 12px 16px;
                    border-top: 1px solid var(--ai-bubble-border);
                    display: flex;
                    gap: 8px;
                    align-items: center;
                    justify-content: space-between;
                }

                .ai-help {
                    font-size: 13px;
                    color: var(--ai-bubble-text-secondary);
                    line-height: 1.4;
                }

                /* Modal Overlay */
                #aiBubbleModalOverlay {
                    position: fixed;
                    inset: 0;
                    background: rgba(0, 0, 0, 0.35);
                    z-index: 2147483648;
                    display: none;
                    align-items: center;
                    justify-content: center;
                    padding: 24px;
                }

                #aiBubbleModalOverlay.visible {
                    display: flex;
                    animation: aiBubbleMenuSlideIn 0.18s ease-out;
                }

                .ai-modal {
                    width: min(560px, 92vw);
                    max-height: min(680px, 86vh);
                    overflow: hidden;
                    border-radius: 16px;
                    border: 1px solid var(--ai-bubble-border);
                    background: var(--ai-bubble-bg);
                    backdrop-filter: blur(20px);
                    -webkit-backdrop-filter: blur(20px);
                    box-shadow: var(--ai-bubble-shadow);
                }

                .ai-modal-header {
                    padding: 14px 16px;
                    border-bottom: 1px solid var(--ai-bubble-border);
                    display: flex;
                    align-items: center;
                    justify-content: space-between;
                    gap: 10px;
                }

                .ai-modal-title {
                    font-weight: 700;
                    color: var(--ai-bubble-text);
                    display: flex;
                    align-items: center;
                    gap: 8px;
                }

                .ai-modal-body {
                    padding: 14px 16px;
                    overflow: auto;
                    max-height: calc(min(680px, 86vh) - 56px - 66px);
                }

                .ai-modal-footer {
                    padding: 12px 16px;
                    border-top: 1px solid var(--ai-bubble-border);
                    display: flex;
                    gap: 8px;
                    justify-content: flex-end;
                }

                .ai-field {
                    display: flex;
                    flex-direction: column;
                    gap: 6px;
                    margin-bottom: 12px;
                }

                .ai-field label {
                    font-size: 12px;
                    font-weight: 600;
                    color: var(--ai-bubble-text-secondary);
                    text-transform: uppercase;
                    letter-spacing: 0.4px;
                }

                .ai-input, .ai-textarea {
                    width: 100%;
                    padding: 10px 12px;
                    border: 1px solid var(--ai-bubble-border);
                    border-radius: 10px;
                    background: var(--ai-bubble-input-bg);
                    color: var(--ai-bubble-text);
                    font-size: 14px;
                    font-family: inherit;
                    outline: none;
                }

                .ai-textarea {
                    min-height: 140px;
                    resize: vertical;
                    font-family: inherit;
                }

                .ai-input:focus, .ai-textarea:focus {
                    border-color: var(--ai-bubble-accent);
                    box-shadow: 0 0 0 3px rgba(var(--ai-bubble-accent-rgb), 0.15);
                }

                .ai-bot-checkboxes {
                    display: grid;
                    grid-template-columns: 1fr 1fr;
                    gap: 8px 12px;
                    padding-top: 4px;
                }

                .ai-bot-checkbox {
                    display: flex;
                    align-items: center;
                    gap: 8px;
                    font-size: 13px;
                    color: var(--ai-bubble-text);
                }

                /* Tooltip */
                .ai-tooltip {
                    position: absolute;
                    background: var(--ai-bubble-bg-solid);
                    color: var(--ai-bubble-text);
                    padding: 6px 10px;
                    border-radius: 6px;
                    font-size: 12px;
                    white-space: nowrap;
                    pointer-events: none;
                    opacity: 0;
                    transform: translateY(4px);
                    transition: all 0.2s ease;
                    box-shadow: var(--ai-bubble-shadow-sm);
                    z-index: 2147483648;
                }

                .ai-tooltip.visible {
                    opacity: 1;
                    transform: translateY(0);
                }

                /* Compact Mode */
                #aiBubbleContainer.compact .ai-menu-item {
                    padding: 8px 10px;
                    gap: 8px;
                }

                #aiBubbleContainer.compact .ai-menu-item-icon {
                    width: 28px;
                    height: 28px;
                    font-size: 14px;
                }

                #aiBubbleContainer.compact .ai-menu-list {
                    padding: 4px;
                }
            `);
        }

        _hexToRgb(hex) {
            const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
            return result
                ? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}`
                : '99, 102, 241';
        }

        _fa(className, title = '') {
            const safeTitle = title ? ` title="${title.replace(/\"/g, '&quot;')}"` : '';
            return `<i class="${className}"${safeTitle} aria-hidden="true"></i>`;
        }

        _hotkeyToKeys(hk) {
            const key = (hk && hk.key ? hk.key : '').toString().trim();
            const keys = [];
            if (hk && hk.ctrl) keys.push('Ctrl');
            if (hk && hk.shift) keys.push('Shift');
            if (hk && hk.alt) keys.push('Alt');
            if (key) keys.push(key.toUpperCase());
            return keys;
        }

        _hotkeyHtml(hk) {
            return this._hotkeyToKeys(hk).map(k => `<span class="ai-hotkey-key">${this._escapeHtml(k)}</span>`).join('');
        }

        _matchesHotkey(e, hk, { strict = true } = {}) {
            if (!hk || !hk.key) return false;
            const keyOk = (e.key || '').toUpperCase() === (hk.key || '').toUpperCase();
            if (!keyOk) return false;

            const reqCtrl = !!hk.ctrl;
            const reqShift = !!hk.shift;
            const reqAlt = !!hk.alt;

            if (reqCtrl && !e.ctrlKey) return false;
            if (reqShift && !e.shiftKey) return false;
            if (reqAlt && !e.altKey) return false;

            if (strict) {
                if (!reqCtrl && e.ctrlKey) return false;
                if (!reqShift && e.shiftKey) return false;
                if (!reqAlt && e.altKey) return false;
            }

            return true;
        }

        _escapeHtml(s) {
            return (s || '').toString()
                .replace(/&/g, '&amp;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;')
                .replace(/"/g, '&quot;')
                .replace(/'/g, '&#039;');
        }

        _getFaviconUrl(url) {
            try {
                const host = new URL(url).hostname;
                return `https://www.google.com/s2/favicons?domain=${encodeURIComponent(host)}&sz=64`;
            } catch {
                return '';
            }
        }

        _getFaviconFallbacks(url) {
            try {
                const u = new URL(url);
                const host = u.hostname;
                return [
                    `https://www.google.com/s2/favicons?domain=${encodeURIComponent(host)}&sz=64`,
                    `https://icons.duckduckgo.com/ip3/${encodeURIComponent(host)}.ico`,
                    `${u.origin}/favicon.ico`
                ];
            } catch {
                return [];
            }
        }

        _getCurrentBotId() {
            const host = window.location.hostname;
            const match = AI_SITES.find(s => {
                try {
                    return new URL(s.url).hostname === host;
                } catch {
                    return false;
                }
            });
            return match ? match.id : null;
        }

        _applyTypography() {
            const preset = this.configManager.get('fontPreset');
            const map = {
                system: { family: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif", link: null },
                inter: { family: "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif", link: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap' },
                poppins: { family: "Poppins, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif", link: 'https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap' },
                'jetbrains-mono': { family: "'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace", link: 'https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&display=swap' }
            };
            const selected = map[preset] || map.system;
            document.documentElement.style.setProperty('--ai-bubble-font', selected.family);
            if (selected.link) {
                const id = 'aiBubbleFontLink';
                let link = document.getElementById(id);
                if (!link) {
                    link = document.createElement('link');
                    link.id = id;
                    link.rel = 'stylesheet';
                    document.head.appendChild(link);
                }
                link.href = selected.link;
            }
        }

        _applyMotionPreset() {
            const preset = this.configManager.get('motionPreset');
            const duration = preset === 'subtle' ? '4.2s' : '3.1s';
            this.elements.container?.style?.setProperty('--ai-bubble-wiggle-duration', duration);
        }

        _createElements() {
            // Main Container
            this.elements.container = document.createElement('div');
            this.elements.container.id = 'aiBubbleContainer';
            document.body.appendChild(this.elements.container);

            // Tooltip
            this.elements.tooltip = document.createElement('div');
            this.elements.tooltip.id = 'aiBubbleTooltip';
            this.elements.tooltip.setAttribute('aria-hidden', 'true');
            document.body.appendChild(this.elements.tooltip);

            // Main Bubble Button
            this.elements.button = document.createElement('button');
            this.elements.button.id = 'aiBubbleButton';
            this.elements.button.setAttribute('aria-label', 'Open AI Menu');
            this.elements.button.setAttribute('aria-expanded', 'false');
            this.elements.button.innerHTML = `
                <span class="bubble-icon">${this._fa('fa-solid fa-wand-magic-sparkles')}</span>
                <span class="splash" aria-hidden="true"></span>
            `;
            this.elements.container.appendChild(this.elements.button);

            // Resize Handle
            this.elements.resize = document.createElement('div');
            this.elements.resize.id = 'aiBubbleResize';
            this.elements.resize.innerHTML = ICONS.resize;
            this.elements.resize.setAttribute('data-tooltip', 'Drag to resize');
            this.elements.container.appendChild(this.elements.resize);

            // Sidebar Window Resize Handle (only shown when popup is open)
            this.elements.sidebarResize = document.createElement('div');
            this.elements.sidebarResize.id = 'aiSidebarResize';
            this.elements.sidebarResize.innerHTML = ICONS.resize;
            this.elements.sidebarResize.setAttribute('data-tooltip', 'Drag to resize sidebar window');
            this.elements.container.appendChild(this.elements.sidebarResize);

            // Menu
            this._createMenu();

            // Settings Panel
            this._createSettings();

            // Prompt Manager Panel
            this._createPromptManager();

            // Modal overlay (for prompt editor/import/guidelines)
            this._createModalOverlay();
        }

        _createMenu() {
            this.elements.menu = document.createElement('div');
            this.elements.menu.id = 'aiBubbleMenu';
            this.elements.menu.setAttribute('role', 'menu');
            this.elements.menu.setAttribute('aria-hidden', 'true');

            // Search
            const searchContainer = document.createElement('div');
            searchContainer.className = 'ai-menu-search';
            searchContainer.innerHTML = `
                <div class="ai-search-wrapper">
                    <span class="ai-search-icon">${this._fa('fa-solid fa-magnifying-glass')}</span>
                    <input type="text" class="ai-search-input" placeholder="Search AI assistants..." aria-label="Search">
                </div>
            `;
            this.elements.menu.appendChild(searchContainer);
            this.elements.searchInput = searchContainer.querySelector('.ai-search-input');

            // Items List
            this.elements.menuList = document.createElement('div');
            this.elements.menuList.className = 'ai-menu-list';
            this.elements.menu.appendChild(this.elements.menuList);

            // Footer
            const footer = document.createElement('div');
            footer.className = 'ai-menu-footer';
            footer.innerHTML = `
                <button class="ai-menu-footer-btn" data-action="settings" data-tooltip="Settings">
                    ${this._fa('fa-solid fa-gear')}
                    <span>Settings</span>
                </button>
                <button class="ai-menu-footer-btn" data-action="prompts" data-tooltip="Prompt Manager">
                    ${this._fa('fa-solid fa-book')}
                    <span>Prompts</span>
                </button>
                <button class="ai-menu-footer-btn danger" data-action="hide" data-tooltip="Hide bubble">
                    ${this._fa('fa-solid fa-eye-slash')}
                    <span>Hide</span>
                </button>
            `;
            this.elements.menu.appendChild(footer);

            this.elements.container.appendChild(this.elements.menu);

            this._renderMenuItems();
        }

        _renderMenuItems() {
            const favorites = this.configManager.get('favorites') || [];
            const hiddenSites = this.configManager.get('hiddenSites') || [];
            const query = this.state.searchQuery.toLowerCase();

            let sites = AI_SITES.filter(site => {
                if (hiddenSites.includes(site.id)) return false;
                if (query && !site.name.toLowerCase().includes(query)) return false;
                return true;
            });

            // Sort favorites first
            sites.sort((a, b) => {
                const aFav = favorites.includes(a.id);
                const bFav = favorites.includes(b.id);
                if (aFav && !bFav) return -1;
                if (!aFav && bFav) return 1;
                return 0;
            });

            this.elements.menuList.innerHTML = '';

            if (sites.length === 0) {
                this.elements.menuList.innerHTML = `
                    <div class="ai-menu-empty">
                        <div class="ai-menu-empty-icon">${this._fa('fa-solid fa-magnifying-glass')}</div>
                        <div>No AI assistants found</div>
                    </div>
                `;
                return;
            }

            // Add favorites section if there are favorites
            const favoriteSites = sites.filter(s => favorites.includes(s.id));
            const otherSites = sites.filter(s => !favorites.includes(s.id));

            if (favoriteSites.length > 0 && !query) {
                const favTitle = document.createElement('div');
                favTitle.className = 'ai-menu-section-title';
                favTitle.textContent = 'Favorites';
                this.elements.menuList.appendChild(favTitle);

                favoriteSites.forEach((site, index) => {
                    this.elements.menuList.appendChild(this._createMenuItem(site, favorites, index));
                });

                if (otherSites.length > 0) {
                    const divider = document.createElement('div');
                    divider.className = 'ai-menu-divider';
                    this.elements.menuList.appendChild(divider);

                    const allTitle = document.createElement('div');
                    allTitle.className = 'ai-menu-section-title';
                    allTitle.textContent = 'All AI Assistants';
                    this.elements.menuList.appendChild(allTitle);
                }
            }

            otherSites.forEach((site, index) => {
                this.elements.menuList.appendChild(
                    this._createMenuItem(site, favorites, favoriteSites.length + index)
                );
            });
        }

        _createMenuItem(site, favorites, index) {
            const isFavorite = favorites.includes(site.id);
            const showLoginBadges = this.configManager.get('showLoginBadges');
            const favicon = this._getFaviconUrl(site.url);
            const faviconFallbacks = this._getFaviconFallbacks(site.url);

            const anim = this.state.lastFavoriteToggle;
            const shouldAnim = !!(anim && anim.id === site.id && (Date.now() - anim.at) < 900);

            const item = document.createElement('button');
            item.className = 'ai-menu-item';
            item.setAttribute('role', 'menuitem');
            item.dataset.url = site.url;
            item.dataset.id = site.id;
            item.style.animationDelay = `${index * 0.03}s`;

            item.innerHTML = `
                <div class="ai-menu-item-icon" style="background: ${site.color}20; color: ${site.color}">
                    ${favicon ? `<img class="ai-bot-favicon" alt="" src="${favicon}">` : ''}
                    <span class="ai-bot-fallback">${this._fa(site.faIcon || 'fa-solid fa-robot')}</span>
                </div>
                <div class="ai-menu-item-content">
                    <div class="ai-menu-item-name">${site.name}</div>
                    ${site.loginNeeded && showLoginBadges ? `
                        <div class="ai-menu-item-badge">
                            ${this._fa('fa-solid fa-right-to-bracket')}
                            <span>Login required</span>
                        </div>
                    ` : ''}
                </div>
                <div class="ai-menu-item-actions">
                    <button class="ai-menu-item-fav ${isFavorite ? 'active' : ''} ${shouldAnim ? 'star-anim' : ''}" data-action="favorite" title="${isFavorite ? 'Remove from favorites' : 'Add to favorites'}">
                        ${this._fa(isFavorite ? 'fa-solid fa-star' : 'fa-regular fa-star')}
                    </button>
                    <button class="ai-menu-item-open" data-action="open-window" title="Open ${site.name} in window">
                        ${this._fa('fa-regular fa-window-restore')}
                    </button>
                    <button class="ai-menu-item-open secondary" data-action="open-tab" title="Open ${site.name} in new tab">
                        ${this._fa('fa-solid fa-arrow-up-right-from-square')}
                    </button>
                </div>
            `;

            // Favicon fallback
            const img = item.querySelector('.ai-bot-favicon');
            const fallback = item.querySelector('.ai-bot-fallback');
            if (fallback && (!img || !favicon)) {
                fallback.style.display = 'flex';
            }
            if (img && fallback) {
                img.dataset.faviconIdx = '0';
                img.addEventListener('error', () => {
                    const idx = parseInt(img.dataset.faviconIdx || '0', 10);
                    const next = faviconFallbacks[idx + 1];
                    if (next) {
                        img.dataset.faviconIdx = String(idx + 1);
                        img.src = next;
                        return;
                    }
                    img.style.display = 'none';
                    fallback.style.display = 'flex';
                });
            }

            // Add visible class after a frame for animation
            requestAnimationFrame(() => {
                item.classList.add('visible');
            });

            return item;
        }

        _createSettings() {
            this.elements.settings = document.createElement('div');
            this.elements.settings.id = 'aiBubbleSettings';
            this.elements.settings.setAttribute('aria-hidden', 'true');

            const config = this.configManager.config;
            const colors = ['#6366f1', '#8b5cf6', '#a855f7', '#ec4899', '#ef4444', '#f59e0b', '#10b981', '#14b8a6', '#0ea5e9', '#6b7280'];

            const toggleHotkeyHtml = this._hotkeyHtml(config.hotkey);
            const unhideHotkeyHtml = this._hotkeyHtml(config.unhideHotkey);

            this.elements.settings.innerHTML = `
                <div class="ai-settings-header">
                    <button class="ai-settings-back" data-action="back-to-menu" aria-label="Back" data-tooltip="Back to AI menu">
                        ${this._fa('fa-solid fa-chevron-left')}
                    </button>
                    <div class="ai-settings-title">
                        ${this._fa('fa-solid fa-gear')}
                        <span>Settings</span>
                    </div>
                    <button class="ai-settings-close" data-action="close-settings" aria-label="Close" data-tooltip="Close">
                        ${this._fa('fa-solid fa-xmark')}
                    </button>
                </div>
                <div class="ai-settings-content">
                    <div class="ai-settings-section">
                        <div class="ai-settings-section-title">
                            ${this._fa('fa-solid fa-palette')}
                            <span>Appearance</span>
                        </div>
                        <div class="ai-setting-row">
                            <div>
                                <div class="ai-setting-label">Theme</div>
                            </div>
                            <div class="ai-theme-selector">
                                <button class="ai-theme-btn ${config.theme === 'light' ? 'active' : ''}" data-theme="light" title="Light">
                                    ${this._fa('fa-solid fa-sun')}
                                </button>
                                <button class="ai-theme-btn ${config.theme === 'dark' ? 'active' : ''}" data-theme="dark" title="Dark">
                                    ${this._fa('fa-solid fa-moon')}
                                </button>
                                <button class="ai-theme-btn ${config.theme === 'auto' ? 'active' : ''}" data-theme="auto" title="System">
                                    ${this._fa('fa-solid fa-desktop')}
                                </button>
                            </div>
                        </div>
                        <div class="ai-setting-row">
                            <div>
                                <div class="ai-setting-label">Accent Color</div>
                            </div>
                            <div class="ai-color-picker">
                                ${colors.map(color => `
                                    <button class="ai-color-swatch ${config.accentColor === color ? 'active' : ''}"
                                        style="background: ${color}" data-color="${color}" title="${color}">
                                    </button>
                                `).join('')}
                            </div>
                        </div>
                        <div class="ai-setting-row">
                            <div>
                                <div class="ai-setting-label">Bubble Size</div>
                            </div>
                            <div class="ai-slider-container">
                                <input type="range" class="ai-slider" id="bubbleSizeSlider"
                                    min="40" max="80" value="${config.bubbleSize}">
                                <div class="ai-slider-value">${config.bubbleSize}px</div>
                            </div>
                        </div>
                        <div class="ai-setting-row">
                            <div>
                                <div class="ai-setting-label">Opacity</div>
                            </div>
                            <div class="ai-slider-container">
                                <input type="range" class="ai-slider" id="bubbleOpacitySlider"
                                    min="0.3" max="1" step="0.1" value="${config.bubbleOpacity}">
                                <div class="ai-slider-value">${Math.round(config.bubbleOpacity * 100)}%</div>
                            </div>
                        </div>
                    </div>

                    <div class="ai-settings-section">
                        <div class="ai-settings-section-title">
                            ${this._fa('fa-solid fa-font')}
                            <span>Typography</span>
                        </div>
                        <div class="ai-setting-row">
                            <div>
                                <div class="ai-setting-label">Font</div>
                                <div class="ai-setting-desc">Applies to the bubble UI</div>
                            </div>
                            <div>
                                <select class="ai-select" id="aiFontPreset">
                                    <option value="system" ${config.fontPreset === 'system' ? 'selected' : ''}>System</option>
                                    <option value="inter" ${config.fontPreset === 'inter' ? 'selected' : ''}>Inter</option>
                                    <option value="poppins" ${config.fontPreset === 'poppins' ? 'selected' : ''}>Poppins</option>
                                    <option value="jetbrains-mono" ${config.fontPreset === 'jetbrains-mono' ? 'selected' : ''}>JetBrains Mono</option>
                                </select>
                            </div>
                        </div>
                        <div class="ai-setting-row">
                            <div>
                                <div class="ai-setting-label">Motion</div>
                                <div class="ai-setting-desc">Bubble micro-animations</div>
                            </div>
                            <div>
                                <select class="ai-select" id="aiMotionPreset">
                                    <option value="subtle" ${config.motionPreset === 'subtle' ? 'selected' : ''}>Subtle</option>
                                    <option value="lively" ${config.motionPreset === 'lively' ? 'selected' : ''}>Lively</option>
                                </select>
                            </div>
                        </div>
                    </div>

                    <div class="ai-settings-section">
                        <div class="ai-settings-section-title">
                            ${this._fa('fa-solid fa-sliders')}
                            <span>Behavior</span>
                        </div>
                        <div class="ai-setting-row">
                            <div>
                                <div class="ai-setting-label">Open links in</div>
                                <div class="ai-setting-desc">Default when clicking a bot</div>
                            </div>
                            <div class="ai-theme-selector">
                                <button class="ai-theme-btn ${config.openMode === 'window' ? 'active' : ''}" data-open-mode="window" title="Window">
                                    ${this._fa('fa-regular fa-window-restore')}
                                </button>
                                <button class="ai-theme-btn ${config.openMode === 'tab' ? 'active' : ''}" data-open-mode="tab" title="New tab">
                                    ${this._fa('fa-solid fa-arrow-up-right-from-square')}
                                </button>
                            </div>
                        </div>
                        <div class="ai-setting-row">
                            <div>
                                <div class="ai-setting-label">Auto-open Prompts on AI sites</div>
                                <div class="ai-setting-desc">Show prompt manager on supported AI pages</div>
                            </div>
                            <div class="ai-toggle ${config.openPromptManagerOnAISites ? 'active' : ''}" data-setting="openPromptManagerOnAISites"></div>
                        </div>
                        <div class="ai-setting-row">
                            <div>
                                <div class="ai-setting-label">Snap to Corners</div>
                                <div class="ai-setting-desc">Only snaps when dropped near a corner</div>
                            </div>
                            <div class="ai-toggle ${config.snapToCorners ? 'active' : ''}" data-setting="snapToCorners"></div>
                        </div>
                        <div class="ai-setting-row">
                            <div>
                                <div class="ai-setting-label">Compact Mode</div>
                                <div class="ai-setting-desc">Smaller menu items</div>
                            </div>
                            <div class="ai-toggle ${config.compactMode ? 'active' : ''}" data-setting="compactMode"></div>
                        </div>
                        <div class="ai-setting-row">
                            <div>
                                <div class="ai-setting-label">Show Login Badges</div>
                                <div class="ai-setting-desc">Indicate sites requiring login</div>
                            </div>
                            <div class="ai-toggle ${config.showLoginBadges ? 'active' : ''}" data-setting="showLoginBadges"></div>
                        </div>
                        <div class="ai-setting-row">
                            <div>
                                <div class="ai-setting-label">Tooltips</div>
                                <div class="ai-setting-desc">Show helpful hover tips</div>
                            </div>
                            <div class="ai-toggle ${config.enableTooltips ? 'active' : ''}" data-setting="enableTooltips"></div>
                        </div>
                        <div class="ai-setting-row">
                            <div>
                                <div class="ai-setting-label">Sound Effects</div>
                                <div class="ai-setting-desc">Click sounds</div>
                            </div>
                            <div class="ai-toggle ${config.enableSounds ? 'active' : ''}" data-setting="enableSounds"></div>
                        </div>
                    </div>

                    <div class="ai-settings-section">
                        <div class="ai-settings-section-title">
                            ${this._fa('fa-solid fa-keyboard')}
                            <span>Keyboard Shortcut</span>
                        </div>
                        <div class="ai-setting-row">
                            <div>
                                <div class="ai-setting-label">Toggle Bubble</div>
                            </div>
                            <div class="ai-hotkey-row">
                                <div class="ai-hotkey-display" id="aiHotkeyToggleDisplay">${toggleHotkeyHtml}</div>
                                <button class="ai-mini-btn" data-action="edit-hotkey" data-hotkey="hotkey" data-tooltip="Change shortcut">
                                    ${this._fa('fa-solid fa-pen-to-square')}
                                </button>
                            </div>
                        </div>
                        <div class="ai-setting-row">
                            <div>
                                <div class="ai-setting-label">Unhide Bubble</div>
                            </div>
                            <div class="ai-hotkey-row">
                                <div class="ai-hotkey-display" id="aiHotkeyUnhideDisplay">${unhideHotkeyHtml}</div>
                                <button class="ai-mini-btn" data-action="edit-hotkey" data-hotkey="unhideHotkey" data-tooltip="Change shortcut">
                                    ${this._fa('fa-solid fa-pen-to-square')}
                                </button>
                            </div>
                        </div>
                    </div>

                    <div class="ai-settings-section">
                        <div class="ai-settings-section-title">
                            ${this._fa('fa-solid fa-window-maximize')}
                            <span>Sidebar Window</span>
                        </div>
                        <div class="ai-setting-row">
                            <div>
                                <div class="ai-setting-label">Width</div>
                            </div>
                            <div class="ai-slider-container">
                                <input type="range" class="ai-slider" id="sidebarWidthSlider"
                                    min="300" max="600" step="20" value="${config.sidebarWidth}">
                                <div class="ai-slider-value">${config.sidebarWidth}px</div>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="ai-settings-footer">
                    <button class="ai-settings-footer-btn secondary" data-action="reset">
                        ${this._fa('fa-solid fa-rotate-left')}
                        <span>Reset</span>
                    </button>
                    <div class="ai-credit">Made by Mayukhjit Chakraborty</div>
                </div>
            `;

            this.elements.container.appendChild(this.elements.settings);
        }

        _createPromptManager() {
            this.elements.prompts = document.createElement('div');
            this.elements.prompts.id = 'aiBubblePrompts';
            this.elements.prompts.setAttribute('aria-hidden', 'true');

            this.elements.prompts.innerHTML = `
                <div class="ai-prompts-header">
                    <button class="ai-prompts-back" data-action="back-to-menu" aria-label="Back" data-tooltip="Back to AI menu">
                        ${this._fa('fa-solid fa-chevron-left')}
                    </button>
                    <div class="ai-prompts-title">
                        ${this._fa('fa-solid fa-book')}
                        <span>Prompt Manager</span>
                    </div>
                    <button class="ai-prompts-close" data-action="close-prompts" aria-label="Close" data-tooltip="Close">
                        ${this._fa('fa-solid fa-xmark')}
                    </button>
                </div>
                <div class="ai-prompts-content">
                    <div class="ai-prompts-toolbar">
                        <div class="ai-search-wrapper" style="flex: 1;">
                            <span class="ai-search-icon">${this._fa('fa-solid fa-magnifying-glass')}</span>
                            <input type="text" class="ai-search-input" id="aiPromptSearch" placeholder="Search prompts..." aria-label="Search prompts">
                        </div>
                        <select class="ai-tag-filter" id="aiPromptTagFilter" aria-label="Tag filter">
                            <option value="">All tags</option>
                        </select>
                    </div>
                    <div class="ai-prompts-list" id="aiPromptsList"></div>
                </div>
                <div class="ai-prompts-footer">
                    <div class="ai-help">Made by Mayukhjit Chakraborty</div>
                    <div style="display:flex; gap:8px;">
                        <button class="ai-mini-btn" data-action="import-prompts" title="Import JSON">
                            ${this._fa('fa-solid fa-file-import')}
                        </button>
                        <button class="ai-mini-btn" data-action="add-prompt" title="Add prompt">
                            ${this._fa('fa-solid fa-plus')}
                        </button>
                    </div>
                </div>
            `;

            this.elements.container.appendChild(this.elements.prompts);

            this.elements.promptSearchInput = this.elements.prompts.querySelector('#aiPromptSearch');
            this.elements.promptTagFilter = this.elements.prompts.querySelector('#aiPromptTagFilter');
            this.elements.promptsList = this.elements.prompts.querySelector('#aiPromptsList');

            this.elements.prompts.addEventListener('click', (e) => {
                const action = e.target.closest('[data-action]');
                if (!action) return;
                const type = action.dataset.action;

                if (type === 'back-to-menu') {
                    this.soundManager.play('click');
                    this._closePrompts();
                    this._openMenu();
                    return;
                }

                if (type === 'close-prompts') {
                    this.soundManager.play('close');
                    this._closePrompts();
                    return;
                }

                if (type === 'add-prompt') {
                    this.soundManager.play('click');
                    this._openPromptEditor();
                    return;
                }

                if (type === 'import-prompts') {
                    this.soundManager.play('click');
                    this._openPromptImport();
                    return;
                }

                if (type === 'use-prompt') {
                    this.soundManager.play('click');
                    const card = action.closest('[data-prompt-id]');
                    const id = card?.dataset?.promptId;
                    if (!id) return;
                    const prompt = this.promptLibrary.list().find(p => p.id === id);
                    if (!prompt) return;
                    this._usePrompt(prompt);
                    return;
                }

                if (type === 'edit-prompt') {
                    this.soundManager.play('click');
                    const card = action.closest('[data-prompt-id]');
                    const id = card?.dataset?.promptId;
                    if (!id) return;
                    const prompt = this.promptLibrary.list().find(p => p.id === id);
                    if (!prompt) return;
                    this._openPromptEditor(prompt);
                    return;
                }

                if (type === 'delete-prompt') {
                    this.soundManager.play('click');
                    const card = action.closest('[data-prompt-id]');
                    const id = card?.dataset?.promptId;
                    if (!id) return;
                    this._confirmDeletePrompt(id);
                    return;
                }
            });

            this.elements.promptSearchInput.addEventListener('input', (e) => {
                this.state.promptSearchQuery = (e.target.value || '').toString();
                this._renderPrompts();
            });

            this.elements.promptTagFilter.addEventListener('change', (e) => {
                this.state.promptTagFilter = (e.target.value || '').toString();
                this._renderPrompts();
            });
        }

        _createModalOverlay() {
            this.elements.modalOverlay = document.createElement('div');
            this.elements.modalOverlay.id = 'aiBubbleModalOverlay';
            this.elements.modalOverlay.setAttribute('aria-hidden', 'true');
            this.elements.modalOverlay.innerHTML = `
                <div class="ai-modal" role="dialog" aria-modal="true">
                    <div class="ai-modal-header">
                        <div class="ai-modal-title" id="aiModalTitle">${this._fa('fa-solid fa-layer-group')}<span>Modal</span></div>
                        <button class="ai-mini-btn" data-modal-action="close" aria-label="Close">
                            ${this._fa('fa-solid fa-xmark')}
                        </button>
                    </div>
                    <div class="ai-modal-body" id="aiModalBody"></div>
                    <div class="ai-modal-footer" id="aiModalFooter"></div>
                </div>
            `;

            document.body.appendChild(this.elements.modalOverlay);

            this.elements.modalOverlay.addEventListener('click', (e) => {
                const btn = e.target.closest('[data-modal-action]');
                if (btn) {
                    const action = btn.dataset.modalAction;
                    if (action === 'close') {
                        this.soundManager.play('close');
                        this._closeModal();
                    } else {
                        this._handleModalAction(action);
                    }
                    return;
                }

                // Click outside modal closes
                if (e.target === this.elements.modalOverlay) {
                    this.soundManager.play('close');
                    this._closeModal();
                }
            });

            this._modalActionHandlers = {};
        }

        _openPrompts() {
            this._closeMenu();
            this._closeSettings();
            if (!this.elements.prompts) return;

            this.state.isPromptsOpen = true;
            this.elements.prompts.classList.add('visible');
            this.elements.prompts.setAttribute('aria-hidden', 'false');

            // Sync tag filter options before rendering
            this._populatePromptTagOptions();
            this._renderPrompts();
            this.elements.promptSearchInput?.focus?.();

            this._requestEnsureUIInView();
        }

        _closePrompts() {
            if (!this.elements.prompts) return;
            this.state.isPromptsOpen = false;
            this.elements.prompts.classList.remove('visible');
            this.elements.prompts.setAttribute('aria-hidden', 'true');
        }

        _populatePromptTagOptions() {
            if (!this.elements.promptTagFilter) return;
            const prompts = this.promptLibrary.list();
            const tagSet = new Set();
            prompts.forEach(p => (p.tags || []).forEach(t => tagSet.add(t)));
            const tags = Array.from(tagSet).sort((a, b) => a.localeCompare(b));

            const current = this.state.promptTagFilter || '';
            this.elements.promptTagFilter.innerHTML = `
                <option value="" ${current === '' ? 'selected' : ''}>All tags</option>
                ${tags.map(t => `<option value="${this._escapeHtml(t)}" ${current === t ? 'selected' : ''}>${this._escapeHtml(t)}</option>`).join('')}
            `;
        }

        _renderPrompts() {
            if (!this.elements.promptsList) return;
            const botId = this._getCurrentBotId();
            const q = (this.state.promptSearchQuery || '').trim().toLowerCase();
            const tag = (this.state.promptTagFilter || '').trim();

            let items = this.promptLibrary.list();
            if (botId) {
                items = items.filter(p => p.bots === 'all' || (Array.isArray(p.bots) && p.bots.includes(botId)));
            }
            if (tag) {
                items = items.filter(p => Array.isArray(p.tags) && p.tags.includes(tag));
            }
            if (q) {
                items = items.filter(p => {
                    const hay = `${p.name || ''}\n${p.text || ''}\n${(p.tags || []).join(' ')}`.toLowerCase();
                    return hay.includes(q);
                });
            }

            if (items.length === 0) {
                this.elements.promptsList.innerHTML = `
                    <div class="ai-menu-empty" style="padding: 18px 8px;">
                        <div class="ai-menu-empty-icon">${this._fa('fa-solid fa-book-open')}</div>
                        <div>No prompts found</div>
                    </div>
                `;
                return;
            }

            this.elements.promptsList.innerHTML = items.map(p => {
                const botsLabel = p.bots === 'all'
                    ? 'All bots'
                    : (Array.isArray(p.bots) ? `${p.bots.length} bot${p.bots.length === 1 ? '' : 's'}` : 'All bots');
                const tagHtml = (p.tags || []).slice(0, 8).map(t => `<span class="ai-tag">${this._escapeHtml(t)}</span>`).join('');
                return `
                    <div class="ai-prompt-card" data-prompt-id="${this._escapeHtml(p.id)}">
                        <div class="ai-prompt-top">
                            <div style="flex: 1; min-width: 0;">
                                <div class="ai-prompt-name">${this._escapeHtml(p.name)}</div>
                                <div class="ai-prompt-meta">${this._escapeHtml(botsLabel)}</div>
                            </div>
                            <div class="ai-prompt-actions">
                                <button class="ai-mini-btn primary" data-action="use-prompt" title="Use">
                                    ${this._fa('fa-solid fa-paper-plane')}
                                </button>
                                <button class="ai-mini-btn" data-action="edit-prompt" title="Edit">
                                    ${this._fa('fa-solid fa-pen-to-square')}
                                </button>
                                <button class="ai-mini-btn danger" data-action="delete-prompt" title="Delete">
                                    ${this._fa('fa-solid fa-trash')}
                                </button>
                            </div>
                        </div>
                        ${tagHtml ? `<div class="ai-tag-row">${tagHtml}</div>` : ''}
                    </div>
                `;
            }).join('');
        }

        _openModal({ titleHtml, bodyHtml, footerHtml, handlers }) {
            if (!this.elements.modalOverlay) return;
            const titleEl = this.elements.modalOverlay.querySelector('#aiModalTitle');
            const bodyEl = this.elements.modalOverlay.querySelector('#aiModalBody');
            const footerEl = this.elements.modalOverlay.querySelector('#aiModalFooter');
            if (titleEl) titleEl.innerHTML = titleHtml;
            if (bodyEl) bodyEl.innerHTML = bodyHtml;
            if (footerEl) footerEl.innerHTML = footerHtml;
            this._modalActionHandlers = handlers || {};

            this.elements.modalOverlay.classList.add('visible');
            this.elements.modalOverlay.setAttribute('aria-hidden', 'false');
        }

        _closeModal() {
            if (!this.elements.modalOverlay) return;
            // If a hotkey capture is in progress, always clean it up.
            this._isCapturingHotkey = false;
            try {
                if (this._hotkeyCaptureHandler) {
                    document.removeEventListener('keydown', this._hotkeyCaptureHandler, true);
                }
            } catch {
                // ignore
            }
            this._hotkeyCaptureHandler = null;

            this.elements.modalOverlay.classList.remove('visible');
            this.elements.modalOverlay.setAttribute('aria-hidden', 'true');
            this._modalActionHandlers = {};
        }

        _refreshHotkeyDisplays() {
            const toggleEl = document.getElementById('aiHotkeyToggleDisplay');
            const unhideEl = document.getElementById('aiHotkeyUnhideDisplay');
            if (toggleEl) toggleEl.innerHTML = this._hotkeyHtml(this.configManager.get('hotkey'));
            if (unhideEl) unhideEl.innerHTML = this._hotkeyHtml(this.configManager.get('unhideHotkey'));
        }

        _openHotkeyCapture(settingKey) {
            const label = settingKey === 'unhideHotkey' ? 'Unhide Bubble' : 'Toggle Bubble';
            const current = this._hotkeyToKeys(this.configManager.get(settingKey)).join(' + ') || 'None';
            this._isCapturingHotkey = true;

            this._openModal({
                titleHtml: `${this._fa('fa-solid fa-keyboard')}<span>Set Shortcut</span>`,
                bodyHtml: `
                    <div class="ai-help" style="margin-bottom: 10px;"><strong>${this._escapeHtml(label)}</strong></div>
                    <div class="ai-help" style="margin-bottom: 10px;">Current: <strong>${this._escapeHtml(current)}</strong></div>
                    <div class="ai-help">Press a key combo now (Ctrl/Shift/Alt + key). Press Esc to cancel.</div>
                `,
                footerHtml: `
                    <button class="ai-mini-btn" data-modal-action="close" data-tooltip="Cancel">${this._fa('fa-solid fa-ban')}</button>
                    <button class="ai-mini-btn" data-modal-action="reset" data-tooltip="Reset to default">${this._fa('fa-solid fa-rotate-left')}</button>
                `,
                handlers: {
                    reset: () => {
                        const def = DEFAULT_CONFIG?.[settingKey];
                        if (def) this.configManager.set(settingKey, { ...def });
                        this._refreshHotkeyDisplays();
                        this._closeModal();
                    }
                }
            });

            const onKeyDown = (e) => {
                if (!this._isCapturingHotkey) return;
                e.preventDefault();
                e.stopPropagation();

                if (e.key === 'Escape') {
                    this._closeModal();
                    return;
                }

                const k = (e.key || '').toString();
                const isModifierOnly = (k === 'Shift' || k === 'Control' || k === 'Alt' || k === 'Meta');
                if (isModifierOnly) return;

                const hk = {
                    ctrl: !!e.ctrlKey,
                    shift: !!e.shiftKey,
                    alt: !!e.altKey,
                    key: (k.length === 1 ? k.toUpperCase() : k)
                };

                if (!hk.ctrl && !hk.shift && !hk.alt) {
                    const bodyEl = document.getElementById('aiModalBody');
                    if (bodyEl) {
                        bodyEl.insertAdjacentHTML('afterbegin', `<div class="ai-help" style="color:#ef4444; margin-bottom: 10px;">Use at least one modifier (Ctrl/Shift/Alt) to avoid conflicts.</div>`);
                    }
                    return;
                }

                this.configManager.set(settingKey, hk);
                this._refreshHotkeyDisplays();
                this._closeModal();
            };

            this._hotkeyCaptureHandler = onKeyDown;
            document.addEventListener('keydown', onKeyDown, true);
        }

        _setupTooltips() {
            const show = (text, target) => {
                if (!this.configManager.get('enableTooltips')) return;
                if (!this.elements.tooltip) return;
                if (!text) return;

                this.elements.tooltip.textContent = text;
                this.elements.tooltip.classList.add('visible');
                this.elements.tooltip.setAttribute('aria-hidden', 'false');

                const vp = this._getViewportBounds();
                const r = target.getBoundingClientRect();
                const pad = 8;

                // prefer above, fallback below
                let left = r.left + (r.width / 2);
                let top = r.top - pad;

                const tt = this.elements.tooltip.getBoundingClientRect();
                left = left - (tt.width / 2);
                top = top - tt.height;

                const minLeft = vp.left + 8;
                const maxLeft = (vp.left + vp.width) - tt.width - 8;
                left = Math.max(minLeft, Math.min(left, maxLeft));

                const minTop = vp.top + 8;
                if (top < minTop) {
                    top = r.bottom + pad;
                    const maxTop = (vp.top + vp.height) - tt.height - 8;
                    top = Math.max(minTop, Math.min(top, maxTop));
                }

                this.elements.tooltip.style.left = `${left}px`;
                this.elements.tooltip.style.top = `${top}px`;
            };

            const getTip = (el) => (el?.getAttribute?.('data-tooltip') || '').toString().trim();

            const onOver = (e) => {
                const target = e.target.closest?.('[data-tooltip]');
                if (!target) return;
                if (!this.elements.container.contains(target) && !(this.elements.modalOverlay && this.elements.modalOverlay.contains(target))) return;
                show(getTip(target), target);
            };

            const onOut = (e) => {
                const target = e.target.closest?.('[data-tooltip]');
                if (!target) return;
                const related = e.relatedTarget;
                if (related && target.contains(related)) return;
                this._hideTooltip();
            };

            this.elements.container.addEventListener('mouseover', onOver);
            this.elements.container.addEventListener('mouseout', onOut);
            this.elements.container.addEventListener('mousedown', () => this._hideTooltip(), true);

            if (this.elements.modalOverlay) {
                this.elements.modalOverlay.addEventListener('mouseover', onOver);
                this.elements.modalOverlay.addEventListener('mouseout', onOut);
                this.elements.modalOverlay.addEventListener('mousedown', () => this._hideTooltip(), true);
            }

            document.addEventListener('scroll', () => this._hideTooltip(), true);
        }

        _hideTooltip() {
            if (!this.elements.tooltip) return;
            this.elements.tooltip.classList.remove('visible');
            this.elements.tooltip.setAttribute('aria-hidden', 'true');
        }

        _handleModalAction(action) {
            const fn = this._modalActionHandlers?.[action];
            if (typeof fn === 'function') fn();
        }

        _openPromptEditor(existing = null) {
            const isEdit = !!existing;
            const safeName = existing ? this._escapeHtml(existing.name) : '';
            const safeText = existing ? this._escapeHtml(existing.text) : '';
            const safeTags = existing ? this._escapeHtml((existing.tags || []).join(', ')) : '';
            const bots = existing ? existing.bots : 'all';
            const botIds = AI_SITES.map(s => s.id);
            const selected = bots === 'all' ? new Set(['all']) : new Set(Array.isArray(bots) ? bots : ['all']);

            const botsHtml = `
                <div class="ai-bot-checkboxes">
                    <label class="ai-bot-checkbox">
                        <input type="checkbox" id="aiBotsAll" ${selected.has('all') ? 'checked' : ''}>
                        <span>All bots</span>
                    </label>
                    ${botIds.map(id => {
                        const site = AI_SITES.find(s => s.id === id);
                        const label = site ? site.name : id;
                        const checked = selected.has('all') ? '' : (selected.has(id) ? 'checked' : '');
                        return `
                            <label class="ai-bot-checkbox">
                                <input type="checkbox" class="aiBotCheck" value="${this._escapeHtml(id)}" ${checked}>
                                <span>${this._escapeHtml(label)}</span>
                            </label>
                        `;
                    }).join('')}
                </div>
            `;

            this._openModal({
                titleHtml: `${this._fa('fa-solid fa-pen-to-square')}<span>${isEdit ? 'Edit Prompt' : 'Add Prompt'}</span>`,
                bodyHtml: `
                    <div class="ai-field">
                        <label for="aiPromptName">Name</label>
                        <input class="ai-input" id="aiPromptName" type="text" value="${safeName}" placeholder="e.g., Summarize with action items">
                    </div>
                    <div class="ai-field">
                        <label for="aiPromptText">Prompt</label>
                        <textarea class="ai-textarea" id="aiPromptText" placeholder="Write your prompt...">${safeText}</textarea>
                    </div>
                    <div class="ai-field">
                        <label for="aiPromptTags">Tags (comma-separated)</label>
                        <input class="ai-input" id="aiPromptTags" type="text" value="${safeTags}" placeholder="e.g., summary, writing">
                    </div>
                    <div class="ai-field">
                        <label>Bots</label>
                        ${botsHtml}
                    </div>
                    <div class="ai-help">Template variables supported: <strong>{{content}}</strong>, <strong>{{context}}</strong>, etc.</div>
                `,
                footerHtml: `
                    <button class="ai-mini-btn" data-modal-action="close" title="Cancel">${this._fa('fa-solid fa-ban')}</button>
                    <button class="ai-mini-btn primary" data-modal-action="save" title="Save">${this._fa('fa-solid fa-check')}</button>
                `,
                handlers: {
                    save: () => {
                        const name = (document.getElementById('aiPromptName')?.value || '').trim();
                        const text = (document.getElementById('aiPromptText')?.value || '').toString();
                        const tags = (document.getElementById('aiPromptTags')?.value || '').toString();
                        const all = !!document.getElementById('aiBotsAll')?.checked;
                        const checkedBots = Array.from(document.querySelectorAll('.aiBotCheck')).filter(x => x.checked).map(x => x.value);

                        const prompt = {
                            id: existing?.id,
                            name,
                            text,
                            tags,
                            bots: all ? 'all' : checkedBots
                        };
                        const saved = this.promptLibrary.upsert(prompt);
                        if (!saved) return;
                        this._closeModal();
                        this._populatePromptTagOptions();
                        this._renderPrompts();
                    }
                }
            });

            // keep checkbox logic simple: when All is checked, disable others
            setTimeout(() => {
                const all = document.getElementById('aiBotsAll');
                const others = Array.from(document.querySelectorAll('.aiBotCheck'));
                const sync = () => {
                    const isAll = !!all?.checked;
                    others.forEach(cb => { cb.disabled = isAll; if (isAll) cb.checked = false; });
                };
                all?.addEventListener('change', sync);
                sync();
            }, 0);
        }

        _openPromptImport() {
            this._openModal({
                titleHtml: `${this._fa('fa-solid fa-file-import')}<span>Import Prompts (JSON)</span>`,
                bodyHtml: `
                    <div class="ai-field">
                        <label for="aiImportJson">Paste JSON</label>
                        <textarea class="ai-textarea" id="aiImportJson" placeholder='[{"name":"My prompt","text":"...","tags":["tag"],"bots":"all"}]'></textarea>
                    </div>
                    <div class="ai-help">Supported formats: an array of prompts, or {"prompts": [...]}.</div>
                `,
                footerHtml: `
                    <button class="ai-mini-btn" data-modal-action="close" title="Cancel">${this._fa('fa-solid fa-ban')}</button>
                    <button class="ai-mini-btn primary" data-modal-action="import" title="Import">${this._fa('fa-solid fa-check')}</button>
                `,
                handlers: {
                    import: () => {
                        const jsonText = (document.getElementById('aiImportJson')?.value || '').toString();
                        try {
                            const res = this.promptLibrary.importFromJson(jsonText);
                            this._closeModal();
                            this._populatePromptTagOptions();
                            this._renderPrompts();
                            this.soundManager.play('open');
                        } catch (e) {
                            const bodyEl = document.getElementById('aiModalBody');
                            if (bodyEl) {
                                const msg = (e && e.message) ? e.message : 'Invalid JSON';
                                bodyEl.insertAdjacentHTML('afterbegin', `<div class="ai-help" style="color: #ef4444; margin-bottom: 10px;">${this._escapeHtml(msg)}</div>`);
                            }
                        }
                    }
                }
            });
        }

        _confirmDeletePrompt(id) {
            const prompt = this.promptLibrary.list().find(p => p.id === id);
            if (!prompt) return;
            this._openModal({
                titleHtml: `${this._fa('fa-solid fa-triangle-exclamation')}<span>Delete Prompt</span>`,
                bodyHtml: `<div class="ai-help">Delete <strong>${this._escapeHtml(prompt.name)}</strong>? This cannot be undone.</div>`,
                footerHtml: `
                    <button class="ai-mini-btn" data-modal-action="close" title="Cancel">${this._fa('fa-solid fa-ban')}</button>
                    <button class="ai-mini-btn danger" data-modal-action="delete" title="Delete">${this._fa('fa-solid fa-trash')}</button>
                `,
                handlers: {
                    delete: () => {
                        this.promptLibrary.remove(id);
                        this._closeModal();
                        this._populatePromptTagOptions();
                        this._renderPrompts();
                    }
                }
            });
        }

        _extractTemplateVars(text) {
            const vars = new Set();
            const re = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
            let m;
            while ((m = re.exec(text || '')) !== null) {
                vars.add(m[1]);
            }
            return Array.from(vars);
        }

        _usePrompt(prompt) {
            const vars = this._extractTemplateVars(prompt.text);
            if (vars.length === 0) {
                this._insertIntoChat(prompt.text);
                return;
            }

            const fields = vars.map(v => `
                <div class="ai-field">
                    <label>${this._escapeHtml(v)}</label>
                    <input class="ai-input" data-var="${this._escapeHtml(v)}" type="text" placeholder="${this._escapeHtml(v)}">
                </div>
            `).join('');

            this._openModal({
                titleHtml: `${this._fa('fa-solid fa-wand-magic-sparkles')}<span>Fill Template</span>`,
                bodyHtml: `
                    <div class="ai-help" style="margin-bottom: 10px;">${this._escapeHtml(prompt.name)}</div>
                    ${fields}
                `,
                footerHtml: `
                    <button class="ai-mini-btn" data-modal-action="close" title="Cancel">${this._fa('fa-solid fa-ban')}</button>
                    <button class="ai-mini-btn primary" data-modal-action="apply" title="Insert">${this._fa('fa-solid fa-check')}</button>
                `,
                handlers: {
                    apply: () => {
                        const inputs = Array.from(document.querySelectorAll('[data-var]'));
                        const values = {};
                        inputs.forEach(i => { values[i.getAttribute('data-var')] = i.value || ''; });
                        let text = prompt.text;
                        Object.keys(values).forEach(k => {
                            const re = new RegExp(`\\{\\{\\s*${k.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\s*\\}\\}`, 'g');
                            text = text.replace(re, values[k]);
                        });
                        this._closeModal();
                        this._insertIntoChat(text);
                    }
                }
            });
        }

        _findChatInput() {
            const preferred = [
                'textarea#prompt-textarea',
                'textarea[placeholder*="Message" i]',
                'textarea[placeholder*="Send" i]',
                'div[contenteditable="true"][role="textbox"]'
            ];

            for (const sel of preferred) {
                const el = document.querySelector(sel);
                if (el && this._isProbablyChatInput(el)) return el;
            }

            const candidates = Array.from(document.querySelectorAll('textarea, div[contenteditable="true"], [contenteditable="true"]'))
                .filter(el => this._isProbablyChatInput(el));

            if (candidates.length === 0) return null;
            candidates.sort((a, b) => {
                const ra = a.getBoundingClientRect();
                const rb = b.getBoundingClientRect();
                const scoreA = (ra.width * ra.height) + (ra.bottom * 2);
                const scoreB = (rb.width * rb.height) + (rb.bottom * 2);
                return scoreB - scoreA;
            });
            return candidates[0] || null;
        }

        _insertIntoChat(text) {
            const input = this._findChatInput();
            if (!input) {
                this._openModal({
                    titleHtml: `${this._fa('fa-solid fa-circle-info')}<span>Could not find chat input</span>`,
                    bodyHtml: `<div class="ai-help">Open a conversation page and click a prompt again.</div>`,
                    footerHtml: `<button class="ai-mini-btn primary" data-modal-action="close">${this._fa('fa-solid fa-check')}</button>`,
                    handlers: {}
                });
                return;
            }

            try {
                input.focus?.();

                if (input.tagName === 'TEXTAREA' || input.tagName === 'INPUT') {
                    input.value = text;
                    input.dispatchEvent(new Event('input', { bubbles: true }));
                } else {
                    input.textContent = text;
                    input.dispatchEvent(new Event('input', { bubbles: true }));
                }
            } catch (e) {
                // no-op
            }
        }

        _loadState() {
            const position = this.configManager.get('position');
            const isHidden = this.configManager.get('isHidden');

            if (position.left !== null && position.top !== null) {
                this.elements.container.style.left = `${position.left}px`;
                this.elements.container.style.top = `${position.top}px`;
            } else {
                this.elements.container.style.right = `${position.right}px`;
                this.elements.container.style.bottom = `${position.bottom}px`;
            }

            if (isHidden) {
                this.elements.container.classList.add('hidden');
            }

            if (this.configManager.get('compactMode')) {
                this.elements.container.classList.add('compact');
            }
        }

        _updateBubbleSize() {
            const size = this.configManager.get('bubbleSize');
            this.elements.container.style.setProperty('--bubble-size', `${size}px`);

            // Scale icons with bubble size
            const mainIcon = Math.max(18, Math.min(34, Math.round(size * 0.42)));
            const resizeIcon = Math.max(14, Math.min(24, Math.round(size * 0.28)));
            this.elements.container.style.setProperty('--ai-bubble-main-icon-size', `${mainIcon}px`);
            this.elements.container.style.setProperty('--ai-bubble-resize-size', `${resizeIcon}px`);
        }

        _updateAccentColor() {
            const color = this.configManager.get('accentColor');
            document.documentElement.style.setProperty('--ai-bubble-accent', color);
            document.documentElement.style.setProperty('--ai-bubble-accent-rgb', this._hexToRgb(color));
        }

        _setupEventListeners() {
            // Button click
            this.elements.button.addEventListener('click', (e) => {
                e.stopPropagation();

                const now = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
                if (this.state.isDragging || this.state.isResizing || this.state.isSidebarResizing || (this.state.suppressClickUntil && now < this.state.suppressClickUntil)) {
                    return;
                }

                // micro-animations
                this.elements.button.classList.remove('popping', 'splashing');
                void this.elements.button.offsetWidth;
                this.elements.button.classList.add('popping', 'splashing');
                setTimeout(() => {
                    this.elements.button.classList.remove('popping', 'splashing');
                }, 650);

                // If the sidebar popup window is open, clicking the bubble closes it
                if (this._hasOpenSidebarWindow()) {
                    this._closeSidebarWindow();
                    return;
                }

                // If any panels are open, clicking the bubble closes them
                if (this.state.isSettingsOpen) {
                    this._closeSettings();
                    return;
                }
                if (this.state.isMenuOpen) {
                    this._closeMenu();
                    return;
                }
                if (this.state.isPromptsOpen) {
                    this._closePrompts();
                    return;
                }

                // On AI bot pages, toggle prompt manager
                if (this._getCurrentBotId()) {
                    this._openPrompts();
                    return;
                }

                this._toggleMenu();
            });

            // Drag handling
            this._setupDrag();

            // Resize handling
            this._setupResize();

            // Sidebar window resize handling
            this._setupSidebarWindowResize();

            // Menu interactions
            this._setupMenuEvents();

            // Settings interactions
            this._setupSettingsEvents();

            // Global events
            this._setupGlobalEvents();

            // Tooltips
            this._setupTooltips();
        }

        _setupDrag() {
            let startX, startY, startLeft, startTop;
            let hasMoved = false;
            let handleRaf = 0;

            const onStart = (e) => {
                if (e.target.closest('#aiBubbleResize') ||
                    e.target.closest('#aiSidebarResize') ||
                    e.target.closest('#aiBubbleMenu') ||
                    e.target.closest('#aiBubbleSettings') ||
                    e.target.closest('#aiBubblePrompts') ||
                    e.target.closest('#aiBubbleModalOverlay')) return;

                const clientX = e.touches ? e.touches[0].clientX : e.clientX;
                const clientY = e.touches ? e.touches[0].clientY : e.clientY;

                startX = clientX;
                startY = clientY;
                const rect = this.elements.container.getBoundingClientRect();
                startLeft = rect.left;
                startTop = rect.top;
                hasMoved = false;

                this.state.isDragging = true;
                this.elements.container.classList.add('dragging');

                if (this.hideTimeout) {
                    clearTimeout(this.hideTimeout);
                    this.hideTimeout = null;
                }
            };

            const onMove = (e) => {
                if (!this.state.isDragging) return;

                const clientX = e.touches ? e.touches[0].clientX : e.clientX;
                const clientY = e.touches ? e.touches[0].clientY : e.clientY;

                const deltaX = clientX - startX;
                const deltaY = clientY - startY;

                if (Math.abs(deltaX) > 3 || Math.abs(deltaY) > 3) {
                    hasMoved = true;
                }

                const size = this.configManager.get('bubbleSize');
                const vp = this._getViewportBounds();
                const minLeft = vp.left;
                const minTop = vp.top;
                const maxLeft = vp.left + vp.width - size;
                const maxTop = vp.top + vp.height - size;

                const newLeft = Math.max(minLeft, Math.min(startLeft + deltaX, maxLeft));
                const newTop = Math.max(minTop, Math.min(startTop + deltaY, maxTop));

                this.elements.container.style.left = `${newLeft}px`;
                this.elements.container.style.top = `${newTop}px`;
                this.elements.container.style.right = 'auto';
                this.elements.container.style.bottom = 'auto';

                if (this._hasOpenSidebarWindow()) {
                    if (!handleRaf) {
                        handleRaf = requestAnimationFrame(() => {
                            handleRaf = 0;
                            this._updateSidebarResizeHandlePlacement();
                        });
                    }
                }
            };

            const onEnd = () => {
                if (!this.state.isDragging) return;

                this.state.isDragging = false;
                this.elements.container.classList.remove('dragging');

                if (hasMoved) {
                    const now = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
                    this.state.suppressClickUntil = now + 450;
                    this._maybeSnapToCorner();
                }

                if (this._hasOpenSidebarWindow()) {
                    this._updateSidebarResizeHandlePlacement();
                }
            };

            this.elements.button.addEventListener('mousedown', onStart);
            this.elements.button.addEventListener('touchstart', onStart, { passive: true });

            document.addEventListener('mousemove', onMove);
            document.addEventListener('touchmove', onMove, { passive: true });

            document.addEventListener('mouseup', onEnd);
            document.addEventListener('touchend', onEnd);
        }

        _setupResize() {
            const onStart = (e) => {
                e.preventDefault();
                e.stopPropagation();

                const clientX = e.touches ? e.touches[0].clientX : e.clientX;
                const clientY = e.touches ? e.touches[0].clientY : e.clientY;

                this.state.isResizing = true;
                this.state.startSize = this.configManager.get('bubbleSize');
                this.state.startX = clientX;
                this.state.startY = clientY;
            };

            const onMove = (e) => {
                if (!this.state.isResizing) return;

                const clientX = e.touches ? e.touches[0].clientX : e.clientX;
                const clientY = e.touches ? e.touches[0].clientY : e.clientY;

                const delta = Math.max(clientX - this.state.startX, clientY - this.state.startY);
                const newSize = Math.max(40, Math.min(80, this.state.startSize + delta));

                this.configManager.set('bubbleSize', newSize);
                this._updateBubbleSize();

                // Update slider if settings is open
                const slider = document.getElementById('bubbleSizeSlider');
                if (slider) {
                    slider.value = newSize;
                    slider.nextElementSibling.textContent = `${newSize}px`;
                }
            };

            const onEnd = () => {
                this.state.isResizing = false;
            };

            this.elements.resize.addEventListener('mousedown', onStart);
            this.elements.resize.addEventListener('touchstart', onStart, { passive: false });

            document.addEventListener('mousemove', onMove);
            document.addEventListener('touchmove', onMove, { passive: true });

            document.addEventListener('mouseup', onEnd);
            document.addEventListener('touchend', onEnd);
        }

        _setupSidebarWindowResize() {
            if (!this.elements.sidebarResize) return;

            let startX = 0;
            let startY = 0;
            let startWidth = 0;
            let startHeight = 0;
            let raf = 0;
            let latestW = 0;
            let latestH = 0;

            const clamp = (n, min, max) => Math.max(min, Math.min(n, max));

            const getHostBounds = () => this._getSidebarHostBounds();

            const getCurrentSize = () => {
                const host = getHostBounds();
                const cfgW = this.configManager.get('sidebarWidth') || 420;
                const cfgPct = this.configManager.get('sidebarHeightPercent') || 0.92;
                const cfgH = clamp(Math.round(host.height * cfgPct), 320, Math.max(360, host.height - 24));

                let w = cfgW;
                let h = cfgH;
                try {
                    if (this._sidebarWindow && !this._sidebarWindow.closed) {
                        w = this._sidebarWindow.outerWidth || w;
                        h = this._sidebarWindow.outerHeight || h;
                    }
                } catch {
                    // ignore
                }

                w = clamp(Math.round(w), 300, Math.max(320, host.width - 24));
                h = clamp(Math.round(h), 320, Math.max(360, host.height - 24));
                return { w, h, host };
            };

            const applySize = (width, height) => {
                if (!this._hasOpenSidebarWindow()) return;
                const host = getHostBounds();

                const w = clamp(Math.round(width), 300, Math.max(320, host.width - 24));
                const h = clamp(Math.round(height), 320, Math.max(360, host.height - 24));
                const left = Math.max(host.left, Math.round(host.left + host.width - w));
                const top = Math.max(host.top, Math.min(Math.round(host.top + (host.height - h) / 2), Math.round(host.top + host.height - h)));

                try { this._sidebarWindow.resizeTo(w, h); } catch { /* ignore */ }
                try { this._sidebarWindow.moveTo(left, top); } catch { /* ignore */ }

                this.configManager.set('sidebarWidth', w);
                this.configManager.set('sidebarHeightPercent', clamp(h / host.height, 0.4, 0.98));

                const slider = document.getElementById('sidebarWidthSlider');
                if (slider) {
                    slider.value = w;
                    slider.nextElementSibling.textContent = `${w}px`;
                }
            };

            const scheduleApply = () => {
                if (raf) return;
                raf = requestAnimationFrame(() => {
                    raf = 0;
                    applySize(latestW, latestH);
                });
            };

            const onStart = (e) => {
                if (!this._hasOpenSidebarWindow()) return;
                e.preventDefault();
                e.stopPropagation();

                const clientX = e.touches ? e.touches[0].clientX : e.clientX;
                const clientY = e.touches ? e.touches[0].clientY : e.clientY;

                const { w, h } = getCurrentSize();
                startX = clientX;
                startY = clientY;
                startWidth = w;
                startHeight = h;

                // Ensure placement is up-to-date before we pick directions
                this._updateSidebarResizeHandlePlacement();

                const corner = this.state.sidebarResizeCorner || 'tl';
                const dirMap = {
                    br: { sx: 1, sy: 1, cursor: 'se-resize' },
                    bl: { sx: -1, sy: 1, cursor: 'sw-resize' },
                    tr: { sx: 1, sy: -1, cursor: 'ne-resize' },
                    tl: { sx: -1, sy: -1, cursor: 'nw-resize' }
                };
                const dir = dirMap[corner] || dirMap.tl;
                this._sidebarResizeDir = dir;

                this.state.isSidebarResizing = true;
                this.elements.container.classList.add('sidebar-resizing');
                this.elements.container.style.setProperty('--ai-sidebar-resize-cursor', dir.cursor);
                document.documentElement.style.cursor = dir.cursor;

                if (this.hideTimeout) {
                    clearTimeout(this.hideTimeout);
                    this.hideTimeout = null;
                }
            };

            const onMove = (e) => {
                if (!this.state.isSidebarResizing) return;
                const clientX = e.touches ? e.touches[0].clientX : e.clientX;
                const clientY = e.touches ? e.touches[0].clientY : e.clientY;

                const dx = clientX - startX;
                const dy = clientY - startY;

                const dir = this._sidebarResizeDir || { sx: 1, sy: 1 };
                latestW = startWidth + (dx * dir.sx);
                latestH = startHeight + (dy * dir.sy);
                scheduleApply();
            };

            const onEnd = () => {
                if (!this.state.isSidebarResizing) return;
                this.state.isSidebarResizing = false;
                this.elements.container.classList.remove('sidebar-resizing');
                this.elements.container.style.removeProperty('--ai-sidebar-resize-cursor');
                document.documentElement.style.cursor = '';
                this._sidebarResizeDir = null;

                const now = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
                this.state.suppressClickUntil = now + 250;
            };

            this.elements.sidebarResize.addEventListener('mousedown', onStart);
            this.elements.sidebarResize.addEventListener('touchstart', onStart, { passive: false });

            document.addEventListener('mousemove', onMove);
            document.addEventListener('touchmove', onMove, { passive: true });

            document.addEventListener('mouseup', onEnd);
            document.addEventListener('touchend', onEnd);
        }

        _setupMenuEvents() {
            // Search input
            this.elements.searchInput.addEventListener('input', (e) => {
                this.state.searchQuery = e.target.value;
                this._renderMenuItems();
            });

            // Menu item clicks
            this.elements.menu.addEventListener('click', (e) => {
                const action = e.target.closest('[data-action]');
                if (action) {
                    const actionType = action.dataset.action;

                    switch (actionType) {
                        case 'settings':
                            this.soundManager.play('click');
                            this._closeMenu();
                            this._openSettings();
                            break;
                        case 'prompts':
                            this.soundManager.play('click');
                            this._closeMenu();
                            this._openPrompts();
                            break;
                        case 'hide':
                            this.soundManager.play('close');
                            this._closeMenu();
                            this._hide();
                            break;
                        case 'favorite':
                            e.stopPropagation();
                            this.soundManager.play('click');
                            const item = action.closest('.ai-menu-item');
                            if (item) {
                                this._toggleFavorite(item.dataset.id);
                            }
                            break;
                        case 'open-window':
                            e.stopPropagation();
                            this.soundManager.play('open');
                            const menuItem = action.closest('.ai-menu-item');
                            if (menuItem) {
                                this._openWindow(menuItem.dataset.url);
                                this._closeMenu();
                            }
                            break;
                        case 'open-tab':
                            e.stopPropagation();
                            this.soundManager.play('open');
                            const mi = action.closest('.ai-menu-item');
                            if (mi) {
                                this._openTab(mi.dataset.url);
                                this._closeMenu();
                            }
                            break;
                    }
                    return;
                }

                // Direct click on menu item
                const menuItem = e.target.closest('.ai-menu-item');
                if (menuItem && menuItem.dataset.url) {
                    this.soundManager.play('open');
                    const mode = this.configManager.get('openMode');
                    if (mode === 'tab') this._openTab(menuItem.dataset.url);
                    else this._openWindow(menuItem.dataset.url);
                    this._closeMenu();
                }
            });

            // Hover behavior
            this.elements.container.addEventListener('mouseenter', () => {
                if (this.hideTimeout) {
                    clearTimeout(this.hideTimeout);
                    this.hideTimeout = null;
                }
            });

            this.elements.container.addEventListener('mouseleave', () => {
                if (this.state.isMenuOpen && !this.state.isSettingsOpen) {
                    const now = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
                    if (this.state.isDragging || this.state.isResizing || this.state.isSidebarResizing || (this.state.suppressClickUntil && now < this.state.suppressClickUntil)) return;
                    const hasQuery = !!(this.state.searchQuery && this.state.searchQuery.trim().length > 0);
                    const active = document.activeElement;
                    const searchFocused = active === this.elements.searchInput;
                    if (searchFocused || hasQuery) return;
                    this.hideTimeout = setTimeout(() => {
                        this._closeMenu();
                    }, 300);
                }
            });
        }

        _setupSettingsEvents() {
            this.elements.settings.addEventListener('click', (e) => {
                const action = e.target.closest('[data-action]');
                if (action) {
                    const actionType = action.dataset.action;

                    if (actionType === 'back-to-menu') {
                        this.soundManager.play('click');
                        this._closeSettings();
                        this._openMenu();
                        return;
                    }

                    if (actionType === 'edit-hotkey') {
                        this.soundManager.play('click');
                        const key = (action.dataset.hotkey || '').toString();
                        if (key === 'hotkey' || key === 'unhideHotkey') {
                            this._openHotkeyCapture(key);
                        }
                        return;
                    }

                    switch (actionType) {
                        case 'close-settings':
                            this.soundManager.play('close');
                            this._closeSettings();
                            break;
                        case 'reset':
                            this.soundManager.play('click');
                            this._resetSettings();
                            break;
                    }
                    return;
                }

                // Open mode buttons
                const openModeBtn = e.target.closest('[data-open-mode]');
                if (openModeBtn) {
                    this.soundManager.play('click');
                    const mode = openModeBtn.dataset.openMode;
                    if (mode === 'window' || mode === 'tab') {
                        this.configManager.set('openMode', mode);
                        this.elements.settings.querySelectorAll('[data-open-mode]').forEach(btn => {
                            btn.classList.toggle('active', btn.dataset.openMode === mode);
                        });
                    }
                    return;
                }

                // Theme buttons
                const themeBtn = e.target.closest('[data-theme]');
                if (themeBtn) {
                    this.soundManager.play('click');
                    const theme = themeBtn.dataset.theme;
                    this.themeManager.setTheme(theme);
                    this.elements.settings.querySelectorAll('.ai-theme-btn').forEach(btn => {
                        btn.classList.toggle('active', btn.dataset.theme === theme);
                    });
                    return;
                }

                // Color swatches
                const colorSwatch = e.target.closest('[data-color]');
                if (colorSwatch) {
                    this.soundManager.play('click');
                    const color = colorSwatch.dataset.color;
                    this.configManager.set('accentColor', color);
                    this._updateAccentColor();
                    this.elements.settings.querySelectorAll('.ai-color-swatch').forEach(swatch => {
                        swatch.classList.toggle('active', swatch.dataset.color === color);
                    });
                    return;
                }

                // Toggle switches
                const toggle = e.target.closest('.ai-toggle');
                if (toggle) {
                    this.soundManager.play('click');
                    const setting = toggle.dataset.setting;
                    const newValue = !this.configManager.get(setting);
                    this.configManager.set(setting, newValue);
                    toggle.classList.toggle('active', newValue);

                    if (setting === 'compactMode') {
                        this.elements.container.classList.toggle('compact', newValue);
                    } else if (setting === 'showLoginBadges') {
                        this._renderMenuItems();
                    } else if (setting === 'enableTooltips') {
                        this._hideTooltip();
                    }
                }
            });

            // Selects
            this.elements.settings.addEventListener('change', (e) => {
                if (e.target.id === 'aiFontPreset') {
                    const value = (e.target.value || 'system').toString();
                    this.configManager.set('fontPreset', value);
                    this._applyTypography();
                } else if (e.target.id === 'aiMotionPreset') {
                    const value = (e.target.value || 'lively').toString();
                    this.configManager.set('motionPreset', value);
                    this._applyMotionPreset();
                }
            });

            // Sliders
            this.elements.settings.addEventListener('input', (e) => {
                if (e.target.id === 'bubbleSizeSlider') {
                    const value = parseInt(e.target.value);
                    this.configManager.set('bubbleSize', value);
                    this._updateBubbleSize();
                    e.target.nextElementSibling.textContent = `${value}px`;
                } else if (e.target.id === 'bubbleOpacitySlider') {
                    const value = parseFloat(e.target.value);
                    this.configManager.set('bubbleOpacity', value);
                    this.elements.container.style.setProperty('--bubble-opacity', value);
                    e.target.nextElementSibling.textContent = `${Math.round(value * 100)}%`;
                } else if (e.target.id === 'sidebarWidthSlider') {
                    const value = parseInt(e.target.value);
                    this.configManager.set('sidebarWidth', value);
                    e.target.nextElementSibling.textContent = `${value}px`;

                    if (this._hasOpenSidebarWindow()) {
                        const host = this._getSidebarHostBounds();
                        const pct = this.configManager.get('sidebarHeightPercent') || 0.92;
                        const height = Math.min(host.height * pct, host.height - 24);
                        this._applySidebarWindowSize(value, height);
                    }
                }
            });
        }

        _setupGlobalEvents() {
            // Click outside to close
            document.addEventListener('click', (e) => {
                if (!this.elements.container.contains(e.target)) {
                    if (this.state.isMenuOpen) this._closeMenu();
                    if (this.state.isSettingsOpen) this._closeSettings();
                    if (this.state.isPromptsOpen) this._closePrompts();
                }

                // Recovery: if hidden, click bottom-right corner to show
                if (this.elements.container.classList.contains('hidden')) {
                    const margin = 28;
                    const inCorner = e.clientX >= (window.innerWidth - margin) && e.clientY >= (window.innerHeight - margin);
                    if (inCorner) this._show();
                }
            });

            // Keyboard shortcut
            document.addEventListener('keydown', (e) => {
                if (this._isCapturingHotkey) return;
                const hotkey = this.configManager.get('hotkey');
                const unhide = this.configManager.get('unhideHotkey');
                const isHidden = this.elements.container.classList.contains('hidden');

                // Dedicated unhide hotkey (allow extra modifiers for reliability)
                if (isHidden && this._matchesHotkey(e, unhide, { strict: false })) {
                    e.preventDefault();
                    this._show();
                    return;
                }

                // Toggle hotkey (strict match to avoid accidental triggers)
                if (this._matchesHotkey(e, hotkey, { strict: true })) {
                    e.preventDefault();
                    this._toggleVisibility();
                }

                // Escape to close
                if (e.key === 'Escape') {
                    // Recovery: if hidden, double-tap Escape to show
                    if (this.elements.container.classList.contains('hidden')) {
                        const now = Date.now();
                        if (now - (this._lastEscapeAt || 0) < 800) {
                            this._show();
                            this._lastEscapeAt = 0;
                            return;
                        }
                        this._lastEscapeAt = now;
                        return;
                    }

                    if (this.state.isSettingsOpen) this._closeSettings();
                    else if (this.state.isMenuOpen) this._closeMenu();
                    else if (this.state.isPromptsOpen) this._closePrompts();
                }
            });

            // Window resize
            let resizeTimeout;
            window.addEventListener('resize', () => {
                if (resizeTimeout) clearTimeout(resizeTimeout);
                resizeTimeout = setTimeout(() => {
                    this._clampToViewport();
                    if (this.state.isMenuOpen || this.state.isSettingsOpen || this.state.isPromptsOpen) {
                        this._requestEnsureUIInView();
                    }
                }, 100);
            });

            // Zoom / visual viewport changes (mobile + pinch zoom)
            if (window.visualViewport) {
                const vv = window.visualViewport;
                this._lastVisualViewport = {
                    offsetLeft: vv.offsetLeft || 0,
                    offsetTop: vv.offsetTop || 0,
                    width: vv.width,
                    height: vv.height
                };

                this._vvRaf = 0;

                const onVvChange = () => {
                    if (this.state.isDragging || this.state.isResizing) return;
                    if (this._vvRaf) return;
                    this._vvRaf = requestAnimationFrame(() => {
                        this._vvRaf = 0;
                        this._preservePositionOnVisualViewportChange();
                        if (this.state.isMenuOpen || this.state.isSettingsOpen || this.state.isPromptsOpen) {
                            this._requestEnsureUIInView();
                        }
                    });
                };
                vv.addEventListener('resize', onVvChange);
                vv.addEventListener('scroll', onVvChange);
            }
        }

        _toggleMenu() {
            if (this.state.isMenuOpen) {
                this._closeMenu();
            } else {
                this._openMenu();
            }
        }

        _openMenu() {
            if (this.state.isSettingsOpen) this._closeSettings();

            this.state.isMenuOpen = true;
            this.elements.menu.classList.add('visible');
            this.elements.menu.setAttribute('aria-hidden', 'false');
            this.elements.button.setAttribute('aria-expanded', 'true');
            this.soundManager.play('open');

            this._requestEnsureUIInView();

            // Focus search input
            setTimeout(() => {
                this.elements.searchInput.focus();
            }, 100);
        }

        _closeMenu() {
            this.state.isMenuOpen = false;
            this.elements.menu.classList.add('closing');
            this.elements.menu.setAttribute('aria-hidden', 'true');
            this.elements.button.setAttribute('aria-expanded', 'false');

            setTimeout(() => {
                this.elements.menu.classList.remove('visible', 'closing');
            }, 200);

            // Clear search
            this.state.searchQuery = '';
            this.elements.searchInput.value = '';
            this._renderMenuItems();
        }

        _openSettings() {
            this.state.isSettingsOpen = true;
            this.elements.settings.classList.add('visible');
            this.elements.settings.setAttribute('aria-hidden', 'false');

            this._requestEnsureUIInView();
        }

        _closeSettings() {
            this.state.isSettingsOpen = false;
            this.elements.settings.classList.remove('visible');
            this.elements.settings.setAttribute('aria-hidden', 'true');
        }

        _toggleFavorite(siteId) {
            const favorites = this.configManager.get('favorites') || [];
            const index = favorites.indexOf(siteId);
            const willBeFavorite = index === -1;

            if (index > -1) {
                favorites.splice(index, 1);
            } else {
                favorites.push(siteId);
            }

            this.state.lastFavoriteToggle = { id: siteId, on: willBeFavorite, at: Date.now() };

            this.configManager.set('favorites', favorites);
            this._renderMenuItems();
        }

        _openWindow(url) {
            try {
                const host = this._getSidebarHostBounds();
                const sidebarWidth = this.configManager.get('sidebarWidth');
                const sidebarHeightPercent = this.configManager.get('sidebarHeightPercent');

                const maxWidth = Math.max(320, host.width - 24);
                const maxHeight = Math.max(360, host.height - 24);

                const width = Math.min(sidebarWidth, maxWidth);
                const height = Math.min(host.height * sidebarHeightPercent, maxHeight);

                const left = Math.max(host.left, Math.round(host.left + host.width - width));
                const top = Math.max(host.top, Math.min(Math.round(host.top + (host.height - height) / 2), Math.round(host.top + host.height - height)));

                const features = `width=${width},height=${height},left=${left},top=${top},menubar=no,toolbar=no,location=yes,status=no,resizable=yes,scrollbars=yes`;
                const urlWithParam = `${url}${url.includes('?') ? '&' : '?'}ai_sidebar_window=true`;

                const newWindow = window.open(urlWithParam, 'AIFloatingSidebar', features);

                if (newWindow && !newWindow.closed) {
                    this._sidebarWindow = newWindow;
                    this.elements.container.classList.add('sidebar-open');
                    this._updateSidebarResizeHandlePlacement();
                    this._startSidebarConstraintLoop();
                    try { newWindow.focus(); } catch { /* ignore */ }
                }

                if (!newWindow || newWindow.closed) {
                    console.warn('Popup blocked. Opening in current tab.');
                    window.open(url, '_blank', 'noopener,noreferrer');
                }
            } catch (error) {
                console.error('Error opening sidebar:', error);
                window.open(url, '_blank', 'noopener,noreferrer');
            }
        }

        _openTab(url) {
            try {
                window.open(url, '_blank', 'noopener,noreferrer');
            } catch (e) {
                window.open(url, '_blank');
            }
        }

        _hasOpenSidebarWindow() {
            const open = !!(this._sidebarWindow && !this._sidebarWindow.closed);
            if (!open) {
                this.elements.container.classList.remove('sidebar-open');
                this._clearSidebarResizeCornerClasses();
                this._stopSidebarConstraintLoop();
            }
            return open;
        }

        _getSidebarHostBounds() {
            // Prefer the browser *viewport* bounds (webpage area) so the sidebar feels "stuck" inside the site.
            // In Chrome we can approximate the screen-space viewport rectangle using:
            // - outer window top-left in screen coords (screenX/screenY)
            // - inner viewport size (innerWidth/innerHeight)
            // - outer - inner as "chrome" thickness (estimated split between sides/top/bottom)
            try {
                const left = (typeof window.screenX === 'number') ? window.screenX : (window.screenLeft || 0);
                const top = (typeof window.screenY === 'number') ? window.screenY : (window.screenTop || 0);
                const width = window.outerWidth || window.innerWidth;
                const height = window.outerHeight || window.innerHeight;
                const innerW = window.innerWidth || width;
                const innerH = window.innerHeight || height;

                if (width && height && innerW && innerH && isFinite(left) && isFinite(top)) {
                    const chromeW = Math.max(0, width - innerW);
                    const chromeH = Math.max(0, height - innerH);

                    // Assume left/right chrome are roughly symmetric.
                    const insetLeft = Math.round(chromeW / 2);
                    const insetRight = chromeW - insetLeft;

                    // Bottom border is usually small; approximate as left inset.
                    const insetBottom = Math.min(Math.max(0, insetLeft), chromeH);
                    const insetTop = Math.max(0, chromeH - insetBottom);

                    const viewportLeft = left + insetLeft;
                    const viewportTop = top + insetTop;
                    const viewportWidth = Math.max(1, width - insetLeft - insetRight);
                    const viewportHeight = Math.max(1, height - insetTop - insetBottom);

                    // Guard: if computed viewport looks wrong, fall back to outer.
                    if (viewportWidth > 50 && viewportHeight > 50) {
                        return { left: viewportLeft, top: viewportTop, width: viewportWidth, height: viewportHeight };
                    }

                    return { left, top, width, height };
                }
            } catch {
                // ignore
            }

            // Fallback to available screen work area.
            const s = window.screen || { width: window.innerWidth, height: window.innerHeight };
            const left = (typeof s.availLeft === 'number') ? s.availLeft : 0;
            const top = (typeof s.availTop === 'number') ? s.availTop : 0;
            const width = (typeof s.availWidth === 'number') ? s.availWidth : (s.width || window.innerWidth);
            const height = (typeof s.availHeight === 'number') ? s.availHeight : (s.height || window.innerHeight);
            return { left, top, width, height };
        }

        _constrainSidebarWindowToHost() {
            if (!this._hasOpenSidebarWindow()) return;

            const host = this._getSidebarHostBounds();
            let w = this.configManager.get('sidebarWidth') || 420;
            let h = Math.round((this.configManager.get('sidebarHeightPercent') || 0.92) * host.height);

            try {
                w = this._sidebarWindow.outerWidth || w;
                h = this._sidebarWindow.outerHeight || h;
            } catch {
                // ignore
            }

            this._applySidebarWindowSize(w, h);
        }

        _startSidebarConstraintLoop() {
            if (this._sidebarConstraintTimer) return;
            this._sidebarConstraintTimer = setInterval(() => {
                if (!this._hasOpenSidebarWindow()) {
                    this._stopSidebarConstraintLoop();
                    return;
                }
                this._constrainSidebarWindowToHost();
            }, 750);
        }

        _stopSidebarConstraintLoop() {
            if (this._sidebarConstraintTimer) {
                clearInterval(this._sidebarConstraintTimer);
                this._sidebarConstraintTimer = null;
            }
        }

        _clearSidebarResizeCornerClasses() {
            const c = this.elements.container;
            if (!c) return;
            c.classList.remove('sidebar-resize-tl', 'sidebar-resize-tr', 'sidebar-resize-bl', 'sidebar-resize-br');
        }

        _updateSidebarResizeHandlePlacement() {
            if (!this.elements.container) return;
            if (!this._hasOpenSidebarWindow()) return;

            const vp = this._getViewportBounds();
            const rect = this.elements.container.getBoundingClientRect();
            const centerX = (rect.left + rect.width / 2) - vp.left;
            const centerY = (rect.top + rect.height / 2) - vp.top;

            const isLeft = centerX < (vp.width / 2);
            const isTop = centerY < (vp.height / 2);

            // Place the handle on the "inward" corner so resizing feels natural.
            // Bubble top-left => handle bottom-right, etc.
            let corner = 'tl';
            if (isTop && isLeft) corner = 'br';
            else if (isTop && !isLeft) corner = 'bl';
            else if (!isTop && isLeft) corner = 'tr';
            else corner = 'tl';

            this.state.sidebarResizeCorner = corner;
            this._clearSidebarResizeCornerClasses();
            this.elements.container.classList.add(`sidebar-resize-${corner}`);
        }

        _applySidebarWindowSize(width, height) {
            if (!this._hasOpenSidebarWindow()) return;
            const host = this._getSidebarHostBounds();

            const w = Math.max(300, Math.min(Math.round(width), Math.max(320, host.width - 24)));
            const h = Math.max(320, Math.min(Math.round(height), Math.max(360, host.height - 24)));

            const left = Math.max(host.left, Math.round(host.left + host.width - w));
            const top = Math.max(host.top, Math.min(Math.round(host.top + (host.height - h) / 2), Math.round(host.top + host.height - h)));

            try { this._sidebarWindow.resizeTo(w, h); } catch { /* ignore */ }
            try { this._sidebarWindow.moveTo(left, top); } catch { /* ignore */ }

            this.elements.container.classList.add('sidebar-open');
            this._updateSidebarResizeHandlePlacement();
        }

        _closeSidebarWindow() {
            try {
                if (this._sidebarWindow && !this._sidebarWindow.closed) {
                    this._sidebarWindow.close();
                }
            } catch {
                // ignore
            }

            this.elements.container.classList.remove('sidebar-open');
            this._clearSidebarResizeCornerClasses();
            this._stopSidebarConstraintLoop();
        }

        _hide() {
            this.elements.container.classList.add('hidden');
            this.configManager.set('isHidden', true);
        }

        _show() {
            this.elements.container.classList.remove('hidden');
            this.configManager.set('isHidden', false);
        }

        _toggleVisibility() {
            const isHidden = this.elements.container.classList.contains('hidden');
            if (isHidden) {
                this._show();
            } else {
                this._hide();
            }
        }

        _savePosition() {
            const computed = window.getComputedStyle(this.elements.container);
            const left = parseFloat(computed.left);
            const top = parseFloat(computed.top);
            const rect = (Number.isFinite(left) && Number.isFinite(top)) ? null : this.elements.container.getBoundingClientRect();
            this.configManager.set('position', {
                left: rect ? rect.left : left,
                top: rect ? rect.top : top,
                right: null,
                bottom: null
            });
        }

        _getCurrentLeftTop() {
            const computed = window.getComputedStyle(this.elements.container);
            const left = parseFloat(computed.left);
            const top = parseFloat(computed.top);
            if (Number.isFinite(left) && Number.isFinite(top)) return { left, top };
            const rect = this.elements.container.getBoundingClientRect();
            return { left: rect.left, top: rect.top };
        }

        _requestEnsureUIInView() {
            if (this._ensureUiRaf) return;
            this._ensureUiRaf = requestAnimationFrame(() => {
                this._ensureUiRaf = 0;
                this._ensureUIInView();
            });
        }

        _ensureUIInView() {
            // Bubble itself
            this._clampToViewport();

            const panels = [];
            if (this.state.isMenuOpen) panels.push(this.elements.menu);
            if (this.state.isSettingsOpen) panels.push(this.elements.settings);
            if (this.state.isPromptsOpen) panels.push(this.elements.prompts);

            // Shrink scroll areas if viewport is tight
            for (const panel of panels) {
                if (panel && panel.classList.contains('visible')) {
                    this._autoFitPanel(panel);
                }
            }

            // Move bubble so attached panels stay fully visible
            for (let pass = 0; pass < 2; pass++) {
                const vp = this._getViewportBounds();
                const margin = 10;
                const vpLeft = vp.left + margin;
                const vpTop = vp.top + margin;
                const vpRight = vp.left + vp.width - margin;
                const vpBottom = vp.top + vp.height - margin;

                let totalDx = 0;
                let totalDy = 0;

                for (const panel of panels) {
                    if (!panel || !panel.classList.contains('visible')) continue;
                    const rect = panel.getBoundingClientRect();

                    if (rect.left < vpLeft) totalDx = Math.max(totalDx, (vpLeft - rect.left));
                    if (rect.right > vpRight) totalDx = Math.min(totalDx, (vpRight - rect.right));
                    if (rect.top < vpTop) totalDy = Math.max(totalDy, (vpTop - rect.top));
                    if (rect.bottom > vpBottom) totalDy = Math.min(totalDy, (vpBottom - rect.bottom));
                }

                if (!totalDx && !totalDy) break;

                const bubblePos = this._getCurrentLeftTop();
                this.elements.container.style.left = `${bubblePos.left + totalDx}px`;
                this.elements.container.style.top = `${bubblePos.top + totalDy}px`;
                this.elements.container.style.right = 'auto';
                this.elements.container.style.bottom = 'auto';

                this._clampToViewport();
            }
        }

        _autoFitPanel(panelEl) {
            const vp = this._getViewportBounds();
            const margin = 10;
            const available = Math.max(220, (vp.height - (margin * 2)));

            if (panelEl.id === 'aiBubbleMenu') {
                const list = panelEl.querySelector('.ai-menu-list');
                if (!list) return;
                const panelRect = panelEl.getBoundingClientRect();
                const listRect = list.getBoundingClientRect();
                const chrome = Math.max(0, panelRect.height - listRect.height);
                const target = Math.max(160, available - chrome);
                const cap = this.configManager.get('compactMode') ? 260 : 320;
                list.style.maxHeight = `${Math.min(cap, target)}px`;
                return;
            }

            if (panelEl.id === 'aiBubbleSettings') {
                const content = panelEl.querySelector('.ai-settings-content');
                const header = panelEl.querySelector('.ai-settings-header');
                const footer = panelEl.querySelector('.ai-settings-footer');
                if (!content) return;

                const chrome = (header?.getBoundingClientRect().height || 0) + (footer?.getBoundingClientRect().height || 0) + 24;
                const target = Math.max(160, available - chrome);
                content.style.maxHeight = `${Math.min(400, target)}px`;
                return;
            }

            if (panelEl.id === 'aiBubblePrompts') {
                const content = panelEl.querySelector('.ai-prompts-content');
                const header = panelEl.querySelector('.ai-prompts-header');
                const footer = panelEl.querySelector('.ai-prompts-footer');
                if (!content) return;

                const chrome = (header?.getBoundingClientRect().height || 0) + (footer?.getBoundingClientRect().height || 0) + 24;
                const target = Math.max(180, available - chrome);
                content.style.maxHeight = `${Math.min(480, target)}px`;
                return;
            }
        }

        _getViewportBounds() {
            const vv = window.visualViewport;
            if (vv && Number.isFinite(vv.width) && Number.isFinite(vv.height)) {
                return {
                    left: vv.offsetLeft || 0,
                    top: vv.offsetTop || 0,
                    width: vv.width,
                    height: vv.height
                };
            }
            return { left: 0, top: 0, width: window.innerWidth, height: window.innerHeight };
        }

        _getSnapCorners() {
            const size = this.configManager.get('bubbleSize');
            const vp = this._getViewportBounds();
            const padding = 12;

            return [
                { left: vp.left + padding, top: vp.top + padding },
                { left: vp.left + vp.width - size - padding, top: vp.top + padding },
                { left: vp.left + padding, top: vp.top + vp.height - size - padding },
                { left: vp.left + vp.width - size - padding, top: vp.top + vp.height - size - padding }
            ];
        }

        _maybeSnapToCorner() {
            if (!this.configManager.get('snapToCorners')) {
                this._clampToViewport();
                return;
            }

            const rect = this.elements.container.getBoundingClientRect();
            const corners = this._getSnapCorners();
            const radius = 110; // px: snap only when dropped near a corner

            let best = corners[0];
            let bestD = Number.POSITIVE_INFINITY;
            for (const c of corners) {
                const dx = rect.left - c.left;
                const dy = rect.top - c.top;
                const d = Math.sqrt((dx * dx) + (dy * dy));
                if (d < bestD) {
                    bestD = d;
                    best = c;
                }
            }

            if (bestD <= radius) {
                this.elements.container.style.left = `${best.left}px`;
                this.elements.container.style.top = `${best.top}px`;
                this.elements.container.style.right = 'auto';
                this.elements.container.style.bottom = 'auto';
            }

            this._clampToViewport();
        }

        _preservePositionOnVisualViewportChange() {
            const vv = window.visualViewport;
            if (!vv) {
                this._clampToViewport();
                return;
            }

            const last = this._lastVisualViewport;
            this._lastVisualViewport = {
                offsetLeft: vv.offsetLeft || 0,
                offsetTop: vv.offsetTop || 0,
                width: vv.width,
                height: vv.height
            };

            if (!last || !Number.isFinite(last.width) || !Number.isFinite(last.height)) {
                this._clampToViewport();
                return;
            }

            const size = this.configManager.get('bubbleSize');
            const pos = this._getCurrentLeftTop();

            const oldAvailW = Math.max(1, last.width - size);
            const oldAvailH = Math.max(1, last.height - size);
            const fracX = Math.max(0, Math.min(1, (pos.left - (last.offsetLeft || 0)) / oldAvailW));
            const fracY = Math.max(0, Math.min(1, (pos.top - (last.offsetTop || 0)) / oldAvailH));

            const newAvailW = Math.max(1, vv.width - size);
            const newAvailH = Math.max(1, vv.height - size);

            const rawLeft = (vv.offsetLeft || 0) + (fracX * newAvailW);
            const rawTop = (vv.offsetTop || 0) + (fracY * newAvailH);

            const minLeft = (vv.offsetLeft || 0);
            const minTop = (vv.offsetTop || 0);
            const maxLeft = (vv.offsetLeft || 0) + vv.width - size;
            const maxTop = (vv.offsetTop || 0) + vv.height - size;

            const newLeft = Math.max(minLeft, Math.min(rawLeft, maxLeft));
            const newTop = Math.max(minTop, Math.min(rawTop, maxTop));

            this.elements.container.style.left = `${newLeft}px`;
            this.elements.container.style.top = `${newTop}px`;
            this.elements.container.style.right = 'auto';
            this.elements.container.style.bottom = 'auto';

            this._savePosition();
        }

        _clampToViewport() {
            const pos = this._getCurrentLeftTop();
            const size = this.configManager.get('bubbleSize');

            const vp = this._getViewportBounds();
            const minLeft = vp.left;
            const minTop = vp.top;
            const maxLeft = vp.left + vp.width - size;
            const maxTop = vp.top + vp.height - size;

            const newLeft = Math.max(minLeft, Math.min(pos.left, maxLeft));
            const newTop = Math.max(minTop, Math.min(pos.top, maxTop));

            this.elements.container.style.left = `${newLeft}px`;
            this.elements.container.style.top = `${newTop}px`;
            this.elements.container.style.right = 'auto';
            this.elements.container.style.bottom = 'auto';

            this._savePosition();

            if (this._hasOpenSidebarWindow()) {
                this._updateSidebarResizeHandlePlacement();
            }
        }

        _resetSettings() {
            if (confirm('Reset all settings to defaults? Your favorites will be preserved.')) {
                const favorites = this.configManager.get('favorites');
                this.configManager.reset();
                this.configManager.set('favorites', favorites);

                // Reload page to apply changes
                location.reload();
            }
        }
    }

    // // Initialize
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => new AIFloatingBubble());
    } else {
        new AIFloatingBubble();
    }
})();