Google 快覽

在 Google 搜尋結果頁面利用快捷鍵進行快速瀏覽。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name                Google Navigation
// @name:zh-TW          Google 快覽
// @name:zh-CN          Google 快覽
// @namespace           veringsek
// @match               http://*.google.com/search*
// @match               http://*.google.ad/search*
// @match               http://*.google.ae/search*
// @match               http://*.google.com.af/search*
// @match               http://*.google.com.ag/search*
// @match               http://*.google.com.ai/search*
// @match               http://*.google.al/search*
// @match               http://*.google.am/search*
// @match               http://*.google.co.ao/search*
// @match               http://*.google.com.ar/search*
// @match               http://*.google.as/search*
// @match               http://*.google.at/search*
// @match               http://*.google.com.au/search*
// @match               http://*.google.az/search*
// @match               http://*.google.ba/search*
// @match               http://*.google.com.bd/search*
// @match               http://*.google.be/search*
// @match               http://*.google.bf/search*
// @match               http://*.google.bg/search*
// @match               http://*.google.com.bh/search*
// @match               http://*.google.bi/search*
// @match               http://*.google.bj/search*
// @match               http://*.google.com.bn/search*
// @match               http://*.google.com.bo/search*
// @match               http://*.google.com.br/search*
// @match               http://*.google.bs/search*
// @match               http://*.google.bt/search*
// @match               http://*.google.co.bw/search*
// @match               http://*.google.by/search*
// @match               http://*.google.com.bz/search*
// @match               http://*.google.ca/search*
// @match               http://*.google.cd/search*
// @match               http://*.google.cf/search*
// @match               http://*.google.cg/search*
// @match               http://*.google.ch/search*
// @match               http://*.google.ci/search*
// @match               http://*.google.co.ck/search*
// @match               http://*.google.cl/search*
// @match               http://*.google.cm/search*
// @match               http://*.google.cn/search*
// @match               http://*.google.com.co/search*
// @match               http://*.google.co.cr/search*
// @match               http://*.google.com.cu/search*
// @match               http://*.google.cv/search*
// @match               http://*.google.com.cy/search*
// @match               http://*.google.cz/search*
// @match               http://*.google.de/search*
// @match               http://*.google.dj/search*
// @match               http://*.google.dk/search*
// @match               http://*.google.dm/search*
// @match               http://*.google.com.do/search*
// @match               http://*.google.dz/search*
// @match               http://*.google.com.ec/search*
// @match               http://*.google.ee/search*
// @match               http://*.google.com.eg/search*
// @match               http://*.google.es/search*
// @match               http://*.google.com.et/search*
// @match               http://*.google.fi/search*
// @match               http://*.google.com.fj/search*
// @match               http://*.google.fm/search*
// @match               http://*.google.fr/search*
// @match               http://*.google.ga/search*
// @match               http://*.google.ge/search*
// @match               http://*.google.gg/search*
// @match               http://*.google.com.gh/search*
// @match               http://*.google.com.gi/search*
// @match               http://*.google.gl/search*
// @match               http://*.google.gm/search*
// @match               http://*.google.gr/search*
// @match               http://*.google.com.gt/search*
// @match               http://*.google.gy/search*
// @match               http://*.google.com.hk/search*
// @match               http://*.google.hn/search*
// @match               http://*.google.hr/search*
// @match               http://*.google.ht/search*
// @match               http://*.google.hu/search*
// @match               http://*.google.co.id/search*
// @match               http://*.google.ie/search*
// @match               http://*.google.co.il/search*
// @match               http://*.google.im/search*
// @match               http://*.google.co.in/search*
// @match               http://*.google.iq/search*
// @match               http://*.google.is/search*
// @match               http://*.google.it/search*
// @match               http://*.google.je/search*
// @match               http://*.google.com.jm/search*
// @match               http://*.google.jo/search*
// @match               http://*.google.co.jp/search*
// @match               http://*.google.co.ke/search*
// @match               http://*.google.com.kh/search*
// @match               http://*.google.ki/search*
// @match               http://*.google.kg/search*
// @match               http://*.google.co.kr/search*
// @match               http://*.google.com.kw/search*
// @match               http://*.google.kz/search*
// @match               http://*.google.la/search*
// @match               http://*.google.com.lb/search*
// @match               http://*.google.li/search*
// @match               http://*.google.lk/search*
// @match               http://*.google.co.ls/search*
// @match               http://*.google.lt/search*
// @match               http://*.google.lu/search*
// @match               http://*.google.lv/search*
// @match               http://*.google.com.ly/search*
// @match               http://*.google.co.ma/search*
// @match               http://*.google.md/search*
// @match               http://*.google.me/search*
// @match               http://*.google.mg/search*
// @match               http://*.google.mk/search*
// @match               http://*.google.ml/search*
// @match               http://*.google.com.mm/search*
// @match               http://*.google.mn/search*
// @match               http://*.google.ms/search*
// @match               http://*.google.com.mt/search*
// @match               http://*.google.mu/search*
// @match               http://*.google.mv/search*
// @match               http://*.google.mw/search*
// @match               http://*.google.com.mx/search*
// @match               http://*.google.com.my/search*
// @match               http://*.google.co.mz/search*
// @match               http://*.google.com.na/search*
// @match               http://*.google.com.ng/search*
// @match               http://*.google.com.ni/search*
// @match               http://*.google.ne/search*
// @match               http://*.google.nl/search*
// @match               http://*.google.no/search*
// @match               http://*.google.com.np/search*
// @match               http://*.google.nr/search*
// @match               http://*.google.nu/search*
// @match               http://*.google.co.nz/search*
// @match               http://*.google.com.om/search*
// @match               http://*.google.com.pa/search*
// @match               http://*.google.com.pe/search*
// @match               http://*.google.com.pg/search*
// @match               http://*.google.com.ph/search*
// @match               http://*.google.com.pk/search*
// @match               http://*.google.pl/search*
// @match               http://*.google.pn/search*
// @match               http://*.google.com.pr/search*
// @match               http://*.google.ps/search*
// @match               http://*.google.pt/search*
// @match               http://*.google.com.py/search*
// @match               http://*.google.com.qa/search*
// @match               http://*.google.ro/search*
// @match               http://*.google.ru/search*
// @match               http://*.google.rw/search*
// @match               http://*.google.com.sa/search*
// @match               http://*.google.com.sb/search*
// @match               http://*.google.sc/search*
// @match               http://*.google.se/search*
// @match               http://*.google.com.sg/search*
// @match               http://*.google.sh/search*
// @match               http://*.google.si/search*
// @match               http://*.google.sk/search*
// @match               http://*.google.com.sl/search*
// @match               http://*.google.sn/search*
// @match               http://*.google.so/search*
// @match               http://*.google.sm/search*
// @match               http://*.google.sr/search*
// @match               http://*.google.st/search*
// @match               http://*.google.com.sv/search*
// @match               http://*.google.td/search*
// @match               http://*.google.tg/search*
// @match               http://*.google.co.th/search*
// @match               http://*.google.com.tj/search*
// @match               http://*.google.tl/search*
// @match               http://*.google.tm/search*
// @match               http://*.google.tn/search*
// @match               http://*.google.to/search*
// @match               http://*.google.com.tr/search*
// @match               http://*.google.tt/search*
// @match               http://*.google.com.tw/search*
// @match               http://*.google.co.tz/search*
// @match               http://*.google.com.ua/search*
// @match               http://*.google.co.ug/search*
// @match               http://*.google.co.uk/search*
// @match               http://*.google.com.uy/search*
// @match               http://*.google.co.uz/search*
// @match               http://*.google.com.vc/search*
// @match               http://*.google.co.ve/search*
// @match               http://*.google.vg/search*
// @match               http://*.google.co.vi/search*
// @match               http://*.google.com.vn/search*
// @match               http://*.google.vu/search*
// @match               http://*.google.ws/search*
// @match               http://*.google.rs/search*
// @match               http://*.google.co.za/search*
// @match               http://*.google.co.zm/search*
// @match               http://*.google.co.zw/search*
// @match               http://*.google.cat/search*
// @match               https://*.google.com/search*
// @match               https://*.google.ad/search*
// @match               https://*.google.ae/search*
// @match               https://*.google.com.af/search*
// @match               https://*.google.com.ag/search*
// @match               https://*.google.com.ai/search*
// @match               https://*.google.al/search*
// @match               https://*.google.am/search*
// @match               https://*.google.co.ao/search*
// @match               https://*.google.com.ar/search*
// @match               https://*.google.as/search*
// @match               https://*.google.at/search*
// @match               https://*.google.com.au/search*
// @match               https://*.google.az/search*
// @match               https://*.google.ba/search*
// @match               https://*.google.com.bd/search*
// @match               https://*.google.be/search*
// @match               https://*.google.bf/search*
// @match               https://*.google.bg/search*
// @match               https://*.google.com.bh/search*
// @match               https://*.google.bi/search*
// @match               https://*.google.bj/search*
// @match               https://*.google.com.bn/search*
// @match               https://*.google.com.bo/search*
// @match               https://*.google.com.br/search*
// @match               https://*.google.bs/search*
// @match               https://*.google.bt/search*
// @match               https://*.google.co.bw/search*
// @match               https://*.google.by/search*
// @match               https://*.google.com.bz/search*
// @match               https://*.google.ca/search*
// @match               https://*.google.cd/search*
// @match               https://*.google.cf/search*
// @match               https://*.google.cg/search*
// @match               https://*.google.ch/search*
// @match               https://*.google.ci/search*
// @match               https://*.google.co.ck/search*
// @match               https://*.google.cl/search*
// @match               https://*.google.cm/search*
// @match               https://*.google.cn/search*
// @match               https://*.google.com.co/search*
// @match               https://*.google.co.cr/search*
// @match               https://*.google.com.cu/search*
// @match               https://*.google.cv/search*
// @match               https://*.google.com.cy/search*
// @match               https://*.google.cz/search*
// @match               https://*.google.de/search*
// @match               https://*.google.dj/search*
// @match               https://*.google.dk/search*
// @match               https://*.google.dm/search*
// @match               https://*.google.com.do/search*
// @match               https://*.google.dz/search*
// @match               https://*.google.com.ec/search*
// @match               https://*.google.ee/search*
// @match               https://*.google.com.eg/search*
// @match               https://*.google.es/search*
// @match               https://*.google.com.et/search*
// @match               https://*.google.fi/search*
// @match               https://*.google.com.fj/search*
// @match               https://*.google.fm/search*
// @match               https://*.google.fr/search*
// @match               https://*.google.ga/search*
// @match               https://*.google.ge/search*
// @match               https://*.google.gg/search*
// @match               https://*.google.com.gh/search*
// @match               https://*.google.com.gi/search*
// @match               https://*.google.gl/search*
// @match               https://*.google.gm/search*
// @match               https://*.google.gr/search*
// @match               https://*.google.com.gt/search*
// @match               https://*.google.gy/search*
// @match               https://*.google.com.hk/search*
// @match               https://*.google.hn/search*
// @match               https://*.google.hr/search*
// @match               https://*.google.ht/search*
// @match               https://*.google.hu/search*
// @match               https://*.google.co.id/search*
// @match               https://*.google.ie/search*
// @match               https://*.google.co.il/search*
// @match               https://*.google.im/search*
// @match               https://*.google.co.in/search*
// @match               https://*.google.iq/search*
// @match               https://*.google.is/search*
// @match               https://*.google.it/search*
// @match               https://*.google.je/search*
// @match               https://*.google.com.jm/search*
// @match               https://*.google.jo/search*
// @match               https://*.google.co.jp/search*
// @match               https://*.google.co.ke/search*
// @match               https://*.google.com.kh/search*
// @match               https://*.google.ki/search*
// @match               https://*.google.kg/search*
// @match               https://*.google.co.kr/search*
// @match               https://*.google.com.kw/search*
// @match               https://*.google.kz/search*
// @match               https://*.google.la/search*
// @match               https://*.google.com.lb/search*
// @match               https://*.google.li/search*
// @match               https://*.google.lk/search*
// @match               https://*.google.co.ls/search*
// @match               https://*.google.lt/search*
// @match               https://*.google.lu/search*
// @match               https://*.google.lv/search*
// @match               https://*.google.com.ly/search*
// @match               https://*.google.co.ma/search*
// @match               https://*.google.md/search*
// @match               https://*.google.me/search*
// @match               https://*.google.mg/search*
// @match               https://*.google.mk/search*
// @match               https://*.google.ml/search*
// @match               https://*.google.com.mm/search*
// @match               https://*.google.mn/search*
// @match               https://*.google.ms/search*
// @match               https://*.google.com.mt/search*
// @match               https://*.google.mu/search*
// @match               https://*.google.mv/search*
// @match               https://*.google.mw/search*
// @match               https://*.google.com.mx/search*
// @match               https://*.google.com.my/search*
// @match               https://*.google.co.mz/search*
// @match               https://*.google.com.na/search*
// @match               https://*.google.com.ng/search*
// @match               https://*.google.com.ni/search*
// @match               https://*.google.ne/search*
// @match               https://*.google.nl/search*
// @match               https://*.google.no/search*
// @match               https://*.google.com.np/search*
// @match               https://*.google.nr/search*
// @match               https://*.google.nu/search*
// @match               https://*.google.co.nz/search*
// @match               https://*.google.com.om/search*
// @match               https://*.google.com.pa/search*
// @match               https://*.google.com.pe/search*
// @match               https://*.google.com.pg/search*
// @match               https://*.google.com.ph/search*
// @match               https://*.google.com.pk/search*
// @match               https://*.google.pl/search*
// @match               https://*.google.pn/search*
// @match               https://*.google.com.pr/search*
// @match               https://*.google.ps/search*
// @match               https://*.google.pt/search*
// @match               https://*.google.com.py/search*
// @match               https://*.google.com.qa/search*
// @match               https://*.google.ro/search*
// @match               https://*.google.ru/search*
// @match               https://*.google.rw/search*
// @match               https://*.google.com.sa/search*
// @match               https://*.google.com.sb/search*
// @match               https://*.google.sc/search*
// @match               https://*.google.se/search*
// @match               https://*.google.com.sg/search*
// @match               https://*.google.sh/search*
// @match               https://*.google.si/search*
// @match               https://*.google.sk/search*
// @match               https://*.google.com.sl/search*
// @match               https://*.google.sn/search*
// @match               https://*.google.so/search*
// @match               https://*.google.sm/search*
// @match               https://*.google.sr/search*
// @match               https://*.google.st/search*
// @match               https://*.google.com.sv/search*
// @match               https://*.google.td/search*
// @match               https://*.google.tg/search*
// @match               https://*.google.co.th/search*
// @match               https://*.google.com.tj/search*
// @match               https://*.google.tl/search*
// @match               https://*.google.tm/search*
// @match               https://*.google.tn/search*
// @match               https://*.google.to/search*
// @match               https://*.google.com.tr/search*
// @match               https://*.google.tt/search*
// @match               https://*.google.com.tw/search*
// @match               https://*.google.co.tz/search*
// @match               https://*.google.com.ua/search*
// @match               https://*.google.co.ug/search*
// @match               https://*.google.co.uk/search*
// @match               https://*.google.com.uy/search*
// @match               https://*.google.co.uz/search*
// @match               https://*.google.com.vc/search*
// @match               https://*.google.co.ve/search*
// @match               https://*.google.vg/search*
// @match               https://*.google.co.vi/search*
// @match               https://*.google.com.vn/search*
// @match               https://*.google.vu/search*
// @match               https://*.google.ws/search*
// @match               https://*.google.rs/search*
// @match               https://*.google.co.za/search*
// @match               https://*.google.co.zm/search*
// @match               https://*.google.co.zw/search*
// @match               https://*.google.cat/search*
// @grant               none
// @version             0.3.4
// @author              veringsek
// @description         Navigate through Google Search with a set of hotkeys. 
// @description:zh-TW   在 Google 搜尋結果頁面利用快捷鍵進行快速瀏覽。
// @description:zh-CN   在 Google 搜尋結果頁面利用快捷鍵進行快速瀏覽。
// ==/UserScript==

(function () {
    let css = document.createElement('style');
    css.type = 'text/css';
    css.innerText = `
        :root {
            --google-navigation--button-size: 30px;
            --google-navigation--stroke-color: black;
            --google-navigation--background-color: white;
            --google-navigation--button-opacity: 1;
        }
        
        .google-navigation--button {
            position: absolute;
            left: calc(-5 / 3 * var(--google-navigation--button-size));
            border: 2px var(--google-navigation--stroke-color) solid;
            border-radius: 5px;
            background: var(--google-navigation--background-color);
            box-shadow: 0px 2px var(--google-navigation--stroke-color);
            color: var(--google-navigation--stroke-color) !important;
            opacity: var(--google-navigation--button-opacity);
            text-decoration: none !important;
            width: var(--google-navigation--button-size);
            height: var(--google-navigation--button-size);
            line-height: var(--google-navigation--button-size);
            text-align: center;
            font-size: calc(2 / 3 * var(--google-navigation--button-size));
            user-select: none;
            transition: all 0.1s linear;
        
            animation: spawn 0.2s;
        }
        
        .google-navigation--button.keydown {
            background: var(--google-navigation--stroke-color);
            color: var(--google-navigation--background-color) !important;
            transform: scale(0.8);
        }
        
        .google-navigation--switch {
            opacity: 0;
        }
        
        .google-navigation--switch.keydown {
            opacity: 1;
        }
        
        @keyframes spawn {
            from {
                transform: scale(0);
            }
        }
    `;
    document.head.appendChild(css);
})();

let GoogleNavigation = {};
GoogleNavigation.GN_KEYDOWN_TIMER_DURATION = 1000;
GoogleNavigation.GN_KEYDOWN_TIMER_CANCELED = 'GN_KEYDOWN_TIMER_CANCELED';
GoogleNavigation.GN_KEYPRESS_SENSITIVITY = 500;
GoogleNavigation.GN_KEYPRESS_CANCELED = 'GN_KEYPRESS_CANCELED';
GoogleNavigation.GN_BUTTON_SIZE = 30;
GoogleNavigation.GN_BUTTON_SIZE_HALF = GoogleNavigation.GN_BUTTON_SIZE / 2;
GoogleNavigation.keydowns = new Set();
GoogleNavigation.tmrKeydown = undefined;
globalThis.GoogleNavigation = GoogleNavigation;

function setCSSVars() {
    document.documentElement.style.setProperty(
        '--google-navigation--button-size',
        `${globalThis.GoogleNavigation.GN_BUTTON_SIZE}px`
    );
    document.documentElement.style.setProperty(
        '--google-navigation--stroke-color',
        globalThis.getComputedStyle(document.body)['color']
    );
    document.documentElement.style.setProperty(
        '--google-navigation--background-color',
        globalThis.getComputedStyle(document.body)['backgroundColor']
    );
}

function makeTemplate() {
    let template = document.createElement('a');
    template.classList.add('google-navigation--button');
    template.commandKeydown = function () {
        this.classList.add('keydown');
        let documentElement = document.documentElement;
        documentElement.scroll({
            top: this.getBoundingClientRect().top + documentElement.scrollTop - window.innerHeight / 2,
            behavior: 'smooth'
        });
        globalThis.GoogleNavigation.tmrKeydown = globalThis.setTimeout(() => {
            globalThis.clearTimeout(globalThis.GoogleNavigation.tmrKeydown);
            globalThis.GoogleNavigation.tmrKeydown = globalThis.GoogleNavigation.GN_KEYDOWN_TIMER_CANCELED;
            this.classList.remove('keydown');
            this.commandCancel();
        }, globalThis.GoogleNavigation.GN_KEYDOWN_TIMER_DURATION);
    };
    template.commandKeyup = function () {
        this.classList.remove('keydown');
        this.commandPress();
    };
    template.commandPress = function () {
        if (document.getElementById('google-navigation--switch-Control').keydown) {
            window.open(this.link, '_blank');
        } else {
            window.open(this.link, '_self');
        }
    };
    template.commandCancel = function () {
        return;
    };
    globalThis.GoogleNavigation.buttonTemplate = template;
}

function getClonedButton(key, link) {
    let string = key;
    if (Array.isArray(key)) {
        [key, string] = key;
    }
    let cloned = globalThis.GoogleNavigation.buttonTemplate.cloneNode();
    cloned.commandKeydown = globalThis.GoogleNavigation.buttonTemplate.commandKeydown;
    cloned.commandKeyup = globalThis.GoogleNavigation.buttonTemplate.commandKeyup;
    cloned.commandPress = globalThis.GoogleNavigation.buttonTemplate.commandPress;
    cloned.commandCancel = globalThis.GoogleNavigation.buttonTemplate.commandCancel;
    cloned.innerHTML = string;
    cloned.classList.add(`google-navigation--button-${key}`);
    if (link) {
        cloned.link = link;
        cloned.setAttribute('link', link);
    }
    return cloned;
}

function plantButtons() {
    let search = document.getElementById('search');
    let rcnt = document.getElementById('rcnt');
    let links = [...document.getElementsByClassName('LC20lb')];
    let num = 0;
    for (let link of links) {
        if (getCollapseSectionParent(link)) continue;
        if (num >= 10) break;
        let href = link.parentElement.href;
        let cloned;
        cloned = getClonedButton(num, href);
        cloned.style.top = `${link.offsetTop + link.clientHeight / 2 - GoogleNavigation.GN_BUTTON_SIZE_HALF}px`;
        link.insertBefore(cloned, link.children[0]);

        let node = link;
        let notInRcnt = false;
        do {
            if (!(node instanceof Element)) {
                notInRcnt = true;
                break;
            }
            let style = window.getComputedStyle(node);
            if (style['overflow'] === 'hidden') {
                node.style.overflow = 'visible';
            }
            if (style['contain'] === 'layout paint') {
                node.style.contain = 'unset';
            }
            node = node.parentElement;
        } while (node !== rcnt);
        if (notInRcnt) continue;
        
        setButtonEnter(document.activeElement === document.body);
        num += 1;
    }

    if (search) {
        let cloned = getClonedButton(['Control', 'CTRL']);
        cloned.id = 'google-navigation--switch-Control';
        cloned.classList.add('google-navigation--switch');
        cloned.style.left = '-150px';
        cloned.style.width = '80px';
        cloned.style.fontWeight = 'bold';
        cloned.keydown = false;
        cloned.commandKeydown = function () {
            this.tmrKeypress = globalThis.setTimeout(() => {
                this.tmrKeypress = globalThis.GoogleNavigation.GN_KEYPRESS_CANCELED;
            }, globalThis.GoogleNavigation.GN_KEYPRESS_SENSITIVITY);
        };
        cloned.commandKeyup = function () {
            if (this.tmrKeypress === globalThis.GoogleNavigation.GN_KEYPRESS_CANCELED) {
                this.tmrKeypress = undefined;
            } else if (this.tmrKeypress) {
                this.keydown = !this.keydown;
                if (this.keydown) {
                    this.classList.add('keydown');
                } else {
                    this.classList.remove('keydown');
                }
            }
        };
        search.insertBefore(cloned, search.children[0]);
    }

    let btnWiki = document.getElementsByClassName('ruhjFe')[0];
    if (btnWiki) {
        let cloned = getClonedButton(['p', 'P'], btnWiki.href);
        let card = getWidgetParent(btnWiki);
        if (!card) {
            card = document.getElementById('rhs');
            cloned.style.left = `calc(2 / 3 * var(--google-navigation--button-size) + 100%)`;
        }
        card.style.position = 'relative';
        card.insertBefore(cloned, card.children[0]);
    }

    let translatorWidgetMain = document.getElementById('tw-main');
    let translatorWidgetSourceTextTA = document.getElementById('tw-source-text-ta');
    if (translatorWidgetMain && translatorWidgetSourceTextTA) {
        let cloned = getClonedButton(['t', 'T']);
        let rectMain = translatorWidgetMain.getBoundingClientRect();
        let rectText = translatorWidgetSourceTextTA.getBoundingClientRect();
        let top = (rectText.bottom + rectText.top) / 2 - rectMain.top - GoogleNavigation.GN_BUTTON_SIZE_HALF;
        cloned.style.top = `${top}px`;
        cloned.commandPress = function () {
            document.getElementById('tw-source-text-ta').focus();
        };
        translatorWidgetMain.style.position = 'relative';
        translatorWidgetMain.insertBefore(cloned, translatorWidgetMain.children[0]);
    }

    let calculatorWidget = document.getElementById('cwos')?.parentElement?.parentElement;
    if (calculatorWidget) {
        let calculatorWidgetParent = getWidgetParent(calculatorWidget);
        let cloned = getClonedButton(['t', 'T']);
        let top = calculatorWidgetParent.clientHeight / 2 - globalThis.GoogleNavigation.GN_BUTTON_SIZE_HALF;
        cloned.style.top = `${top}px`;
        cloned.commandPress = function () {
            document.getElementById('cwos').parentElement.parentElement.focus();
        };
        calculatorWidgetParent.insertBefore(cloned, calculatorWidgetParent.children[0]);
    }

    let pnprev = document.getElementById('pnprev');
    if (pnprev) {
        let cloned = getClonedButton([',', ','], pnprev.href);
        cloned.style.top = '-3px';
        cloned.style.left = '-40px';
        pnprev.style.position = 'relative';
        pnprev.insertBefore(cloned, pnprev.children[0]);
    }
    let pnnext = document.getElementById('pnnext');
    if (pnnext) {
        let cloned = getClonedButton(['.', '‧'], pnnext.href);
        cloned.style.top = '-3px';
        cloned.style.left = 'calc(100% + 5px)';
        pnnext.style.position = 'relative';
        pnnext.insertBefore(cloned, pnnext.children[0]);
    }
}

function isCommanding() {
    let target = document.activeElement;
    return !(['INPUT', 'TEXTAREA'].includes(target.tagName) || target.classList.contains('jlkklc'));
}

function cssVar(name) {
    return globalThis.getComputedStyle(document.documentElement).getPropertyValue(name);
}

function getParent(element, condition) {
    let node = element;
    do {
        if (condition(node)) return node;
        node = node.parentElement;
    } while (node !== document.body);
    return null;
}

function getWidgetParent(element) {
    return getParent(element, node => {
        let computedStyle = globalThis.getComputedStyle(node);
        let borderWidth = computedStyle.borderWidth;
        return borderWidth !== '' && !(/^0\D*/.test(borderWidth));
    });
}

function getCollapseSectionParent(element) {
    return getParent(element, node => node.getAttribute('jscontroller') === 'Da4hkd');
}

function assignEventListeners() {
    let onFocusChanged = function (ev) {
        document.documentElement.style.setProperty(
            '--google-navigation--button-opacity',
            isCommanding() ? 1 : 0.5
        );
        setButtonEnter(document.activeElement === document.body);
    };
    window.addEventListener('focus', onFocusChanged, true);
    window.addEventListener('blur', onFocusChanged, true);

    document.body.addEventListener('keydown', function (ev) {
        if (globalThis.GoogleNavigation.keydowns.has(ev.key)) return;
        globalThis.GoogleNavigation.keydowns.add(ev.key);
        if (isCommanding()) {
            let button = document.getElementsByClassName(`google-navigation--button-${ev.key}`)[0];
            if (button) {
                button.commandKeydown();
            }
        }
    });

    document.body.addEventListener('keyup', function (ev) {
        globalThis.GoogleNavigation.keydowns.delete(ev.key);
        if (globalThis.GoogleNavigation.tmrKeydown === globalThis.GoogleNavigation.GN_KEYDOWN_TIMER_CANCELED) {
            globalThis.GoogleNavigation.tmrKeydown = undefined;
            return;
        }
        globalThis.clearTimeout(globalThis.GoogleNavigation.tmrKeydown);
        globalThis.GoogleNavigation.tmrKeydown = undefined;
        if (isCommanding()) {
            let button = document.getElementsByClassName(`google-navigation--button-${ev.key}`)[0];
            if (button) {
                button.commandKeyup();
            }
        }
    });
}

function setButtonEnter(enabled) {
    let btn0 = document.getElementsByClassName('google-navigation--button-0')[0];
    if (enabled) {
        btn0.innerHTML = '⮨';
        btn0.classList.add('google-navigation--button-Enter');
    } else {
        btn0.innerHTML = '0';
        btn0.classList.remove('google-navigation--button-Enter');
    }
}

setCSSVars();
makeTemplate();
plantButtons();
assignEventListeners();