UX fix for Google Gemini - Buttons to Links

Replace Google Gemini's non-standard button behavior with proper <a> links. Allows native browser operations like opening chats in new tabs (Ctrl+Click / Middle Mouse), copying link addresses, and standard right-click menus on chat history, Gems, and new chat buttons.

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

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

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

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

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

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         UX fix for Google Gemini - Buttons to Links
// @namespace    https://gemini.google.com/
// @version      1.0.0
// @description  Replace Google Gemini's non-standard button behavior with proper <a> links. Allows native browser operations like opening chats in new tabs (Ctrl+Click / Middle Mouse), copying link addresses, and standard right-click menus on chat history, Gems, and new chat buttons.
// @author       Mykyta Shcherbyna
// @license      MIT
// @match        https://gemini.google.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=gemini.google.com
// @grant        none
// @run-at       document-idle
// @supportURL   https://github.com/mshcherbyna99/
// ==/UserScript==

(function() {
    'use strict';

    const CSS_HOVER_CLASS = 'gemini-linkifier-hover-state';

    const styleSheet = document.createElement('style');
    styleSheet.textContent = `
        .${CSS_HOVER_CLASS} {
            background-color: rgba(255, 255, 255, 0.08) !important;
            border-radius: 100px;
            transition: background-color 0.1s ease;
        }
        @media (prefers-color-scheme: light) {
            .${CSS_HOVER_CLASS} {
                background-color: rgba(0, 0, 0, 0.08) !important;
            }
        }
        div.conversation.${CSS_HOVER_CLASS} { border-radius: 16px; }
        side-nav-entry-button.${CSS_HOVER_CLASS} { border-radius: 100px; }
        div.library-item-card.${CSS_HOVER_CLASS} { border-radius: 12px; }
    `;
    document.head.appendChild(styleSheet);

    function liftElement(el) {
        if (!el) return;
        const style = window.getComputedStyle(el);
        if (style.position === 'static') el.style.position = 'relative';
        el.style.zIndex = '10';
    }

    function createGhostLink(href, visualTarget) {
        const link = document.createElement('a');
        link.href = href;
        Object.assign(link.style, {
            position: 'absolute', top: '0', left: '0', width: '100%', height: '100%',
            zIndex: '5', textDecoration: 'none', cursor: 'pointer', outline: 'none'
        });

        link.addEventListener('click', (e) => {
            if (e.button === 0 && !e.ctrlKey && !e.metaKey) {
                e.preventDefault();
                link.style.display = 'none';
                const elemBelow = document.elementFromPoint(e.clientX, e.clientY);
                if (elemBelow) elemBelow.click();
                link.style.display = 'block';
            }
        });

        if (visualTarget) {
            link.addEventListener('mouseenter', () => visualTarget.classList.add(CSS_HOVER_CLASS));
            link.addEventListener('mouseleave', () => visualTarget.classList.remove(CSS_HOVER_CLASS));
        }
        return link;
    }

    function processConversations() {
        document.querySelectorAll('div.conversation:not([data-linkified="true"])').forEach(node => {
            const jslog = node.getAttribute('jslog');
            if (!jslog) return;
            const match = jslog.match(/"(?:c_)?([a-f0-9]+)"/);
            if (!match) return;

            node.style.position = 'relative';
            const link = createGhostLink(`/app/${match[1]}`, node);
            liftElement(node.querySelector('.conversation-actions-container'));
            node.appendChild(link);
            node.dataset.linkified = 'true';
        });
    }

    function processGemsList() {
        document.querySelectorAll('div.bot-item:not([data-linkified="true"])').forEach(node => {
            const jslog = node.getAttribute('jslog');
            if (!jslog) return;
            const match = jslog.match(/"([^"]+)",(?:null|\d)/);
            if (!match) return;

            node.style.position = 'relative';
            const visual = node.querySelector('button.bot-new-conversation-button') || node;
            const link = createGhostLink(`/gem/${match[1]}`, visual);
            liftElement(node.querySelector('bot-actions-menu'));
            node.appendChild(link);
            node.dataset.linkified = 'true';
        });
    }

    function processStandardNav(selector, url) {
        const container = document.querySelector(selector);
        if (!container || container.dataset.linkified === 'true') return;
        const visual = container.querySelector('.side-nav-entry-button') || container;
        container.style.position = 'relative';
        container.appendChild(createGhostLink(url, visual));
        container.dataset.linkified = 'true';
    }

    function processNewChat() {
        const container = document.querySelector('side-nav-action-button[data-test-id="new-chat-button"]');
        if (!container || container.dataset.linkified === 'true') return;
        const btn = container.querySelector('button[data-test-id="expanded-button"]');
        if (!btn) return;
        container.style.position = 'relative';
        container.appendChild(createGhostLink('/app', btn));
        liftElement(container.querySelector('button[data-test-id="temp-chat-button"]'));
        container.dataset.linkified = 'true';
    }

    function scanAll() {
        processConversations();
        processNewChat();
        processGemsList();
        processStandardNav('side-nav-entry-button[data-test-id="bot-list-side-nav-entry-button"]', '/gems/view');
        processStandardNav('side-nav-entry-button[data-test-id="my-stuff-side-nav-entry-button"]', '/mystuff');
    }

    let timeout;
    const observer = new MutationObserver((mutations) => {
        if (mutations.some(m => m.addedNodes.length > 0)) {
            clearTimeout(timeout);
            timeout = setTimeout(scanAll, 300);
        }
    });

    scanAll();
    observer.observe(document.body, { childList: true, subtree: true });

})();