Mobile Element Selector Enhanced (Fixed v4)

모바일 요소 선택기

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Mobile Element Selector Enhanced (Fixed v4)
// @author       ZNJXL (Main code) & Gemini (UI)
// @version      1.3.4
// @namespace    http://tampermonkey.net/
// @description  모바일 요소 선택기 
// @match        *://*/*
// @license      MIT
// @grant        GM_setClipboard
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(async function() {
    'use strict';
    const SCRIPT_ID = "[MES v1.3.4]";

    // --- 기본 설정 값 정의 ---
    const DEFAULT_SETTINGS = {
        includeSiteName: true,
        buttonSizeScale: 1.0,
        panelOpacity: 0.95
    };

    // --- 설정 값 로드 및 검증 ---
    let includeSiteName, buttonSizeScale, panelOpacity;
    try {
        const loadedIncludeSiteName = await GM_getValue('includeSiteName', DEFAULT_SETTINGS.includeSiteName);
        const loadedButtonSizeScale = await GM_getValue('buttonSizeScale', DEFAULT_SETTINGS.buttonSizeScale);
        const loadedPanelOpacity = await GM_getValue('panelOpacity', DEFAULT_SETTINGS.panelOpacity);

        includeSiteName = typeof loadedIncludeSiteName === 'boolean' ? loadedIncludeSiteName : DEFAULT_SETTINGS.includeSiteName;

        buttonSizeScale = parseFloat(loadedButtonSizeScale);
        if (isNaN(buttonSizeScale) || buttonSizeScale < 0.5 || buttonSizeScale > 2.0) {
            buttonSizeScale = DEFAULT_SETTINGS.buttonSizeScale;
        }

        panelOpacity = parseFloat(loadedPanelOpacity);
        if (isNaN(panelOpacity) || panelOpacity < 0.1 || panelOpacity > 1.0) {
            panelOpacity = DEFAULT_SETTINGS.panelOpacity;
        }

    } catch (e) {
        includeSiteName = DEFAULT_SETTINGS.includeSiteName;
        buttonSizeScale = DEFAULT_SETTINGS.buttonSizeScale;
        panelOpacity = DEFAULT_SETTINGS.panelOpacity;
    }

    // --- 전역 변수 ---
    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');
    try {
        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; visibility: visible !important;
        }
        #mobile-block-panel, #mobile-settings-panel { opacity: var(--panel-opacity) !important; backface-visibility: hidden; -webkit-backface-visibility: hidden; }
        .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);
    } catch (e) {}

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


    // --- 함수 정의 ---
    function makeDraggable(el) { if (!el) 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'; } 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 }); }
    function setBlockMode(enabled) { if (!toggleBtn || !panel || !settingsPanel) return; selecting = enabled; 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(); }
    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) {} 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) { 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) {} } 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) {} } 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) { 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() {
        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);

            // 설정 패널 생성
            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.1" max="1.0" step="0.05" value="${panelOpacity}"></div> <button id="settings-close" class="mb-btn ui-ignore">닫기</button>`;
            document.body.appendChild(settingsPanel);

            // 토글 버튼 생성
            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);

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

        } catch (e) {}
    }

    function initializeUIReferences() {
        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');

              // **** 중요: 참조 설정 후, 슬라이더 등의 초기값을 다시 한번 검증된 값으로 설정 ****
              // 이미 innerHTML에서 value를 설정했지만, 만일을 대비해 JS로도 설정
              if (settingsButtonSizeSlider) settingsButtonSizeSlider.value = buttonSizeScale;
              if (settingsPanelOpacitySlider) settingsPanelOpacitySlider.value = panelOpacity;

             setupEventListeners(); // 이벤트 리스너 설정 호출
             makeDraggable(panel); makeDraggable(settingsPanel); makeDraggable(toggleBtn); // 드래그 기능 초기화
         } catch (e) {}
    }

    // --- 이벤트 리스너 설정 함수 ---
    function setupEventListeners() {
         try {
             // 요소 선택 리스너 (v1.3.3 로그 유지 또는 추가)
             const uiExcludeSelector = '.ui-ignore';
             document.addEventListener('touchstart', e => { if (!selecting) return; const isUIElement = e.target.closest(uiExcludeSelector); 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); 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) { touchMoved = true; if (selectedEl) selectedEl.classList.remove('selected-element'); } }, { passive: true });
             document.addEventListener('touchend', e => { if (!selecting) return; const isUIElement = e.target.closest(uiExcludeSelector); if (isUIElement) return; if (touchMoved) { touchMoved = false; if (selectedEl) selectedEl.classList.add('selected-element'); return; } try { e.preventDefault(); e.stopImmediatePropagation(); } catch (err) {} const touch = e.changedTouches[0]; if (!touch) return; const targetEl = document.elementFromPoint(touch.clientX, touch.clientY); if (targetEl && !targetEl.closest(uiExcludeSelector) && targetEl !== document.body && targetEl !== document.documentElement) { removeSelectionHighlight(); resetPreview(); selectedEl = targetEl; initialTouchedElement = targetEl; selectedEl.classList.add('selected-element'); if (blockerSlider) blockerSlider.value = 0; updateInfo(); } else { removeSelectionHighlight(); resetPreview(); updateInfo(); } }, { capture: true, passive: false });

             // 버튼 및 슬라이더 리스너 (v1.3.3 동일 + 로그 유지)
             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) { 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) {} 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', () => { if(settingsPanel) { const currentDisplay = settingsPanel.style.display; settingsPanel.style.display = currentDisplay === 'block' ? 'none' : 'block'; } else {} });
             if (cancelBtn) cancelBtn.addEventListener('click', () => { setBlockMode(false); });
             if (toggleBtn) toggleBtn.addEventListener('click', () => { setBlockMode(!selecting); });
             if (settingsCloseBtn) settingsCloseBtn.addEventListener('click', () => { if(settingsPanel) settingsPanel.style.display = 'none'; else {} });
             if (settingsToggleSiteBtn) settingsToggleSiteBtn.addEventListener('click', async () => { includeSiteName = !includeSiteName; settingsToggleSiteBtn.textContent = includeSiteName ? "사이트명: ON" : "사이트명: OFF"; await GM_setValue('includeSiteName', includeSiteName); });
             // **** 중요: 슬라이더 input 이벤트에서 GM_setValue 호출 시, 변환된 숫자 값을 저장 ****
             if (settingsButtonSizeSlider && buttonSizeValueSpan) settingsButtonSizeSlider.addEventListener('input', async (e) => { const value = parseFloat(e.target.value); if (!isNaN(value)) { buttonSizeScale = 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) => { const value = parseFloat(e.target.value); if (!isNaN(value)) { panelOpacity = value; opacityValueSpan.textContent = panelOpacity.toFixed(2); document.documentElement.style.setProperty('--panel-opacity', panelOpacity); await GM_setValue('panelOpacity', panelOpacity); /* 숫자 값 저장 */ } });

         } catch (e) {}
    }

    // --- 초기화 실행 ---
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', createUIElements);
    } else {
        createUIElements();
    }

})();