MDK Links

Sélectionner, copier ou ouvrir des liens.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name         MDK Links
// @namespace    MDK Scripts
// @version      6.1
// @description  Sélectionner, copier ou ouvrir des liens.
// @author       MDK
// @license      MIT
// @match        *://*/*
// @icon         data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 0 24 24' fill='none' stroke='%23007bff' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71'%3E%3C/path%3E%3Cpath d='M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71'%3E%3C/path%3E%3C/svg%3E
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_setClipboard
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @grant        GM_openInTab
// ==/UserScript==

(function() {
    'use strict';

    // ==========================================
    // CONSTANTES GLOBALES (Lisibilité)
    // ==========================================
    const MOUSE = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };
    const ACTIONS = { COPY: 'copy', OPEN: 'open' };
    const MAX_TABS_SECURITY = 25; // Limite de sécurité pour l'ouverture d'onglets

    // ==========================================
    // 1. ÉTAT GLOBAL DU SCRIPT (STATE)
    // ==========================================
    const State = {
        isDragging: false,
        shouldBlockContextMenu: false,
        actionType: null,
        startPageX: 0, startPageY: 0, // Coordonnées absolues dans le document
        clientMouseX: 0, clientMouseY: 0, // Position de la souris à l'écran (viewport)
        selectedLinks: [],
        cachedLinksGeometry: [],
        keyupTimeout: null,
        liveForeground: false,
        rafId: null
    };

    // ==========================================
    // 2. CATALOGUE DE STYLES
    // ==========================================
    const StylesManager = {
        catalog: {
            'starwars': { name: '🚀 Star Wars (Galactique)', css: `.lasso-selected { background: #000000 !important; color: #ffe81f !important; font-family: "Impact", "Arial Black", sans-serif !important; font-weight: bold !important; text-transform: uppercase !important; letter-spacing: 2px !important; border: 2px solid #ffe81f !important; padding: 2px 6px; text-shadow: 0 0 4px rgba(255, 232, 31, 0.6) !important; box-shadow: 0 0 10px rgba(0,0,0,0.8) !important; }` },
            'stranger': { name: '🩸 Stranger Things (Rétro 80s)', css: `.lasso-selected { background: #0c0202 !important; color: #f11313 !important; font-family: "Bookman Old Style", "Georgia", serif !important; font-weight: 900 !important; text-shadow: 0 0 6px #ff0000, 0 0 15px #8b0000 !important; letter-spacing: 1px !important; border-top: 2px solid #f11313 !important; border-bottom: 2px solid #f11313 !important; padding: 3px 6px; }` },
            'breaking': { name: '🧪 Breaking Bad (Alchimie)', css: `.lasso-selected { background: #0f381f !important; color: #ffffff !important; font-family: "Arial", "Helvetica", sans-serif !important; font-weight: bold !important; border: 2px solid #4ba74a !important; padding: 2px 8px; box-shadow: 0 0 12px rgba(75, 167, 74, 0.7), inset 0 0 6px rgba(0,0,0,0.5) !important; border-radius: 0px !important; }` },
            'fallout': { name: '☢️ Fallout Pip-Boy', css: `.lasso-selected { background: #001100 !important; color: #33ff33 !important; font-family: "Courier New", monospace !important; font-weight: bold !important; border: 2px solid #33ff33 !important; padding: 2px 4px; text-shadow: 0 0 6px #33ff33 !important; box-shadow: inset 0 0 8px #003300, 0 0 8px rgba(51,255,51,0.5) !important; letter-spacing: 1px; }` },
            'cyberpunk': { name: '🤖 Cyberpunk 2077', css: `.lasso-selected { background: #fcee0a !important; color: #000000 !important; font-family: "Arial Black", sans-serif !important; font-weight: 900 !important; text-transform: uppercase; border-left: 4px solid #00f0ff !important; padding: 2px 6px; box-shadow: 3px 3px 0px #00f0ff !important; transform: skewX(-5deg); display: inline-block; }` },
            'chiaroscuro': { name: '🌓 Clair-Obscur (Art)', css: `.lasso-selected { background: #0a0806 !important; color: #ffeedd !important; border: 1px solid #9e7844 !important; box-shadow: 0 0 15px rgba(255,140,0,0.25), inset 0 0 12px rgba(0,0,0,0.9) !important; text-shadow: 0 0 3px rgba(255,238,221,0.5) !important; font-style: italic !important; font-family: "Georgia", serif !important; padding: 2px 6px; }` },
            'matrix': { name: '📟 Code Matrix (Vert)', css: `.lasso-selected { background: #000000 !important; color: #00ff00 !important; font-family: monospace !important; font-weight: bold !important; border: 1px solid #00ff00 !important; padding: 2px; text-shadow: 0 0 3px #00ff00 !important; }` },
            'amber': { name: '🍂 Terminal Ambre (Retro)', css: `.lasso-selected { background: #110800 !important; color: #ffb000 !important; font-family: monospace !important; font-weight: bold !important; border: 1px solid #ffb000 !important; padding: 2px; text-shadow: 0 0 4px #ffb000 !important; }` },
            'synthwave': { name: '🌆 Cyberpunk / Synthwave', css: `.lasso-selected { background: rgba(43, 0, 71, 0.85) !important; color: #00ffff !important; border: 2px solid #ff007f !important; box-shadow: 0 0 10px #ff007f, inset 0 0 5px #00ffff !important; text-shadow: 0 0 3px #00ffff !important; border-radius: 4px; }` },
            'magma': { name: '🔥 Magma Enflammé', css: `.lasso-selected { border-radius: 4px; box-shadow: 0 0 12px #ff4500, inset 0 0 6px #ff8c00 !important; background: rgba(30, 5, 0, 0.95) !important; color: #ffcc00 !important; font-weight: bold !important; text-shadow: 0 0 2px #ff4500; }` },
            'blueprint': { name: '📐 Plan Bleu (Blueprint)', css: `.lasso-selected { background: #0033aa !important; color: #ffffff !important; outline: 2px dashed #ffffff !important; outline-offset: -1px; background-image: linear-gradient(rgba(255,255,255,.15) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.15) 1px, transparent 1px) !important; background-size: 8px 8px !important; font-family: sans-serif; }` },
            'halo': { name: '🔵 Halo Électrique', css: `.lasso-selected { box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.6) !important; border-radius: 2px; }` },
            'highlighter': { name: '🟡 Surligneur Jaune', css: `.lasso-selected { background: #fff59d !important; color: #000000 !important; padding: 2px 4px; border-radius: 3px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }` },
            'custom': { name: '🛠️ Personnalisé (CSS)', css: '' }
        },
        element: null,
        init() {
            this.element = document.createElement('style');
            this.element.id = 'lasso-dynamic-style';
            document.head.appendChild(this.element);
            this.apply(GM_getValue('selectedStyle', 'starwars'));
        },
        apply(key, customCSS) {
            this.element.textContent = this.catalog[key]?.css || customCSS || GM_getValue('customCSS', '');
        }
    };

    GM_addStyle(`
        :root { --popup-bg: #ffffff; --popup-text: #333333; --popup-border: #cccccc; --popup-input-bg: #ffffff; --focus-outline: #007bff; }
        @media (prefers-color-scheme: dark) { :root { --popup-bg: #1f1f1f; --popup-text: #f0f0f0; --popup-border: #444444; --popup-input-bg: #2d2d2d; } }
        .gm-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: rgba(0,0,0,0.6); display: flex; justify-content: center; align-items: center; z-index: 2147483647; font-family: sans-serif; animation: fadeIn 0.15s ease-out; }
        @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
        .gm-popup { background-color: var(--popup-bg); color: var(--popup-text); padding: 20px; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.4); width: 480px; display: flex; flex-direction: column; gap: 16px; border: 1px solid var(--popup-border); box-sizing: border-box; }
        .gm-popup-header { display: flex; align-items: center; gap: 10px; width: 100%; margin-bottom: 2px; }
        .gm-popup-icon { display: flex; align-items: center; justify-content: center; color: var(--focus-outline); }
        .gm-popup-title { margin: 0; font-size: 19px; font-weight: 800; text-align: left; letter-spacing: 0.5px; }
        .gm-popup-row { display: flex; justify-content: space-between; align-items: center; width: 100%; }
        .gm-popup-footer { display: flex; justify-content: space-between; margin-top: 4px; gap: 10px; width: 100%; }
        .gm-popup-input { width: 200px; padding: 6px 10px; background: var(--popup-input-bg); color: var(--popup-text); border: 1px solid var(--popup-border); border-radius: 4px; box-sizing: border-box; font-size: 14px; }
        .gm-popup-input:focus, .gm-btn-action:focus { outline: 2px solid var(--focus-outline) !important; outline-offset: 0px; }
        .gm-key-container { display: flex; gap: 4px; }
        .gm-key-pill { position: relative; display: inline-flex; align-items: center; justify-content: center; padding: 5px 10px; background: var(--popup-input-bg); color: var(--popup-text); border: 1px solid var(--popup-border); border-radius: 6px; font-size: 12px; font-weight: 600; cursor: pointer; transition: all 0.15s ease; min-width: 46px; user-select: none; box-shadow: 0 1px 2px rgba(0,0,0,0.1); }
        .gm-key-pill input { position: absolute; clip: rect(0, 0, 0, 0); padding: 0; border: 0; height: 1px; width: 1px; overflow: hidden; }
        .gm-key-pill.active { background: #007bff; color: #ffffff; border-color: #007bff; box-shadow: 0 2px 6px rgba(0, 123, 255, 0.4); }
        .gm-key-pill:focus-within { outline: 2px solid #ff8c00 !important; outline-offset: 1px; }
        .gm-btn-action { flex: 1; padding: 10px 14px; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; transition: background 0.15s ease; text-align: center; font-size: 14px; white-space: nowrap; }
        #btn-save { background: #28a745; } #btn-cancel { background: #6c757d; } #btn-reset { background: #dc3545; }
        #btn-save:hover { background: #218838; } #btn-cancel:hover { background: #5a6268; } #btn-reset:hover { background: #bd2130; }

        /* Styles de la zone des déclencheurs unifiés (Compatible Thèmes) */
        .gm-popup-fieldset { background: var(--popup-input-bg) !important; color: var(--popup-text) !important; border: 1px solid var(--popup-border) !important; display: flex !important; flex-direction: column; gap: 12px; padding: 16px !important; border-radius: 6px; box-sizing: border-box; width: 100%; }
    `);

    // ==========================================
    // 3. COMPOSANTS GRAPHIQUES DU LASSO
    // ==========================================
    const UI = {
        lasso: document.createElement('div'),
        counter: document.createElement('div'),
        toast: document.createElement('div'),

        init() {
            Object.assign(this.lasso.style, { position: 'absolute', border: '2px dashed #007bff', backgroundColor: 'rgba(0, 123, 255, 0.1)', display: 'none', pointerEvents: 'none', zIndex: '999999' });
            Object.assign(this.counter.style, { position: 'fixed', padding: '6px 12px', backgroundColor: '#222', color: '#fff', borderRadius: '5px', fontSize: '13px', fontWeight: 'bold', display: 'none', pointerEvents: 'none', zIndex: '1000000', boxShadow: '0 2px 8px rgba(0,0,0,0.4)', whiteSpace: 'nowrap', fontFamily: 'sans-serif' });
            Object.assign(this.toast.style, { position: 'fixed', bottom: '20px', left: '50%', transform: 'translateX(-50%)', padding: '10px 20px', backgroundColor: '#28a745', color: '#fff', borderRadius: '5px', zIndex: '1000000', display: 'none', fontFamily: 'sans-serif', fontWeight: 'bold' });
            document.body.append(this.lasso, this.counter, this.toast);
        },
        showToast(msg) {
            this.toast.textContent = msg;
            this.toast.style.display = 'block';
            setTimeout(() => this.toast.style.display = 'none', 2000);
        },
        updateCounter() {
            if (!State.isDragging) return;
            if (State.actionType === ACTIONS.COPY) {
                this.counter.textContent = `Copier ${State.selectedLinks.length} lien${State.selectedLinks.length > 1 ? 's' : ''}`;
            } else if (State.actionType === ACTIONS.OPEN) {
                const modeLabel = State.liveForeground ? '👁️ Avant-plan' : '💤 Arrière-plan';
                this.counter.textContent = `Ouvrir ${State.selectedLinks.length} lien${State.selectedLinks.length > 1 ? 's' : ''} [${modeLabel}]`;
            }
        }
    };

    function getActionFromModifiers(e) {
        const matchCopy = (e.ctrlKey === GM_getValue('copyCtrl', true) && e.altKey === GM_getValue('copyAlt', false) && e.shiftKey === GM_getValue('copyShift', false));
        const matchOpen = (e.ctrlKey === GM_getValue('openCtrl', true) && e.altKey === GM_getValue('openAlt', false) && e.shiftKey === GM_getValue('openShift', true));
        return matchCopy ? ACTIONS.COPY : (matchOpen ? ACTIONS.OPEN : null);
    }

    // ==========================================
    // 4. MOTEUR DU LASSO (COORDONNÉES ABSOLUES PAGE)
    // ==========================================
    const LassoEngine = {
        cacheGeometries() {
            const scrollX = window.scrollX;
            const scrollY = window.scrollY;

            State.cachedLinksGeometry = Array.from(document.querySelectorAll('a[href]'))
                .filter(a => {
                    if (a.id === 'preview-link') return false;
                    const hrefAttr = a.getAttribute('href').trim().toLowerCase();
                    if (hrefAttr.startsWith('javascript:') || hrefAttr === '#' || hrefAttr === '') {
                        return false;
                    }
                    return true;
                })
                .map(a => {
                    const r = a.getBoundingClientRect();
                    return {
                        el: a,
                        url: a.href,
                        left: r.left + scrollX,
                        top: r.top + scrollY,
                        right: r.right + scrollX,
                        bottom: r.bottom + scrollY,
                        isSelected: false
                    };
                });
        },
        render() {
            if (!State.isDragging) return;

            const currentPageX = State.clientMouseX + window.scrollX;
            const currentPageY = State.clientMouseY + window.scrollY;

            const left = Math.min(currentPageX, State.startPageX);
            const top = Math.min(currentPageY, State.startPageY);
            const w = Math.abs(currentPageX - State.startPageX);
            const h = Math.abs(currentPageY - State.startPageY);

            Object.assign(UI.lasso.style, { left: left + 'px', top: top + 'px', width: w + 'px', height: h + 'px' });

            const uniqueUrls = new Set();
            const len = State.cachedLinksGeometry.length;

            for (let i = 0; i < len; i++) {
                const item = State.cachedLinksGeometry[i];
                const isInside = (item.left < left + w && item.right > left && item.top < top + h && item.bottom > top);

                if (isInside) {
                    uniqueUrls.add(item.url);
                    if (!item.isSelected) {
                        item.el.classList.add('lasso-selected');
                        item.isSelected = true;
                    }
                } else {
                    if (item.isSelected) {
                        item.el.classList.remove('lasso-selected');
                        item.isSelected = false;
                    }
                }
            }

            State.selectedLinks = Array.from(uniqueUrls);

            if (State.selectedLinks.length === 0) {
                UI.counter.style.display = 'none';
            } else {
                UI.updateCounter();

                UI.counter.style.display = 'block';
                const counterWidth = UI.counter.offsetWidth || 120;
                const counterHeight = UI.counter.offsetHeight || 30;
                const gap = 15;

                let targetX = State.clientMouseX + gap;
                if (targetX + counterWidth > window.innerWidth) {
                    targetX = State.clientMouseX - gap - counterWidth;
                }

                let targetY = State.clientMouseY + gap;
                if (targetY + counterHeight > window.innerHeight) {
                    targetY = State.clientMouseY - gap - counterHeight;
                }

                targetX = Math.max(5, targetX);
                targetY = Math.max(5, targetY);

                Object.assign(UI.counter.style, { left: targetX + 'px', top: targetY + 'px' });
            }

            State.rafId = null;
        }
    };

    // ==========================================
    // 5. MODALE DE CONFIGURATION
    // ==========================================
    function createSettingsUI() {
        if (document.getElementById('gm-settings-popup')) return;

        const initialStyle = GM_getValue('selectedStyle', 'starwars');
        const initialCustomCSS = GM_getValue('customCSS', '');

        const overlay = document.createElement('div');
        overlay.id = 'gm-settings-popup';
        overlay.className = 'gm-overlay';

        const popup = document.createElement('div');
        popup.className = 'gm-popup';
        popup.setAttribute('role', 'dialog');
        popup.setAttribute('aria-modal', 'true');

        popup.innerHTML = `
            <div class="gm-popup-header">
                <div class="gm-popup-icon">
                    <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
                </div>
                <h3 class="gm-popup-title">MDK Links</h3>
            </div>

            <fieldset id="trigger-fieldset" class="gm-popup-fieldset">
                <div style="display: flex; flex-direction: column; gap: 4px; color: inherit;">
                    <span style="font-size: 13px; font-weight: bold; text-align: left; margin-bottom: 4px;">Copier dans le presse-papier</span>
                    <div style="display: flex; align-items: center; justify-content: space-between; width: 100%;">
                        <div class="gm-key-container">
                            <label class="gm-key-pill" id="lbl-copy-ctrl" style="font-family: sans-serif;"><input type="checkbox" id="chk-copy-ctrl">Ctrl</label>
                            <label class="gm-key-pill" id="lbl-copy-alt" style="font-family: sans-serif;"><input type="checkbox" id="chk-copy-alt">Alt</label>
                            <label class="gm-key-pill" id="lbl-copy-shift" style="font-family: sans-serif;"><input type="checkbox" id="chk-copy-shift">Shift</label>
                        </div>
                        <div style="font-weight: bold; opacity: 0.8; font-size: 14px;">+</div>
                        <select id="mouse-copy-select" class="gm-popup-input" style="width: 150px !important; padding: 5px 8px;">
                            <option value="${MOUSE.LEFT}">Clic Gauche</option>
                            <option value="${MOUSE.MIDDLE}">Clic Milieu</option>
                            <option value="${MOUSE.RIGHT}">Clic Droit</option>
                        </select>
                    </div>
                </div>

                <div style="display: flex; flex-direction: column; gap: 4px; color: inherit; margin-top: 1.2em;">
                    <div style="display: flex; justify-content: space-between; align-items: center; width: 100%; margin-bottom: 4px;">
                        <span style="font-size: 13px; font-weight: bold;">Ouvrir les liens dans un nouvel onglet</span>
                        <label id="lbl-open-foreground" class="gm-key-pill" style="font-size: 11px; padding: 2px 8px; width: 150px !important; box-sizing: border-box; height: 18px; display: inline-flex; align-items: center; justify-content: center; margin: 0; border-radius: 4px; font-family: sans-serif; text-shadow: none;">
                            <input type="checkbox" id="chk-open-foreground">
                            <span id="txt-open-foreground">Arrière-plan 💤</span>
                        </label>
                    </div>
                    <div style="display: flex; align-items: center; justify-content: space-between; width: 100%;">
                        <div class="gm-key-container">
                            <label class="gm-key-pill" id="lbl-open-ctrl" style="font-family: sans-serif;"><input type="checkbox" id="chk-open-ctrl">Ctrl</label>
                            <label class="gm-key-pill" id="lbl-open-alt" style="font-family: sans-serif;"><input type="checkbox" id="chk-open-alt">Alt</label>
                            <label class="gm-key-pill" id="lbl-open-shift" style="font-family: sans-serif;"><input type="checkbox" id="chk-open-shift">Shift</label>
                        </div>
                        <div style="font-weight: bold; opacity: 0.8; font-size: 14px;">+</div>
                        <select id="mouse-open-select" class="gm-popup-input" style="width: 150px !important; padding: 5px 8px;">
                            <option value="${MOUSE.LEFT}">Clic Gauche</option>
                            <option value="${MOUSE.MIDDLE}">Clic Milieu</option>
                            <option value="${MOUSE.RIGHT}">Clic Droit</option>
                        </select>
                    </div>
                </div>

                <div id="trigger-error" style="display: none; color: #ff3333 !important; font-weight: 800 !important; text-align: center !important; font-size: 13px; margin-top: 2px; width: 100%; text-shadow: none !important;">
                    ⚠️ Combinaison déjà utilisée pour une autre action !
                </div>
            </fieldset>

            <div style="font-size: 11px; opacity: 0.8; line-height: 1.4; text-align: left; margin-top: -4px; margin-bottom: 2px; padding: 0 4px; font-style: italic;">
                💡 <strong>Bascule dynamique durant la sélection :</strong><br>
                Changer d'action (Copier ⇄ Ouvrir) en appuyant sur le raccourci clavier correspondant.<br>
                Si "Ouvrir" est actif, appuyez sur <strong>A</strong> pour basculer le mode d'ouverture (👁️ ⇄ 💤).
            </div>

            <div class="gm-popup-row">
                <label style="font-weight: bold;">Style de sélection :</label>
                <select id="style-select" class="gm-popup-input" style="width: 200px !important;">
                    ${Object.keys(StylesManager.catalog).map(k=>`<option value="${k}">${StylesManager.catalog[k].name}</option>`).join('')}
                </select>
            </div>

            <div id="preview-wrapper" style="height:60px; border:1px solid var(--popup-border); border-radius:4px; display:flex; align-items:center; justify-content:center; background:#111; overflow:hidden;">
                <a href="#" id="preview-link" class="lasso-selected" onclick="return false;" style="text-decoration:none; color: inherit;">Exemple de lien sélectionné</a>
            </div>
            <textarea id="custom-css-input" class="gm-popup-input" style="display:none; width:100%; height:80px; text-align:left; font-family:monospace;" placeholder=".lasso-selected {\\n  outline: 2px dotted red !important;\\n}"></textarea>

            <div class="gm-popup-footer">
                <button id="btn-save" class="gm-btn-action" type="button" accesskey="e"><u>E</u>nregistrer</button>
                <button id="btn-cancel" class="gm-btn-action" type="button" accesskey="a"><u>A</u>nnuler</button>
                <button id="btn-reset" class="gm-btn-action" type="button" accesskey="r"><u>R</u>éinitialiser</button>
            </div>
        `;

        overlay.appendChild(popup);
        document.body.appendChild(overlay);

        const select = document.getElementById('style-select');
        const mouseCopySelect = document.getElementById('mouse-copy-select');
        const mouseOpenSelect = document.getElementById('mouse-open-select');
        const textarea = document.getElementById('custom-css-input');

        const copyCtrl = document.getElementById('chk-copy-ctrl'), copyAlt = document.getElementById('chk-copy-alt'), copyShift = document.getElementById('chk-copy-shift');
        const openCtrl = document.getElementById('chk-open-ctrl'), openAlt = document.getElementById('chk-open-alt'), openShift = document.getElementById('chk-open-shift'), openForeground = document.getElementById('chk-open-foreground');

        const lblCopyCtrl = document.getElementById('lbl-copy-ctrl'), lblCopyAlt = document.getElementById('lbl-copy-alt'), lblCopyShift = document.getElementById('lbl-copy-shift');
        const lblOpenCtrl = document.getElementById('lbl-open-ctrl'), lblOpenAlt = document.getElementById('lbl-open-alt'), lblOpenShift = document.getElementById('lbl-open-shift'), lblOpenForeground = document.getElementById('lbl-open-foreground');
        const txtOpenForeground = document.getElementById('txt-open-foreground');

        // Initialisation avec les valeurs stockées ou les valeurs par défaut
        copyCtrl.checked = GM_getValue('copyCtrl', true); copyAlt.checked = GM_getValue('copyAlt', false); copyShift.checked = GM_getValue('copyShift', false);
        mouseCopySelect.value = GM_getValue('copyButton', MOUSE.RIGHT); // Copie par défaut : Ctrl + Clic Droit
        openCtrl.checked = GM_getValue('openCtrl', true); openAlt.checked = GM_getValue('openAlt', false); openShift.checked = GM_getValue('openShift', true);
        mouseOpenSelect.value = GM_getValue('openButton', MOUSE.RIGHT); // Ouvrir par défaut : Ctrl + Shift + Clic Droit
        openForeground.checked = GM_getValue('openInForeground', false);

        const syncPillState = (chk, lbl) => lbl.classList.toggle('active', chk.checked);

        const syncForegroundState = () => {
            if (openForeground.checked) {
                lblOpenForeground.style.background = '#00ff00'; lblOpenForeground.style.color = '#000000';
                txtOpenForeground.textContent = "Avant-plan 👁️";
            } else {
                lblOpenForeground.style.background = '#111'; lblOpenForeground.style.color = '#00ff00';
                txtOpenForeground.textContent = "Arrière-plan 💤";
            }
        };

        syncPillState(copyCtrl, lblCopyCtrl); syncPillState(copyAlt, lblCopyAlt); syncPillState(copyShift, lblCopyShift);
        syncPillState(openCtrl, lblOpenCtrl); syncPillState(openAlt, lblOpenAlt); syncPillState(openShift, lblOpenShift);
        syncForegroundState();

        const checkTriggerConflict = () => {
            const bCopy = parseInt(mouseCopySelect.value, 10), bOpen = parseInt(mouseOpenSelect.value, 10);
            const isConflict = (copyCtrl.checked === openCtrl.checked && copyAlt.checked === openAlt.checked && copyShift.checked === openShift.checked && bCopy === bOpen);

            const errorElement = document.getElementById('trigger-error');
            const saveButton = document.getElementById('btn-save');

            errorElement.style.display = isConflict ? 'block' : 'none';
            saveButton.disabled = isConflict;
            Object.assign(saveButton.style, isConflict ? { opacity: '0.4', cursor: 'not-allowed', pointerEvents: 'none' } : { opacity: '1', cursor: 'pointer', pointerEvents: 'auto' });
        };

        copyCtrl.onchange = () => { syncPillState(copyCtrl, lblCopyCtrl); checkTriggerConflict(); };
        copyAlt.onchange = () => { syncPillState(copyAlt, lblCopyAlt); checkTriggerConflict(); };
        copyShift.onchange = () => { syncPillState(copyShift, lblCopyShift); checkTriggerConflict(); };
        openCtrl.onchange = () => { syncPillState(openCtrl, lblOpenCtrl); checkTriggerConflict(); };
        openAlt.onchange = () => { syncPillState(openAlt, lblOpenAlt); checkTriggerConflict(); };
        openShift.onchange = () => { syncPillState(openShift, lblOpenShift); checkTriggerConflict(); };
        openForeground.onchange = () => syncForegroundState();
        mouseCopySelect.onchange = checkTriggerConflict;
        mouseOpenSelect.onchange = checkTriggerConflict;

        select.value = initialStyle;
        if(select.value === 'custom') { textarea.style.display = 'block'; textarea.value = initialCustomCSS; }

        const updateLivePreview = () => {
            if (select.value === 'custom') { textarea.style.display = 'block'; StylesManager.apply('custom', textarea.value); }
            else { textarea.style.display = 'none'; StylesManager.apply(select.value); }
        };

        const setupWheel = (element) => {
            element.addEventListener('wheel', (e) => {
                e.preventDefault(); e.stopPropagation();
                let idx = element.selectedIndex + (e.deltaY > 0 ? 1 : -1);
                element.selectedIndex = Math.max(0, Math.min(element.options.length - 1, idx));
                if (element === select) updateLivePreview();
                else checkTriggerConflict();
            }, { passive: false });
        };

        setupWheel(select); setupWheel(mouseCopySelect); setupWheel(mouseOpenSelect);
        select.onchange = updateLivePreview;
        textarea.oninput = updateLivePreview;

        const cancelAction = () => {
            removeModalListeners();
            StylesManager.apply(initialStyle, initialCustomCSS);
            overlay.remove();
        };

        const saveAction = () => {
            const bCopy = parseInt(mouseCopySelect.value, 10), bOpen = parseInt(mouseOpenSelect.value, 10);
            if (copyCtrl.checked === openCtrl.checked && copyAlt.checked === openAlt.checked && copyShift.checked === openShift.checked && bCopy === bOpen) return;

            GM_setValue('copyCtrl', copyCtrl.checked); GM_setValue('copyAlt', copyAlt.checked); GM_setValue('copyShift', copyShift.checked); GM_setValue('copyButton', bCopy);
            GM_setValue('openCtrl', openCtrl.checked); GM_setValue('openAlt', openAlt.checked); GM_setValue('openShift', openShift.checked); GM_setValue('openButton', bOpen);
            GM_setValue('openInForeground', openForeground.checked); GM_setValue('selectedStyle', select.value);
            if(select.value === 'custom') GM_setValue('customCSS', textarea.value);

            const saveBtn = document.getElementById('btn-save');
            if (saveBtn) saveBtn.textContent = "Enregistré ! ✅";

            setTimeout(() => {
                removeModalListeners();
                StylesManager.apply(select.value);
                overlay.remove();
            }, 400);
        };

        const resetAction = () => {
            // Remise à zéro vers les valeurs d'usine
            copyCtrl.checked = true; copyAlt.checked = false; copyShift.checked = false;
            mouseCopySelect.value = MOUSE.RIGHT;
            openCtrl.checked = true; openAlt.checked = false; openShift.checked = true; // Ctrl + Shift + Clic Droit
            mouseOpenSelect.value = MOUSE.RIGHT; openForeground.checked = false;

            syncPillState(copyCtrl, lblCopyCtrl); syncPillState(copyAlt, lblCopyAlt); syncPillState(copyShift, lblCopyShift);
            syncPillState(openCtrl, lblOpenCtrl); syncPillState(openAlt, lblOpenAlt); syncPillState(openShift, lblOpenShift);
            syncForegroundState();
            checkTriggerConflict();
        };

        document.getElementById('btn-save').onclick = saveAction;
        document.getElementById('btn-cancel').onclick = cancelAction;
        document.getElementById('btn-reset').onclick = resetAction;
        overlay.onclick = (e) => { if(e.target === overlay) cancelAction(); };

        const eventTypes = ['keydown', 'keyup', 'keypress'];

        const modalKeyInterceptor = (e) => {
            const isInsideModal = e.target.closest('#gm-settings-popup');

            if (e.type === 'keydown') {
                if (e.key === 'Escape') { e.preventDefault(); e.stopImmediatePropagation(); cancelAction(); return; }

                if (e.key === 'Tab') {
                    const focusable = popup.querySelectorAll('input, select, textarea, button');
                    const visible = Array.from(focusable).filter(el => el.offsetWidth > 0 || el.offsetHeight > 0);
                    if (visible.length > 0) {
                        const first = visible[0];
                        const last = visible[visible.length - 1];
                        if (e.shiftKey) {
                            if (e.target === first) { last.focus(); e.preventDefault(); e.stopImmediatePropagation(); return; }
                        } else {
                            if (e.target === last) { first.focus(); e.preventDefault(); e.stopImmediatePropagation(); return; }
                        }
                    }
                }

                if (e.key === 'Enter' && e.target.tagName !== 'TEXTAREA') {
                    e.preventDefault(); e.stopImmediatePropagation();
                    const sBtn = document.getElementById('btn-save');
                    if(!sBtn.disabled) saveAction(); return;
                }
                if (e.altKey && e.key.toLowerCase() === 'e') {
                    e.preventDefault(); e.stopImmediatePropagation();
                    const sBtn = document.getElementById('btn-save');
                    if(!sBtn.disabled) saveAction(); return;
                }
                if (e.altKey && e.key.toLowerCase() === 'a') { e.preventDefault(); e.stopImmediatePropagation(); cancelAction(); return; }
                if (e.altKey && e.key.toLowerCase() === 'r') { e.preventDefault(); e.stopImmediatePropagation(); resetAction(); return; }
            }

            if (!isInsideModal) { e.preventDefault(); e.stopImmediatePropagation(); }
        };

        const removeModalListeners = () => eventTypes.forEach(type => window.removeEventListener(type, modalKeyInterceptor, true));
        eventTypes.forEach(type => window.addEventListener(type, modalKeyInterceptor, true));

        popup.addEventListener('keydown', (e) => e.stopPropagation());
        popup.addEventListener('keyup', (e) => e.stopPropagation());
        popup.addEventListener('keypress', (e) => e.stopPropagation());

        setTimeout(() => {
            const focusable = popup.querySelectorAll('input, select, textarea, button');
            const visible = Array.from(focusable).filter(el => el.offsetWidth > 0 || el.offsetHeight > 0);
            if (visible.length > 0) visible[0].focus();
        }, 50);
    }

    // ==========================================
    // 6. ÉCOUTEURS ET LOGIQUE GESTUELLE
    // ==========================================
    document.addEventListener('mousedown', (e) => {
        const matchCopy = (e.button === parseInt(GM_getValue('copyButton', MOUSE.RIGHT), 10) && e.ctrlKey === GM_getValue('copyCtrl', true) && e.altKey === GM_getValue('copyAlt', false) && e.shiftKey === GM_getValue('copyShift', false));
        const matchOpen = (e.button === parseInt(GM_getValue('openButton', MOUSE.RIGHT), 10) && e.ctrlKey === GM_getValue('openCtrl', true) && e.altKey === GM_getValue('openAlt', false) && e.shiftKey === GM_getValue('openShift', true));

        if (matchCopy || matchOpen) {
            e.preventDefault();
            State.isDragging = true;
            State.actionType = matchCopy ? ACTIONS.COPY : ACTIONS.OPEN;
            State.liveForeground = GM_getValue('openInForeground', false);
            State.shouldBlockContextMenu = (e.button === MOUSE.RIGHT);

            State.startPageX = e.clientX + window.scrollX;
            State.startPageY = e.clientY + window.scrollY;
            State.clientMouseX = e.clientX;
            State.clientMouseY = e.clientY;

            LassoEngine.cacheGeometries();

            Object.assign(UI.lasso.style, { left: State.startPageX + 'px', top: State.startPageY + 'px', width: '0px', height: '0px', display: 'block' });
        } else {
            State.shouldBlockContextMenu = false;
        }
    });

    document.addEventListener('mousemove', (e) => {
        if (!State.isDragging) return;
        State.clientMouseX = e.clientX;
        State.clientMouseY = e.clientY;

        if (!State.rafId) {
            State.rafId = requestAnimationFrame(LassoEngine.render);
        }
    });

    window.addEventListener('scroll', () => {
        if (!State.isDragging) return;
        if (!State.rafId) {
            State.rafId = requestAnimationFrame(LassoEngine.render);
        }
    }, { passive: true });

    document.addEventListener('keydown', (e) => {
        if(e.key === 'Escape' && State.isDragging) { resetUI(); return; }

        if (State.isDragging) {
            if (e.key.toLowerCase() === 'a' && State.actionType === ACTIONS.OPEN) {
                e.preventDefault();
                State.liveForeground = !State.liveForeground;
                UI.updateCounter();
                return;
            }

            clearTimeout(State.keyupTimeout);
            const currentAction = getActionFromModifiers(e);
            if (currentAction && currentAction !== State.actionType) {
                State.actionType = currentAction;
                UI.updateCounter();
            }
        }
    });

    document.addEventListener('keyup', (e) => {
        if (State.isDragging) {
            const currentAction = getActionFromModifiers(e);
            if (currentAction && currentAction !== State.actionType) {
                clearTimeout(State.keyupTimeout);
                State.keyupTimeout = setTimeout(() => {
                    if (State.isDragging) {
                        State.actionType = currentAction;
                        UI.updateCounter();
                    }
                }, 100);
            } else if (!currentAction) {
                clearTimeout(State.keyupTimeout);
            }
        }
    });

    document.addEventListener('mouseup', () => {
        if(State.isDragging) {
            clearTimeout(State.keyupTimeout);
            if(State.rafId) { cancelAnimationFrame(State.rafId); State.rafId = null; }

            if(State.selectedLinks.length) {
                if (State.actionType === ACTIONS.COPY) {
                    GM_setClipboard(State.selectedLinks.join('\n'));
                    UI.showToast(`${State.selectedLinks.length} liens copiés !`);
                } else if (State.actionType === ACTIONS.OPEN) {
                    if (State.selectedLinks.length > MAX_TABS_SECURITY) {
                        if (!confirm(`⚠️ Attention : Vous allez ouvrir ${State.selectedLinks.length} onglets en même temps. Continuer ?`)) {
                            resetUI();
                            return;
                        }
                    }
                    State.selectedLinks.forEach(url => GM_openInTab(url, { active: State.liveForeground, insert: true }));
                    UI.showToast(`${State.selectedLinks.length} onglets ouverts !`);
                }
            }
            resetUI();
        }
    });

    document.addEventListener('contextmenu', (e) => { if (State.shouldBlockContextMenu) { e.preventDefault(); State.shouldBlockContextMenu = false; } });

    function resetUI() {
        State.isDragging = false; State.actionType = null; State.cachedLinksGeometry = [];
        clearTimeout(State.keyupTimeout);
        UI.lasso.style.display = UI.counter.style.display = 'none';
        document.querySelectorAll('.lasso-selected').forEach(a => { if(a.id !== 'preview-link') a.classList.remove('lasso-selected'); });
    }

    // --- Initialisation ---
    StylesManager.init();
    UI.init();

    if (typeof GM_registerMenuCommand !== 'undefined') {
        GM_registerMenuCommand("⚙️ Configurer", createSettingsUI);
    }
})();