Hide Web Elements Pro v2.30

Double-Tap Hide with Animated Preview, Isolated CSS Rules, Cosmetic Engine

スクリプトをインストールするには、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         Hide Web Elements Pro v2.30 
// @version      2.30
// @description  Double-Tap Hide with Animated Preview, Isolated CSS Rules, Cosmetic Engine
// @author       KTZ
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-end
// @license      MIT
// @namespace https://greasyfork.org/users/1620673
// ==/UserScript==

(function() {
    'use strict';

    const UI_HOST_ID = 'hider-ui-root';
    const isMobile = /Android|iPhone|iPad/i.test(navigator.userAgent);
    const btnSize = isMobile ? '50px' : '44px';

    let isSelecting = false;
    let currentScope = 'site'; // 'site' or 'link'
    let previewElement = null; // Tracks the currently highlighted element

    // --- 1. Cosmetic Filtering Engine ---
    function getExactSelector(el) {
        if (!el || el.nodeType !== Node.ELEMENT_NODE) return '';

        const path = [];
        let current = el;

        while (current && current.nodeType === Node.ELEMENT_NODE && current.tagName.toLowerCase() !== 'body' && current.tagName.toLowerCase() !== 'html') {
            let selector = current.tagName.toLowerCase();

            if (current.id && !/^\d+$/.test(current.id) && !/.{15,}/.test(current.id)) {
                selector = `#${CSS.escape(current.id)}`;
                path.unshift(selector);
                break;
            }

            let hasGoodClass = false;
            if (current.className && typeof current.className === 'string') {
                const classes = current.className.trim().split(/\s+/).filter(c => {
                    return c.length > 2 && !/^[a-zA-Z0-9]{10,}$/.test(c) && !/^hover:/i.test(c);
                });

                if (classes.length > 0) {
                    selector += `.${CSS.escape(classes[0])}`;
                    if (classes[1]) selector += `.${CSS.escape(classes[1])}`;
                    hasGoodClass = true;
                }
            }

            if (!hasGoodClass) {
                let index = 1, sibling = current.previousElementSibling;
                while (sibling) { index++; sibling = sibling.previousElementSibling; }
                selector += `:nth-child(${index})`;
            }

            path.unshift(selector);
            if (hasGoodClass && path.length >= 2) break;

            current = current.parentElement;
        }

        if (path.length > 0 && !path[0].startsWith('#') && !path[0].includes('.')) {
             path.unshift('body');
        }
        return path.join(' > ');
    }

    // --- 2. UI Injection ---
    function injectUI() {
        if (document.getElementById(UI_HOST_ID)) return;
        if (!document.body) return;

        const host = document.createElement('div');
        host.id = UI_HOST_ID;
        host.style.cssText = 'position:fixed; top:0; right:0; bottom:0; width:0; z-index:2147483647; pointer-events:none;';
        document.body.appendChild(host);

        const s = document.createElement('style');
        // Added the animated preview class (.hider-preview-highlight) and keyframes
        s.textContent = `
            @keyframes hider-preview-pulse {
                0% { outline: 3px dotted #ef4444; outline-offset: -3px; box-shadow: inset 0 0 0 rgba(239, 68, 68, 0.1); }
                50% { outline: 3px dotted #ef4444; outline-offset: 2px; box-shadow: inset 0 0 15px rgba(239, 68, 68, 0.4); }
                100% { outline: 3px dotted #ef4444; outline-offset: -3px; box-shadow: inset 0 0 0 rgba(239, 68, 68, 0.1); }
            }
            .hider-preview-highlight {
                animation: hider-preview-pulse 1s infinite !important;
                cursor: pointer !important;
                border-radius: 2px !important;
            }

            .hider-btn {
                position: fixed; right: 0; width: ${btnSize}; height: ${btnSize};
                background: rgba(255, 255, 255, 0.2); backdrop-filter: blur(15px);
                border: 1px solid rgba(255,255,255,0.3); border-radius: 12px 0 0 12px;
                color: white; display: flex; align-items: center; justify-content: center;
                cursor: pointer; font-size: 10px; font-weight: 700; text-transform: uppercase;
                transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); pointer-events: auto;
                transform: translateX(calc(${btnSize} - 15px));
            }
            @media (hover: hover) {
                .hider-btn:hover { transform: translateX(0); width: 70px; background: rgba(255,255,255,0.4); }
            }
            .hider-btn.active { transform: translateX(0) !important; width: 70px !important; }

            #btn-select.active { background: #3b82f6 !important; }
            #btn-scope.scope-link { background: #10b981 !important; }
            #btn-manage.active { background: rgba(239, 68, 68, 0.8) !important; transform: translateX(0) !important; width: 70px !important; }

            #btn-select { top: 20px; }
            #btn-scope { top: calc(20px + ${btnSize} + 10px); }
            #btn-manage { top: calc(20px + (${btnSize} * 2) + 20px); background: rgba(239, 68, 68, 0.4); }

            #hider-panel {
                position: fixed; right: 10px; top: 100px; width: 300px; max-height: 70vh;
                background: rgba(0,0,0,0.85); backdrop-filter: blur(20px); border-radius: 16px;
                padding: 15px; color: white; display: none; flex-direction: column; overflow-y: auto; pointer-events: auto;
            }
            .panel-header { font-weight:bold; margin-bottom:12px; border-bottom: 2px solid #555; padding-bottom: 8px; display: flex; justify-content: space-between; align-items: center; }
            .section-title { font-size: 11px; color: #aaa; text-transform: uppercase; margin: 10px 0 5px 0; letter-spacing: 1px; }
            .list-item { font-size: 11px; margin-bottom: 6px; border-bottom: 1px solid #333; padding-bottom: 4px; display: flex; justify-content: space-between; align-items: center; }
            .list-item span { word-break: break-all; padding-right: 8px; }
            .hider-btn-small { cursor: pointer; border: none; border-radius: 4px; padding: 4px 8px; color: white; font-weight: bold; background: #ff4444; }
            .hider-btn-small:hover { background: #cc0000; }
            #close-p { background: #555; padding: 6px 10px; } #close-p:hover { background: #777; }
        `;
        (document.head || document.documentElement).appendChild(s);

        host.innerHTML = `
            <div class="hider-btn" id="btn-select">Hide</div>
            <div class="hider-btn" id="btn-scope">SITE</div>
            <div class="hider-btn" id="btn-manage">List</div>

            <div id="hider-panel">
                <div class="panel-header">
                    <span>Manage Hidden Items</span>
                    <button class="hider-btn-small" id="close-p">X</button>
                </div>
                <div class="section-title">Hidden Everywhere (Site)</div>
                <div id="list-site"></div>

                <div class="section-title" style="margin-top: 15px;">Hidden Here Only (Link)</div>
                <div id="list-link"></div>
            </div>
        `;

        document.getElementById('btn-select').onclick = (e) => {
            isSelecting = !isSelecting;
            const btnScope = document.getElementById('btn-scope');
            const btnManage = document.getElementById('btn-manage');
            const panel = document.getElementById('hider-panel');

            if (isSelecting) {
                e.target.classList.add('active');
                btnScope.classList.add('active');
                panel.style.display = 'none';
                btnManage.classList.remove('active');
            } else {
                e.target.classList.remove('active');
                btnScope.classList.remove('active');

                // Clear preview if selection mode is turned off manually
                if (previewElement) {
                    previewElement.classList.remove('hider-preview-highlight');
                    previewElement = null;
                }
            }
        };

        const btnScope = document.getElementById('btn-scope');
        btnScope.onclick = () => {
            if (!isSelecting) return;
            currentScope = currentScope === 'site' ? 'link' : 'site';
            btnScope.textContent = currentScope === 'site' ? 'SITE' : 'LINK';
            btnScope.classList.toggle('scope-link', currentScope === 'link');
        };

        document.getElementById('btn-manage').onclick = (e) => {
            const panel = document.getElementById('hider-panel');
            const btnSelect = document.getElementById('btn-select');
            const btnScope = document.getElementById('btn-scope');

            if (panel.style.display === 'flex') {
                panel.style.display = 'none';
                e.target.classList.remove('active');
            } else {
                panel.style.display = 'flex';
                e.target.classList.add('active');

                // Ensure selection is turned off when managing lists
                isSelecting = false;
                btnSelect.classList.remove('active');
                btnScope.classList.remove('active');
                if (previewElement) {
                    previewElement.classList.remove('hider-preview-highlight');
                    previewElement = null;
                }

                renderList();
            }
        };

        document.getElementById('close-p').onclick = () => {
            document.getElementById('hider-panel').style.display = 'none';
            document.getElementById('btn-manage').classList.remove('active');
        };
    }

    // --- 3. Style & Storage (Isolated CSS Rules) ---
    function updateStyles() {
        let styleEl = document.getElementById('hider-dynamic-styles');
        if (!styleEl) {
            styleEl = document.createElement('style');
            styleEl.id = 'hider-dynamic-styles';
            (document.head || document.documentElement).appendChild(styleEl);
        }

        const siteData = GM_getValue('hider_site_' + window.location.hostname, []);
        const linkData = GM_getValue('hider_link_' + window.location.href, []);
        const combined = [...siteData, ...linkData];

        const hideRules = combined
            .filter(sel => sel && sel.trim() !== '')
            .map(sel => `${sel} { display: none !important; }`)
            .join('\n');

        styleEl.textContent = hideRules;
    }

    function renderList() {
        const siteKey = 'hider_site_' + window.location.hostname;
        const linkKey = 'hider_link_' + window.location.href;
        const siteData = GM_getValue(siteKey, []);
        const linkData = GM_getValue(linkKey, []);

        const cSite = document.getElementById('list-site');
        const cLink = document.getElementById('list-link');
        if(!cSite || !cLink) return;

        cSite.innerHTML = ''; cLink.innerHTML = '';

        if (siteData.length === 0) cSite.innerHTML = `<div style="font-size:10px; color:#777;">None</div>`;
        siteData.forEach((sel, i) => {
            const div = document.createElement('div'); div.className = 'list-item';
            div.innerHTML = `<span>${sel.substring(0, 25)}...</span> <button class="hider-btn-small" data-i="${i}">X</button>`;
            div.querySelector('button').onclick = (e) => {
                siteData.splice(e.target.dataset.i, 1); GM_setValue(siteKey, siteData);
                updateStyles(); renderList();
            };
            cSite.appendChild(div);
        });

        if (linkData.length === 0) cLink.innerHTML = `<div style="font-size:10px; color:#777;">None</div>`;
        linkData.forEach((sel, i) => {
            const div = document.createElement('div'); div.className = 'list-item';
            div.innerHTML = `<span>${sel.substring(0, 25)}...</span> <button class="hider-btn-small" data-i="${i}">X</button>`;
            div.querySelector('button').onclick = (e) => {
                linkData.splice(e.target.dataset.i, 1); GM_setValue(linkKey, linkData);
                updateStyles(); renderList();
            };
            cLink.appendChild(div);
        });
    }

    // --- 4. Global Capture Click (UPDATED: Double-Tap Preview) ---
    document.addEventListener('click', (e) => {
        if (!isSelecting) return;
        if (e.target.closest('#' + UI_HOST_ID)) return;

        e.preventDefault();
        e.stopPropagation();

        // Check if the user is clicking the currently highlighted element
        if (previewElement === e.target) {
            // TAP 2: Commit and hide
            previewElement.classList.remove('hider-preview-highlight');

            const selector = getExactSelector(e.target);
            if (!selector) {
                previewElement = null; // Reset if selector generation fails
                return;
            }

            // Force it to vanish immediately for visual feedback
            e.target.style.setProperty('display', 'none', 'important');

            if (currentScope === 'site') {
                const key = 'hider_site_' + window.location.hostname;
                const s = GM_getValue(key, []);
                if (!s.includes(selector)) { s.push(selector); GM_setValue(key, s); }
            } else {
                const key = 'hider_link_' + window.location.href;
                const s = GM_getValue(key, []);
                if (!s.includes(selector)) { s.push(selector); GM_setValue(key, s); }
            }

            updateStyles();
            previewElement = null; // Clear the track so we can select another
        } else {
            // TAP 1: Highlight
            if (previewElement) {
                // Clear the previously previewed element if the user clicked somewhere else
                previewElement.classList.remove('hider-preview-highlight');
            }
            previewElement = e.target;
            previewElement.classList.add('hider-preview-highlight');
        }

    }, true);

    // --- 5. Permanent Long-Press & Right-Click Enabler ---
    function enableContextMenus() {
        const blockEvents = ['contextmenu', 'selectstart', 'copy', 'paste', 'dragstart'];
        blockEvents.forEach(evt => {
            document.addEventListener(evt, (e) => {
                e.stopPropagation();
            }, true);
        });

        const cssUnlock = document.createElement('style');
        cssUnlock.textContent = `* { -webkit-touch-callout: default !important; }`;
        (document.head || document.documentElement).appendChild(cssUnlock);
    }
    enableContextMenus();

    // --- 6. The "Under the Hood" Engine (MutationObserver) ---
    let lastUrl = window.location.href;

    const observer = new MutationObserver(() => {
        if (!document.getElementById(UI_HOST_ID)) injectUI();
        if (!document.getElementById('hider-dynamic-styles')) updateStyles();

        if (window.location.href !== lastUrl) {
            lastUrl = window.location.href;
            updateStyles();
            const panel = document.getElementById('hider-panel');
            if (panel && panel.style.display === 'flex') renderList();
        }
    });

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

    injectUI();
    updateStyles();

})();