MES(Mobile Element Selector)

Material M3의 진보한 디자인, 아름다운 애니메이션, 완벽한 기능을 가진 모바일 요소 선택기

当前为 2025-05-02 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         MES(Mobile Element Selector)
// @author       삼플 with Gemini
// @version      1.2.3
// @description  Material M3의 진보한 디자인, 아름다운 애니메이션, 완벽한 기능을 가진 모바일 요소 선택기
// @match        *://*/*
// @license      MIT
// @grant        GM_setClipboard
// @grant        GM_setValue
// @grant        GM_getValue
// @namespace https://adguard.com
// ==/UserScript==

(async function() {
    'use strict';
    const SCRIPT_ID = "[MES v1.2.3 M3]";
    const ADGUARD_LOGO_URL = 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/AdGuard.svg/500px-AdGuard.svg.png';

    const STRINGS = {
        panelTitle: '요소 차단',
        settingsTitle: 'MES by 삼플',
        listTitle: '저장된 차단 규칙',
        selectedElementLabel: '선택된 요소 (CSS 선택자)',
        parentLevelLabel: '상위 요소 선택 레벨:',
        copy: '복사',
        preview: '미리보기',
        restorePreview: '되돌리기',
        saveRule: '규칙 저장',
        list: '목록',
        settings: '설정',
        cancel: '취소',
        close: '닫기',
        includeSiteNameLabel: '규칙에 사이트명 포함',
        useAdguardLogoLabel: '토글 버튼 AdGuard 로고',
        panelOpacityLabel: '패널 투명도',
        toggleSizeLabel: '토글 버튼 크기',
        toggleOpacityLabel: '토글 버튼 투명도',
        tempDisableLabel: '모든 규칙 임시 비활성화',
        backupLabel: '규칙 백업 (JSON)',
        restoreLabel: '규칙 복원 (JSON)',
        togglePositionLabel: '토글 버튼 위치',
        posTopLeft: '좌상',
        posTopRight: '우상',
        posBottomLeft: '좌하',
        posBottomRight: '우하',
        on: 'ON',
        off: 'OFF',
        noRules: '저장된 규칙이 없습니다.',
        noElementSelected: '⚠️ 선택된 요소가 없습니다.',
        cannotGenerateSelector: '❌ 유효한 선택자를 생성할 수 없습니다.',
        selectorCopied: '✅ 선택자가 복사되었습니다!',
        clipboardError: '❌ 클립보드 복사 실패',
        promptCopy: '선택자를 직접 복사하세요:',
        alreadyHidden: 'ℹ️ 이미 숨겨진 요소입니다.',
        previewDifferentElement: '⚠️ 다른 요소가 미리보기 중입니다.',
        ruleSavedReloading: '✅ 규칙 저장됨! 적용 중...',
        ruleSavedApplyFailed: '⚠️ 규칙은 저장했으나 즉시 적용 실패.',
        ruleAddError: '❌ 규칙 추가 중 오류:',
        ruleExists: 'ℹ️ 이미 저장된 규칙입니다.',
        listShowError: '❌ 목록 표시 중 오류 발생',
        ruleCopied: '✅ 규칙 복사됨',
        ruleDeleted: '🗑️ 규칙 삭제됨',
        ruleDeleteError: '❌ 규칙 삭제 실패',
        settingsSaved: '✅ 설정 저장됨',
        settingsSaveError: '❌ 설정 저장 실패',
        backupStarting: '💾 규칙 백업 파일 다운로드를 시작합니다.',
        backupError: '❌ 규칙 백업 실패',
        restorePrompt: '📁 복원할 JSON 파일을 선택하세요.',
        restoreSuccess: '✅ 규칙 복원 완료! 적용 중...',
        restoreErrorInvalidFile: '❌ 잘못된 파일 형식 또는 내용입니다.',
        restoreErrorGeneral: '❌ 규칙 복원 실패',
        blockingApplied: (count) => `✅ ${count}개의 규칙 적용됨`,
        blockingApplyError: '❌ 규칙 적용 중 오류 발생',
        tempBlockingOn: '🚫 모든 규칙 임시 비활성화됨',
        tempBlockingOff: '✅ 규칙 다시 활성화됨'
    };

    const DEFAULT_SETTINGS = {
        includeSiteName: true,
        panelOpacity: 0.65,
        toggleSizeScale: 1.0,
        toggleOpacity: 1.0,
        showAdguardLogo: false,
        tempBlockingDisabled: false,
        toggleBtnCorner: 'bottom-right' // New setting for corner position
    };

    let settings = {};
    const SETTINGS_KEY = 'mobileElementSelectorSettings_v1_2'; // Updated key to potentially reset old position data
    const BLOCKED_SELECTORS_KEY = 'mobileBlockedSelectors_v2';

    async function loadSettings() {
        let storedSettings = {};
        try {
            const storedValue = await GM_getValue(SETTINGS_KEY, JSON.stringify(DEFAULT_SETTINGS));
            storedSettings = JSON.parse(storedValue || '{}');
        } catch (e) {
            console.error(SCRIPT_ID, `Error loading settings from GM_getValue('${SETTINGS_KEY}'), using defaults.`, e);
            storedSettings = { ...DEFAULT_SETTINGS };
        }

        settings = { ...DEFAULT_SETTINGS, ...storedSettings };

        settings.panelOpacity = parseFloat(settings.panelOpacity);
        if (isNaN(settings.panelOpacity) || settings.panelOpacity < 0.1 || settings.panelOpacity > 1.0) {
            settings.panelOpacity = DEFAULT_SETTINGS.panelOpacity;
        }
        settings.toggleSizeScale = parseFloat(settings.toggleSizeScale);
        if (isNaN(settings.toggleSizeScale) || settings.toggleSizeScale < 0.5 || settings.toggleSizeScale > 2.0) {
            settings.toggleSizeScale = DEFAULT_SETTINGS.toggleSizeScale;
        }
        settings.toggleOpacity = parseFloat(settings.toggleOpacity);
        if (isNaN(settings.toggleOpacity) || settings.toggleOpacity < 0.1 || settings.toggleOpacity > 1.0) {
            settings.toggleOpacity = DEFAULT_SETTINGS.toggleOpacity;
        }
        settings.includeSiteName = typeof settings.includeSiteName === 'boolean' ? settings.includeSiteName : DEFAULT_SETTINGS.includeSiteName;
        settings.showAdguardLogo = typeof settings.showAdguardLogo === 'boolean' ? settings.showAdguardLogo : DEFAULT_SETTINGS.showAdguardLogo;
        settings.tempBlockingDisabled = typeof settings.tempBlockingDisabled === 'boolean' ? settings.tempBlockingDisabled : DEFAULT_SETTINGS.tempBlockingDisabled;

        const validCorners = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];
        if (!validCorners.includes(settings.toggleBtnCorner)) {
            settings.toggleBtnCorner = DEFAULT_SETTINGS.toggleBtnCorner;
        }

        console.log(SCRIPT_ID, "Settings loaded:", settings);
    }

    async function saveSettings() {
        try {
            await GM_setValue(SETTINGS_KEY, JSON.stringify(settings));
            console.log(SCRIPT_ID, "Settings saved:", settings);
        } catch (e) {
            console.error(SCRIPT_ID, `Error saving settings to GM_setValue('${SETTINGS_KEY}')`, e);
            showToast(STRINGS.settingsSaveError, 'error');
        }
    }

    const style = document.createElement('style');

    function updateCSSVariables() {
        document.documentElement.style.setProperty('--panel-opacity', settings.panelOpacity);
        document.documentElement.style.setProperty('--toggle-size', `${56 * settings.toggleSizeScale}px`);
        document.documentElement.style.setProperty('--toggle-opacity', settings.toggleOpacity);

        document.querySelectorAll('#mobile-block-panel, #mobile-settings-panel, #mobile-blocklist-panel').forEach(p => {
            p.style.setProperty('background-color', `rgba(40, 43, 48, ${settings.panelOpacity})`, 'important');
        });
        if (toggleBtn) {
            toggleBtn.style.setProperty('width', `var(--toggle-size)`, 'important');
            toggleBtn.style.setProperty('height', `var(--toggle-size)`, 'important');
            toggleBtn.style.setProperty('opacity', `var(--toggle-opacity)`, 'important');
        }
    }

    function applyToggleBtnPosition() {
        if (!toggleBtn) return;

        // Reset all potential positioning properties
        toggleBtn.style.top = 'auto';
        toggleBtn.style.left = 'auto';
        toggleBtn.style.bottom = 'auto';
        toggleBtn.style.right = 'auto';
        toggleBtn.style.transform = '';

        const margin = '20px'; // Consistent margin from corner

        switch (settings.toggleBtnCorner) {
            case 'top-left':
                toggleBtn.style.top = margin;
                toggleBtn.style.left = margin;
                break;
            case 'top-right':
                toggleBtn.style.top = margin;
                toggleBtn.style.right = margin;
                break;
            case 'bottom-left':
                toggleBtn.style.bottom = margin;
                toggleBtn.style.left = margin;
                break;
            case 'bottom-right':
            default: // Default to bottom-right
                toggleBtn.style.bottom = margin;
                toggleBtn.style.right = margin;
                break;
        }
        console.log(SCRIPT_ID, "Applied toggle button corner:", settings.toggleBtnCorner);
    }

    style.textContent = `
:root {
    --md-sys-color-primary: #a0c9ff; --md-sys-color-on-primary: #00325a;
    --md-sys-color-primary-container: #004880; --md-sys-color-on-primary-container: #d1e4ff;
    --md-sys-color-secondary: #bdc7dc; --md-sys-color-on-secondary: #283141;
    --md-sys-color-secondary-container: #3e4758; --md-sys-color-on-secondary-container: #dae2f9;
    --md-sys-color-tertiary: #e0bddd; --md-sys-color-on-tertiary: #402843;
    --md-sys-color-tertiary-container: #583e5a; --md-sys-color-on-tertiary-container: #fdd9fa;
    --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005;
    --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6;
    --md-sys-color-background: #1a1c1e; --md-sys-color-on-background: #e3e2e6;
    --md-sys-color-surface: #1a1c1e; --md-sys-color-on-surface: #e3e2e6;
    --md-sys-color-surface-variant: #43474e; --md-sys-color-on-surface-variant: #c3c6cf;
    --md-sys-color-outline: #8d9199; --md-sys-color-shadow: #000000;
    --md-sys-color-inverse-surface: #e3e2e6; --md-sys-color-inverse-on-surface: #2f3033;
    --md-sys-color-surface-container-high: rgba(227, 226, 230, 0.16);
    --md-sys-color-success: #90ee90; --md-sys-color-success-container: rgba(144, 238, 144, 0.1);
    --md-sys-color-warning: #ffcc80;
    --panel-opacity: ${DEFAULT_SETTINGS.panelOpacity};
    --toggle-size: ${56 * DEFAULT_SETTINGS.toggleSizeScale}px;
    --toggle-opacity: ${DEFAULT_SETTINGS.toggleOpacity};
    --md-ref-typeface-plain: 'Roboto', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    --md-sys-typescale-body-large-font-family: var(--md-ref-typeface-plain);
    --md-sys-typescale-body-large-font-size: 16px;
    --md-sys-typescale-label-large-font-size: 14px;
    --md-sys-typescale-label-medium-font-size: 12px;
    --md-sys-typescale-label-small-font-size: 11px;
    --md-sys-typescale-title-medium-font-size: 18px;
}

.mobile-block-ui { z-index: 2147483646 !important; touch-action: manipulation !important; font-family: var(--md-sys-typescale-body-large-font-family); box-sizing: border-box; position: fixed !important; visibility: visible !important; color: var(--md-sys-color-on-surface); -webkit-tap-highlight-color: transparent !important; }

#mobile-block-panel, #mobile-settings-panel, #mobile-blocklist-panel {
    background-color: rgba(40, 43, 48, var(--panel-opacity)) !important; backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
    color: var(--md-sys-color-on-surface); border-radius: 20px !important;
    box-shadow: 0 12px 17px 2px rgba(0,0,0,0.14), 0 5px 22px 4px rgba(0,0,0,0.12), 0 7px 8px -4px rgba(0,0,0,0.20) !important;
    border: 1px solid rgba(255, 255, 255, 0.12); padding: 18px 20px; width: calc(100% - 40px); max-width: 380px;
    display: none;
    opacity: 0;
    backface-visibility: hidden; -webkit-backface-visibility: hidden; overflow: hidden;
    transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease-out;
    will-change: transform, opacity;
    cursor: grab;
}

#mobile-block-panel { bottom: 20px; left: 50%; transform: translateX(-50%) translateY(100px) scale(0.95); z-index: 2147483645 !important; }
#mobile-settings-panel, #mobile-blocklist-panel { top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.9); z-index: 2147483647 !important; max-width: 340px; }

#mobile-block-panel.visible {
    opacity: 1;
    transform: translateX(-50%) translateY(0) scale(1);
}
#mobile-settings-panel.visible, #mobile-blocklist-panel.visible {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
}

.mb-panel-title { font-size: var(--md-sys-typescale-title-medium-font-size); font-weight: 500; color: var(--md-sys-color-on-surface); text-align: center; margin: 0 0 24px 0; }

.mb-slider { width: 100%; margin: 15px 0; -webkit-appearance: none; appearance: none; background: var(--md-sys-color-surface-variant); height: 5px; border-radius: 3px; outline: none; cursor: pointer; transition: background 0.3s ease; }
.mb-slider:hover { background: var(--md-sys-color-outline); }
.mb-slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 22px; height: 22px; background: var(--md-sys-color-primary); border-radius: 50%; cursor: pointer; border: none; box-shadow: 0 1px 3px rgba(0,0,0,0.4); transition: background 0.3s ease, box-shadow 0.3s ease; }
.mb-slider::-moz-range-thumb { width: 22px; height: 22px; background: var(--md-sys-color-primary); border-radius: 50%; cursor: pointer; border: none; box-shadow: 0 1px 3px rgba(0,0,0,0.4); transition: background 0.3s ease, box-shadow 0.3s ease; }
.mb-slider:active::-webkit-slider-thumb { box-shadow: 0 0 0 10px rgba(var(--md-sys-color-primary-rgb, 160, 201, 255), 0.25); }
.mb-slider:active::-moz-range-thumb { box-shadow: 0 0 0 10px rgba(var(--md-sys-color-primary-rgb, 160, 201, 255), 0.25); }

.selected-element {
    outline: 3px solid var(--md-sys-color-error) !important;
    outline-offset: 2px;
    box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.45) !important;
    background-color: rgba(255, 82, 82, 0.15) !important;
    z-index: 2147483644 !important;
    transition: background-color 0.1s ease, outline 0.1s ease, box-shadow 0.1s ease;
    pointer-events: none;
}

#mobile-block-toggleBtn {
    /* Position is set by JS (applyToggleBtnPosition) */
    z-index: 2147483646 !important; background-color: var(--md-sys-color-primary-container) !important; color: var(--md-sys-color-on-primary-container) !important;
    opacity: var(--toggle-opacity) !important; width: var(--toggle-size) !important; height: var(--toggle-size) !important; border-radius: 18px !important; border: none !important; cursor: pointer !important;
    box-shadow: 0 6px 10px 0 rgba(0,0,0,0.14), 0 1px 18px 0 rgba(0,0,0,0.12), 0 3px 5px -1px rgba(0,0,0,0.20) !important;
    transition: background-color 0.3s ease, transform 0.2s ease, box-shadow 0.2s ease, opacity 0.3s ease, border 0.2s ease, top 0.3s ease, left 0.3s ease, bottom 0.3s ease, right 0.3s ease;
    display: flex !important; align-items: center !important; justify-content: center !important; overflow: hidden !important; backface-visibility: hidden; -webkit-backface-visibility: hidden; position: fixed !important; -webkit-tap-highlight-color: transparent !important;
}
#mobile-block-toggleBtn:active { transform: scale(0.95); box-shadow: 0 2px 4px -1px rgba(0,0,0,0.2), 0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12) !important; }
#mobile-block-toggleBtn.selecting {
    background-color: var(--md-sys-color-primary) !important;
    color: var(--md-sys-color-on-primary) !important;
    box-shadow: 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.20) !important;
}
/* Remove editing-pos class, positioning is handled by corner setting */
#mobile-block-toggleBtn .toggle-icon { width: 55%; height: 55%; display: block; margin: auto; background-color: currentColor; mask-size: contain; mask-repeat: no-repeat; mask-position: center; -webkit-mask-size: contain; -webkit-mask-repeat: no-repeat; -webkit-mask-position: center; }
#mobile-block-toggleBtn .toggle-icon-plus { mask-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>'); -webkit-mask-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>'); }
#mobile-block-toggleBtn.selecting .toggle-icon-plus { mask-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor"><path d="M0 0h24v24H0z" fill="none"/><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>'); -webkit-mask-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor"><path d="M0 0h24v24H0z" fill="none"/><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>'); }
#mobile-block-toggleBtn .toggle-icon-adguard { background-image: url('${ADGUARD_LOGO_URL}'); background-size: contain; background-repeat: no-repeat; background-position: center; background-color: transparent !important; mask-image: none; -webkit-mask-image: none; width: 60%; height: 60%; }

.mb-btn { padding: 10px 24px; border: none; border-radius: 20px !important; font-size: var(--md-sys-typescale-label-large-font-size); font-weight: 500; cursor: pointer; transition: background-color 0.2s ease, transform 0.1s ease, box-shadow 0.2s ease; text-align: center; box-shadow: 0 1px 2px 0 rgba(0,0,0,0.3), 0 1px 3px 1px rgba(0,0,0,0.15); min-width: 64px; min-height: 40px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; opacity: 1 !important; -webkit-tap-highlight-color: transparent !important; line-height: 1.5; display: inline-flex; align-items: center; justify-content: center; }
.mb-btn:hover { box-shadow: 0 1px 2px 0 rgba(0,0,0,0.3), 0 2px 6px 2px rgba(0,0,0,0.15); }
.mb-btn:active { transform: scale(0.97); box-shadow: none; }
.mb-btn.primary { background-color: var(--md-sys-color-primary); color: var(--md-sys-color-on-primary); }
.mb-btn.primary:hover { background-color: #b0d3ff; } .mb-btn.primary:active { background-color: #c0daff; }
.mb-btn.secondary { background-color: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); }
.mb-btn.secondary:hover { background-color: #545d6e; } .mb-btn.secondary:active { background-color: #6a7385; }
.mb-btn.tertiary { background-color: var(--md-sys-color-tertiary-container); color: var(--md-sys-color-on-tertiary-container); }
.mb-btn.tertiary:hover { background-color: #6f5471; } .mb-btn.tertiary:active { background-color: #866a89; }
.mb-btn.error { background-color: var(--md-sys-color-error-container); color: var(--md-sys-color-on-error-container); }
.mb-btn.error:hover { background-color: #b12025; } .mb-btn.error:active { background-color: #c83c40; }
.mb-btn.surface { background-color: var(--md-sys-color-surface-variant); color: var(--md-sys-color-on-surface-variant); }
.mb-btn.surface:hover { background-color: #53575e; } .mb-btn.surface:active { background-color: #63676e; }
.mb-btn.outline { background-color: transparent; color: var(--md-sys-color-primary); border: 1px solid var(--md-sys-color-outline); box-shadow: none; }
.mb-btn.outline:hover { background-color: rgba(var(--md-sys-color-primary-rgb, 160, 201, 255), 0.08); }
.mb-btn.outline:active { background-color: rgba(var(--md-sys-color-primary-rgb, 160, 201, 255), 0.12); }

.button-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(90px, 1fr)); gap: 12px; margin-top: 24px; }
#blocker-info-wrapper { margin-bottom: 15px; padding: 10px 14px; background-color: var(--md-sys-color-surface-variant); border-radius: 12px; border: 1px solid var(--md-sys-color-outline); }
#blocker-info-label { display: block; font-size: var(--md-sys-typescale-label-medium-font-size); color: var(--md-sys-color-on-surface-variant); margin-bottom: 6px; font-weight: 500; }
#blocker-info { display: block; color: var(--md-sys-color-on-surface); font-size: var(--md-sys-typescale-label-large-font-size); line-height: 1.45; word-break: break-all; min-height: 1.45em; font-family: 'Consolas', 'Monaco', monospace; max-height: 6em; overflow-y: auto; }
#blocker-info:empty::after { content: '없음'; color: var(--md-sys-color-on-surface-variant); font-style: italic; }
label[for="blocker-slider"] { display: block; font-size: var(--md-sys-typescale-label-medium-font-size); color: var(--md-sys-color-on-surface-variant); margin-bottom: 5px; margin-top: 10px; }

.settings-item { margin-bottom: 20px; display: flex; flex-direction: column; gap: 10px; }
.settings-item label { display: flex; justify-content: space-between; align-items: center; font-size: var(--md-sys-typescale-label-large-font-size); color: var(--md-sys-color-on-surface-variant); }
.settings-item label .settings-label-text { flex-grow: 1; margin-right: 10px; }
.settings-value { color: var(--md-sys-color-on-surface); font-weight: 500; font-size: var(--md-sys-typescale-label-medium-font-size); padding-left: 10px; }
#settings-toggle-site, #settings-adguard-logo, #settings-temp-disable { min-width: 70px; padding: 8px 14px; font-size: var(--md-sys-typescale-label-medium-font-size); flex-shrink: 0; }
#settings-toggle-site.active, #settings-adguard-logo.active, #settings-temp-disable.active { background-color: var(--md-sys-color-primary); color: var(--md-sys-color-on-primary); }
#settings-toggle-site:not(.active), #settings-adguard-logo:not(.active), #settings-temp-disable:not(.active) { background-color: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); }
#settings-close, #settings-backup, #settings-restore { width: 100%; margin-top: 10px; }
#settings-restore-input { display: none; }

/* Corner Selector Styles */
.corner-selector-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin-top: 5px; }
.corner-btn { padding: 8px 12px; min-width: 60px; font-size: var(--md-sys-typescale-label-medium-font-size); }
.corner-btn.active { background-color: var(--md-sys-color-primary); color: var(--md-sys-color-on-primary); }
.corner-btn:not(.active) { background-color: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); }

#blocklist-container { max-height: calc(70vh - 150px); overflow-y: auto; margin: 20px 0; padding-right: 8px; display: flex; flex-direction: column; gap: 10px; }
.blocklist-item { display: flex; justify-content: space-between; align-items: center; padding: 12px 14px; background-color: rgba(var(--md-sys-color-surface-variant-rgb, 67, 71, 78), 0.5); border-radius: 12px; border: 1px solid transparent; transition: background-color 0.2s, border-color 0.2s, opacity 0.3s ease, transform 0.3s ease; }
.blocklist-item:hover { background-color: rgba(var(--md-sys-color-surface-variant-rgb, 67, 71, 78), 0.7); border-color: var(--md-sys-color-outline); }
.blocklist-item span { flex: 1; word-break: break-all; margin-right: 12px; font-size: var(--md-sys-typescale-label-medium-font-size); color: var(--md-sys-color-on-surface-variant); font-family: 'Consolas', 'Monaco', monospace; }
.blocklist-controls { display: flex; gap: 6px; flex-shrink: 0; }
.blocklist-btn { padding: 6px 10px; min-width: auto; min-height: 32px; font-size: var(--md-sys-typescale-label-small-font-size); border-radius: 16px !important; }
.blocklist-btn-delete { background-color: var(--md-sys-color-error-container); color: var(--md-sys-color-on-error-container); }
.blocklist-btn-copy { background-color: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); }
#blocklist-empty { text-align:center; color: var(--md-sys-color-on-surface-variant); padding: 20px 0; }

#mes-toast-container { position: fixed; bottom: 90px; left: 50%; transform: translateX(-50%); z-index: 2147483647 !important; display: flex; flex-direction: column-reverse; align-items: center; gap: 10px; pointer-events: none; width: max-content; max-width: 90%; }
.mes-toast { background-color: var(--md-sys-color-inverse-surface); color: var(--md-sys-color-inverse-on-surface); padding: 14px 20px; border-radius: 8px; box-shadow: 0 3px 5px -1px rgba(0,0,0,0.2), 0 6px 10px 0 rgba(0,0,0,0.14), 0 1px 18px 0 rgba(0,0,0,0.12); font-size: var(--md-sys-typescale-label-large-font-size); opacity: 0; transform: translateY(20px); transition: opacity 0.3s ease, transform 0.3s ease, background-color 0.3s ease; pointer-events: all; max-width: 100%; text-align: center; }
.mes-toast.show { opacity: 1; transform: translateY(0); }
.mes-toast.info { background-color: #333; color: white; }
.mes-toast.success { background-color: var(--md-sys-color-success-container); color: var(--md-sys-color-success); }
.mes-toast.error { background-color: var(--md-sys-color-error-container); color: var(--md-sys-color-on-error-container); }
.mes-toast.warning { background-color: #4d3a00; color: var(--md-sys-color-warning); }
    `;
    document.head.appendChild(style);

    let panel, settingsPanel, toggleBtn, listPanel, toastContainer;
    function createUIElements() {
        toastContainer = document.createElement('div');
        toastContainer.id = 'mes-toast-container';
        toastContainer.className = 'mobile-block-ui';
        document.body.appendChild(toastContainer);

        panel = document.createElement('div');
        panel.id = 'mobile-block-panel';
        panel.className = 'mobile-block-ui';
        panel.innerHTML = `
            <div id="blocker-info-wrapper">
                <span id="blocker-info-label">${STRINGS.selectedElementLabel}</span>
                <div id="blocker-info"></div>
            </div>
            <label for="blocker-slider" style="display: block; font-size: var(--md-sys-typescale-label-medium-font-size); color: var(--md-sys-color-on-surface-variant); margin-bottom: 5px;">${STRINGS.parentLevelLabel}</label>
            <input type="range" id="blocker-slider" class="mb-slider" min="0" max="10" value="0" aria-label="Parent Level Selector">
            <div class="button-grid">
                <button id="blocker-copy" class="mb-btn secondary">${STRINGS.copy}</button>
                <button id="blocker-preview" class="mb-btn secondary">${STRINGS.preview}</button>
                <button id="blocker-add-block" class="mb-btn primary">${STRINGS.saveRule}</button>
                <button id="blocker-list" class="mb-btn tertiary">${STRINGS.list}</button>
                <button id="blocker-settings" class="mb-btn tertiary">${STRINGS.settings}</button>
                <button id="blocker-cancel" class="mb-btn surface">${STRINGS.cancel}</button>
            </div>`;
        document.body.appendChild(panel);

        listPanel = document.createElement('div');
        listPanel.id = 'mobile-blocklist-panel';
        listPanel.className = 'mobile-block-ui';
        listPanel.innerHTML = `
            <h3 class="mb-panel-title">${STRINGS.listTitle}</h3>
            <div id="blocklist-container"></div>
            <button id="blocklist-close" class="mb-btn surface" style="width: 100%; margin-top: 15px;">${STRINGS.close}</button>`;
        document.body.appendChild(listPanel);

        settingsPanel = document.createElement('div');
        settingsPanel.id = 'mobile-settings-panel';
        settingsPanel.className = 'mobile-block-ui';
        settingsPanel.innerHTML = `
            <h3 class="mb-panel-title">${STRINGS.settingsTitle}</h3>
            <div class="settings-item">
                <label><span class="settings-label-text">${STRINGS.includeSiteNameLabel}</span>
                    <button id="settings-toggle-site" class="mb-btn ${settings.includeSiteName ? 'active' : ''}">${settings.includeSiteName ? STRINGS.on : STRINGS.off}</button>
                </label>
            </div>
            <div class="settings-item">
                <label><span class="settings-label-text">${STRINGS.useAdguardLogoLabel}</span>
                    <button id="settings-adguard-logo" class="mb-btn ${settings.showAdguardLogo ? 'active' : ''}">${settings.showAdguardLogo ? STRINGS.on : STRINGS.off}</button>
                </label>
            </div>
             <div class="settings-item">
                <label><span class="settings-label-text">${STRINGS.tempDisableLabel}</span>
                    <button id="settings-temp-disable" class="mb-btn ${settings.tempBlockingDisabled ? 'active error' : 'secondary'}">${settings.tempBlockingDisabled ? STRINGS.on : STRINGS.off}</button>
                </label>
            </div>
            <div class="settings-item">
                <label for="settings-panel-opacity">
                    <span class="settings-label-text">${STRINGS.panelOpacityLabel}</span>
                    <span id="opacity-value" class="settings-value">${settings.panelOpacity.toFixed(2)}</span>
                </label>
                <input id="settings-panel-opacity" type="range" class="mb-slider" min="0.1" max="1.0" step="0.05" value="${settings.panelOpacity}" aria-label="Panel Opacity">
            </div>
            <div class="settings-item">
                <label for="settings-toggle-size">
                    <span class="settings-label-text">${STRINGS.toggleSizeLabel}</span>
                    <span id="toggle-size-value" class="settings-value">${settings.toggleSizeScale.toFixed(1)}x</span>
                </label>
                <input id="settings-toggle-size" type="range" class="mb-slider" min="0.5" max="2.0" step="0.1" value="${settings.toggleSizeScale}" aria-label="Toggle Button Size">
            </div>
            <div class="settings-item">
                <label for="settings-toggle-opacity">
                    <span class="settings-label-text">${STRINGS.toggleOpacityLabel}</span>
                    <span id="toggle-opacity-value" class="settings-value">${settings.toggleOpacity.toFixed(2)}</span>
                </label>
                <input id="settings-toggle-opacity" type="range" class="mb-slider" min="0.1" max="1.0" step="0.05" value="${settings.toggleOpacity}" aria-label="Toggle Button Opacity">
            </div>
            <div class="settings-item">
                 <label><span class="settings-label-text">${STRINGS.togglePositionLabel}</span></label>
                 <div class="corner-selector-grid">
                     <button id="corner-tl" data-corner="top-left" class="mb-btn corner-btn">${STRINGS.posTopLeft}</button>
                     <button id="corner-tr" data-corner="top-right" class="mb-btn corner-btn">${STRINGS.posTopRight}</button>
                     <button id="corner-bl" data-corner="bottom-left" class="mb-btn corner-btn">${STRINGS.posBottomLeft}</button>
                     <button id="corner-br" data-corner="bottom-right" class="mb-btn corner-btn">${STRINGS.posBottomRight}</button>
                 </div>
            </div>
            <div class="button-grid" style="margin-top: 20px; grid-template-columns: 1fr 1fr;">
                 <button id="settings-backup" class="mb-btn outline">${STRINGS.backupLabel}</button>
                 <button id="settings-restore" class="mb-btn outline">${STRINGS.restoreLabel}</button>
                 <input type="file" id="settings-restore-input" accept=".json">
            </div>
            <button id="settings-close" class="mb-btn surface" style="width: 100%; margin-top: 20px;">${STRINGS.close}</button>`;
        document.body.appendChild(settingsPanel);

        toggleBtn = document.createElement('button');
        toggleBtn.id = 'mobile-block-toggleBtn';
        toggleBtn.className = 'mobile-block-ui';
        toggleBtn.setAttribute('aria-label', 'Toggle Element Selector');
        document.body.appendChild(toggleBtn);

        updateCSSVariables();
        updateToggleIcon();
        applyToggleBtnPosition();

        initRefsAndEvents();
        applyBlocking();
    }

    function showToast(message, type = 'info', duration = 3000) {
        if (!toastContainer) {
            console.warn(SCRIPT_ID, "Toast container not ready for message:", message);
            return;
        }
        const toast = document.createElement('div');
        toast.className = `mes-toast ${type}`;
        toast.textContent = message;
        toastContainer.appendChild(toast);

        void toast.offsetWidth;

        requestAnimationFrame(() => {
            toast.classList.add('show');
        });

        setTimeout(() => {
            toast.classList.remove('show');
            toast.addEventListener('transitionend', () => {
                try { toast.remove(); } catch (e) { }
            }, { once: true });
            setTimeout(() => {
                try { toast.remove(); } catch (e) { }
            }, 500);
        }, duration);
    }

    let selecting = false;
    let selectedEl = null;
    let initialTouchedElement = null;
    let touchStartX = 0, touchStartY = 0, touchMoved = false;
    const moveThreshold = 15;
    let blockedSelectorsCache = [];

    async function loadBlockedSelectors() {
        let stored = '[]';
        try {
            stored = await GM_getValue(BLOCKED_SELECTORS_KEY, '[]');
            const parsed = JSON.parse(stored);
            blockedSelectorsCache = Array.isArray(parsed) ? parsed : [];
            console.log(SCRIPT_ID, `Loaded ${blockedSelectorsCache.length} rules from storage.`);
            return blockedSelectorsCache;
        } catch (e) {
            console.error(SCRIPT_ID, `Error parsing blocked selectors from key '${BLOCKED_SELECTORS_KEY}', resetting. Stored value:`, stored, e);
            try {
                await GM_setValue(BLOCKED_SELECTORS_KEY, '[]');
            } catch (resetError) {
                 console.error(SCRIPT_ID, "Failed to reset storage after parse error", resetError);
            }
            blockedSelectorsCache = [];
            return [];
        }
    }

    async function saveBlockedSelectors(list) {
        const selectorsToSave = Array.isArray(list) ? list : [];
        try {
            await GM_setValue(BLOCKED_SELECTORS_KEY, JSON.stringify(selectorsToSave));
            blockedSelectorsCache = [...selectorsToSave];
            console.log(SCRIPT_ID, `Saved ${selectorsToSave.length} rules.`);
        } catch (e) {
            console.error(SCRIPT_ID, "Error saving blocked selectors to GM:", e);
            showToast(STRINGS.settingsSaveError, 'error');
        }
    }

    const originalDisplayMap = new Map();

    async function applyBlocking(showToastNotification = false) {
        if (settings.tempBlockingDisabled) {
            console.log(SCRIPT_ID, "Blocking temporarily disabled. Skipping application.");
            disableAllBlocking(false);
            return 0;
        }

        console.log(SCRIPT_ID, "Applying block rules...");
        if (blockedSelectorsCache.length === 0) {
            await loadBlockedSelectors();
        }

        let count = 0;
        let appliedCount = 0;
        const currentHostname = location.hostname;

        blockedSelectorsCache.forEach(rule => {
            if (typeof rule !== 'string' || !rule.includes('##')) {
                 console.warn(SCRIPT_ID, "Skipping invalid block rule format:", rule);
                 return;
            }

            const parts = rule.split('##');
            const domain = parts[0];
            const cssSelector = parts[1];

            if (!cssSelector) {
                 console.warn(SCRIPT_ID, "Skipping rule with empty selector:", rule);
                 return;
            }
            if (domain && domain !== '*' && currentHostname !== domain) {
                return;
            }

            try {
                const elements = document.querySelectorAll(cssSelector);
                elements.forEach(el => {
                     const isHiddenByScript = el.style.display === 'none' && el.hasAttribute('data-mes-hidden');
                     const isNaturallyHidden = window.getComputedStyle(el).display === 'none';

                     if (!isHiddenByScript && !isNaturallyHidden) {
                         if (!originalDisplayMap.has(el)) {
                             originalDisplayMap.set(el, el.style.display || 'unset');
                         }
                         el.style.setProperty('display', 'none', 'important');
                         el.setAttribute('data-mes-hidden', 'true');
                         count++;
                     } else if (isHiddenByScript) {
                        if (!originalDisplayMap.has(el)) {
                           originalDisplayMap.set(el, 'unset');
                        }
                     }
                });
                if(elements.length > 0) appliedCount++;

            } catch (e) {
                 // Ignore errors
            }
        });

        if (count > 0) console.log(SCRIPT_ID, `Applied ${appliedCount} rules, hid ${count} new elements.`);
        else console.log(SCRIPT_ID, `Applied ${appliedCount} rules, no new elements needed hiding.`);

        if (showToastNotification && appliedCount > 0 && !settings.tempBlockingDisabled) {
            showToast(STRINGS.blockingApplied(appliedCount), 'success', 2000);
        }
        return appliedCount;
    }

    function disableAllBlocking(showToastNotification = true) {
        console.log(SCRIPT_ID, "Disabling all blocking rules temporarily...");
        let restoredCount = 0;
        document.querySelectorAll('[data-mes-hidden="true"]').forEach(el => {
            const originalDisplay = originalDisplayMap.get(el);
            if (originalDisplay === 'unset') {
                el.style.removeProperty('display');
            } else if (originalDisplay !== undefined) {
                el.style.setProperty('display', originalDisplay, '');
            } else {
                 el.style.removeProperty('display');
            }
            el.removeAttribute('data-mes-hidden');
            restoredCount++;
        });
        console.log(SCRIPT_ID, `Restored display for ${restoredCount} elements.`);
        if (showToastNotification) {
            showToast(STRINGS.tempBlockingOn, 'warning', 2500);
        }
    }

    async function enableAllBlocking(showToastNotification = true) {
        console.log(SCRIPT_ID, "Re-enabling blocking rules...");
        const appliedCount = await applyBlocking(false);
        if (showToastNotification && appliedCount > 0) {
            showToast(STRINGS.tempBlockingOff, 'success', 2000);
        } else if (showToastNotification) {
            showToast(STRINGS.tempBlockingOff, 'info', 1500);
        }
    }

    function updateToggleIcon() {
        if (!toggleBtn) return;
        if (settings.showAdguardLogo) {
            toggleBtn.innerHTML = `<span class="toggle-icon toggle-icon-adguard" aria-hidden="true"></span>`;
        } else {
            toggleBtn.innerHTML = `<span class="toggle-icon toggle-icon-plus" aria-hidden="true"></span>`;
        }
        toggleBtn.classList.toggle('selecting', selecting);
        // No longer need editing-pos class
    }

    function generateSelector(el, maxDepth = 7, requireUnique = true) {
        if (!el || el.nodeType !== 1 || el.closest('.mobile-block-ui')) return '';

        if (el.id) {
            const id = el.id;
            const escapedId = CSS.escape(id);
            if (!/^\d+$/.test(id) && id.length > 2 && !id.startsWith('ember') && !id.startsWith('react') && !id.includes(':')) {
                try {
                    if (document.querySelectorAll(`#${escapedId}`).length === 1) {
                        return `#${escapedId}`;
                    }
                } catch (e) { }
            }
        }

        const parts = [];
        let current = el;
        let depth = 0;

        while (current && current.tagName && depth < maxDepth) {
            const tagName = current.tagName.toLowerCase();
            if (tagName === 'body' || tagName === 'html') break;
            if (current.closest('.mobile-block-ui')) {
                current = current.parentElement;
                continue;
            }

            let part = tagName;
            let addedSpecificity = false;

            const stableClasses = Array.from(current.classList)
                .filter(c => c && c.length > 2 &&
                              !/^[a-z]{1,2}$/i.test(c) &&
                              !/\d/.test(c) &&
                              !/active|select|focus|hover|disabled|open|closed|visible|hidden|js-|ui-/i.test(c) &&
                              !/^[A-Z0-9]{4,}$/.test(c) &&
                              !c.includes('--') && !c.includes('__') &&
                              !['selected-element', 'mobile-block-ui'].some(uiClass => c.includes(uiClass)))
                .slice(0, 2);

            if (stableClasses.length > 0) {
                part += '.' + stableClasses.map(c => CSS.escape(c)).join('.');
                addedSpecificity = true;
            }

            if (!addedSpecificity || (current.parentElement && !current.parentElement.closest('.mobile-block-ui'))) {
                const siblings = current.parentElement ? Array.from(current.parentElement.children) : [];
                const sameTagSiblings = siblings.filter(sib => sib.tagName === current.tagName && !sib.closest('.mobile-block-ui'));

                if (sameTagSiblings.length > 1) {
                    const index = sameTagSiblings.indexOf(current) + 1;
                    if (index > 0) {
                        part += `:nth-of-type(${index})`;
                        addedSpecificity = true;
                    }
                }
            }

            parts.unshift(part);

             if (requireUnique && parts.length > 0 && depth > 0) {
                 const tempSelector = parts.join(' > ');
                 try {
                     if (document.querySelectorAll(tempSelector).length === 1) {
                          console.log(SCRIPT_ID, `Unique selector found early: ${tempSelector}`);
                         return tempSelector;
                     }
                 } catch (e) { }
             }

            current = current.parentElement;
            depth++;
        }

        let finalSelector = parts.join(' > ');

        if (requireUnique && finalSelector) {
            try {
                const matches = document.querySelectorAll(finalSelector);
                if (matches.length !== 1) {
                    console.warn(SCRIPT_ID, `Generated selector "${finalSelector}" matches ${matches.length} elements. Trying parent recursively.`);
                    if (el.parentElement && !el.parentElement.closest('.mobile-block-ui') && maxDepth > 0) {
                       const parentSelector = generateSelector(el.parentElement, maxDepth -1, false);
                       if (parentSelector) {
                           const combinedSelector = parentSelector + " > " + finalSelector;
                           try {
                               if (document.querySelectorAll(combinedSelector).length === 1) {
                                   console.log(SCRIPT_ID, `Using combined unique selector: ${combinedSelector}`);
                                   return combinedSelector;
                               } else {
                                    console.warn(SCRIPT_ID, `Combined selector "${combinedSelector}" still not unique.`);
                               }
                           } catch(e) { }
                       }
                    }
                     console.warn(SCRIPT_ID, `Could not guarantee uniqueness for: ${finalSelector}`);
                    return finalSelector;
                }
            } catch (e) {
                 console.error(SCRIPT_ID, `Error validating selector "${finalSelector}":`, e);
                 return '';
            }
        }

        if (!finalSelector || finalSelector === 'body' || finalSelector === 'html') {
             return '';
        }

        return finalSelector;
    }

    function initRefsAndEvents() {
        const infoLabel = panel.querySelector('#blocker-info-label');
        const info = panel.querySelector('#blocker-info');
        const slider = panel.querySelector('#blocker-slider');
        const copyBtn = panel.querySelector('#blocker-copy');
        const previewBtn = panel.querySelector('#blocker-preview');
        const addBtn = panel.querySelector('#blocker-add-block');
        const listBtn = panel.querySelector('#blocker-list');
        const settingsBtn = panel.querySelector('#blocker-settings');
        const cancelBtn = panel.querySelector('#blocker-cancel');

        const listContainer = listPanel.querySelector('#blocklist-container');
        const listClose = listPanel.querySelector('#blocklist-close');

        const settingsClose = settingsPanel.querySelector('#settings-close');
        const toggleSiteBtn = settingsPanel.querySelector('#settings-toggle-site');
        const adguardLogoToggleBtn = settingsPanel.querySelector('#settings-adguard-logo');
        const tempDisableBtn = settingsPanel.querySelector('#settings-temp-disable');
        const panelOpacitySlider = settingsPanel.querySelector('#settings-panel-opacity');
        const panelOpacityValue = settingsPanel.querySelector('#opacity-value');
        const toggleSizeSlider = settingsPanel.querySelector('#settings-toggle-size');
        const toggleSizeValue = settingsPanel.querySelector('#toggle-size-value');
        const toggleOpacitySlider = settingsPanel.querySelector('#settings-toggle-opacity');
        const toggleOpacityValue = settingsPanel.querySelector('#toggle-opacity-value');
        const cornerButtons = settingsPanel.querySelectorAll('.corner-btn'); // Get all corner buttons
        const backupBtn = settingsPanel.querySelector('#settings-backup');
        const restoreBtn = settingsPanel.querySelector('#settings-restore');
        const restoreInput = settingsPanel.querySelector('#settings-restore-input');

        let isPreviewHidden = false;
        let previewedElement = null;

        function removeSelectionHighlight() {
            if (selectedEl) {
                selectedEl.classList.remove('selected-element');
            }
            selectedEl = null;
            if (slider) slider.value = 0;
            if (info) info.textContent = '';
        }

        function resetPreview() {
            if (isPreviewHidden && previewedElement) {
                try {
                    const originalDisplay = previewedElement.dataset._original_display;
                    if (originalDisplay === 'unset') {
                        previewedElement.style.removeProperty('display');
                    } else if (originalDisplay !== undefined) {
                        previewedElement.style.setProperty('display', originalDisplay, '');
                    }
                    delete previewedElement.dataset._original_display;
                    if(previewedElement === selectedEl) {
                        previewedElement.classList.add('selected-element');
                    }
                } catch (e) {
                     console.warn(SCRIPT_ID, "Error resetting preview style:", e)
                }
            }
            if (previewBtn) {
                previewBtn.textContent = STRINGS.preview;
                previewBtn.classList.remove('tertiary');
                previewBtn.classList.add('secondary');
            }
            isPreviewHidden = false;
            previewedElement = null;
        }

        function updateInfo() {
            if (!info) return;
            const selectorText = selectedEl ? generateSelector(selectedEl, 7, false) : '';
            info.textContent = selectorText;
            infoLabel.style.display = 'block';
        }

        let activePanel = null;
        function setPanelVisibility(panelElement, visible) {
            if (!panelElement) return;

            if (visible) {
                [panel, settingsPanel, listPanel].forEach(p => {
                    if (p && p !== panelElement && p.classList.contains('visible')) {
                        p.classList.remove('visible');
                        const transitionEndHandler = () => {
                            if (!p.classList.contains('visible')) p.style.display = 'none';
                            p.removeEventListener('transitionend', transitionEndHandler);
                        };
                        p.addEventListener('transitionend', transitionEndHandler);
                        setTimeout(() => {
                             if (!p.classList.contains('visible')) p.style.display = 'none';
                             p.removeEventListener('transitionend', transitionEndHandler);
                        }, 350);
                    }
                });

                activePanel = panelElement;
                panelElement.style.display = 'block';
                requestAnimationFrame(() => {
                    requestAnimationFrame(() => {
                        panelElement.classList.add('visible');
                    });
                });
            } else {
                 if (activePanel === panelElement) activePanel = null;
                 panelElement.classList.remove('visible');
                 const transitionEndHandler = () => {
                     if (!panelElement.classList.contains('visible')) panelElement.style.display = 'none';
                     panelElement.removeEventListener('transitionend', transitionEndHandler);
                 };
                 panelElement.addEventListener('transitionend', transitionEndHandler);
                 setTimeout(() => {
                      if (!panelElement.classList.contains('visible')) panelElement.style.display = 'none';
                      panelElement.removeEventListener('transitionend', transitionEndHandler);
                 }, 350);
                 // No longer need to check isEditingTogglePosition here
            }
        }

        async function addBlockRule(selector) {
            console.log('[addBlockRule] Attempting for selector:', selector);
            if (!selector) {
                return { success: false, message: STRINGS.cannotGenerateSelector };
            }

            let fullSelector = "##" + selector;
            if (settings.includeSiteName) {
                const hostname = location.hostname;
                if (!hostname) {
                    console.error(SCRIPT_ID, "Could not get location.hostname");
                    return { success: false, message: '호스트 이름을 가져올 수 없습니다.' };
                }
                fullSelector = hostname + fullSelector;
            }

            if (blockedSelectorsCache.includes(fullSelector)) {
                console.log(SCRIPT_ID, "Rule already exists:", fullSelector);
                return { success: false, message: STRINGS.ruleExists };
            }

            const updatedList = [...blockedSelectorsCache, fullSelector];
            await saveBlockedSelectors(updatedList);

            console.log(SCRIPT_ID, "Rule added:", fullSelector);
            return { success: true, rule: fullSelector };
        }

        async function showList() {
            console.log('[showList] Function called');
            try {
                const arr = await loadBlockedSelectors();
                console.log(`[showList] Rendering ${arr.length} rules.`);
                listContainer.innerHTML = '';

                if (arr.length === 0) {
                    listContainer.innerHTML = `<p id="blocklist-empty">${STRINGS.noRules}</p>`;
                } else {
                    arr.forEach((rule, index) => {
                        const item = document.createElement('div');
                        item.className = 'blocklist-item';

                        const span = document.createElement('span');
                        span.textContent = rule;
                        span.title = rule;

                        const controlsDiv = document.createElement('div');
                        controlsDiv.className = 'blocklist-controls';

                        const copyButton = document.createElement('button');
                        copyButton.className = 'mb-btn blocklist-btn blocklist-btn-copy';
                        copyButton.textContent = STRINGS.copy;
                        copyButton.title = '규칙 복사';
                        copyButton.addEventListener('click', () => {
                            try {
                                GM_setClipboard(rule);
                                showToast(STRINGS.ruleCopied, 'success', 2000);
                            } catch (copyError) {
                                console.error(SCRIPT_ID, "Error copying rule to clipboard:", copyError);
                                showToast(STRINGS.clipboardError, 'error');
                            }
                        });

                        const deleteButton = document.createElement('button');
                        deleteButton.className = 'mb-btn blocklist-btn blocklist-btn-delete';
                        deleteButton.textContent = '삭제';
                        deleteButton.title = '규칙 삭제';
                        deleteButton.addEventListener('click', async () => {
                            console.log('[showList] Delete button clicked for rule:', rule);
                            try {
                                const currentIndex = blockedSelectorsCache.indexOf(rule);
                                if (currentIndex > -1) {
                                    blockedSelectorsCache.splice(currentIndex, 1);
                                    await saveBlockedSelectors(blockedSelectorsCache);

                                    item.style.opacity = '0';
                                    item.style.transform = 'translateX(20px) scale(0.95)';
                                    setTimeout(async () => {
                                        item.remove();
                                        if (listContainer.childElementCount === 0) {
                                             listContainer.innerHTML = `<p id="blocklist-empty">${STRINGS.noRules}</p>`;
                                        }
                                        await applyBlocking(false);
                                        showToast(STRINGS.ruleDeleted, 'info', 2000);
                                    }, 300);

                                } else {
                                     console.warn("Rule not found in cache for deletion:", rule);
                                     showToast(STRINGS.ruleDeleteError, 'error');
                                     await showList();
                                }
                            } catch (deleteError) {
                                console.error(SCRIPT_ID, "Error deleting rule:", deleteError);
                                showToast(STRINGS.ruleDeleteError, 'error');
                            }
                        });

                        controlsDiv.append(copyButton, deleteButton);
                        item.append(span, controlsDiv);
                        listContainer.append(item);
                    });
                }
                 console.log('[showList] Rendering list panel.');
                 setPanelVisibility(listPanel, true);

            } catch (error) {
                console.error(SCRIPT_ID, "Error in showList:", error);
                showToast(STRINGS.listShowError, 'error');
                setPanelVisibility(listPanel, false);
            }
        }

        function setBlockMode(enabled) {
            if (!toggleBtn || !panel) return; // Removed edit mode check

            selecting = enabled;
            toggleBtn.classList.toggle('selecting', enabled);
            updateToggleIcon();

            if (enabled) {
                setPanelVisibility(panel, true);
                if (selectedEl) {
                    selectedEl.classList.add('selected-element');
                }
                updateInfo();
            } else {
                setPanelVisibility(panel, false);
                if (activePanel === listPanel) setPanelVisibility(listPanel, false);
                if (activePanel === settingsPanel) setPanelVisibility(settingsPanel, false);

                removeSelectionHighlight();
                resetPreview();
                initialTouchedElement = null;
            }
            console.log(SCRIPT_ID, "Selection mode:", enabled ? "ON" : "OFF");
        }

        // Removed toggleEditPositionMode function

        console.log(SCRIPT_ID, 'Attaching event listeners...');

        toggleBtn.addEventListener('click', () => {
            setBlockMode(!selecting); // Simple toggle, no edit mode check needed
        });

        copyBtn.addEventListener('click', () => {
            if (!selectedEl) { showToast(STRINGS.noElementSelected, 'warning'); return; }
            const selector = generateSelector(selectedEl, 7, true);
            if (!selector) { showToast(STRINGS.cannotGenerateSelector, 'error'); return; }

            let finalSelector = "##" + selector;
            if (settings.includeSiteName) {
                 finalSelector = location.hostname + finalSelector;
            }
            try {
                GM_setClipboard(finalSelector);
                showToast(STRINGS.selectorCopied, 'success');
            } catch (err) {
                console.error(SCRIPT_ID, "Error copying to clipboard:", err);
                showToast(STRINGS.clipboardError, 'error');
                try { prompt(STRINGS.promptCopy, finalSelector); } catch (e) { }
            }
        });

        previewBtn.addEventListener('click', () => {
            if (!selectedEl) { showToast(STRINGS.noElementSelected, 'warning'); return; }

            if (!isPreviewHidden) {
                if (window.getComputedStyle(selectedEl).display === 'none') {
                    showToast(STRINGS.alreadyHidden, 'info');
                    return;
                }
                const currentDisplay = selectedEl.style.display;
                selectedEl.dataset._original_display = currentDisplay === '' ? 'unset' : currentDisplay;
                selectedEl.style.setProperty('display', 'none', 'important');

                previewBtn.textContent = STRINGS.restorePreview;
                previewBtn.classList.remove('secondary');
                previewBtn.classList.add('tertiary');
                isPreviewHidden = true;
                previewedElement = selectedEl;
                selectedEl.classList.remove('selected-element');
                console.log(SCRIPT_ID, "Previewing hide for:", selectedEl);

            } else {
                 if (previewedElement && previewedElement !== selectedEl) {
                     showToast(STRINGS.previewDifferentElement, 'warning');
                     return;
                 }
                 resetPreview();
                 console.log(SCRIPT_ID, "Restored preview for:", previewedElement);
            }
        });

        addBtn.addEventListener('click', async () => {
            console.log('[addBtn] Clicked');
            if (!selectedEl) { showToast(STRINGS.noElementSelected, 'warning'); return; }

            try {
                const selector = generateSelector(selectedEl, 7, true);
                console.log('[addBtn] Generated selector for saving:', selector);
                if (!selector) { showToast(STRINGS.cannotGenerateSelector, 'error'); return; }

                const result = await addBlockRule(selector);
                console.log('[addBtn] addBlockRule result:', result);

                if (result.success) {
                     showToast(STRINGS.ruleSavedReloading, 'success', 2000);
                     try {
                         const ruleSelector = result.rule.split('##')[1];
                         document.querySelectorAll(ruleSelector).forEach(el => {
                             if (!originalDisplayMap.has(el)) {
                                 originalDisplayMap.set(el, el.style.display || 'unset');
                             }
                             el.style.setProperty('display', 'none', 'important');
                             el.setAttribute('data-mes-hidden', 'true');
                         });
                     } catch (applyError) {
                         console.error(SCRIPT_ID, "Error applying rule immediately after save:", applyError);
                         showToast(STRINGS.ruleSavedApplyFailed, 'warning', 3000);
                     }
                     setBlockMode(false);

                } else {
                    showToast(result.message || STRINGS.ruleAddError, result.success ? 'success' : 'info');
                }
            } catch (error) {
                console.error(SCRIPT_ID, "Error during Save Rule click:", error);
                showToast(`${STRINGS.ruleAddError} ${error.message}`, 'error');
            }
        });

        listBtn.addEventListener('click', () => {
            console.log('[listBtn] Clicked');
             setPanelVisibility(panel, false);
             showList();
        });

        settingsBtn.addEventListener('click', () => {
             console.log('[settingsBtn] Clicked');
             setPanelVisibility(panel, false);
             setPanelVisibility(settingsPanel, true);
        });

        cancelBtn.addEventListener('click', () => {
            setBlockMode(false);
        });

        listClose.addEventListener('click', () => {
            console.log('[listClose] Clicked');
            setPanelVisibility(listPanel, false);
            if (selecting) {
                console.log('[listClose] Restoring main panel');
                setPanelVisibility(panel, true);
            }
        });

        settingsClose.addEventListener('click', () => {
             console.log('[settingsClose] Clicked');
             setPanelVisibility(settingsPanel, false);
             if (selecting) {
                 console.log('[settingsClose] Restoring main panel');
                 setPanelVisibility(panel, true);
             }
             // No need to check isEditingTogglePosition
        });

        toggleSiteBtn.addEventListener('click', async () => {
            settings.includeSiteName = !settings.includeSiteName;
            toggleSiteBtn.textContent = settings.includeSiteName ? STRINGS.on : STRINGS.off;
            toggleSiteBtn.classList.toggle('active', settings.includeSiteName);
            await saveSettings();
            showToast(STRINGS.settingsSaved, 'info', 1500);
        });

        adguardLogoToggleBtn.addEventListener('click', async () => {
            settings.showAdguardLogo = !settings.showAdguardLogo;
            adguardLogoToggleBtn.textContent = settings.showAdguardLogo ? STRINGS.on : STRINGS.off;
            adguardLogoToggleBtn.classList.toggle('active', settings.showAdguardLogo);
            updateToggleIcon();
            await saveSettings();
            showToast(STRINGS.settingsSaved, 'info', 1500);
        });

        tempDisableBtn.addEventListener('click', async () => {
            settings.tempBlockingDisabled = !settings.tempBlockingDisabled;
            tempDisableBtn.textContent = settings.tempBlockingDisabled ? STRINGS.on : STRINGS.off;
            tempDisableBtn.classList.toggle('active', settings.tempBlockingDisabled);
            tempDisableBtn.classList.toggle('error', settings.tempBlockingDisabled);
            tempDisableBtn.classList.toggle('secondary', !settings.tempBlockingDisabled);

            if (settings.tempBlockingDisabled) {
                disableAllBlocking();
            } else {
                await enableAllBlocking();
            }
            await saveSettings();
        });

        // Corner button listeners
        const updateCornerButtons = (activeCorner) => {
            cornerButtons.forEach(btn => {
                btn.classList.toggle('active', btn.dataset.corner === activeCorner);
            });
        };

        cornerButtons.forEach(button => {
            button.addEventListener('click', async () => {
                const selectedCorner = button.dataset.corner;
                if (settings.toggleBtnCorner !== selectedCorner) {
                    settings.toggleBtnCorner = selectedCorner;
                    updateCornerButtons(selectedCorner);
                    applyToggleBtnPosition();
                    await saveSettings();
                    showToast(STRINGS.settingsSaved, 'info', 1500);
                }
            });
        });

        // Initialize corner button active state
        updateCornerButtons(settings.toggleBtnCorner);


        let saveTimeout;
        const debounceSaveSettings = () => {
            clearTimeout(saveTimeout);
            saveTimeout = setTimeout(async () => {
                await saveSettings();
                console.log(SCRIPT_ID, "Settings saved via debounce");
            }, 500);
        };

        panelOpacitySlider.addEventListener('input', e => {
            const newValue = parseFloat(e.target.value);
            settings.panelOpacity = newValue;
            panelOpacityValue.textContent = newValue.toFixed(2);
            document.documentElement.style.setProperty('--panel-opacity', newValue);
             document.querySelectorAll('#mobile-block-panel, #mobile-settings-panel, #mobile-blocklist-panel').forEach(p => {
                  p.style.setProperty('background-color', `rgba(40, 43, 48, ${newValue})`, 'important');
             });
            debounceSaveSettings();
        });

        toggleSizeSlider.addEventListener('input', e => {
            const newValue = parseFloat(e.target.value);
            settings.toggleSizeScale = newValue;
            toggleSizeValue.textContent = newValue.toFixed(1) + 'x';
            document.documentElement.style.setProperty('--toggle-size', `${56 * newValue}px`);
            if (toggleBtn) {
                 toggleBtn.style.setProperty('width', `var(--toggle-size)`, 'important');
                 toggleBtn.style.setProperty('height', `var(--toggle-size)`, 'important');
            }
            debounceSaveSettings();
        });

        toggleOpacitySlider.addEventListener('input', e => {
            const newValue = parseFloat(e.target.value);
            settings.toggleOpacity = newValue;
            toggleOpacityValue.textContent = newValue.toFixed(2);
            document.documentElement.style.setProperty('--toggle-opacity', newValue);
            if (toggleBtn) {
                 toggleBtn.style.setProperty('opacity', newValue, 'important');
            }
            debounceSaveSettings();
        });

        backupBtn.addEventListener('click', async () => {
            try {
                const rules = await loadBlockedSelectors();
                if (rules.length === 0) {
                    showToast('ℹ️ 백업할 규칙이 없습니다.', 'info');
                    return;
                }
                const jsonString = JSON.stringify(rules, null, 2);
                const blob = new Blob([jsonString], { type: 'application/json' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
                a.href = url;
                a.download = `mobile_element_selector_backup_${timestamp}.json`;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                URL.revokeObjectURL(url);
                showToast(STRINGS.backupStarting, 'success');
            } catch (err) {
                console.error(SCRIPT_ID, "Backup failed:", err);
                showToast(STRINGS.backupError, 'error');
            }
        });

        restoreBtn.addEventListener('click', () => {
             restoreInput.click();
        });

        restoreInput.addEventListener('change', async (event) => {
            const file = event.target.files[0];
            if (!file) return;

            const reader = new FileReader();
            reader.onload = async (e) => {
                try {
                    const content = e.target.result;
                    const parsedRules = JSON.parse(content);

                    if (!Array.isArray(parsedRules) || !parsedRules.every(item => typeof item === 'string')) {
                         throw new Error("Invalid file content - expected an array of strings.");
                    }
                    if (!parsedRules.every(item => item.includes('##') || parsedRules.length === 0)) {
                         console.warn(SCRIPT_ID, "Restored rules contain items without '##'. Proceeding anyway.");
                    }

                    await saveBlockedSelectors(parsedRules);
                    await applyBlocking(true);
                    showToast(STRINGS.restoreSuccess, 'success', 2500);

                    if (listPanel.classList.contains('visible')) {
                        await showList();
                    }
                    if (settingsPanel.classList.contains('visible')) {
                       tempDisableBtn.classList.toggle('active', settings.tempBlockingDisabled);
                       tempDisableBtn.classList.toggle('error', settings.tempBlockingDisabled);
                       tempDisableBtn.classList.toggle('secondary', !settings.tempBlockingDisabled);
                       tempDisableBtn.textContent = settings.tempBlockingDisabled ? STRINGS.on : STRINGS.off;
                    }

                } catch (err) {
                    console.error(SCRIPT_ID, "Restore failed:", err);
                     if (err instanceof SyntaxError || err.message.includes("Invalid file content")) {
                         showToast(STRINGS.restoreErrorInvalidFile, 'error');
                     } else {
                         showToast(STRINGS.restoreErrorGeneral, 'error');
                     }
                } finally {
                     restoreInput.value = '';
                }
            };
            reader.onerror = (e) => {
                 console.error(SCRIPT_ID, "File reading error:", e);
                 showToast(STRINGS.restoreErrorGeneral, 'error');
                 restoreInput.value = '';
            };
            reader.readAsText(file);
        });

         document.addEventListener('touchstart', e => {
             if (!selecting) return; // Only active when selecting

             // Ignore touches starting on any UI element
             if (e.target.closest('.mobile-block-ui')) {
                 initialTouchedElement = null;
                 return;
             }

             const touch = e.touches[0];
             touchStartX = touch.clientX;
             touchStartY = touch.clientY;
             touchMoved = false;

             const potentialTarget = document.elementFromPoint(touchStartX, touchStartY);
             if (potentialTarget && !potentialTarget.closest('.mobile-block-ui') && potentialTarget.tagName !== 'BODY' && potentialTarget.tagName !== 'HTML') {
                 initialTouchedElement = potentialTarget;
             } else {
                 initialTouchedElement = null;
             }
         }, { passive: true });

         document.addEventListener('touchmove', e => {
             if (!selecting || touchMoved || !e.touches[0]) return;

              // Ignore moves starting on UI (shouldn't happen if start is ignored)
              if (e.target.closest('.mobile-block-ui')) return;


             const touch = e.touches[0];
             const dx = touch.clientX - touchStartX;
             const dy = touch.clientY - touchStartY;
             if (Math.sqrt(dx * dx + dy * dy) > moveThreshold) {
                 touchMoved = true;
                 if (selectedEl) {
                     selectedEl.classList.remove('selected-element');
                 }
                 initialTouchedElement = null;
             }
         }, { passive: true });

         document.addEventListener('touchend', e => {
             if (!selecting) return;

             const touchEndTarget = e.target;

             // Ignore touches ending on UI buttons or the toggle button itself
             if (touchEndTarget.closest('.mobile-block-ui .mb-btn') || touchEndTarget === toggleBtn || toggleBtn.contains(touchEndTarget)) {
                 touchMoved = false; // Reset move flag but allow click
                 return;
             }
              // Ignore touches ending on panel backgrounds etc.
             if (touchEndTarget.closest('.mobile-block-ui')) {
                 touchMoved = false;
                 return;
             }


             if (!touchMoved) {
                 try {
                     e.preventDefault();
                     e.stopImmediatePropagation();
                 } catch (err) {
                      console.warn(SCRIPT_ID, "Could not preventDefault/stopImmediatePropagation on touchend:", err);
                 }
             } else {
                 touchMoved = false;
                 return; // It was a drag on the page
             }

             // Process tap for selection
             const touch = e.changedTouches[0];
             if (!touch) return;

             let targetEl = initialTouchedElement;
             if (!targetEl || targetEl.closest('.mobile-block-ui')) {
                  targetEl = document.elementFromPoint(touch.clientX, touch.clientY);
             }
             while (targetEl && (targetEl.nodeType !== 1 || targetEl.closest('.mobile-block-ui'))) {
                 targetEl = targetEl.parentElement;
             }

             if (targetEl && targetEl.tagName !== 'BODY' && targetEl.tagName !== 'HTML') {
                 removeSelectionHighlight();
                 resetPreview();
                 selectedEl = targetEl;
                 initialTouchedElement = selectedEl;
                 selectedEl.classList.add('selected-element');
                 if (slider) slider.value = 0;
                 updateInfo();
             } else {
                 removeSelectionHighlight();
                 resetPreview();
                 updateInfo();
                 initialTouchedElement = null;
             }
         }, { capture: true, passive: false });

        slider.addEventListener('input', (e) => {
            if (!initialTouchedElement) {
                if (selectedEl) {
                    initialTouchedElement = selectedEl;
                } else {
                    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('.mobile-block-ui')) {
                    break;
                }
                current = current.parentElement;
            }
            if (selectedEl !== current) {
                if (selectedEl) {
                    selectedEl.classList.remove('selected-element');
                }
                selectedEl = current;
                selectedEl.classList.add('selected-element');
                updateInfo();
            }
        });

        function makePanelDraggable(el) { // Renamed for clarity
            if (!el) return;
            let startX, startY, elementStartX, elementStartY;
            let dragging = false;
            let movedSinceStart = false;
            const dragThreshold = 5;

            const handleTouchStart = (e) => {
                let interactiveTarget = e.target.closest('button, input[type="range"], input[type="file"], select, textarea, .blocklist-item, #blocklist-container, #blocker-info');
                if (interactiveTarget && el.contains(interactiveTarget)) {
                    if (interactiveTarget.id === 'blocklist-container' || interactiveTarget.id === 'blocker-info') {
                        if (interactiveTarget.scrollHeight > interactiveTarget.clientHeight) {
                            dragging = false; return;
                        }
                    } else {
                        dragging = false; return;
                    }
                }

                if (e.touches.length > 1) { dragging = false; return; }

                dragging = true;
                movedSinceStart = false;

                const touch = e.touches[0];
                startX = touch.clientX;
                startY = touch.clientY;
                const rect = el.getBoundingClientRect();
                elementStartX = rect.left;
                elementStartY = rect.top;

                el.style.transition = 'none';
                el.style.cursor = 'grabbing';
            };

            const handleTouchMove = (e) => {
                if (!dragging || e.touches.length > 1) return;

                const touch = e.touches[0];
                const dx = touch.clientX - startX;
                const dy = touch.clientY - startY;

                if (!movedSinceStart) {
                    if (Math.sqrt(dx * dx + dy * dy) > dragThreshold) {
                        movedSinceStart = true;
                        try { e.preventDefault(); } catch {}
                    } else { return; }
                } else { try { e.preventDefault(); } catch {} }

                let newX = elementStartX + dx;
                let newY = elementStartY + 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';
                el.style.transform = ''; // Use left/top for panels
            };

            const handleTouchEnd = async (e) => {
                if (!dragging) return;
                dragging = false;
                el.style.transition = '';
                el.style.cursor = 'grab';

                if (movedSinceStart) {
                    // No need to save panel positions currently
                    try { /* Optional event handling */ } catch {}
                } else {
                    // Click/Tap occurred
                }
                movedSinceStart = false;
            };

            el.addEventListener('touchstart', handleTouchStart, { passive: true });
            el.addEventListener('touchmove', handleTouchMove, { passive: false });
            el.addEventListener('touchend', handleTouchEnd, { passive: false });
            el.addEventListener('touchcancel', handleTouchEnd, { passive: false });
        }

        // Apply dragging only to panels
        makePanelDraggable(panel);
        makePanelDraggable(settingsPanel);
        makePanelDraggable(listPanel);
        // Do NOT make toggleBtn draggable

        console.log(SCRIPT_ID, 'Initialization complete.');
    }

    async function run() {
        await loadSettings();
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', createUIElements);
        } else {
            createUIElements();
        }
    }

    run().catch(error => {
        console.error(SCRIPT_ID, "Unhandled error during script initialization:", error);
    });

})();