dperelman / BombManual.com Tab Title and TOC

// ==UserScript==
// @name         BombManual.com Tab Title and TOC
// @downloadURL  https://openuserjs.org/install/dperelman/BombManual.com_Tab_Title_and_TOC.user.js
// @updateURL    https://openuserjs.org/meta/dperelman/BombManual.com_Tab_Title_and_TOC.meta.js
// @version      0.1
// @description  Update tab title while scrolling and show a table of contents.
// @author       Daniel Perelman <perelman@aweirdimagination.net>
// @license      MIT
// @match        https://www.bombmanual.com/web/*
// @match        https://bombmanual.com/web/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    window.addEventListener("load", (event) => {
        const pages = document.getElementsByClassName('page');
        const visiblePages = new Map();

        function titleFor(el) {
            let title = el.id;
            const titles = el.getElementsByClassName('page-header-section-title');
            const h2 = el.querySelector('h2');
            if (titles.length && !el.id.startsWith("Appendix")) {
                title = titles[0].innerText;
            } else if (h2) {
                title = h2.innerText;
            }

            if (title == "www.keeptalkinggame.com") {
                title = "Title Page";
            } else if (title == "Keypads") {
                title = "Symbols";
            } else if (title.startsWith("Appendix")) {
                title = title.substring(9);
            }

            return title;
        }

        function updateTitleFor(el) {
            const title = titleFor(el);
            document.title = title + " - Keep Talking and Nobody Explodes - Bomb Defusal Manual - en - v1";
        }

        const toc = document.createElement('ul');
        for (const page of pages) {
            if (page.id) {
                const link = document.createElement('a');
                link.href = '#' + page.id;
                link.innerText = titleFor(page);
                const item = document.createElement('li');
                item.appendChild(link);
                toc.appendChild(item);
            }
        }
        pages[1].querySelector(".page-content").appendChild(toc);

        let observer = new IntersectionObserver((entries, observer) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    visiblePages.set(entry.target, entry.intersectionRatio);
                } else {
                    visiblePages.delete(entry.target);
                }
            });

            let max = -1;
            for (const page of pages) {
                const intersectionRatio = visiblePages.get(page);
                if (intersectionRatio && intersectionRatio > max) {
                    max = intersectionRatio;
                    updateTitleFor(page);
                }
            }
        }, {
            threshold: [0, 0.1, 0.25, 0.5, 0.75, 0.9, 1],
        });

        for (const page of pages) {
            observer.observe(page);
        }
    });


})();