Mobile Element Selector

모바일 요소 선택기

目前為 2025-04-16 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Mobile Element Selector
// @author       ZNJXL
// @version      1.3
// @namespace    http://tampermonkey.net/
// @description  모바일 요소 선택기
// @match        *://*/*
// @license      MIT
// @grant        GM_setClipboard
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(async function() {
    'use strict';
    console.log("[MES v1.3.3] Script starting..."); // 버전 정보 추가

    // --- 초기 설정 값 로드 ---
    let includeSiteName, buttonSizeScale, panelOpacity;
    try {
        includeSiteName = await GM_getValue('includeSiteName', true);
        buttonSizeScale = await GM_getValue('buttonSizeScale', 1.0);
        panelOpacity = await GM_getValue('panelOpacity', 0.95);
        console.log("[MES v1.3.3] Settings loaded", { includeSiteName, buttonSizeScale, panelOpacity });
    } catch (e) {
        console.error("[MES v1.3.3] Error loading settings from GM_getValue", e);
        includeSiteName = true; buttonSizeScale = 1.0; panelOpacity = 0.95;
    }

    // --- 전역 변수 ---
    let selecting = false;
    let selectedEl = null;
    let initialTouchedElement = null;
    let touchStartX = 0, touchStartY = 0;
    let touchMoved = false;
    const moveThreshold = 10;
    const BLOCKED_SELECTORS_KEY = 'mobileBlockedSelectors';

    // --- CSS 정의 ---
    const style = document.createElement('style');
    style.textContent = `
    :root {
        --panel-opacity: ${panelOpacity};
        --btn-padding: ${10 * buttonSizeScale}px;
        --btn-font-size: ${14 * buttonSizeScale}px;
        --btn-min-width: ${80 * buttonSizeScale}px;
    }
    .mobile-block-ui {
        z-index: 9999 !important;
        touch-action: manipulation !important;
        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        box-sizing: border-box;
        position: fixed !important;
        /* display 속성은 여기서 제거! 개별 요소나 JS로 제어 */
        visibility: visible !important; /* 숨겨진 상태가 아닌 이상 보이도록 */
    }
    #mobile-block-panel, #mobile-settings-panel {
         opacity: var(--panel-opacity) !important;
         backface-visibility: hidden; -webkit-backface-visibility: hidden;
         /* 초기 상태는 JS에서 display: none 으로 설정 */
    }
    /* ... (나머지 CSS는 이전과 동일) ... */
    .mb-slider { width: 100%; margin: 10px 0; -webkit-appearance: none; appearance: none; background: #555; height: 8px; border-radius: 5px; outline: none; cursor: pointer; }
    .mb-slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 20px; height: 20px; background: #4CAF50; border-radius: 50%; cursor: pointer; border: 2px solid #fff; box-shadow: 0 2px 5px rgba(0,0,0,0.3); }
    .mb-slider::-moz-range-thumb { width: 20px; height: 20px; background: #4CAF50; border-radius: 50%; cursor: pointer; border: 2px solid #fff; box-shadow: 0 2px 5px rgba(0,0,0,0.3); }
    .selected-element { background-color: rgba(255, 0, 0, 0.3) !important; outline: 1px dashed red !important; box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.1); z-index: 9998 !important; transition: background-color 0.1s ease, outline 0.1s ease, box-shadow 0.1s ease; }
    #mobile-block-panel { bottom: 15px; left: 50%; transform: translateX(-50%); width: calc(100% - 30px); max-width: 350px; background: rgba(40, 40, 40, 0.95); color: #eee; padding: 15px; border-radius: 12px; box-shadow: 0 5px 15px rgba(0,0,0,0.6); z-index: 10001 !important; border-top: 1px solid rgba(255, 255, 255, 0.1); /* display: none; JS에서 설정 */ }
    #mobile-settings-panel { top: 50%; left: 50%; transform: translate(-50%, -50%); width: calc(100% - 40px); max-width: 300px; background: rgba(50, 50, 50, 0.95); color: #eee; padding: 20px; border-radius: 12px; box-shadow: 0 8px 20px rgba(0,0,0,0.7); z-index: 10003 !important; border: 1px solid rgba(255, 255, 255, 0.15); /* display: none; JS에서 설정 */ }
    #mobile-block-toggleBtn { top: 15px !important; left: 15px !important; z-index: 10002 !important; background: rgba(0,0,0,0.2) !important; width: 40px !important; height: 40px !important; border-radius: 50% !important; border: none !important; cursor: pointer !important; font-size: 0 !important; box-shadow: 0 2px 5px rgba(0,0,0,0.3) !important; transition: background 0.3s ease, transform 0.2s ease; display: flex !important; align-items: center !important; justify-content: center !important; opacity: 1 !important; backface-visibility: hidden; -webkit-backface-visibility: hidden; }
    #mobile-block-toggleBtn:active { transform: scale(0.9); }
    #mobile-block-toggleBtn.selecting { background: rgba(255,87,34,0.8) !important; }
    #mobile-block-toggleBtn .button-plus { font-size: 24px !important; color: #fff !important; line-height: 40px !important; }
    .mb-btn { padding: var(--btn-padding); border: none; border-radius: 8px; color: #fff; font-size: var(--btn-font-size); cursor: pointer; transition: background 0.2s ease, transform 0.1s ease, box-shadow 0.2s ease; background-color: #555; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.2); min-width: var(--btn-min-width); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; opacity: 1 !important; }
    .mb-btn:active { transform: scale(0.97); box-shadow: inset 0 2px 4px rgba(0,0,0,0.3); }
    #blocker-copy { background: linear-gradient(145deg, #2196F3, #1976D2); } #blocker-preview { background: linear-gradient(145deg, #ff9800, #f57c00); } #blocker-add-block { background: linear-gradient(145deg, #f44336, #c62828); } #blocker-settings { background: linear-gradient(145deg, #9C27B0, #7B1FA2); } #blocker-cancel { background: linear-gradient(145deg, #607D8B, #455A64); } #settings-close { background: linear-gradient(145deg, #607D8B, #455A64); margin-top: 15px; width: 100%; } #settings-toggle-site { background: linear-gradient(145deg, #009688, #00796B); }
    .button-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(var(--btn-min-width), 1fr)); gap: 8px; margin-top: 15px; } #blocker-info-wrapper { position: relative; margin-bottom: 10px; } #blocker-info { display: block; color: #90ee90; font-size: 13px; line-height: 1.4; background-color: rgba(0,0,0,0.3); padding: 5px 8px; border-radius: 4px; word-break: break-all; min-height: 1.4em; } .settings-item { margin-bottom: 15px; } .settings-item label { display: block; font-size: 13px; color: #ccc; margin-bottom: 5px; } .settings-value { float: right; color: #fff; font-weight: bold; }
    `;
    document.head.appendChild(style);
    console.log("[MES v1.3.3] Style appended.");

    // --- UI 요소 변수 선언 ---
    let panel, settingsPanel, toggleBtn;
    let blockerInfo, blockerSlider, copyBtn, previewBtn, addBlockBtn, settingsBtn, cancelBtn;
    let settingsToggleSiteBtn, settingsButtonSizeSlider, settingsPanelOpacitySlider, settingsCloseBtn, buttonSizeValueSpan, opacityValueSpan;

    // --- 함수 정의 ---

    // 드래그 기능 (v1.3.2 버전 유지 - 이 부분은 문제가 없어 보임)
    function makeDraggable(el) { /* ... (v1.3.2와 동일) ... */
        if (!el) { console.warn("[MES v1.3.3] makeDraggable called with null element"); return; }
        let startX, startY, dragStartX, dragStartY; let dragging = false, moved = false; let initialTransform = '';
        const handleTouchStart = (e) => {
            const targetTag = e.target.tagName.toLowerCase(); if (['input', 'button', 'textarea', 'select'].includes(targetTag) || e.target.closest('.mb-btn, .mb-slider')) return; if (dragging) return; dragging = true; moved = false;
            const touch = e.touches[0]; startX = touch.clientX; startY = touch.clientY;
            const computedStyle = window.getComputedStyle(el); dragStartX = parseFloat(computedStyle.left) || 0; dragStartY = parseFloat(computedStyle.top) || 0; initialTransform = computedStyle.transform !== 'none' ? computedStyle.transform : '';
            el.style.transition = 'none';
        };
        const handleTouchMove = (e) => {
            if (!dragging) return; const touch = e.touches[0]; const dx = touch.clientX - startX; const dy = touch.clientY - startY;
            if (!moved && Math.sqrt(dx * dx + dy * dy) > moveThreshold) { moved = true; if (initialTransform) { el.style.transform = 'none'; /* console.log("[MES v1.3.3] Transform removed on first move."); */ } try { e.preventDefault(); } catch {} }
            if (moved) { let newX = dragStartX + dx; let newY = dragStartY + dy; const elWidth = el.offsetWidth; const elHeight = el.offsetHeight; const parentWidth = window.innerWidth; const parentHeight = window.innerHeight; newX = Math.max(0, Math.min(newX, parentWidth - elWidth)); newY = Math.max(0, Math.min(newY, parentHeight - elHeight)); el.style.left = newX + 'px'; el.style.top = newY + 'px'; el.style.right = 'auto'; el.style.bottom = 'auto'; }
        };
        const handleTouchEnd = (e) => { if (!dragging) return; dragging = false; el.style.transition = ''; if (moved) { try { e.preventDefault(); e.stopPropagation(); } catch {} } };
        el.addEventListener('touchstart', handleTouchStart, { passive: true }); el.addEventListener('touchmove', handleTouchMove, { passive: false }); el.addEventListener('touchend', handleTouchEnd, { passive: false }); el.addEventListener('touchcancel', handleTouchEnd, { passive: false });
        // console.log("[MES v1.3.3] Draggable listener added for", el.id || el.tagName);
    }


    // 선택 모드 설정/해제
    function setBlockMode(enabled) {
        console.log(`[MES v1.3.3] setBlockMode called with: ${enabled}`); // 로그 추가
        if (!toggleBtn || !panel || !settingsPanel) { console.error("[MES v1.3.3] UI elements not ready for setBlockMode."); return; }
        selecting = enabled; // selecting 상태 업데이트
        toggleBtn.classList.toggle('selecting', enabled);
        panel.style.display = enabled ? 'block' : 'none'; // 패널 보이기/숨기기
        settingsPanel.style.display = 'none'; // 설정 패널은 항상 숨김 처리
        if (!enabled) {
            removeSelectionHighlight();
            resetPreview();
        }
        if (blockerSlider) blockerSlider.value = 0;
        updateInfo();
        console.log(`[MES v1.3.3] setBlockMode finished. selecting=${selecting}, panel.display=${panel.style.display}`); // 로그 추가
    }

    // 나머지 함수들 (v1.3.2와 동일 - 필요시 내부 로그 추가 가능)
    function removeSelectionHighlight() { /* ... */ if (selectedEl) { selectedEl.classList.remove('selected-element'); selectedEl = null; initialTouchedElement = null; } }
    let isPreviewHidden = false; let previewedElement = null;
    function resetPreview() { /* ... */ if (isPreviewHidden && previewedElement) { try { previewedElement.style.display = previewedElement.dataset._original_display || ''; delete previewedElement.dataset._original_display; } catch (e) { console.error("[MES v1.3.3] Error resetting preview display:", e); } if (previewBtn) previewBtn.textContent = '미리보기'; isPreviewHidden = false; previewedElement = null; } else if (previewBtn) { previewBtn.textContent = '미리보기'; } }
    function updateInfo() { /* ... */ if (blockerInfo) { blockerInfo.textContent = selectedEl ? generateSelector(selectedEl) : '없음'; } }
    function generateSelector(el) { /* ... (v1.3.2와 동일) ... */
        if (!el || el.nodeType !== 1 || el.closest('.ui-ignore')) return '';
        const parts = []; let current = el; const maxDepth = 7; let depth = 0;
        while (current && current.tagName && current.tagName.toLowerCase() !== 'body' && current.tagName.toLowerCase() !== 'html' && depth < maxDepth) {
            const parent = current.parentElement; if (current.classList.contains('ui-ignore')) { current = parent; continue; }
            const tagName = current.tagName.toLowerCase(); let selectorPart = tagName;
            if (current.id && !/\d/.test(current.id)) { try { selectorPart = `#${CSS.escape(current.id)}`; parts.unshift(selectorPart); depth++; break; } catch (e) { /* console.warn("Could not escape ID:", current.id, e); */ } }
            if (!selectorPart.startsWith('#')) {
                const classes = Array.from(current.classList).filter(c => c && !c.startsWith('ember-') && !c.startsWith('react-') && !/^[a-zA-Z]{1,2}$/.test(c) && !/\d/.test(c.substring(0,1)) && !['selected-element', 'mobile-block-ui', 'ui-ignore'].includes(c));
                if (classes.length > 0) { try { selectorPart += '.' + classes.map(c => CSS.escape(c)).join('.'); } catch (e) { /* console.warn("Could not escape class names:", classes, e); */ } }
                else if (parent && !parent.closest('.ui-ignore')) { const siblings = Array.from(parent.children).filter(sibling => !sibling.classList.contains('ui-ignore')); let sameTagIndex = 0; let currentIndex = -1; for (let i = 0; i < siblings.length; i++) { if (siblings[i].tagName === current.tagName) { sameTagIndex++; if (siblings[i] === current) { currentIndex = sameTagIndex; } } } if (currentIndex > 0 && sameTagIndex > 1) { selectorPart = `${tagName}:nth-of-type(${currentIndex})`; } }
            }
            parts.unshift(selectorPart); depth++;
            if (!parent || parent.tagName.toLowerCase() === 'body' || parent.tagName.toLowerCase() === 'html') break; current = parent;
        }
         let finalSelector = parts.join(' > '); const lastIdIndex = finalSelector.lastIndexOf('#'); if (lastIdIndex > 0) { finalSelector = finalSelector.substring(lastIdIndex); } else if (finalSelector.length > 150) { finalSelector = parts.slice(-2).join(' > '); } if (!finalSelector || finalSelector === 'body' || finalSelector === 'html') return ''; return finalSelector;
    }
    async function loadBlockedSelectors() { /* ... */ const stored = await GM_getValue(BLOCKED_SELECTORS_KEY, '[]'); try { return JSON.parse(stored); } catch (e) { console.error("[MES v1.3.3] Error parsing blocked selectors:", e); await GM_setValue(BLOCKED_SELECTORS_KEY, '[]'); return []; } }
    async function saveBlockedSelectors(selectors) { /* ... */ const selectorsToSave = Array.isArray(selectors) ? selectors : []; await GM_setValue(BLOCKED_SELECTORS_KEY, JSON.stringify(selectorsToSave)); }
    async function addBlockRule(selector) { /* ... */ if (!selector) return { success: false, message: '유효하지 않은 선택자입니다.' }; let fullSelector = "##" + selector; if (includeSiteName) { const hostname = location.hostname; if (!hostname) return { success: false, message: '호스트 이름을 가져올 수 없습니다.'}; fullSelector = hostname + fullSelector; } const blockedSelectors = await loadBlockedSelectors(); if (!blockedSelectors.includes(fullSelector)) { blockedSelectors.push(fullSelector); await saveBlockedSelectors(blockedSelectors); return { success: true, rule: fullSelector }; } return { success: false, message: '이미 저장된 규칙입니다.' }; }


    // --- UI 생성 및 초기화 함수 ---
    function createUIElements() {
        console.log("[MES v1.3.3] Creating UI elements...");
        try {
            // 메인 제어판 생성
            panel = document.createElement('div');
            panel.id = 'mobile-block-panel';
            panel.classList.add('mobile-block-ui', 'ui-ignore');
            panel.style.display = 'none'; // 명시적으로 숨김
            panel.innerHTML = `<div id="blocker-info-wrapper" class="ui-ignore"><span style="font-size: 12px; color: #ccc;" class="ui-ignore">선택된 요소:</span><span id="blocker-info" class="ui-ignore">없음</span></div> <input type="range" id="blocker-slider" class="mb-slider ui-ignore" min="0" max="10" value="0"> <div class="button-grid ui-ignore"><button id="blocker-copy" class="mb-btn ui-ignore">복사</button><button id="blocker-preview" class="mb-btn ui-ignore">미리보기</button><button id="blocker-add-block" class="mb-btn ui-ignore">차단(저장)</button><button id="blocker-settings" class="mb-btn ui-ignore">설정</button><button id="blocker-cancel" class="mb-btn ui-ignore">취소</button></div>`;
            document.body.appendChild(panel);
            console.log("[MES v1.3.3] Main panel appended.");

            // 설정 패널 생성
            settingsPanel = document.createElement('div');
            settingsPanel.id = 'mobile-settings-panel';
            settingsPanel.classList.add('mobile-block-ui', 'ui-ignore');
            settingsPanel.style.display = 'none'; // 명시적으로 숨김
            settingsPanel.innerHTML = `<h3 style="text-align: center; margin-top: 0; margin-bottom: 20px; color: #fff;" class="ui-ignore">설정</h3> <div class="settings-item ui-ignore"><label for="settings-toggle-site" class="ui-ignore">사이트명 포함 규칙:</label><button id="settings-toggle-site" class="mb-btn ui-ignore">${includeSiteName ? "사이트명: ON" : "사이트명: OFF"}</button></div> <div class="settings-item ui-ignore"><label for="settings-button-size" class="ui-ignore">UI 크기: <span id="button-size-value" class="settings-value ui-ignore">${buttonSizeScale.toFixed(1)}x</span></label><input type="range" id="settings-button-size" class="mb-slider ui-ignore" min="0.8" max="1.5" step="0.1" value="${buttonSizeScale}"></div> <div class="settings-item ui-ignore"><label for="settings-panel-opacity" class="ui-ignore">패널 투명도: <span id="opacity-value" class="settings-value ui-ignore">${panelOpacity.toFixed(2)}</span></label><input type="range" id="settings-panel-opacity" class="mb-slider ui-ignore" min="0.5" max="1.0" step="0.05" value="${panelOpacity}"></div> <button id="settings-close" class="mb-btn ui-ignore">닫기</button>`;
            document.body.appendChild(settingsPanel);
            console.log("[MES v1.3.3] Settings panel appended.");

            // 토글 버튼 생성
            toggleBtn = document.createElement('button');
            toggleBtn.id = 'mobile-block-toggleBtn';
            toggleBtn.classList.add('mobile-block-ui', 'ui-ignore');
            toggleBtn.innerHTML = '<span class="button-plus ui-ignore">+</span>';
            document.body.appendChild(toggleBtn);
            console.log("[MES v1.3.3] Toggle button appended.");

            // UI 요소 참조 설정
            initializeUIReferences();

        } catch (e) {
            console.error("[MES v1.3.3] Error creating UI elements!", e);
        }
    }

    function initializeUIReferences() {
        console.log("[MES v1.3.3] Initializing UI references...");
        try {
             blockerInfo = panel.querySelector('#blocker-info'); blockerSlider = panel.querySelector('#blocker-slider'); copyBtn = panel.querySelector('#blocker-copy'); previewBtn = panel.querySelector('#blocker-preview'); addBlockBtn = panel.querySelector('#blocker-add-block'); settingsBtn = panel.querySelector('#blocker-settings'); cancelBtn = panel.querySelector('#blocker-cancel');
             settingsToggleSiteBtn = settingsPanel.querySelector('#settings-toggle-site'); settingsButtonSizeSlider = settingsPanel.querySelector('#settings-button-size'); settingsPanelOpacitySlider = settingsPanel.querySelector('#settings-panel-opacity'); settingsCloseBtn = settingsPanel.querySelector('#settings-close'); buttonSizeValueSpan = settingsPanel.querySelector('#button-size-value'); opacityValueSpan = settingsPanel.querySelector('#opacity-value');
             setupEventListeners(); // 이벤트 리스너 설정 호출
             makeDraggable(panel); makeDraggable(settingsPanel); makeDraggable(toggleBtn); // 드래그 기능 초기화
             console.log("[MES v1.3.3] UI references and listeners initialized.");
         } catch (e) {
              console.error("[MES v1.3.3] Error initializing UI references!", e);
         }
    }

    // --- 이벤트 리스너 설정 함수 ---
    function setupEventListeners() {
        console.log("[MES v1.3.3] Setting up event listeners...");
         try {
             // 요소 선택 관련 이벤트 (touch) - 로그 추가
             const uiExcludeSelector = '.ui-ignore';
             document.addEventListener('touchstart', e => {
                 if (!selecting) return; // 선택 모드가 아닐 때 무시
                 const isUIElement = e.target.closest(uiExcludeSelector);
                 // console.log(`[MES Touchevent] touchstart: selecting=${selecting}, target=${e.target.tagName}, isUI=${!!isUIElement}`);
                 if (isUIElement) return;
                 const touch = e.touches[0]; touchStartX = touch.clientX; touchStartY = touch.clientY; touchMoved = false;
             }, { passive: true });

             document.addEventListener('touchmove', e => {
                 if (!selecting || touchMoved) return; // 이미 이동했으면 더 이상 체크 안 함
                 const isUIElement = e.target.closest(uiExcludeSelector);
                 // console.log(`[MES Touchevent] touchmove: selecting=${selecting}, target=${e.target.tagName}, isUI=${!!isUIElement}`);
                 if (isUIElement || !e.touches[0]) return;
                 const touch = e.touches[0]; const dx = touch.clientX - startX; const dy = touch.clientY - startY;
                 if (Math.sqrt(dx * dx + dy * dy) > moveThreshold) {
                     // console.log("[MES Touchevent] touchmove: Moved threshold crossed.");
                     touchMoved = true;
                     if (selectedEl) selectedEl.classList.remove('selected-element'); // 이동 시작 시 하이라이트 제거
                 }
             }, { passive: true }); // passive 유지 시도

             document.addEventListener('touchend', e => {
                 console.log(`[MES Touchevent] touchend: selecting=${selecting}, moved=${touchMoved}`); // 로그 추가
                 if (!selecting) return; // 선택 모드가 아니면 종료

                 const isUIElement = e.target.closest(uiExcludeSelector);
                 console.log(`[MES Touchevent] touchend: target=${e.target.tagName}, isUI=${!!isUIElement}`); // 로그 추가
                 if (isUIElement) return; // UI 요소 터치는 무시

                 if (touchMoved) { // 드래그(스크롤) 동작이었으면 선택 로직 실행 안 함
                     touchMoved = false;
                     if (selectedEl) selectedEl.classList.add('selected-element'); // 드래그 끝났으니 하이라이트 복원
                     console.log("[MES Touchevent] touchend: Drag detected, selection skipped.");
                     return;
                 }

                 // --- 실제 탭(선택) 동작 처리 ---
                 console.log("[MES Touchevent] touchend: Tap detected, proceeding with selection.");
                 try {
                     e.preventDefault(); // 기본 동작(링크 이동 등) 방지
                     e.stopImmediatePropagation(); // 다른 리스너 실행 중지
                     console.log("[MES Touchevent] touchend: preventDefault & stopImmediatePropagation called.");
                 } catch (err) {
                     console.error("[MES Touchevent] touchend: Error during preventDefault/stopPropagation:", err);
                 }

                 const touch = e.changedTouches[0];
                 if (!touch) { console.warn("[MES Touchevent] touchend: No changedTouches found."); return; }

                 const targetEl = document.elementFromPoint(touch.clientX, touch.clientY);
                 console.log(`[MES Touchevent] touchend: Element at point (${touch.clientX}, ${touch.clientY}):`, targetEl ? targetEl.tagName : 'null');

                 // 유효한 요소이고 UI 요소가 아닌 경우
                 if (targetEl && !targetEl.closest(uiExcludeSelector) && targetEl !== document.body && targetEl !== document.documentElement) {
                     console.log("[MES Touchevent] touchend: Valid target found, updating selection.");
                     removeSelectionHighlight(); // 이전 하이라이트 제거
                     resetPreview(); // 이전 미리보기 상태 초기화

                     selectedEl = targetEl;
                     initialTouchedElement = targetEl; // 슬라이더 기준점 설정
                     selectedEl.classList.add('selected-element'); // 새 요소 하이라이트
                     if (blockerSlider) blockerSlider.value = 0; // 슬라이더 초기화
                     updateInfo(); // 정보 업데이트
                 } else {
                     console.log("[MES Touchevent] touchend: Invalid target or UI element, clearing selection.");
                     removeSelectionHighlight();
                     resetPreview();
                     updateInfo(); // 정보 '없음'으로 업데이트
                 }

             }, { capture: true, passive: false }); // capture: true 유지

             // --- 버튼 및 슬라이더 리스너 ---
             if (blockerSlider) blockerSlider.addEventListener('input', (e) => { if (!initialTouchedElement) return; resetPreview(); const level = parseInt(e.target.value, 10); let current = initialTouchedElement; for (let i = 0; i < level && current.parentElement; i++) { if (['body', 'html'].includes(current.parentElement.tagName.toLowerCase()) || current.parentElement.closest(uiExcludeSelector)) break; current = current.parentElement; } if (selectedEl !== current) { if (selectedEl) selectedEl.classList.remove('selected-element'); selectedEl = current; selectedEl.classList.add('selected-element'); updateInfo(); } });
             if (copyBtn) copyBtn.addEventListener('click', () => { /* ... */ if (!selectedEl) { alert('선택된 요소가 없습니다.'); return; } const selector = generateSelector(selectedEl); if (!selector) { alert('❌ 유효한 선택자를 생성할 수 없습니다.'); return; } let finalSelector = "##" + selector; if (includeSiteName) finalSelector = location.hostname + finalSelector; try { GM_setClipboard(finalSelector); alert('✅ 선택자가 복사되었습니다!\n' + finalSelector); } catch (err) { console.error("[MES] 클립보드 복사 실패:", err); alert("❌ 클립보드 복사에 실패했습니다."); prompt("선택자를 직접 복사하세요:", finalSelector); } });
             if (previewBtn) previewBtn.addEventListener('click', () => { /* ... */ if (!selectedEl) { alert('선택된 요소가 없습니다.'); return; } if (!isPreviewHidden) { if (window.getComputedStyle(selectedEl).display === 'none') { alert('이미 숨겨진 요소입니다.'); return; } selectedEl.dataset._original_display = selectedEl.style.display || ''; selectedEl.style.display = 'none'; previewBtn.textContent = '되돌리기'; isPreviewHidden = true; previewedElement = selectedEl; selectedEl.classList.remove('selected-element'); } else if (previewedElement === selectedEl) { try { selectedEl.style.display = selectedEl.dataset._original_display || ''; delete selectedEl.dataset._original_display; } catch(e) { console.error("[MES] Error restoring display:", e); } previewBtn.textContent = '미리보기'; isPreviewHidden = false; previewedElement = null; selectedEl.classList.add('selected-element'); } else { alert('다른 요소가 선택되었습니다. 먼저 해당 요소의 미리보기를 되돌리거나 선택을 취소하세요.'); } });
             if (addBlockBtn) addBlockBtn.addEventListener('click', async () => { /* ... */ if (!selectedEl) { alert('선택된 요소가 없습니다.'); return; } const selector = generateSelector(selectedEl); if (!selector) { alert('❌ 유효한 선택자를 생성할 수 없습니다.'); return; } const result = await addBlockRule(selector); if (result.success) { alert(`✅ 차단 규칙이 저장되었습니다:\n${result.rule}\n(주의: 규칙 적용을 위해선 페이지 새로고침 또는 별도 차단 스크립트/확장기능이 필요합니다.)`); } else { alert(`ℹ️ ${result.message}`); } });

             // 설정 버튼 - 로그 추가
             if (settingsBtn) settingsBtn.addEventListener('click', () => {
                 console.log("[MES Button Click] Settings button clicked.");
                 if(settingsPanel) {
                     const currentDisplay = settingsPanel.style.display;
                     settingsPanel.style.display = currentDisplay === 'block' ? 'none' : 'block';
                     console.log(`[MES Button Click] Settings panel display toggled to: ${settingsPanel.style.display}`);
                 } else { console.error("[MES Button Click] settingsPanel not found!"); }
             });
             // 취소 버튼 - 로그 추가
             if (cancelBtn) cancelBtn.addEventListener('click', () => {
                 console.log("[MES Button Click] Cancel button clicked.");
                 setBlockMode(false);
             });
             // 메인 토글 버튼 - 로그 추가
             if (toggleBtn) toggleBtn.addEventListener('click', () => {
                 console.log(`[MES Button Click] Toggle button clicked. Current selecting state: ${selecting}`);
                 setBlockMode(!selecting); // 현재 상태의 반대로 설정
             });

             // 설정 패널 내부 리스너 - 로그 추가
             if (settingsCloseBtn) settingsCloseBtn.addEventListener('click', () => {
                 console.log("[MES Button Click] Settings Close button clicked.");
                 if(settingsPanel) settingsPanel.style.display = 'none';
                 else { console.error("[MES Button Click] settingsPanel not found for close!"); }
             });
             if (settingsToggleSiteBtn) settingsToggleSiteBtn.addEventListener('click', async () => { includeSiteName = !includeSiteName; settingsToggleSiteBtn.textContent = includeSiteName ? "사이트명: ON" : "사이트명: OFF"; await GM_setValue('includeSiteName', includeSiteName); console.log(`[MES Settings] Site name include toggled: ${includeSiteName}`); });
             if (settingsButtonSizeSlider && buttonSizeValueSpan) settingsButtonSizeSlider.addEventListener('input', async (e) => { buttonSizeScale = parseFloat(e.target.value); buttonSizeValueSpan.textContent = `${buttonSizeScale.toFixed(1)}x`; document.documentElement.style.setProperty('--btn-padding', `${10 * buttonSizeScale}px`); document.documentElement.style.setProperty('--btn-font-size', `${14 * buttonSizeScale}px`); document.documentElement.style.setProperty('--btn-min-width', `${80 * buttonSizeScale}px`); await GM_setValue('buttonSizeScale', buttonSizeScale); });
             if (settingsPanelOpacitySlider && opacityValueSpan) settingsPanelOpacitySlider.addEventListener('input', async (e) => { panelOpacity = parseFloat(e.target.value); opacityValueSpan.textContent = panelOpacity.toFixed(2); document.documentElement.style.setProperty('--panel-opacity', panelOpacity); await GM_setValue('panelOpacity', panelOpacity); });

             console.log("[MES v1.3.3] Event listeners set up successfully.");
         } catch (e) {
             console.error("[MES v1.3.3] Error setting up event listeners!", e);
         }
    }

    // --- 초기화 실행 ---
    if (document.readyState === 'loading') {
        console.log("[MES v1.3.3] DOM not ready, waiting for DOMContentLoaded.");
        document.addEventListener('DOMContentLoaded', createUIElements);
    } else {
        console.log("[MES v1.3.3] DOM already ready, creating UI elements.");
        createUIElements();
    }

    console.log("[MES v1.3.3] Script finished initialization phase.");

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址