Mobile Element Selector

모바일 요소 선택기

当前为 2025-04-16 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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.");

})();