Mobile Element Selector

모바일 요소 선택기

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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

(function() {
    'use strict';

    let isSelecting = false;
    let selectedElement = null;
    let elementHistory = [];
    let hiddenElements = new Map(); // 숨겨진 요소 관리

    let config = JSON.parse(localStorage.getItem('ag_selector_config')) || {
        themeColor: '#2ecc71',
        uiOpacity: 0.95,
        btnOpacity: 1.0,
        overlayOpacity: 0.2,
        uiScale: 1.0,
        btnScale: 1.0,
        showLogo: true,
        btnPosition: 'right'
    };

    const inactiveColor = '#888888';
    const getLogo = (color) => `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="width:60%; height:60%;"><path d="M12 2L4 5V11C4 16.1 7.4 20.9 12 22C16.6 20.9 20 16.1 20 11V5L12 2Z" fill="white"/><path d="M10 15.5L6.5 12L7.9 10.6L10 12.7L16.1 6.6L17.5 8L10 15.5Z" fill="${color}" id="ag-svg-check"/></svg>`;

    function createUI() {
        if (document.getElementById('ag-selector-container')) return;

        const container = document.createElement('div');
        container.id = 'ag-selector-container';
        document.body.appendChild(container);

        const style = document.createElement('style');
        style.id = 'ag-main-style';
        document.head.appendChild(style);

        const updateStyles = () => {
            const bSize = Math.max(40, 60 * config.btnScale);
            const currentBtnColor = isSelecting ? config.themeColor : inactiveColor;
            
            style.innerHTML = `
                #ag-selector-btn { 
                    position: fixed !important; bottom: 30px !important; ${config.btnPosition}: 30px !important; 
                    z-index: 2147483647 !important; width: ${bSize}px !important; height: ${bSize}px !important;
                    border-radius: 50% !important; background: ${currentBtnColor} !important; 
                    border: 2.5px solid white !important; box-shadow: 0 4px 20px rgba(0,0,0,0.3) !important; 
                    display: flex !important; align-items: center !important; justify-content: center !important; 
                    opacity: ${config.btnOpacity} !important; cursor: pointer !important; transition: all 0.3s;
                }
                .ag-logo-box { display: ${config.showLogo ? 'flex' : 'none'}; width: 100%; height: 100%; align-items: center; justify-content: center; pointer-events: none; }
                
                #ag-toolbar { 
                    position: fixed !important; bottom: 110px !important; left: 50% !important; 
                    transform: translateX(-50%) scale(${config.uiScale}) !important; 
                    display: ${isSelecting ? 'flex' : 'none'}; 
                    background: rgba(45, 45, 45, ${config.uiOpacity}); backdrop-filter: blur(10px); color: white; 
                    padding: 0 20px !important; height: 50px !important; border-radius: 12px; 
                    z-index: 2147483647 !important; gap: 18px; align-items: center; border: 1px solid rgba(255,255,255,0.1);
                }
                #ag-toolbar span { cursor: pointer; font-size: 20px; display: flex; align-items: center; justify-content: center; opacity: 0.8; transition: 0.2s; user-select: none; }
                #ag-toolbar span:hover { opacity: 1; }

                #ag-settings-panel {
                    position: fixed !important; top: 50% !important; left: 50% !important; 
                    transform: translate(-50%, -50%) scale(${config.uiScale}) !important;
                    background: rgba(255, 255, 255, ${config.uiOpacity}) !important; backdrop-filter: blur(15px); color: #333 !important; 
                    padding: 25px !important; border-radius: 24px !important; z-index: 2147483648 !important; 
                    display: none; width: 280px !important; box-shadow: 0 20px 50px rgba(0,0,0,0.4);
                }
                .s-item { margin-bottom: 12px; }
                .s-label { font-size: 11px; font-weight: bold; color: #666; display: block; margin-bottom: 4px; }
                input[type="range"] { width: 100%; accent-color: ${config.themeColor}; }

                #ag-highlight { 
                    position: absolute !important; border: 2px solid ${config.themeColor}; 
                    pointer-events: none; z-index: 2147483646 !important; display: none;
                    background: ${config.themeColor}${Math.floor(config.overlayOpacity * 255).toString(16).padStart(2, '0')};
                }
                #ag-info-label { 
                    position: absolute; top: 100%; left: 0; background: ${config.themeColor}; 
                    color: white; padding: 2px 8px; font-size: 12px; font-weight: bold; 
                    border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; white-space: nowrap;
                }
            `;
        };
        updateStyles();

        container.innerHTML = `
            <div id="ag-selector-btn"><div class="ag-logo-box">${getLogo(inactiveColor)}</div></div>
            <div id="ag-overlay" style="position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:2147483645;display:none;background:transparent;"></div>
            <div id="ag-highlight"><div id="ag-info-label"></div></div>
            <div id="ag-toolbar">
                <span id="ag-close">✕</span>
                <span id="ag-down">-</span>
                <span id="ag-up">+</span>
                <span id="ag-toggle-vis">👁</span> <span id="ag-settings">⚙</span>
                <span id="ag-save" style="color:${config.themeColor}; font-weight:bold; font-size:22px;">✔</span>
            </div>
            <div id="ag-settings-panel">
                <h3 style="margin:0 0 15px 0; text-align: center; font-size: 16px;">⚙ 설정</h3>
                <div class="s-item"><label class="s-label">툴바 투명도</label><input type="range" id="input-ui-op" min="0.2" max="1.0" step="0.05" value="${config.uiOpacity}"></div>
                <div class="s-item"><label class="s-label">버튼 투명도</label><input type="range" id="input-btn-op" min="0.2" max="1.0" step="0.05" value="${config.btnOpacity}"></div>
                <div class="s-item"><label class="s-label">테마 색상</label><input type="color" id="input-color" value="${config.themeColor}" style="width:100%; height:25px; border:none; background:none;"></div>
                <button id="ag-settings-done" style="width:100%; background:${config.themeColor}; color:white; border:none; padding:10px; border-radius:12px; font-weight:bold; margin-top:5px;">저장 및 닫기</button>
            </div>
        `;

        const btn = container.querySelector('#ag-selector-btn');
        const overlay = container.querySelector('#ag-overlay');
        const highlight = container.querySelector('#ag-highlight');
        const label = container.querySelector('#ag-info-label');
        const toolbar = container.querySelector('#ag-toolbar');

        // --- 숨기기/보이기 기능 로직 ---
        container.querySelector('#ag-toggle-vis').onclick = () => {
            if (!selectedElement) return;
            
            if (selectedElement.style.display === 'none') {
                selectedElement.style.display = hiddenElements.get(selectedElement) || '';
                container.querySelector('#ag-toggle-vis').innerText = '👁';
                highlight.style.display = 'block'; // 다시 보이면 하이라이트 표시
            } else {
                hiddenElements.set(selectedElement, selectedElement.style.display);
                selectedElement.style.display = 'none';
                container.querySelector('#ag-toggle-vis').innerText = '🚫'; // 숨김 상태 아이콘 변경
                highlight.style.display = 'none'; // 숨기면 하이라이트도 숨김
            }
        };

        btn.onclick = () => {
            isSelecting = !isSelecting;
            overlay.style.display = isSelecting ? 'block' : 'none';
            updateStyles();
            if (!isSelecting) {
                highlight.style.display = 'none'; 
                toolbar.style.display = 'none'; 
                selectedElement = null;
                // 끌 때 아이콘 초기화
                container.querySelector('#ag-toggle-vis').innerText = '👁';
            }
        };

        overlay.addEventListener('click', (e) => {
            overlay.style.display = 'none';
            const el = document.elementFromPoint(e.clientX, e.clientY);
            overlay.style.display = 'block';
            if (el && el !== btn && !toolbar.contains(el)) {
                selectedElement = el;
                elementHistory = []; 
                updateHighlight();
            }
        });

        container.querySelector('#ag-up').onclick = () => {
            if (selectedElement?.parentElement && selectedElement.parentElement !== document.body) {
                elementHistory.push(selectedElement); 
                selectedElement = selectedElement.parentElement;
                updateHighlight();
            }
        };

        container.querySelector('#ag-down').onclick = () => {
            if (elementHistory.length > 0) {
                selectedElement = elementHistory.pop();
                updateHighlight();
            }
        };

        function updateHighlight() {
            if(!selectedElement) return;
            const rect = selectedElement.getBoundingClientRect();
            highlight.style.display = 'block';
            highlight.style.width = rect.width + 'px';
            highlight.style.height = rect.height + 'px';
            highlight.style.top = (rect.top + window.scrollY) + 'px';
            highlight.style.left = (rect.left + window.scrollX) + 'px';
            
            label.innerText = `${selectedElement.tagName.toLowerCase()}${selectedElement.id ? '#'+selectedElement.id : ''}`;
            toolbar.style.display = 'flex';
            // 요소가 보이고 있는 상태면 아이콘 👁로 유지
            container.querySelector('#ag-toggle-vis').innerText = selectedElement.style.display === 'none' ? '🚫' : '👁';
        }

        // 설정 및 기타
        container.querySelector('#input-ui-op').oninput = (e) => { config.uiOpacity = e.target.value; updateStyles(); };
        container.querySelector('#input-btn-op').oninput = (e) => { config.btnOpacity = e.target.value; updateStyles(); };
        container.querySelector('#input-color').oninput = (e) => { config.themeColor = e.target.value; updateStyles(); };
        container.querySelector('#ag-settings').onclick = () => container.querySelector('#ag-settings-panel').style.display = 'block';
        container.querySelector('#ag-settings-done').onclick = () => {
            localStorage.setItem('ag_selector_config', JSON.stringify(config));
            container.querySelector('#ag-settings-panel').style.display = 'none';
        };
        container.querySelector('#ag-close').onclick = btn.onclick;
        container.querySelector('#ag-save').onclick = () => {
            const rule = `${window.location.hostname}##${label.innerText}`;
            navigator.clipboard.writeText(rule).then(() => alert("복사 완료!"));
        };
    }

    if (document.readyState === 'complete') createUI();
    else window.addEventListener('load', createUI);
})();