Gemini Keyboard Shortcuts (Power Tweaks)

Power-user hotkeys for Gemini with Win11 fixes, URL param prefill, and a centralized config for selectors & shortcuts. New Chat = ⌘/Ctrl+Shift+O, Sidebar Toggle = ⌘/Ctrl+B.

当前为 2025-09-23 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Gemini Keyboard Shortcuts (Power Tweaks)
// @namespace    http://tampermonkey.net/
// @version      1.3.1
// @description  Power-user hotkeys for Gemini with Win11 fixes, URL param prefill, and a centralized config for selectors & shortcuts. New Chat = ⌘/Ctrl+Shift+O, Sidebar Toggle = ⌘/Ctrl+B.
// @license      MIT
// @author       Henry Getz
// @match        https://gemini.google.com/*
// @match        https://gemini.google.com/u/*
// @match        https://gemini.google.com/app*
// @supportURL   https://github.com/HenryGetz/GeminiPilot/issues
// @grant        none
// @run-at       document-start
// ==/UserScript==
/*

# What’s new (maintainability + fixes)

- **Centralized config**: All shortcut preferences and selectors live in a single `CFG` object at the top.
- **Reliable New Chat selector**: Uses `button[aria-label*='New chat']`.
- **Shortcut updates**:
  - New Chat: **⌘/Ctrl + Shift + O**
  - Sidebar Toggle: **⌘/Ctrl + B** (like VS Code)
  - Help Window: **⌘/Ctrl + Shift + ?**
- **Windows 11 key handling**: Normalizes `event.key` to lowercase so Shift no longer breaks letter detection.
- **URL param prefill**: Open Gemini with a pre-populated prompt via `?q=...` (e.g. `https://gemini.google.com/app?q=Your+Prompt`).
- **onload resiliency**: Rebinds `window.onload = onLoad` inside an IIFE to survive sites where `onload` stops firing.
- **No forced UI repositioning**: Removed custom `bard-mode-switcher` fixed positioning.

# Included Keyboard Shortcuts

## Chat Management
| Shortcut (Mac/Windows)      | Action            |
|:---------------------------:|:------------------|
| ⌘/Ctrl + **Shift + O**      | Open new chat     |
| ⌘/Ctrl + Shift + Backspace  | Delete chat       |
| ⌘/Ctrl + **B**              | Toggle sidebar    |
| ⌥/Alt + 1–9                 | Go to nth chat    |
| ⌘/Ctrl + Shift + =          | Next chat         |
| ⌘/Ctrl + Shift + –          | Previous chat     |

## Text Input and Editing
| Shortcut (Mac/Windows) | Action                           |
|:----------------------:|:----------------------------------|
| Shift + Esc            | Focus chat input                  |
| ⌘/Ctrl + Shift + E     | Edit text                         |
| ⌘/Ctrl + Shift + ;     | Copy last code block              |
| ⌘/Ctrl + Shift + '     | Copy second-to-last code block    |
| ⌘/Ctrl + Shift + C     | Copy response                     |
| ⌘/Ctrl + Shift + K     | Stop/start generation             |

## Draft Navigation
| Shortcut (Mac/Windows) | Action                |
|:----------------------:|:----------------------|
| ⌘/Ctrl + Shift + D     | Generate more drafts  |
| ⌘/Ctrl + Shift + ,     | Next draft            |
| ⌘/Ctrl + Shift + .     | Previous draft        |

## Sharing and Linking
| Shortcut (Mac/Windows) | Action                      |
|:----------------------:|:----------------------------|
| ⌘/Ctrl + Shift + L     | Copy prompt/response link   |
| ⌘/Ctrl + Shift + M     | Copy chat link              |

## Audio and File Shortcuts
| Shortcut (Mac/Windows) | Action            |
|:----------------------:|:------------------|
| ⌘/Ctrl + Shift + Y     | Play/pause audio  |
| ⌘/Ctrl + Shift + S     | Voice to text     |
| ⌘/Ctrl + O             | Open file         |

## Help
| Shortcut (Mac/Windows) | Action             |
|:----------------------:|:-------------------|
| ⌘/Ctrl + Shift + ?     | Open help window   |

*/


(function () {
    'use strict';

    // ====== CENTRAL CONFIG (shortcuts + selectors) ======
    const CFG = {
        behavior: {
            assumeLastResponse: false,
            goToNextChatOnDelete: true,
        },
        timings: { rapidClickDelayMS: 100 },
        hotkeys: {
            newChat:      { key: 'o', shift: true,  cmdOrCtrl: true },
            sidebarToggle:{ key: 'b', shift: false, cmdOrCtrl: true },
            openFile:     { key: 'o', shift: false, cmdOrCtrl: true },
            // Help accepts '?' OR '/' with Shift OR code 'Slash'
            showHelp:     { key: '?', shift: true,  cmdOrCtrl: true, altKeys: ['/', '?'], codes: ['Slash'] },
        },
        selectors: {
            showMoreButton: '[data-test-id="show-more-button"]',
            loadMoreButton: '[data-test-id="load-more-button"]',
            conversation: '[data-test-id="conversation"]',
            selectedConversation: '.selected[data-test-id="conversation"]',
            conversationTitle: '.conversation-title',
            actionsMenuButton: '[data-test-id="actions-menu-button"]',
            deleteButton: '[data-test-id="delete-button"]',
            confirmButton: '[data-test-id="confirm-button"]',
            textInputField: '.text-input-field',
            enterPrompt: '[aria-label="Enter a prompt here"]',
            sendMessageButton: '[aria-label="Send message"]',
            micButton: '[mattooltip="Use microphone"]',
            draftPreviewButton: '.draft-preview-button',
            generateMoreDraftsButton: '[data-test-id="generate-more-drafts-button"]',
            redoButton: '[aria-label=Redo]',
            regenerateDraftsTooltip: '[mattooltip="Regenerate drafts"]',
            editTextTooltip: '[mattooltip="Edit text"]',
            shareButton: '[aria-label*="Share"]',
            shareResponseButton: '[aria-label*="Share response"]',
            shareModeFull: '[data-test-id="share-mode-radio-button-full"] label',
            createButton: '[data-test-id="create-button"]',
            copyPublicLinkButton: '[aria-label="Copy public link"]',
            closeDialogButton: '[aria-label="Close"]',
            uploadButtonPrimary: '.upload-button button',
            uploadButtonAlt: '[aria-label*="Upload files"]',
            sidebarHide: '[aria-label*="Hide side panel"]',
            sidebarShow: '[aria-label*="Show side panel"]',
            mainMenu: '[aria-label*="Main menu"]',
            menuToggleButton: '[data-test-id="menu-toggle-button"]',
            modelSwitcherButton: '[data-test-id="bard-mode-menu-button"]',
            modelMenuPanel: '.mat-mdc-menu-panel',
            modelMenuContent: '.mat-mdc-menu-content',
            modelMenuItem: 'button.mat-mdc-menu-item',
            newChatButton: "button[aria-label*='New chat']",
            modelResponseText: '.model-response-text',
            codeTTSButton: '.response-tts-container button',
            editorCancel: '[aria-label*="Cancel"]',
            hideDuringCopy:
            '.code-block-decoration.footer, .code-block-decoration.header, .table-footer',
            hideDuringCopyRestore:
            '.code-block-decoration.footer, .code-block-decoration.header',
        },
    };

    const { assumeLastResponse, goToNextChatOnDelete } = CFG.behavior;
    const { rapidClickDelayMS } = CFG.timings;

    const hasQuery = window.location.href.includes('?q=');
    let url = new URL(window.location.href);
    let params = new URLSearchParams(url.search);
    let query = unescape(params.get('q') || '');

    window.onload = onLoad;

    function onLoad() {
        // ----- CSS tweaks (minimal & intentional) -----
        const s = document.createElement('style');
        document.head.append(s);
        s.textContent = `
      .conversation-container, .input-area-container, .bottom-container, user-query {
        max-width: -webkit-fill-available !important;
        max-width: fill-available !important;
      }
      #gbwa, .cdk-overlay-backdrop { display: none !important; }
      .code-block-decoration.footer, .code-block-decoration.header {
        user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;
      }
      .mat-mdc-focus-indicator::before { border: none !important; }
    `;
        // NOTE: Per feedback, do NOT force-position the model switcher.

        const nums = ['first','second','third','fourth','fifth','sixth','seventh','eighth','ninth','tenth'];

        // Ensure "Show more" expanded & support ?q=
        let showMoreClicked = false;
        let inputBarClicked = false;
        const initialObserver = new MutationObserver(() => {
            const showMore = document.querySelector(CFG.selectors.showMoreButton);
            const inputBar = document.querySelector(CFG.selectors.textInputField);
            const textInput = document.querySelector(CFG.selectors.enterPrompt);

            if (showMore && !showMoreClicked) {
                showMoreClicked = true;
                simulateClick(showMore);
            }

            if (hasQuery && inputBar && !inputBarClicked) {
                if (textInput) {
                    inputBarClicked = true;
                    params.delete('q');
                    window.history.pushState(null, '', url.origin + url.pathname);

                    setTimeout(() => {
                        inputBar.click();
                        setTimeout(() => {
                            if (textInput.firstChild) textInput.firstChild.remove();
                            for (const line of query.split('\n')) {
                                const p = document.createElement('p');
                                p.innerText = line;
                                textInput.append(p);
                            }
                            const draftsObs = new MutationObserver(() => {
                                const showDrafts = document.querySelector(CFG.selectors.generateMoreDraftsButton);
                                if (showDrafts) {
                                    draftsObs.disconnect();
                                    setTimeout(() => {
                                        url = new URL(window.location.href);
                                        params = new URLSearchParams(url.search);
                                        window.history.pushState(null, '', url.origin + url.pathname);
                                    }, 2000);
                                }
                            });
                            draftsObs.observe(document.body, { childList: true, subtree: true });

                            setTimeout(() => {
                                const sendBtn = document.querySelector(CFG.selectors.sendMessageButton);
                                if (sendBtn) sendBtn.click();
                            }, rapidClickDelayMS);
                        }, rapidClickDelayMS);
                    }, rapidClickDelayMS);
                }
            } else if (inputBar && !inputBarClicked) {
                inputBarClicked = true;
                setTimeout(() => inputBar.click(), rapidClickDelayMS);
            }

            if (showMoreClicked && inputBarClicked) initialObserver.disconnect();
        });
        initialObserver.observe(document.body, { childList: true, subtree: true });

        // ====== Helpers ======
        let c = null;
        function getLastElement(querySelector) {
            const containers = document.querySelectorAll('.conversation-container');
            c = containers[containers.length - 1];
            if (!assumeLastResponse) {
                let mostVisibleElement = null;
                let maxVisibleArea = 0;
                containers.forEach((container) => {
                    const rect = container.getBoundingClientRect();
                    const vh = window.innerHeight;
                    const visibleArea = Math.max(0, Math.min(rect.bottom, vh) - Math.max(rect.top, 0));
                    if (visibleArea > maxVisibleArea && visibleArea !== 0) {
                        maxVisibleArea = visibleArea;
                        mostVisibleElement = container;
                    }
                });
                c = mostVisibleElement || c;
            }
            const list = c ? c.querySelectorAll(querySelector) : [];
            return list[list.length - 1] || null;
        }

        function copyRichTextFromDiv(element) {
            if (!element) return;
            document.querySelectorAll(CFG.selectors.hideDuringCopy)
                .forEach((el) => (el.style.display = 'none'));

            const selection = window.getSelection();
            const range = document.createRange();
            range.selectNodeContents(element);
            selection.removeAllRanges();
            selection.addRange(range);
            try { document.execCommand('copy'); } catch (_) {}
            selection.removeAllRanges();

            setTimeout(() => {
                document.querySelectorAll(CFG.selectors.hideDuringCopyRestore)
                    .forEach((el) => (el.style.display = ''));
            }, rapidClickDelayMS);
        }

        function clearNotifications() {
            for (const ele of document.querySelectorAll('.gemini-key-notification')) ele.remove();
        }

        function notify(text) {
            clearNotifications();
            const div = document.createElement('div');
            div.className = 'gemini-key-notification';
            const tDuration = 125, nDuration = 3000, tLeft = nDuration - tDuration;
            div.textContent = text;
            div.style.cssText = `
        position:fixed; bottom:26px; left:26px; font-family:sans-serif; font-size:.875rem;
        color:#fff; border-radius:4px; background:#333; z-index:2147483647; padding:14px 16px;
        box-shadow:0 3px 5px -1px rgba(0,0,0,.2),0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12);
        transition:opacity ${tDuration}ms ease-in-out, transform ${tDuration}ms ease-in-out;
        transform-origin:center bottom; transform:scale(.8); opacity:0; max-width:calc(100% - 52px); box-sizing:border-box;
      `;
          document.body.appendChild(div);
          setTimeout(() => { div.style.opacity = '1'; div.style.transform = 'scale(1)'; }, 10);
          setTimeout(() => {
              div.style.opacity = '0'; div.style.transform = 'scale(.8)';
              setTimeout(() => { if (div.parentNode) div.remove(); }, tDuration);
          }, tLeft);
      }

        function simulateClick(el) { if (el) el.click(); else throw new Error('Element not found for click().'); }

        // ====== Drafts ======
        let draftIndex = 0;
        let googleDraftCount = 3;
        let waitOnGeneration = false;

        function changeDraft(direction) {
            let draftButtons = document.querySelectorAll(CFG.selectors.draftPreviewButton);
            if (!waitOnGeneration) draftIndex = (draftIndex + direction + googleDraftCount) % googleDraftCount;

            if (!waitOnGeneration && draftButtons[draftIndex]) {
                simulateClick(draftButtons[draftIndex]);
            } else if (!waitOnGeneration) {
                simulateClick(getLastElement(CFG.selectors.generateMoreDraftsButton));
                notify(`Generating ${nums[draftIndex]} draft`);
                waitOnGeneration = true;

                const obs = new MutationObserver(() => {
                    draftButtons = document.querySelectorAll(CFG.selectors.draftPreviewButton);
                    if (draftButtons[draftIndex]) {
                        obs.disconnect();
                        setTimeout(() => {
                            waitOnGeneration = false;
                            simulateClick(draftButtons[draftIndex]);
                        }, rapidClickDelayMS * 2);
                    }
                });
                obs.observe(document.body, { childList: true, subtree: true });
            } else {
                notify('Waiting on generation');
            }
        }
        const nextDraft = () => changeDraft(1);
        const previousDraft = () => changeDraft(-1);

        // ====== Chats ======
        let chatIndex = 0;
        let waitOnLoadingMore = false;

        function changeChat(direction) {
            chatIndex = Array.from(document.querySelectorAll(CFG.selectors.conversation))
                .indexOf(document.querySelector(CFG.selectors.selectedConversation));
            let chatButtons = document.querySelectorAll(CFG.selectors.conversation);

            if (!waitOnLoadingMore) chatIndex = Math.max(0, chatIndex + direction);

            if (!waitOnLoadingMore && chatButtons[chatIndex]) {
                simulateClick(chatButtons[chatIndex]);
                notify(`"${chatButtons[chatIndex].querySelector(CFG.selectors.conversationTitle).innerText.trim()}"`);
            } else if (!waitOnLoadingMore) {
                simulateClick(document.querySelector(CFG.selectors.loadMoreButton));
                notify('Loading chats');
                waitOnLoadingMore = true;

                const obs = new MutationObserver(() => {
                    chatButtons = document.querySelectorAll(CFG.selectors.conversation);
                    if (chatButtons[chatIndex]) {
                        obs.disconnect();
                        setTimeout(() => {
                            waitOnLoadingMore = false;
                            simulateClick(chatButtons[chatIndex]);
                            notify(`"${chatButtons[chatIndex].querySelector(CFG.selectors.conversationTitle).innerText.trim()}"`);
                        }, rapidClickDelayMS * 2);
                    }
                });
                obs.observe(document.body, { childList: true, subtree: true });
            } else {
                notify('Chats loading');
            }
        }
        const nextChat = () => changeChat(1);
        const previousChat = () => changeChat(-1);

        // ====== Model Switching ======
        function switchModel(modelNumber) {
            const modelIndex = modelNumber - 1;
            const switcher = document.querySelector(CFG.selectors.modelSwitcherButton);
            if (!switcher) { notify('Model switcher button not found.'); return; }
            simulateClick(switcher);
            setTimeout(() => {
                const panel = document.body.querySelector(CFG.selectors.modelMenuPanel);
                const content = panel ? panel.querySelector(CFG.selectors.modelMenuContent) : null;
                const buttons = content ? content.querySelectorAll(CFG.selectors.modelMenuItem) : null;
                if (buttons && buttons.length && modelIndex >= 0 && modelIndex < buttons.length) {
                    const btn = buttons[modelIndex];
                    const name = (btn.textContent || '').trim().replace(/\s+/g, ' ') || `Model ${modelNumber}`;
                    simulateClick(btn);
                    notify(`Switched to ${name}`);
                } else {
                    notify(`Model number ${modelNumber} is invalid or not available.`);
                }
            }, 10);
        }

        // ====== Sidebar Toggle ======
        function toggleSidebar() {
            const toggle =
                  document.querySelector(CFG.selectors.sidebarHide) ||
                  document.querySelector(CFG.selectors.sidebarShow) ||
                  document.querySelector(CFG.selectors.mainMenu) ||
                  document.querySelector(CFG.selectors.menuToggleButton);

            if (toggle) simulateClick(toggle);
            else {
                const side = document.querySelector('bard-sidenav-container');
                if (side) side.toggleAttribute('collapsed');
                else notify('Sidebar toggle not found.');
            }
        }

        // ====== New Chat (final selector) ======
        function openNewChat() {
            const btn = document.querySelector(CFG.selectors.newChatButton);
            if (btn) {
                simulateClick(btn);
                setTimeout(() => {
                    const inputBar = document.querySelector(CFG.selectors.textInputField);
                    if (inputBar) inputBar.click();
                }, rapidClickDelayMS);
            } else {
                notify('New Chat button not found.');
            }
        }

        // ====== Key Handling ======
        const isMac = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);

        function handleKeydown(event) {
            const key = (event.key || '').toLowerCase();
            const code = event.code;
            const isCmdOrCtrl = (isMac && event.metaKey) || (!isMac && event.ctrlKey);
            const activeEl = document.activeElement;

            // --- Robust Help shortcut: Cmd/Ctrl + Shift + ? ---
            if (
                isCmdOrCtrl &&
                event.shiftKey &&
                (
                    key === (CFG.hotkeys.showHelp.key || '?') ||
                    (CFG.hotkeys.showHelp.altKeys || []).includes(key) ||
                    (CFG.hotkeys.showHelp.codes || []).includes(code)
                )
            ) {
                event.preventDefault();
                showHelpPopup();
                return;
            }

            // --- Standalone Shortcuts ---

            // Alt+Number jump (1-9/0=10)
            let keyNumber = parseInt(event.code.replace('Digit', ''), 10);
            keyNumber = keyNumber === 0 ? 10 : keyNumber;
            if (event.altKey && keyNumber) {
                const items = document.querySelectorAll(CFG.selectors.conversation);
                if (items[keyNumber - 1]) {
                    items[keyNumber - 1].click();
                    const title = items[keyNumber - 1].querySelector(CFG.selectors.conversationTitle).innerHTML.trim();
                    notify(`"${title}"`);
                }
                event.preventDefault();
                return;
            }

            // Cancel edit (Esc while editing)
            if (key === 'escape' && !event.shiftKey && activeEl?.getAttribute('aria-label')?.includes('Edit prompt')) {
                const cancel = getLastElement(CFG.selectors.editorCancel);
                if (cancel) simulateClick(cancel);
                event.preventDefault();
                return;
            }

            // Model switch: Ctrl + [1-9/0]
            if (event.ctrlKey && keyNumber) {
                switchModel(keyNumber);
                event.preventDefault();
                return;
            }

            // --- Cmd/Ctrl Shortcuts ---

            // Open file: Cmd/Ctrl + O
            if (isCmdOrCtrl && !event.shiftKey && key === CFG.hotkeys.openFile.key) {
                event.preventDefault();
                const upBtn = document.querySelector(CFG.selectors.uploadButtonPrimary)
                || document.querySelector(CFG.selectors.uploadButtonAlt);
                if (upBtn) simulateClick(upBtn);
                return;
            }

            // Sidebar: Cmd/Ctrl + B
            if (isCmdOrCtrl && !event.shiftKey && key === CFG.hotkeys.sidebarToggle.key) {
                event.preventDefault();
                toggleSidebar();
                return;
            }

            // --- Cmd/Ctrl + Shift Shortcuts ---

            // This block now correctly requires BOTH Cmd/Ctrl and Shift to be pressed.
            if (isCmdOrCtrl && event.shiftKey) {
                switch (key) {
                        // New Chat: Cmd/Ctrl + Shift + O
                    case CFG.hotkeys.newChat.key: {
                        openNewChat();
                        event.preventDefault();
                        break;
                    }
                    case 'c': {
                        getLastElement();
                        const resp = c ? c.querySelector(CFG.selectors.modelResponseText) : null;
                        copyRichTextFromDiv(resp);
                        notify('Copied response');
                        event.preventDefault();
                        break;
                    }
                    case 'f': {
                        const mainMenu = document.querySelector(CFG.selectors.mainMenu);
                        simulateClick(mainMenu);
                        event.preventDefault();
                        break;
                    }
                    case 'backspace': {
                        event.preventDefault();
                        chatIndex = Array.from(document.querySelectorAll(CFG.selectors.conversation))
                            .indexOf(document.querySelector(CFG.selectors.selectedConversation));
                        const actions = document.querySelector('.conversation.selected')
                        ?.parentElement?.querySelector(CFG.selectors.actionsMenuButton);
                        simulateClick(actions);
                        setTimeout(() => {
                            simulateClick(document.body.querySelector(CFG.selectors.deleteButton));
                        }, rapidClickDelayMS);
                        setTimeout(() => {
                            simulateClick(document.body.querySelector(CFG.selectors.confirmButton));
                            setTimeout(() => {
                                if (goToNextChatOnDelete) {
                                    const next = document.querySelectorAll(CFG.selectors.conversation)[chatIndex];
                                    if (next) simulateClick(next);
                                }
                            }, rapidClickDelayMS);
                        }, rapidClickDelayMS * 2); // Increased delay to ensure menu is open
                        break;
                    }
                    case 'd': {
                        let el = getLastElement(CFG.selectors.redoButton) || getLastElement(CFG.selectors.regenerateDraftsTooltip);
                        simulateClick(el);
                        event.preventDefault();
                        break;
                    }
                    case 'e': {
                        simulateClick(getLastElement(CFG.selectors.editTextTooltip));
                        event.preventDefault();
                        break;
                    }
                    case ';': {
                        event.preventDefault();
                        getLastElement();
                        const blocks = c ? c.querySelectorAll('code-block') : [];
                        copyRichTextFromDiv(blocks[blocks.length - 1]);
                        notify('Copied last code block to clipboard');
                        break;
                    }
                    case "'": {
                        event.preventDefault();
                        getLastElement();
                        const blocks = c ? c.querySelectorAll('code-block') : [];
                        copyRichTextFromDiv(blocks[blocks.length - 2]);
                        notify('Copied second-last code block to clipboard');
                        break;
                    }
                    case 'm': {
                        simulateClick(getLastElement(CFG.selectors.shareButton));
                        setTimeout(() => simulateClick(document.querySelector(CFG.selectors.shareResponseButton)), rapidClickDelayMS);
                        setTimeout(() => simulateClick(document.querySelector(CFG.selectors.shareModeFull)), rapidClickDelayMS * 2);
                        setTimeout(() => simulateClick(document.querySelector(CFG.selectors.createButton)), rapidClickDelayMS * 3);

                        const obs = new MutationObserver(() => {
                            const element = document.querySelector(CFG.selectors.copyPublicLinkButton);
                            if (element) {
                                obs.disconnect();
                                simulateClick(element);
                                setTimeout(() => {
                                    simulateClick(document.querySelector(CFG.selectors.closeDialogButton));
                                    notify('Chat link copied');
                                }, rapidClickDelayMS);
                            }
                        });
                        obs.observe(document.body, { childList: true, subtree: true });
                        clearNotifications();
                        event.preventDefault();
                        break;
                    }
                    case 'l': {
                        simulateClick(getLastElement(CFG.selectors.shareButton));
                        setTimeout(() => simulateClick(document.querySelector(CFG.selectors.shareResponseButton)), rapidClickDelayMS);
                        setTimeout(() => simulateClick(document.querySelector(CFG.selectors.createButton)), rapidClickDelayMS * 2);

                        const obs = new MutationObserver(() => {
                            const element = document.querySelector(CFG.selectors.copyPublicLinkButton);
                            if (element) {
                                obs.disconnect();
                                simulateClick(element);
                                setTimeout(() => {
                                    simulateClick(document.querySelector(CFG.selectors.closeDialogButton));
                                    notify('Prompt/response link copied');
                                }, rapidClickDelayMS);
                            }
                        });
                        obs.observe(document.body, { childList: true, subtree: true });
                        event.preventDefault();
                        break;
                    }
                    case ',': { previousDraft(); break; }
                    case '.': { nextDraft(); break; }
                    case '-': { event.preventDefault(); previousChat(); break; }
                    case '=': { event.preventDefault(); nextChat(); break; }
                    case 'k': {
                        event.preventDefault();
                        simulateClick(document.querySelector(CFG.selectors.sendMessageButton));
                        break;
                    }
                    case 'y': {
                        simulateClick(getLastElement(CFG.selectors.codeTTSButton));
                        event.preventDefault();
                        break;
                    }
                    case 's': {
                        simulateClick(document.querySelector(CFG.selectors.micButton));
                        event.preventDefault();
                        break;
                    }
                }
            }
        }

        document.addEventListener('keydown', (event) => {
            try { handleKeydown(event); }
            catch (error) { notify(`Failed to execute shortcut: ${error}`); }
        }, { capture: true });

        // ====== Help Popup ======
        function showHelpPopup() {
            const existing = document.getElementById('gemini-help-popup');
            if (existing) {
                existing.remove();
                const overlay0 = document.getElementById('gemini-help-overlay');
                if (overlay0) overlay0.remove();
            }

            const overlay = document.createElement('div'); overlay.id = 'gemini-help-overlay';
            const popup = document.createElement('div'); popup.id = 'gemini-help-popup';

            const style = document.createElement('style');
            style.textContent = `
        #gemini-help-popup{background:#202124;color:#e8eaed;padding:25px;border-radius:8px;box-shadow:0 4px 15px rgba(0,0,0,.2);max-width:700px;max-height:80vh;overflow-y:auto;z-index:2147483647;font-family:sans-serif;line-height:1.6;position:relative}
        #gemini-help-popup h2{color:#8ab4f8;margin:0 0 15px;font-size:1.4em}
        #gemini-help-popup h3{color:#bdc1c6;margin:20px 0 10px;font-size:1.1em;border-bottom:1px solid #5f6368;padding-bottom:5px}
        #gemini-help-popup table{width:100%;border-collapse:collapse;margin-bottom:15px;font-size:.95em}
        #gemini-help-popup th,#gemini-help-popup td{border:1px solid #5f6368;padding:8px 12px;text-align:left}
        #gemini-help-popup th{background:#3c4043}
        #gemini-help-popup code{background:#3c4043;padding:2px 5px;border-radius:4px;font-family:monospace}
        #gemini-help-popup .close-button{position:absolute;top:10px;right:15px;background:none;border:none;font-size:1.8em;color:#9aa0a6;cursor:pointer;line-height:1}
        #gemini-help-popup .close-button:hover{color:#e8eaed}
        #gemini-help-overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:2147483646;display:flex;justify-content:center;align-items:center}
      `;

          const close = document.createElement('button'); close.className = 'close-button'; close.title = 'Close (Esc)'; close.textContent = '×';
          const title = document.createElement('h2'); title.textContent = 'Gemini Keyboard Shortcuts Help';
          const intro = document.createElement('p'); intro.appendChild(document.createTextNode('Press '));
          const esc = document.createElement('code'); esc.textContent = 'Esc';
          intro.appendChild(esc); intro.appendChild(document.createTextNode(" or click the 'X' to close this window."));

          const section = (label, rows) => {
              const h3 = document.createElement('h3'); h3.textContent = label; popup.appendChild(h3);
              const table = document.createElement('table');
              const thead = table.createTHead(); const tr = thead.insertRow();
              const th1 = document.createElement('th'); th1.textContent = 'Shortcut (Mac/Windows)'; tr.appendChild(th1);
              const th2 = document.createElement('th'); th2.textContent = 'Action'; tr.appendChild(th2);
              const tbody = table.createTBody();
              rows.forEach(r => { const row = tbody.insertRow(); const c1 = row.insertCell(); const c2 = row.insertCell();
                                 const code = document.createElement('code'); code.textContent = r.key; c1.appendChild(code); c2.textContent = r.action; });
              popup.appendChild(table);
          };

          popup.appendChild(style); popup.appendChild(close); popup.appendChild(title); popup.appendChild(intro);

          section('Chat Management', [
              { key: '⌘/Ctrl + Shift + O', action: 'Open new chat' },        // UPDATED
              { key: '⌘/Ctrl + Shift + Backspace', action: 'Delete chat' },
              { key: '⌘/Ctrl + B', action: 'Hide/Show sidebar' },            // stays
              { key: '⌥/Alt + 1–9', action: 'Go to nth chat' },
              { key: '⌘/Ctrl + Shift + =', action: 'Next chat' },
              { key: '⌘/Ctrl + Shift + –', action: 'Previous chat' },
          ]);

          section('Text Input and Editing', [
              { key: 'Shift + Esc', action: 'Focus chat input' },
              { key: '⌘/Ctrl + Shift + E', action: 'Edit text' },
              { key: '⌘/Ctrl + Shift + ;', action: 'Copy last code block' },
              { key: "⌘/Ctrl + Shift + '", action: 'Copy second-to-last code block' },
              { key: '⌘/Ctrl + Shift + C', action: 'Copy response' },
              { key: '⌘/Ctrl + Shift + K', action: 'Stop/start generation' },
          ]);

          section('Draft Navigation', [
              { key: '⌘/Ctrl + Shift + D', action: 'Generate more drafts' },
              { key: '⌘/Ctrl + Shift + ,', action: 'Next draft' },
              { key: '⌘/Ctrl + Shift + .', action: 'Previous draft' },
          ]);

          section('Sharing and Linking', [
              { key: '⌘/Ctrl + Shift + L', action: 'Copy prompt/response link' },
              { key: '⌘/Ctrl + Shift + M', action: 'Copy chat link' },
          ]);

          section('Audio and File Shortcuts', [
              { key: '⌘/Ctrl + Shift + Y', action: 'Play/pause audio' },
              { key: '⌘/Ctrl + Shift + S', action: 'Voice to text' },
              { key: '⌘/Ctrl + O', action: 'Open file' },
          ]);

          section('Other', [
              { key: '⌘/Ctrl + Shift + ?', action: 'Show this help' },
              { key: 'Ctrl + 1–9/0', action: 'Switch Model (if available)' },
          ]);

          const h3 = document.createElement('h3'); h3.textContent = 'Disabling the Script'; popup.appendChild(h3);
          const p = document.createElement('p'); p.textContent = 'To temporarily or permanently disable these shortcuts:'; popup.appendChild(p);
          const ol = document.createElement('ol');
          [
              'Open the Tampermonkey extension menu in your browser.',
              'Go to the "Dashboard".',
              'Find the script named "Gemini Keyboard Shortcuts".',
              'Toggle the switch next to the script name to disable it.',
          ].forEach(t => { const li = document.createElement('li'); li.textContent = t; ol.appendChild(li); });
          popup.appendChild(ol);

          const closePopup = () => { overlay.remove(); document.removeEventListener('keydown', escListener); };
          const escListener = (e) => { if ((e.key || '').toLowerCase() === 'escape') closePopup(); };

          overlay.addEventListener('click', (e) => { if (e.target === overlay) closePopup(); });
          close.addEventListener('click', closePopup);
          document.addEventListener('keydown', escListener);

          const style2 = document.createElement('style'); style2.textContent = `#gemini-help-popup{background:#202124}`;
          overlay.appendChild(style2);
          overlay.appendChild(popup);
          document.body.appendChild(overlay);
      }
        // ===== End Help Popup =====
    }
})();