LecteurMedia API Library

Librairie pour l'intégration de médias sur des forums

Ce script ne devrait pas être installé directement. C'est une librairie créée pour d'autres scripts. Elle doit être inclus avec la commande // @require https://update.greatest.deepsurf.us/scripts/554422/1701167/LecteurMedia%20API%20Library.js

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

if (typeof window.LecteurMedia === 'undefined') {
    (function() {
        'use strict';
    
    const IS_DEV_MODE = false; // Mettre à false en production
    const baseUrl = IS_DEV_MODE 
        ? 'https://jvc-preview-proxy-test.lecteurmedia.workers.dev' 
        : 'https://jvc-preview-proxy.lecteurmedia.workers.dev';


    // =========================================================================
    // == FOURNISSEURS DE SERVICES
    // =========================================================================

    /**
     * Structure d'un objet Provider pour le Lecteur Media.
     * @typedef {Object} LecteurMediaProvider
     * @property {string} name - Le nom unique du provider.
     * @property {string} selector - Le sélecteur CSS pour les liens.
     * @property {'base'|'connect'|'wildcard'} category - La catégorie de permissions requises.
     * @property {string|string[]} [connect] - Le(s) domaine(s) requis pour @connect si category est 'connect'.
     * @property {function(HTMLAnchorElement, boolean): (HTMLElement|Promise<HTMLElement>|null)} createEmbedElement - Crée l'élément à intégrer.
     * @property {function(HTMLElement): void} [postProcess] - (Optionnel) Fonction post-intégration.
     */
    const allProviders  = [
        {
            name: 'Instagram',
            selector: 'a[href*="instagram.com/"]',
            category: 'base',
            scriptPromise: null,
            
            loadScript() {
                if (!this.scriptPromise) {
                    this.scriptPromise = new Promise((resolve) => {
                        if (typeof unsafeWindow !== 'undefined' && unsafeWindow.instgrm) return resolve();
                        const script = document.createElement('script');
                        script.async = true;
                        script.charset = 'utf-8';
                        script.src = 'https://www.instagram.com/embed.js';
                        script.onload = resolve;
                        document.head.appendChild(script);
                    });
                }
                return this.scriptPromise;
            },

            createEmbedElement(link, isDarkTheme) {
                try {
                    const url = new URL(link.href);
                    const pathParts = url.pathname.split('/').filter(p => p);
                    
                    const typeIndex = pathParts.findIndex(p => ['p', 'reel', 'tv'].includes(p));
                    
                    if (typeIndex !== -1 && pathParts[typeIndex + 1]) {
                        const shortcode = pathParts[typeIndex + 1];
                        const embedUrl = `https://www.instagram.com/p/${shortcode}/embed/captioned/`;

                        const container = document.createElement('div');
                        container.className = 'bloc-embed';
                        
                        container.innerHTML = `<iframe
                            src="${embedUrl}"
                            class="iframe-embed"
                            style="width: 400px; max-width: 100%; height: 550px; background: white; border: 1px solid #dbdbdb; border-radius: 4px;"
                            frameborder="0"
                            scrolling="no"
                            allowtransparency="true"
                            allowfullscreen="true">
                        </iframe>`;
                        return container;
                    }

                    this.loadScript();

                    let permalink = new URL(link.href);
                    permalink.search = '';
                    permalink.hash = '';
                    const cleanedUrl = permalink.href;

                    const blockquote = document.createElement('blockquote');
                    blockquote.className = 'instagram-media instagram-placeholder';
                    blockquote.setAttribute('data-instgrm-permalink', cleanedUrl);
                    blockquote.setAttribute('data-instgrm-version', '14');
                    blockquote.style.background = isDarkTheme ? '#2f3136' : '#f9f9f9';
                    blockquote.style.border = isDarkTheme ? '1px solid #444' : '1px solid #dbdbdb';
                    blockquote.style.minHeight = '450px'; 

                    const container = document.createElement('div');
                    container.className = 'bloc-embed';
                    container.appendChild(blockquote);
                    
                    return container;

                } catch (e) {
                    console.error("[Instagram] Erreur:", e);
                    return null;
                }
            },

            postProcess(element) {
                if (element.querySelector('blockquote.instagram-media')) {
                    this.loadScript().then(() => {
                        if (typeof unsafeWindow !== 'undefined' && unsafeWindow.instgrm && typeof unsafeWindow.instgrm.Embeds.process === 'function') {
                            unsafeWindow.instgrm.Embeds.process(element);
                        }
                    });
                }
            }
        },
        {
            name: 'Twitter',
            selector: 'a[href*="twitter.com/"], a[href*="x.com/"], a[href*="xcancel.com/"]',
            category: 'connect',
            connect: 'api.vxtwitter.com',
            createEmbedElement(link, isDarkTheme) {
                const parts = link.href.split('/');
                const statusIndex = parts.findIndex(p => p === 'status' || p === 'statuses');
                if (statusIndex === -1 || !parts[statusIndex + 1]) return null;

                const tweetId = parts[statusIndex + 1].split(/[?#]/)[0];
                const screenName = parts[statusIndex - 1];

                const placeholderContainer = document.createElement('div');
                placeholderContainer.className = 'bloc-embed';
                
                const skeletonElement = document.createElement('div');
                skeletonElement.className = 'tweet-skeleton';
                skeletonElement.innerHTML = `
                        <div class="sk-header">
                            <div class="sk-avatar"></div>
                            <div class="sk-meta">
                                <div class="sk-line short"></div>
                                <div class="sk-line medium"></div>
                            </div>
                        </div>
                        <div class="sk-line long"></div>`;
                
                placeholderContainer.appendChild(skeletonElement);

                (async () => {
                    try {
                        const settings = await SettingsManager.getSettings();
                        
                        const cacheKey = `twitter_v2_${tweetId}`;
                        let tweetData = await CacheManager.get(cacheKey);

                        if (!tweetData) {
                            try {
                                const response = await LecteurMedia.raceRequests([
                                    `https://api.vxtwitter.com/${screenName}/status/${tweetId}`,
                                    `https://api.fixupx.com/${screenName}/status/${tweetId}`
                                ]);

                                if (response.status === 200) {
                                    tweetData = JSON.parse(response.responseText);
                                    await CacheManager.set(cacheKey, tweetData);
                                }
                            } catch (e) {
                                console.warn("[Twitter] Echec des API:", e);
                            }
                        }

                        if (tweetData) {
                            let parentTweetHtml = '';
                            if (tweetData.replyingToStatus) {
                                try {
                                    const parentResponse = await LecteurMedia.compatibleHttpRequest({
                                        method: 'GET',
                                        url: `https://api.vxtwitter.com/${tweetData.replyingTo}/status/${tweetData.replyingToStatus}`
                                    });
                                    if (parentResponse.status === 200) {
                                        const parentTweet = JSON.parse(parentResponse.responseText);
                                        parentTweetHtml = `<div class="parent-tweet-container">${VxTwitterRenderer.render(parentTweet, true)}</div>`;
                                    }
                                } catch (e) { }
                            }

                            const mainTweetHtml = VxTwitterRenderer.render(tweetData, false);
                            
                            const tweetEmbed = document.createElement('div');
                            tweetEmbed.className = 'vxtwitter-embed';
                            if (settings.twitterFullHeight) tweetEmbed.classList.add('vxtwitter-full-height');
                            
                            tweetEmbed.innerHTML = parentTweetHtml + mainTweetHtml;

                            if (skeletonElement && skeletonElement.parentNode) {
                                skeletonElement.replaceWith(tweetEmbed);
                            } else {
                                placeholderContainer.innerHTML = '';
                                placeholderContainer.appendChild(tweetEmbed);
                            }

                            const showMoreButton = tweetEmbed.querySelector('.vxtwitter-show-more');
                            if (showMoreButton) {
                                showMoreButton.addEventListener('click', (e) => {
                                    e.preventDefault();
                                    const textElement = tweetEmbed.querySelector('.vxtwitter-text-collapsible');
                                    if (textElement) textElement.classList.add('vxtwitter-text-expanded');
                                    showMoreButton.style.display = 'none';
                                });
                            }

                            const nitterButton = tweetEmbed.querySelector('.vxtwitter-nitter-link');
                            if (nitterButton) {
                                nitterButton.addEventListener('click', (e) => {
                                    e.preventDefault();
                                    const mediaContent = placeholderContainer.querySelector('.media-content');
                                    if (!mediaContent) return;

                                    mediaContent.style.position = 'relative';
                                    mediaContent.style.height = `${mediaContent.offsetHeight}px`;

                                    const loader = document.createElement('div');
                                    loader.className = 'lm-loader-container';
                                    loader.innerHTML = `<div class="lm-spinner"></div><div class="lm-loader-text">Chargement Nitter...</div>`;
                                    mediaContent.appendChild(loader);

                                    const nitterUrl = link.href.replace(/x\.com|twitter\.com/, 'nitter.net');
                                    const iframe = document.createElement('iframe');
                                    iframe.src = nitterUrl;
                                    iframe.className = "iframe-embed iframe-twitter";
                                    iframe.style.cssText = "height: 0; opacity: 0; transition: opacity 0.4s ease, height 0.4s ease;";
                                    
                                    iframe.onload = () => {
                                        loader.style.opacity = '0';
                                        setTimeout(() => loader.remove(), 300);
                                        const targetHeight = '80vh';
                                        mediaContent.style.height = targetHeight;
                                        iframe.style.height = targetHeight;
                                        iframe.style.opacity = '1';
                                    };

                                    tweetEmbed.replaceWith(iframe);
                                    
                                    const header = placeholderContainer.querySelector('.embed-header');
                                    if (header) header.style.cssText = "transition: all 0.3s; opacity: 0; height: 0; margin: 0; overflow:hidden;";
                                });
                            }
                        
                            const statsGroup = tweetEmbed.querySelector('.vxtwitter-stats-group');
                            if (statsGroup) setupStatCarousel(statsGroup);

                        } else {
                            const errorDiv = document.createElement('div');
                            errorDiv.className = 'placeholder-embed tweet-unavailable-placeholder';
                            errorDiv.innerHTML = `
                                <div class="icon-container">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z"></path></svg>
                                </div>
                                <div class="text-container">
                                    <strong>Tweet indisponible</strong>
                                    <a href="${link.href}" target="_blank">Ouvrir le lien original</a>
                                </div>`;
                            
                            if (skeletonElement && skeletonElement.parentNode) {
                                skeletonElement.replaceWith(errorDiv);
                            } else {
                                placeholderContainer.innerHTML = '';
                                placeholderContainer.appendChild(errorDiv);
                            }
                        }

                    } catch (error) {
                        console.error('[Twitter] Erreur fatale:', error);
                        if (skeletonElement && skeletonElement.parentNode) {
                            skeletonElement.innerHTML = 'Erreur de chargement.';
                            skeletonElement.className = 'placeholder-embed';
                        }
                    }
                })();

                return placeholderContainer;
            }
        },
        {
            name: 'TikTok',
            selector: 'a[href*="tiktok.com/"]',
            category: 'connect',
            connect: ['vm.tiktok.com', 'vt.tiktok.com', 'v.tiktok.com', 't.tiktok.com'],
            async createEmbedElement(link) {
                try {
                    let finalUrl = link.href;
                    const hostname = new URL(link.href).hostname;

                    const shortenerDomains = ['vm.tiktok.com', 'vt.tiktok.com', 'v.tiktok.com', 't.tiktok.com'];
                    if (shortenerDomains.includes(hostname)) {
                        const response = await LecteurMedia.compatibleHttpRequest({
                            method: 'HEAD',
                            url: finalUrl
                        });
                        finalUrl = response.finalUrl || finalUrl;
                    }

                    const match = finalUrl.match(/\/(?:video|photo)\/(\d+)/);
                    if (!match || !match[1]) {
                        console.warn('[TikTok] Le lien ne semble pas être une vidéo ou une photo valide :', finalUrl);
                        return null;
                    }

                    const postId = match[1];
                    const iframeUrl = `https://www.tiktok.com/embed/v2/${postId}?lang=fr-FR&referrer=${encodeURIComponent(window.location.href)}`;

                    const container = document.createElement('div');
                    container.className = 'bloc-embed';
                    container.innerHTML = `<iframe
                        src="${iframeUrl}"
                        class="iframe-embed iframe-tiktok iframe-vertical-content"
                        title="TikTok Post"
                        sandbox="allow-scripts allow-same-origin allow-popups"
                        allow="autoplay; encrypted-media"></iframe>`;

                    return container;

                } catch (error) {
                    console.error('[TikTok] Erreur lors de la création de l\'embed :', error);
                    return null;
                }
            },
        },
        {
          name: 'Streamable',
          selector: 'a[href*="streamable.com/"]',
          category: 'connect',
          connect: 'api.streamable.com',
          createEmbedElement(link) {
              const cleanUrl = link.href.replace('/e/', '/');
              const httpsUrl = cleanUrl.replace('http://', 'https://');
              const iframeUrl = httpsUrl.replace('.com/', '.com/e/');

              const container = document.createElement('div');
              container.className = 'bloc-embed';

              const iframe = document.createElement('iframe');
              iframe.src = iframeUrl;
              iframe.className = 'iframe-embed iframe-streamable';
              iframe.title = "Streamable Video";
              iframe.setAttribute('allowfullscreen', '');
              iframe.style.aspectRatio = '16 / 9';
              container.appendChild(iframe);

              (async () => {
                  try {
                      const response = await LecteurMedia.compatibleHttpRequest({
                          method: 'GET',
                          url: `https://api.streamable.com/oembed.json?url=${encodeURIComponent(cleanUrl)}`
                      });

                      if (response.status >= 200 && response.status < 300) {
                          const data = JSON.parse(response.responseText);
                          if (data.width && data.height) {
                              if (data.height > data.width) {
                                  iframe.classList.add('iframe-vertical-content');
                              }
                              iframe.style.aspectRatio = `${data.width} / ${data.height}`;
                          }
                      }
                  } catch (error) {
                      console.error('[Streamable API] Erreur lors de la récupération des métadonnées :', error);
                  }
              })();

              return container;
          }
        },
        {
            name: 'Webmshare',
            selector: 'a[href*="webmshare.com/"]',
            category: 'base',
            createEmbedElement(link) {
                if (!/webmshare\.com\/(play\/)?[\w]+$/.test(link.href)) return null;
                const videoId = link.pathname.split('/').pop();
                if (!videoId) return null;
                const videoUrl = `https://s1.webmshare.com/${videoId}.webm`;
                const container = document.createElement('div');
                container.className = 'bloc-embed';
                container.innerHTML = `<video src="${videoUrl}" class="video-embed" controls autoplay muted loop playsinline></video>`;
                return container;
            }
        },
        {
            name: 'YouTube',
            selector: 'a[href*="youtube.com/watch"], a[href*="youtu.be/"], a[href*="youtube.com/shorts/"], a[href*="youtube.com/live/"]',
            category: 'base',
            parseYoutubeTime(url) {
                try {
                    const urlObj = new URL(url);
                    const timeParamString = urlObj.searchParams.get('t') || urlObj.searchParams.get('start') || (urlObj.hash.includes('t=') ? urlObj.hash.split('t=')[1] : null);

                    if (!timeParamString) return null;

                    let totalSeconds = 0;
                    const timeMatches = timeParamString.matchAll(/(\d+)([hms])/g);
                    let foundMatch = false;

                    for (const match of timeMatches) {
                        foundMatch = true;
                        const value = parseInt(match[1], 10);
                        const unit = match[2];

                        if (unit === 'h') totalSeconds += value * 3600;
                        if (unit === 'm') totalSeconds += value * 60;
                        if (unit === 's') totalSeconds += value;
                    }

                    if (!foundMatch && /^\d+$/.test(timeParamString)) {
                        totalSeconds = parseInt(timeParamString, 10);
                    }

                    return totalSeconds > 0 ? totalSeconds : null;

                } catch(e) {
                    console.error("[YouTube Time Parser] Erreur:", e);
                    return null;
                }
            },

            createEmbedElement(link) {
                const youtubeRegex = /(?:[?&]v=|\/shorts\/|\/live\/|youtu\.be\/)([^?&/\s]{11})/;
                const match = link.href.match(youtubeRegex);

                if (!match || !match[1]) return null;
                const videoId = match[1];

                const isShort = link.href.includes('/shorts/');
                if (isShort) {
                    const iframeUrl = `https://www.youtube.com/embed/${videoId}`;
                    const iframeClasses = 'iframe-embed iframe-youtube-short iframe-vertical-content';
                    const container = document.createElement('div');
                    container.className = 'bloc-embed';
                    container.innerHTML = `<iframe src="${iframeUrl}" class="${iframeClasses}" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>`;
                    return container;
                }

                const startTime = this.parseYoutubeTime(link.href);

                const params = new URLSearchParams();
                if (startTime) params.append('start', startTime);
                params.append('enablejsapi', '1');
                params.append('rel', '0');

                const container = document.createElement('div');
                container.className = 'youtube-facade-container';

                const iframe = document.createElement('iframe');
                iframe.className = 'iframe-embed iframe-youtube';
                iframe.title = "YouTube video player";
                iframe.src = `https://www.youtube.com/embed/${videoId}?${params.toString()}`;
                iframe.setAttribute('frameborder', '0');
                iframe.setAttribute('allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share');
                iframe.setAttribute('allowfullscreen', '');

                const facade = document.createElement('div');
                facade.className = 'youtube-facade-overlay';

                container.appendChild(iframe);
                container.appendChild(facade);

                facade.addEventListener('click', () => {
                    try {
                        iframe.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', 'https://www.youtube.com');
                    } catch (e) {
                        console.error("Erreur lors de l'envoi du message à l'iframe YouTube:", e);
                    }
                    facade.remove();
                }, { once: true });

                const finalContainer = document.createElement('div');
                finalContainer.className = 'bloc-embed';
                finalContainer.appendChild(container);

                return finalContainer;
            }
        },
        {
            name: 'Facebook',
            selector: `
                a[href*="facebook.com/posts/"],
                a[href*="facebook.com/videos/"],
                a[href*="facebook.com/photos/"],
                a[href*="facebook.com/photo/"],
                a[href*="facebook.com/reel/"],
                a[href*="facebook.com/share/"],
                a[href*="facebook.com/photo.php"],
                a[href*="facebook.com/"]
            `,
            category: 'connect',
            connect: 'facebook.com',
            scriptPromise: null,
            loadScript() {
                if (!this.scriptPromise) {
                    this.scriptPromise = new Promise((resolve) => {
                        if (typeof unsafeWindow !== 'undefined' && unsafeWindow.FB) return resolve();
                        unsafeWindow.fbAsyncInit = function() {
                            unsafeWindow.FB.init({ xfbml: true, version: 'v18.0' });
                            resolve();
                        };
                        const script = document.createElement('script');
                        script.async = true; script.defer = true; script.crossOrigin = 'anonymous';
                        script.src = 'https://connect.facebook.net/fr_FR/sdk.js';
                        document.head.appendChild(script);
                    });
                }
                return this.scriptPromise;
            },
            async createEmbedElement(link) {
                this.loadScript();

                let embedUrl = link.href;

                if (link.href.includes('/share/')) {
                    try {
                        const response = await LecteurMedia.compatibleHttpRequest({
                            method: 'HEAD',
                            url: link.href
                        });
                        embedUrl = response.finalUrl || link.href;

                    } catch (error) {
                        console.error('[Facebook] Erreur de résolution du lien de partage:', error);
                    }
                }

                const container = document.createElement('div');
                container.className = 'bloc-embed';

                const isVideo = embedUrl.includes('/videos/') || embedUrl.includes('/reel/');
                const playerType = isVideo ? 'fb-video' : 'fb-post';
                const showText = isVideo ? 'false' : 'true';
                container.innerHTML = `<div class="${playerType}" data-href="${embedUrl}" data-width="550" data-show-text="${showText}"></div>`;
              console.log("facebook embed done");
                return container;
            },
            postProcess(element) {
                this.loadScript().then(() => {
                    if (typeof unsafeWindow !== 'undefined' && unsafeWindow.FB) {
                        unsafeWindow.FB.XFBML.parse(element);
                    }
                });
            }
        },
        {
            name: 'Twitch',
            selector: 'a[href*="twitch.tv/"]',
            category: 'base',
            createEmbedElement(link) {
                const parentHostname = window.location.hostname;

                try {
                    const url = new URL(link.href);
                    const pathParts = url.pathname.split('/').filter(p => p);
                    let iframeSrc = null;

                    if (url.hostname === 'clips.twitch.tv' || pathParts.includes('clip')) {
                        const clipId = pathParts[pathParts.length - 1];
                        if (clipId) {
                            iframeSrc = `https://clips.twitch.tv/embed?clip=${clipId}&parent=${parentHostname}`;
                        }
                    }
                    else if (pathParts[0] === 'videos' && pathParts[1]) {
                        const videoId = pathParts[1];
                        iframeSrc = `https://player.twitch.tv/?video=${videoId}&parent=${parentHostname}`;
                    }
                    // Gère les directs : twitch.tv/CHANNEL_NAME
                    else if (pathParts.length === 1 && pathParts[0]) {
                        const channelName = pathParts[0];
                        iframeSrc = `https://player.twitch.tv/?channel=${channelName}&parent=${parentHostname}`;
                    }

                    if (!iframeSrc) return null;

                    const container = document.createElement('div');
                    container.className = 'bloc-embed';
                    container.innerHTML = `<iframe
                        src="${iframeSrc}"
                        class="iframe-embed iframe-twitch"
                        title="Lecteur vidéo Twitch"
                        allowfullscreen="true"
                        frameborder="0">
                    </iframe>`;
                    return container;

                } catch (e) {
                    console.error('[Twitch] Erreur lors de la création de l\'embed :', e);
                    return null;
                }
            }
        },
        {
            name: 'Vocaroo',
            selector: 'a[href*="vocaroo.com/"], a[href*="voca.ro/"]',
            category: 'base',
            createEmbedElement(link) {
                try {
                    let audioId = link.pathname.split('/').pop();
                    
                    if (!audioId || link.pathname === '/') return null;

                    if (/^s\d/.test(audioId)) {
                        audioId = audioId.substring(2); 
                    }

                    const iframeSrc = `https://vocaroo.com/embed/${audioId}?autoplay=0`;

                    const container = document.createElement('div');
                    container.className = 'bloc-embed';
                    container.innerHTML = `<iframe
                        src="${iframeSrc}"
                        class="iframe-embed iframe-vocaroo"
                        title="Lecteur audio Vocaroo"
                        frameborder="0"
                        allow="autoplay">
                    </iframe>`;

                    return container;

                } catch (e) {
                    console.error('[Vocaroo] Erreur lors de la création de l\'embed :', e);
                    return null;
                }
            }
        },
        {
          name: 'Reddit',
          selector: 'a[href*="reddit.com/"]',
          category: 'connect',
          connect: 'www.reddit.com',
          async createEmbedElement(link, isDarkTheme) {
              IframeResizeManager.init();

              try {
                  let finalUrl;
                  if (link.pathname.includes('/s/')) {
                      const response = await LecteurMedia.compatibleHttpRequest({
                          method: 'HEAD',
                          url: link.href
                      });

                      finalUrl = response.finalUrl || link.href;
                  } else {
                      finalUrl = link.href;
                  }

                  const urlObject = new URL(finalUrl);

                  if (!urlObject.pathname.includes('/comments/')) {
                      return null;
                  }

                  urlObject.hostname = 'embed.reddit.com';
                  urlObject.searchParams.set('embed', 'true');
                  urlObject.searchParams.set('theme', isDarkTheme ? 'dark' : 'light');
                  urlObject.searchParams.set('showmedia', 'true');
                  urlObject.searchParams.set('showmore', 'false');

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      src="${urlObject.toString()}"
                      class="iframe-embed iframe-reddit"
                      title="Contenu Reddit intégré"
                      sandbox="allow-scripts allow-same-origin allow-popups"
                      height="600"
                      allowfullscreen>
                  </iframe>`;

                  return container;

              } catch (e) {
                  console.error('[Reddit] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
        },
        {
          name: 'Vimeo',
          selector: 'a[href*="vimeo.com/"]',
          category: 'base',
          createEmbedElement(link) {
              const match = link.href.match(/vimeo\.com\/(?:video\/)?(\d+)/);
              if (!match || !match[1]) return null;

              const videoId = match[1];
              const iframeUrl = `https://player.vimeo.com/video/${videoId}?autoplay=1&muted=1&loop=1`;

              const container = document.createElement('div');
              container.className = 'bloc-embed';
              container.innerHTML = `<iframe
                  src="${iframeUrl}"
                  class="iframe-embed iframe-youtube"
                  title="Vimeo video player"
                  allow="autoplay; fullscreen; picture-in-picture"
                  frameborder="0"
                  allowfullscreen>
              </iframe>`;
              return container;
          }
        },
        {
          name: 'Dailymotion',
          selector: 'a[href*="dailymotion.com/video/"], a[href*="dai.ly/"]',
          category: 'base',
          createEmbedElement(link) {
              const url = new URL(link.href);
              const pathParts = url.pathname.split('/');
              let videoIdWithSlug = null;

              if (link.hostname.includes('dai.ly')) {
                  videoIdWithSlug = pathParts[1];
              } else {
                  const videoIndex = pathParts.findIndex(p => p === 'video');
                  if (videoIndex !== -1 && pathParts[videoIndex + 1]) {
                      videoIdWithSlug = pathParts[videoIndex + 1];
                  }
              }

              if (!videoIdWithSlug) return null;
              const videoId = videoIdWithSlug.split('_')[0];

              const embedUrl = new URL(`https://www.dailymotion.com/embed/video/${videoId}`);
              embedUrl.search = url.search;

              const container = document.createElement('div');
              container.className = 'bloc-embed';
              container.innerHTML = `<iframe
                  src="${embedUrl.href}"
                  class="iframe-embed iframe-youtube"
                  title="Dailymotion video player"
                  allow="autoplay; fullscreen; picture-in-picture"
                  frameborder="0"
                  allowfullscreen>
              </iframe>`;
              return container;
          }
        },
        {
          name: 'SoundCloud',
          selector: 'a[href*="soundcloud.com/"]',
          category: 'connect',
          connect: 'soundcloud.com',
          async createEmbedElement(link) {
              const pathParts = new URL(link.href).pathname.split('/').filter(p => p);
              if (pathParts.length < 2) return null;

              try {
                  const apiUrl = `https://soundcloud.com/oembed?format=json&url=${encodeURIComponent(link.href)}&maxheight=166&color=%23ff5500&auto_play=false&show_comments=false`;
                  const response = await LecteurMedia.compatibleHttpRequest({
                      method: 'GET',
                      url: apiUrl
                  });

                  if (response.status < 200 || response.status >= 300) {
                      throw new Error(`L'API SoundCloud a retourné le statut ${response.status}`);
                  }

                  const data = JSON.parse(response.responseText);
                  if (!data || !data.html) {
                      console.warn('[SoundCloud] Pas de HTML d\'intégration dans la réponse API pour', link.href);
                      return null;
                  }

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = data.html;
                  return container;

              } catch (error) {
                  console.error('[SoundCloud] Erreur lors de la récupération de l\'embed :', error);
                  return null;
              }
          }
        },
        {
          name: 'StrawPoll',
          selector: 'a[href*="strawpoll.com/"]',
          category: 'base',
          createEmbedElement(link) {
              const url = new URL(link.href);
              const pathParts = url.pathname.split('/').filter(p => p);

              let pollId = null;
              if (pathParts[0] === 'polls' && pathParts[1]) {
                  pollId = pathParts[1];
              } else if (pathParts.length === 1 && pathParts[0]) {
                  pollId = pathParts[0];
              }

              if (!pollId) return null;

              const iframeUrl = `https://strawpoll.com/embed/${pollId}`;

              const container = document.createElement('div');
              container.className = 'bloc-embed';
              container.innerHTML = `<iframe
                  src="${iframeUrl}"
                  class="iframe-embed"
                  style="width: 100%; height: 450px; border: 0;"
                  title="Sondage StrawPoll">
              </iframe>`;
              return container;
          }
        },
        {
          name: 'Imgur',
          selector: 'a[href*="imgur.com/"]',
          category: 'base',
          createEmbedElement(link) {
              try {
                  const url = new URL(link.href);
                   const container = document.createElement('div');
                  container.className = 'bloc-embed';

                  const isDirectMedia = url.hostname === 'i.imgur.com' || /\.(mp4|gifv|webm|jpg|jpeg|png|gif)$/i.test(url.pathname);

                  if (isDirectMedia) {
                      if (/\.(mp4|gifv|webm)$/i.test(url.pathname)) {
                          let videoUrl = url.href.replace('.gifv', '.mp4');
                          container.innerHTML = `<video src="${videoUrl}" class="video-embed" controls autoplay muted loop playsinline></video>`;
                      }
                      else {
                          container.innerHTML = `<img src="${url.href}" class="image-embed" alt="Image depuis Imgur" loading="lazy">`;
                      }
                      return container;
                  }

                 const pathParts = url.pathname.split('/').filter(p => p);
                  if (pathParts.length === 0 || ['upload', 'search'].includes(pathParts[0])) {
                      return null;
                  }

                  const embedId = pathParts.join('/').replace(/\.[^/.]+$/, "");
                  container.classList.add('imgur-embed');

                  const blockquote = document.createElement('blockquote');
                  blockquote.className = 'imgur-embed-pub';
                  blockquote.lang = 'en';
                  blockquote.setAttribute('data-id', embedId);
                  blockquote.innerHTML = `<a href="//imgur.com/${embedId}">${link.textContent || 'Voir sur Imgur'}</a>`;

                  const script = document.createElement('script');
                  script.async = true;
                  script.src = 'https://s.imgur.com/min/embed.js';
                  script.charset = 'utf-8';

                  container.appendChild(blockquote);
                  container.appendChild(script);

                  return container;


              } catch (e) {
                  console.error(`[Imgur] Échec final pour trouver une image valide pour ${link.href}:`, e);
                  const stickerUrl = 'https://risibank.fr/cache/medias/0/5/512/51206/thumb.png';
                  const deadLinkContainer = document.createElement('div');
                  deadLinkContainer.className = 'bloc-embed';
                  deadLinkContainer.innerHTML = `<div class="dead-link-sticker"><img src="${stickerUrl}" alt="[Média supprimé]"><span>[Média supprimé]</span></div>`;
                  return deadLinkContainer;
              }
              return null;
          }
        },
        {
          name: 'Flickr',
          selector: 'a[href*="flickr.com/photos/"], a[href*="flic.kr/p/"]',
          category: 'connect',
          connect: 'www.flickr.com',
          async createEmbedElement(link) {
              try {
                  const apiUrl = `https://www.flickr.com/services/oembed/?url=${encodeURIComponent(link.href)}&format=json&maxwidth=550`;
                  const response = await LecteurMedia.compatibleHttpRequest({
                      method: 'GET',
                      url: apiUrl
                  });

                  if (response.status < 200 || response.status >= 300) {
                      throw new Error(`L'API Flickr a retourné le statut ${response.status}`);
                  }

                  const data = JSON.parse(response.responseText);
                  if (!data || !data.html) {
                      console.warn('[Flickr] Pas de HTML d\'intégration dans la réponse API pour', link.href);
                      return null;
                  }

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = data.html;

                  return container;

              } catch (error) {
                  console.error('[Flickr] Erreur lors de la récupération de l\'embed :', error);
                  return null;
              }
          }
        },
        {
          name: 'Spotify',
          selector: 'a[href*="open.spotify.com/"]',
          category: 'base',
          createEmbedElement(link) {
              try {
                  const url = new URL(link.href);
                  if (!/\/(track|album|playlist|artist|episode|show)\//.test(url.pathname)) {
                      return null;
                  }

                  const embedUrl = url.href.replace(
                      'open.spotify.com/',
                      'open.spotify.com/embed/'
                  );

                  const height = url.pathname.includes('/track/') ? '152' : '352';

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      class="iframe-embed"
                      src="${embedUrl}"
                      style="height: ${height}px;"
                      frameborder="0"
                      allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
                      loading="lazy"
                      title="Lecteur Spotify intégré">
                  </iframe>`;

                  return container;

              } catch (e) {
                  console.error('[Spotify] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
        },
        {
          name: 'Zupimages',
          selector: 'a[href*="zupimages.net/"]',
          category: 'base',
          createEmbedElement(link) {
              try {
                  const url = new URL(link.href);
                  let imageUrl = null;

                  if (url.pathname.includes('viewer.php')) {
                      const imageId = url.searchParams.get('id');
                      if (imageId) {
                          imageUrl = `https://zupimages.net/up/${imageId}`;
                      }
                  }
                  else if (url.pathname.startsWith('/up/') && /\.(jpe?g|png|gif|webp)$/i.test(url.pathname)) {
                      imageUrl = link.href;
                  }

                  if (!imageUrl) {
                      return null;
                  }

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<img src="${imageUrl}" class="image-embed" alt="Image depuis Zupimages" loading="lazy">`;
                  return container;

              } catch (e) {
                  console.error('[Zupimages] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
        },
        {
          name: 'Giphy',
          selector: 'a[href*="giphy.com/"], a[href*="gph.is/"]',
          category: 'connect',
          connect: 'gph.is',
          async createEmbedElement(link) {
              try {
                  let finalUrl = link.href;

                 if (link.hostname === 'gph.is') {
                      const response = await LecteurMedia.compatibleHttpRequest({
                          method: 'HEAD',
                          url: link.href
                      });

                      finalUrl = response.finalUrl || link.href;
                  }

                  const url = new URL(finalUrl);
                  let gifId = null;

                  if (url.hostname === 'media.giphy.com' && url.pathname.includes('/media/')) {
                      const pathParts = url.pathname.split('/');
                      if (pathParts.length > 2 && pathParts[1] === 'media') {
                          gifId = pathParts[2];
                      }
                  } else if (url.hostname === 'giphy.com' && url.pathname.includes('/gifs/')) {
                      const lastPart = url.pathname.split('/').filter(p => p).pop();
                      if (lastPart) {
                          gifId = lastPart.split('-').pop();
                      }
                  }

                  if (!gifId) {
                      return null;
                  }

                  const iframeUrl = `https://giphy.com/embed/${gifId}`;

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      src="${iframeUrl}"
                      class="iframe-embed iframe-youtube"
                      title="Giphy embed"
                      frameborder="0"
                      allowfullscreen>
                  </iframe>`;
                  return container;

              } catch (e) {
                  console.error('[Giphy] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
        },
        {
          name: 'Telegram',
          selector: 'a[href*="t.me/"]',
          category: 'base',
          createEmbedElement(link, isDarkTheme) {
              try {
                  const url = new URL(link.href);
                  const pathParts = url.pathname.split('/').filter(p => p);

                  if (pathParts.length < 2 || !/^\d+$/.test(pathParts[1])) {
                      return null;
                  }

                  const postData = `${pathParts[0]}/${pathParts[1]}`;

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';

                  const script = document.createElement('script');
                  script.async = true;
                  script.src = 'https://telegram.org/js/telegram-widget.js?22';
                  script.setAttribute('data-telegram-post', postData);
                  script.setAttribute('data-width', '100%');
                  script.setAttribute('data-userpic', 'true');

                  if (isDarkTheme) {
                      script.setAttribute('data-dark', '1');
                  }

                  container.appendChild(script);

                  return container;

              } catch (e) {
                  console.error('[Telegram] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
        },
        {
          name: 'GoogleDrive',
          selector: 'a[href*="drive.google.com/"], a[href*="docs.google.com/"]',
          category: 'base',
          createEmbedElement(link) {
              try {
                  const url = new URL(link.href);
                  let embedUrl = null;
                  let iframeClass = 'iframe-embed iframe-google-drive';

                  const docMatch = url.pathname.match(/\/(document|spreadsheets|presentation|drawings)\/d\/([^/]+)/);
                  if (docMatch) {
                      const docType = docMatch[1];
                      const docId = docMatch[2];
                      const embedType = (docType === 'presentation' || docType === 'drawings') ? 'embed' : 'preview';
                      embedUrl = `https://docs.google.com/${docType}/d/${docId}/${embedType}`;

                      if (docType === 'presentation') {
                          iframeClass = 'iframe-embed iframe-google-slides';
                      }
                  }

                  const formMatch = url.pathname.match(/\/forms\/d\/e\/([^/]+)/);
                  if (formMatch) {
                      const formId = formMatch[1];
                      embedUrl = `https://docs.google.com/forms/d/e/${formId}/viewform?embedded=true`;
                  }

                  const fileMatch = url.pathname.match(/\/file\/d\/([^/]+)/);
                  if (fileMatch) {
                      const fileId = fileMatch[1];
                      embedUrl = `https://drive.google.com/file/d/${fileId}/preview`;
                  }

                  const folderMatch = url.pathname.match(/\/drive\/folders\/([^/]+)/);
                  if (folderMatch) {
                      const folderId = folderMatch[1];
                      embedUrl = `https://drive.google.com/embeddedfolderview?id=${folderId}#list`;
                  }

                  if (!embedUrl) {
                      return null;
                  }

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe src="${embedUrl}" class="${iframeClass}" frameborder="0" allowfullscreen></iframe>`;

                  return container;

              } catch (e) {
                  console.error('[GoogleDrive] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
        },
        {
          name: 'IssouTV',
          selector: 'a[href*="issoutv.com/videos/"]',
          category: 'base',
          createEmbedElement(link) {
              try {
                  const videoId = new URL(link.href).pathname.split('/').pop();

                  if (!videoId) return null;
                  const videoUrl = `https://issoutv.com/storage/videos/${videoId}.webm`;

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<video
                      src="${videoUrl}"
                      class="video-embed"
                      controls
                      autoplay
                      muted
                      loop
                      playsinline>
                  </video>`;

                  return container;

              } catch (e) {
                  console.error('[IssouTV] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
        },
        {
          name: 'Bilibili',
          selector: 'a[href*="bilibili.com/video/"]',
          category: 'base',
          createEmbedElement(link) {
              try {
                  const match = link.href.match(/\/video\/(BV[a-zA-Z0-9]+)/);
                  if (!match || !match[1]) {
                      return null;
                  }

                  const videoId = match[1];
                  const iframeUrl = `https://player.bilibili.com/player.html?bvid=${videoId}&page=1&high_quality=1`;

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      src="${iframeUrl}"
                      class="iframe-embed iframe-youtube"
                      title="Lecteur vidéo Bilibili"
                      scrolling="no"
                      border="0"
                      frameborder="0"
                      framespacing="0"
                      allowfullscreen="true">
                  </iframe>`;

                  return container;

              } catch (e) {
                  console.error('[Bilibili] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
        },
        {
          name: 'Koreus',
          selector: 'a[href*="koreus.com/video/"]',
          category: 'base',
          createEmbedElement(link) {
              try {
                  const match = link.href.match(/\/video\/(.+?)\.html/);
                  if (!match || !match[1]) {
                      return null;
                  }

                  const videoId = match[1];
                  const iframeUrl = `https://www.koreus.com/embed/${videoId}`;

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';

                  container.innerHTML = `<iframe
                      src="${iframeUrl}"
                      class="iframe-embed iframe-youtube"
                      title="Lecteur vidéo Koreus"
                      frameborder="0"
                      allowfullscreen>
                  </iframe>`;

                  return container;

              } catch (e) {
                  console.error('[Koreus] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
        },
        {
          name: 'GoogleMaps',
          selector: 'a[href*="google.com/maps/place/"], a[href*="google.com/maps/@"], a[href*="maps.app.goo.gl/"]',
          category: 'connect',
          connect: 'maps.app.goo.gl',
          async createEmbedElement(link) {
              try {
                  let finalUrl = link.href;
                  if (link.hostname === 'maps.app.goo.gl') {
                      const response = await LecteurMedia.compatibleHttpRequest({
                          method: 'HEAD',
                          url: link.href
                      });

                      finalUrl = response.finalUrl || link.href;
                  }

                  const url = new URL(finalUrl);
                  let query = null;

                  const placeMatch = url.pathname.match(/\/place\/([^/]+)/);
                  if (placeMatch && placeMatch[1]) {
                      query = placeMatch[1];
                  }
                  else {
                      const coordsMatch = url.pathname.match(/@(-?\d+\.\d+,-?\d+\.\d+)/);
                      if (coordsMatch && coordsMatch[1]) {
                          query = coordsMatch[1];
                      }
                  }

                  if (!query) {
                      console.warn('[GoogleMaps] Impossible d\'extraire la localisation depuis:', finalUrl);
                      return null;
                  }

                  const embedUrl = `https://maps.google.com/maps?q=${encodeURIComponent(query)}&output=embed&z=15`;

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      class="iframe-embed iframe-google-maps"
                      src="${embedUrl}"
                      frameborder="0"
                      allowfullscreen
                      loading="lazy"
                      title="Carte Google Maps intégrée">
                  </iframe>`;

                  return container;

              } catch (e) {
                  console.error('[GoogleMaps] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
        },
        {
          name: 'AppleMusic',
          selector: 'a[href*="music.apple.com/"]',
          category: 'base',
          createEmbedElement(link) {
              try {
                  const url = new URL(link.href);
                  if (!/\/(album|playlist|station|artist)\//.test(url.pathname)) {
                      return null;
                  }

                  const embedUrl = url.href.replace(
                      'music.apple.com',
                      'embed.music.apple.com'
                  );

                  const isSong = url.searchParams.has('i');
                  const height = isSong ? '175' : '450';

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      class="iframe-embed"
                      src="${embedUrl}"
                      style="height: ${height}px;"
                      frameborder="0"
                      allowfullscreen
                      sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation-by-user-activation"
                      allow="autoplay *; encrypted-media *; fullscreen *"
                      loading="lazy"
                      title="Lecteur Apple Music intégré">
                  </iframe>`;

                  return container;

              } catch (e) {
                  console.error('[AppleMusic] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
        },
        {
          name: 'DeviantArt',
          selector: 'a[href*="deviantart.com/"][href*="/art/"]',
          category: 'connect',
          connect: ['backend.deviantart.com', 'www.deviantart.com'],
          async createEmbedElement(link) {
              try {
                  let normalizedUrl = link.href;
                  const originalUrl = new URL(link.href);
                  if (originalUrl.hostname !== 'www.deviantart.com') {
                      try {
                          const response = await LecteurMedia.compatibleHttpRequest({
                              method: 'HEAD',
                              url: link.href
                          });
                          normalizedUrl = response.finalUrl || link.href;
                      } catch (e) {
                          console.warn('[DeviantArt] La résolution du lien a échoué. On continue avec l\'URL originale.', e);
                          normalizedUrl = link.href;
                      }
                  }

                  const apiUrl = `https://backend.deviantart.com/oembed?url=${encodeURIComponent(normalizedUrl)}`;
                  const response = await LecteurMedia.compatibleHttpRequest({
                      method: 'GET',
                      url: apiUrl
                  });

                  if (response.status < 200 || response.status >= 300) {
                      throw new Error(`L'API DeviantArt a retourné le statut ${response.status}`);
                  }

                  const data = JSON.parse(response.responseText);
                  const imageUrl = data.url || data.thumbnail_url;

                  if (imageUrl) {
                      const container = document.createElement('div');
                      container.className = 'bloc-embed';
                      container.innerHTML = `<img src="${imageUrl}" class="image-embed" alt="${data.title || 'Art depuis DeviantArt'}" loading="lazy">`;
                      return container;
                  } else {
                      console.warn('[DeviantArt] Aucune URL d\'image trouvée dans la réponse de l\'API pour :', normalizedUrl);
                      return null;
                  }

              } catch (error) {
                  console.error('[DeviantArt] Erreur lors de la création de l\'embed :', error);
                  return null;
              }
          }
        },
        {
            name: 'Pinterest',
            selector: 'a[href*="pinterest."][href*="/pin/"]',
            scriptPromise: null,
            category: 'base',
            loadScript() {
                if (!this.scriptPromise) {
                    this.scriptPromise = new Promise((resolve, reject) => {
                        if (window.PinUtils) return resolve();
                        const script = document.createElement('script');
                        script.async = true;
                        script.defer = true;
                        script.src = 'https://assets.pinterest.com/js/pinit.js';
                        script.onload = resolve;
                        script.onerror = () => reject(new Error('Failed to load Pinterest script'));
                        document.head.appendChild(script);
                    });
                }
                return this.scriptPromise;
            },
            createEmbedElement(link) {
                const match = link.href.match(/\/pin\/(\d+)\/?/);
                if (!match || !match[1]) {
                    console.warn('[Pinterest] Impossible d\'extraire l\'ID du Pin depuis :', link.href);
                    return null;
                }
                const pinId = match[1];
                const canonicalUrl = `https://www.pinterest.com/pin/${pinId}/`;

                const container = document.createElement('div');
                container.className = 'bloc-embed';

                const pinEmbed = document.createElement('a');
                pinEmbed.href = canonicalUrl;
                pinEmbed.setAttribute('data-pin-do', 'embedPin');
                pinEmbed.setAttribute('data-pin-width', 'large');
                pinEmbed.setAttribute('data-pin-terse', 'true');

                container.appendChild(pinEmbed);
                return container;
            },
            postProcess() {
                this.loadScript().then(() => {
                    if (window.PinUtils && typeof window.PinUtils.build === 'function') {
                        window.PinUtils.build();
                    }
                }).catch(error => {
                    console.error('[Pinterest] Erreur lors du chargement ou de l\'exécution du script :', error);
                });
            }
        },
        {
          name: 'ImageShack',
          selector: 'a[href*="imageshack.com/i/"]',
          category: 'base',
          createEmbedElement(link) {
              try {
                  const url = new URL(link.href);
                  const pathParts = url.pathname.split('/').filter(p => p);

                  if (pathParts.length !== 2 || pathParts[0] !== 'i' || !pathParts[1]) {
                      return null;
                  }
                  const imageId = pathParts[1];

                  if (imageId.length < 3) {
                      throw new Error("ID d'image ImageShack invalide.");
                  }

                  const base36Part = imageId.substring(0, 2);
                  let filePart = imageId.substring(2);

                  //  Convertir la première partie de base 36 en base 10
                  const serverFolder = parseInt(base36Part, 36);

                  if (isNaN(serverFolder)) {
                      throw new Error(`Échec de la conversion de '${base36Part}' depuis la base 36.`);
                  }

                  if (/[a-zA-Z]$/.test(filePart)) {
                       filePart = filePart.slice(0, -1);
                  }

                  // Le format est : https://imagizer.imageshack.com/v2/{transfo}/{dossier}/{fichier}.jpg
                  const transformationParam = 'xq70';
                  const imageUrl = `https://imagizer.imageshack.com/v2/${transformationParam}/${serverFolder}/${filePart}.jpg`;

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<img src="${imageUrl}" class="image-embed" alt="Image depuis ImageShack" loading="lazy">`;
                  return container;

              } catch (error) {
                  console.error(`[ImageShack] Erreur lors de la transformation de l'URL ${link.href}:`, error.message);
                  return null;
              }
          }
        },
        {
            name: 'Gofundme',
            selector: 'a[href*="gofundme.com/f/"]',
            category: 'base',
            createEmbedElement(link) {
                try {
                    const url = new URL(link.href);
                    if (!url.pathname.startsWith('/f/')) {
                        return null;
                    }
                    const cleanUrlPath = url.pathname;
                    const embedUrl = `https://www.gofundme.com${cleanUrlPath}/widget/large`;

                    const container = document.createElement('div');
                    container.className = 'bloc-embed';
                    container.innerHTML = `<iframe
                        src="${embedUrl}"
                        class="iframe-embed"
                        style="height: 620px; border-radius: 8px;"
                        title="Campagne Gofundme intégrée"
                        frameborder="0"
                        scrolling="no">
                    </iframe>`;

                    return container;

                } catch (e) {
                    console.error('[Gofundme] Erreur lors de la création de l\'embed :', e);
                    return null;
                }
        }
        },
        {
            name: 'Coub',
            selector: 'a[href*="coub.com/view/"]',
            category: 'base',
            createEmbedElement(link) {
                try {
                    const match = link.href.match(/view\/([a-zA-Z0-9]+)/);
                    if (!match || !match[1]) return null;

                    const videoId = match[1];
                    const iframeUrl = `https://coub.com/embed/${videoId}?muted=true&autostart=true&originalSize=false&startWithHD=true`;

                    const container = document.createElement('div');
                    container.className = 'bloc-embed';
                    container.innerHTML = `<iframe
                        src="${iframeUrl}"
                        class="iframe-embed iframe-youtube"
                        title="Coub Video"
                        allow="autoplay"
                        frameborder="0"
                        width="550"
                        height="310">
                    </iframe>`;
                    return container;

                } catch (e) {
                    console.error('[Coub] Erreur lors de la création de l\'embed :', e);
                    return null;
                }
            }
        },
        {
          name: 'Gyazo',
          selector: 'a[href^="https://gyazo.com/"]',
          category: 'connect',
          connect: 'api.gyazo.com',
          async createEmbedElement(link) {
              if (link.hostname === 'i.gyazo.com' && /\.(jpe?g|png|gif|webp)$/i.test(link.pathname)) {
                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<img src="${link.href}" class="image-embed" alt="Image depuis Gyazo" loading="lazy">`;
                  return container;
              }

              if (link.hostname === 'gyazo.com' && link.pathname.length > 1) {
                  try {
                      const response = await LecteurMedia.compatibleHttpRequest({
                          method: 'GET',
                          url: `https://api.gyazo.com/api/oembed?url=${encodeURIComponent(link.href)}`
                      });

                      if (response.status !== 200) {
                          throw new Error(`L'API Gyazo a retourné le statut ${response.status}`);
                      }

                      const data = JSON.parse(response.responseText);

                      if (data && data.url) {
                          const container = document.createElement('div');
                          container.className = 'bloc-embed';
                          container.innerHTML = `<img src="${data.url}" class="image-embed" alt="Image depuis Gyazo" loading="lazy">`;
                          return container;
                      }
                  } catch (error) {
                      console.error('[Gyazo] Erreur lors de la récupération de l\'embed :', error);
                      return null;
                  }
              }
              return null;
          }
        },
        {
          name: 'Codepen',
          selector: 'a[href*="codepen.io/"]',
          category: 'base',
          createEmbedElement(link) {
              if (!link.pathname.includes('/pen/')) return null;

              try {
                  const url = new URL(link.href);
                  url.pathname = url.pathname.replace('/pen/', '/embed/');
                  url.searchParams.set('default-tab', 'result');
                  url.searchParams.set('theme-id', 'dark');

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      src="${url.toString()}"
                      class="iframe-embed"
                      style="height: 450px; border: 1px solid #444;"
                      title="Codepen Embed"
                      scrolling="no"
                      frameborder="0"
                      loading="lazy"
                      allowtransparency="true"
                      allowfullscreen="true">
                  </iframe>`;
                  return container;

              } catch (e) {
                  console.error('[Codepen] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
        },
        {
            name: 'Pastebin',
            selector: 'a[href*="pastebin.com/"]',
            category: 'base',
            createEmbedElement(link) {
                try {
                    const url = new URL(link.href);
                    const pathParts = url.pathname.split('/').filter(p => p);
                    if (pathParts.length !== 1 || !/^[a-zA-Z0-9]{8}$/.test(pathParts[0])) {
                        return null;
                    }

                    const pasteId = pathParts[0];
                    const iframeUrl = `https://pastebin.com/embed_iframe/${pasteId}`;

                    const container = document.createElement('div');
                    container.className = 'bloc-embed';
                    container.innerHTML = `<iframe
                        src="${iframeUrl}"
                        class="iframe-embed"
                        style="height: 400px;"
                        title="Pastebin Embed"
                        sandbox="allow-scripts allow-same-origin"
                        frameborder="0">
                    </iframe>`;
                    return container;

                } catch (e) {
                    console.error('[Pastebin] Erreur lors de la création de l\'embed :', e);
                    return null;
                }
            }
        },
        {
          name: 'Tenor',
          selector: 'a[href*="tenor.com/"][href*="/view/"]',
          category: 'connect',
          connect: 'tenor.com',
          createEmbedElement(link) {
              try {
                  const gifId = link.pathname.split('-').pop();
                  if (!gifId || !/^\d+$/.test(gifId)) {
                      console.warn('[Tenor] ID du GIF non trouvé ou invalide pour le lien :', link.href);
                      return null;
                  }

                  const iframeUrl = `https://tenor.com/embed/${gifId}`;

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';

                  const iframe = document.createElement('iframe');
                  iframe.src = iframeUrl;
                  iframe.className = 'iframe-embed iframe-youtube';
                  iframe.title = "Tenor GIF";
                  iframe.frameBorder = "0";
                  iframe.allowFullscreen = true;
                  iframe.style.width = '100%';
                  iframe.style.minHeight = '200px';

                  container.appendChild(iframe);

                  (async () => {
                      try {
                          const response = await LecteurMedia.compatibleHttpRequest({
                              method: 'GET',
                              url: `https://tenor.com/oembed?url=${encodeURIComponent(link.href)}`
                          });

                          if (response.status === 200) {
                              const data = JSON.parse(response.responseText);
                              if (data && data.width && data.height) {

                                  const aspectRatio = data.width / data.height;
                                  iframe.style.aspectRatio = `${aspectRatio}`;
                                  iframe.style.minHeight = 'auto';
                              }
                          }
                      } catch (error) {
                          console.error('[Tenor] Erreur lors de la récupération des métadonnées oEmbed :', error);
                      }
                  })();

                  return container;

              } catch (e) {
                  console.error('[Tenor] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
        },
        {
          name: 'Postimages',
          selector: 'a[href*="postimg.cc/"]',
          category: 'connect',
          connect: 'postimg.cc',
          async createEmbedElement(link) {
              try {
                  const url = new URL(link.href);
                  if (url.hostname === 'postimg.cc' && url.pathname.startsWith('/image/')) {
                      const response = await LecteurMedia.compatibleHttpRequest({
                          method: 'GET',
                          url: link.href
                      });

                      if (response.status < 200 || response.status >= 300) {
                          throw new Error(`Le serveur de Postimages a retourné le statut ${response.status}`);
                      }

                      const pageHtml = response.responseText;

                      const doc = new DOMParser().parseFromString(pageHtml, 'text/html');
                      const imageUrl = doc.querySelector('meta[property="og:image"]')?.getAttribute('content');

                      if (imageUrl) {
                          const container = document.createElement('div');
                          container.className = 'bloc-embed';
                          container.innerHTML = `<img src="${imageUrl}" class="image-embed" alt="Image depuis Postimages" loading="lazy">`;
                          return container;
                      }
                  }

                  return null;

              } catch (e) {
                  console.error('[Postimages] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
        },
        {
          name: 'ImgBB',
          selector: 'a[href*="ibb.co/"]',
          category: 'connect',
          connect: 'ibb.co',
          async createEmbedElement(link) {
              if (link.pathname.split('/').filter(p => p).length !== 1) {
                  return null;
              }

              try {
                  const response = await LecteurMedia.compatibleHttpRequest({
                      method: 'GET',
                      url: link.href
                  });

                  if (response.status < 200 || response.status >= 300) {
                      throw new Error(`Le serveur d'ImgBB a retourné le statut ${response.status}`);
                  }

                  const pageHtml = response.responseText;

                  const match = pageHtml.match(/<meta\s+property="og:image"\s+content="([^"]+)"/);
                  const imageUrl = match ? match[1] : null;

                  if (imageUrl) {
                      const container = document.createElement('div');
                      container.className = 'bloc-embed';
                      container.innerHTML = `<img src="${imageUrl}" class="image-embed" alt="Image depuis ImgBB" loading="lazy">`;
                      return container;
                  } else {
                      console.warn('[ImgBB] Impossible de trouver l\'URL de l\'image pour :', link.href);
                      return null;
                  }

              } catch (e) {
                  console.error('[ImgBB] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
        },
        {
          name: 'Sketchfab',
          selector: 'a[href*="sketchfab.com/3d-models/"], a[href*="sketchfab.com/models/"]',
          category: 'base',
          createEmbedElement(link) {
              try {
                  const url = new URL(link.href);
                  const modelMatch = url.href.match(/([a-f0-9]{32})/i);

                  if (modelMatch && modelMatch[1]) {
                      const modelId = modelMatch[1];
                      const iframeUrl = `https://sketchfab.com/models/${modelId}/embed`;

                      const container = document.createElement('div');
                      container.className = 'bloc-embed';
                      container.innerHTML = `<iframe
                          title="Modèle 3D Sketchfab"
                          class="iframe-embed iframe-youtube"
                          src="${iframeUrl}"
                          frameborder="0"
                          allow="autoplay; fullscreen; xr-spatial-tracking"
                          xr-spatial-tracking
                          execution-while-out-of-viewport
                          execution-while-not-rendered
                          web-share
                          allowfullscreen>
                      </iframe>`;
                      return container;
                  }

                  return null;

              } catch (e) {
                  console.error('[Sketchfab] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
        },
        {
          name: 'Steam',
          selector: 'a[href*="store.steampowered.com/app/"]',
          category: 'base',
          createEmbedElement(link) {
              try {
                  const match = link.href.match(/\/app\/(\d+)/);
                  if (!match || !match[1]) return null;

                  const appId = match[1];
                  const iframeUrl = `https://store.steampowered.com/widget/${appId}/`;

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      src="${iframeUrl}"
                      class="iframe-embed"
                      style="height: 190px; border-radius: 8px;"
                      title="Widget Steam"
                      frameborder="0">
                  </iframe>`;
                  return container;

              } catch (e) {
                  console.error('[Steam] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
        },
        {
          name: 'Bandcamp',
          selector: 'a[href*=".bandcamp.com/"]',
          category: 'connect',
          connect: '*.bandcamp.com',
          async createEmbedElement(link) {
              if (!link.pathname.includes('/track/') && !link.pathname.includes('/album/')) {
                  return null;
              }

              try {
                  const response = await LecteurMedia.compatibleHttpRequest({
                      method: 'GET',
                      url: link.href
                  });

                  if (response.status < 200 || response.status >= 300) {
                      throw new Error(`Le serveur de Bandcamp a retourné le statut ${response.status}`);
                  }

                  const pageHtml = response.responseText;

                  const doc = new DOMParser().parseFromString(pageHtml, "text/html");
                  const embedUrlMeta = doc.querySelector('meta[property="og:video"]');

                  if (!embedUrlMeta) {
                      console.warn('[Bandcamp] Meta tag "og:video" introuvable pour :', link.href);
                      return null;
                  }

                  let iframeUrl = embedUrlMeta.getAttribute('content');
                  if (!iframeUrl) return null;
                  if (!iframeUrl.endsWith('/')) iframeUrl += '/';
                  iframeUrl += 'transparent=true/';

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      style="border: 0; width: 100%; max-width: 550px; height: 120px;"
                      src="${iframeUrl}"
                      title="Lecteur Bandcamp"
                      seamless>
                  </iframe>`;

                  return container;

              } catch (error) {
                  console.error('[Bandcamp] Erreur lors de la création de l\'embed :', error);
                  return null;
              }
          }
        },
        {
            name: 'Flourish',
            selector: 'a[href*="public.flourish.studio/visualisation/"], a[href*="public.flourish.studio/story/"]',
            category: 'base',
            async createEmbedElement(link) {
                const match = link.href.match(/(visualisation|story)\/\d+/);

                if (!match || !match[0]) {
                    console.warn('[Flourish] Impossible d\'extraire l\'ID de la visualisation:', link.href);
                    return null;
                }

                const embedPath = match[0];
                const iframeUrl = `https://flo.uri.sh/${embedPath}/embed`;

                const container = document.createElement('div');
                container.className = 'bloc-embed';
                container.innerHTML = `<iframe
                    src="${iframeUrl}"
                    class="iframe-embed iframe-flourish"
                    title="Flourish Visualisation"
                    sandbox="allow-scripts allow-same-origin"
                    scrolling="no"
                ></iframe>`;

                return container;
            }
        },
        {
          name: 'DistroKid',
          selector: 'a[href*="distrokid.com/hyperfollow/"]',
          category: 'connect',
          connect: 'distrokid.com',
          async createEmbedElement(link) {
              const defaultAlbumArt = 'https://risibank.fr/cache/medias/0/5/532/53280/full.png';
              const hyperfollowUrl = link.href.split('?')[0];

              try {
                  const response = await LecteurMedia.compatibleHttpRequest({
                      method: 'GET',
                      url: hyperfollowUrl,
                      headers: {
                          'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/5.37.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36'
                      }
                  });

                  if (response.status < 200 || response.status >= 300) {
                      throw new Error(`Le serveur de DistroKid a retourné le statut ${response.status}`);
                  }

                  const responseText = response.responseText;

                  let audioUrl = null;
                  const audioTagMatch = responseText.match(/<audio[^>]+src="([^"]+)"/);
                  if (audioTagMatch && audioTagMatch[1]) {
                      audioUrl = audioTagMatch[1];
                  } else {
                      const jsonDataMatch = responseText.match(/previewData\.tracks\s*=\s*JSON\.parse\(\s*"(.+?)"\s*\);/);
                      if (jsonDataMatch && jsonDataMatch[1]) {
                          const jsonString = new Function(`return "${jsonDataMatch[1]}"`)();
                          const tracks = JSON.parse(jsonString);
                          if (tracks && tracks.length > 0) audioUrl = tracks[0].preview;
                      }
                  }

                  if (!audioUrl) {
                      console.warn('[DistroKid] Impossible de trouver un lien audio pour :', hyperfollowUrl);
                      return null;
                  }

                  const titleMatch = responseText.match(/<title[^>]*>([^<]+) by ([^<]+) - DistroKid<\/title>/);
                  const trackTitle = titleMatch ? titleMatch[1] : 'Titre inconnu';
                  const artistName = titleMatch ? titleMatch[2] : 'Artiste inconnu';

                  let albumArtUrl = defaultAlbumArt;
                  const bodyArtMatch = responseText.match(/<img[^>]+class="artCover[^"]+"[^>]+src="([^"]+)"/);
                  if (bodyArtMatch && bodyArtMatch[1]) {
                      albumArtUrl = bodyArtMatch[1];
                  }

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';

                  container.innerHTML = `
                    <a href="${hyperfollowUrl}" target="_blank" rel="noopener noreferrer" class="distrokid-embed-card">
                        <div class="distrokid-album-art" style="background-image: url('${albumArtUrl}');"></div>
                        <div class="distrokid-content">
                            <div>
                                <div class="distrokid-title">${trackTitle}</div>
                                <div class="distrokid-artist">${artistName}</div>
                            </div>
                            <audio src="${audioUrl}" controls preload="metadata"></audio>
                        </div>
                    </a>
                  `;

                  return container;

              } catch (error) {
                  console.error('[DistroKid] Erreur lors de la création de l\'embed :', error);
                  return null;
              }
          }
        },
        {
          name: 'Discord',
          selector: 'a[href*="discord.gg/"], a[href*="discord.com/invite/"]',
          category: 'connect',
          connect: 'discord.com',
          async createEmbedElement(link) {
              const inviteCodeMatch = link.href.match(/(?:discord\.gg|discord\.com\/invite)\/([a-zA-Z0-9]+)/);
              if (!inviteCodeMatch || !inviteCodeMatch[1]) return null;

              const inviteCode = inviteCodeMatch[1];
              const uniqueId = `discord-invite-${inviteCode}-${Math.random().toString(36).substring(2, 9)}`;
              const placeholderContainer = document.createElement('div');
              placeholderContainer.className = 'bloc-embed';
              placeholderContainer.innerHTML = `<div id="${uniqueId}" class="iframe-embed discord-loading-placeholder">Chargement de l'invitation Discord...</div>`;

              setTimeout(async () => {
                  try {
                      const response = await LecteurMedia.compatibleHttpRequest({
                          method: "GET",
                          url: `https://discord.com/api/v9/invites/${inviteCode}?with_counts=true`
                      });

                      if (response.status !== 200) {
                          throw new Error(`L'API Discord a retourné le statut ${response.status}`);
                      }

                      const placeholder = document.getElementById(uniqueId);
                      if (!placeholder) return;

                      if (response.status !== 200) {
                          placeholder.outerHTML = `<div class="placeholder-embed discord-error-placeholder">Invitation invalide ou expirée.</div>`;
                          return;
                      }

                      const data = JSON.parse(response.responseText);
                      const guild = data.guild;

                      if (!guild || !guild.name) {
                          placeholder.outerHTML = `<div class="placeholder-embed discord-error-placeholder">Invitation invalide ou expirée.</div>`;
                          return;
                      }

                      const iconUrl = guild.icon
                          ? `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.webp?size=128`
                          : 'https://cdn.discordapp.com/embed/avatars/0.png';

                      const isVerified = guild.features.includes('VERIFIED');
                      const isPartnered = guild.features.includes('PARTNERED');

                      let badgeHtml = '';
                      if (isVerified) {
                          badgeHtml = '<span class="discord-badge discord-verified-badge" title="Serveur vérifié"><svg width="16" height="16" viewBox="0 0 16 15.2"><path d="M7.4,11.17,4,8.62,5,7.26l2,1.53L10.64,4l1.36,1Z" fill="currentColor"></path></svg></span>';
                      } else if (isPartnered) {
                          badgeHtml = '<span class="discord-badge discord-partnered-badge" title="Serveur partenaire de Discord"><svg width="16" height="16" viewBox="0 0 1000 1000"><path d="M833 361.3c0-9.8-3.1-19.5-9.4-27.6L600.2 65.1c-14-18.4-36.8-29.2-61-29.2H215.8c-24.2 0-47 10.8-61 29.2L31.3 333.7c-6.2 8.1-9.4 17.8-9.4 27.6v523.2c0 24.2 19.6 43.8 43.8 43.8h754.6c24.2 0 43.8-19.6 43.8-43.8V361.3zm-106.6 96.8l-96.8 96.8c-12.2 12.2-32 12.2-44.2 0l-96.8-96.8c-12.2-12.2-12.2-32 0-44.2l96.8-96.8c12.2-12.2 32-12.2 44.2 0l96.8 96.8c12.2 12.2 12.2 32 0 44.2zM379.8 458.1l-96.8 96.8c-12.2 12.2-32 12.2-44.2 0l-96.8-96.8c-12.2-12.2-12.2-32 0-44.2l96.8-96.8c12.2-12.2 32-12.2 44.2 0l96.8 96.8c12.2 12.2 12.2 32 0 44.2z" fill="currentColor"></path></svg></span>';
                      }

                      const cardHtml = `
                        <div class="discord-invite-card">
                            <div class="discord-header">
                                <img src="${iconUrl}" alt="Icône du serveur" class="discord-server-icon">
                                <div class="discord-server-info">
                                    <div class="discord-server-name">
                                        <span>${guild.name}</span>
                                        ${badgeHtml}
                                    </div>
                                    <div class="discord-member-counts">
                                        <span class="discord-status-dot discord-online"></span>
                                        <span class="discord-count">${data.approximate_presence_count.toLocaleString('fr-FR')} en ligne</span>
                                        <span class="discord-status-dot discord-offline"></span>
                                        <span class="discord-count">${data.approximate_member_count.toLocaleString('fr-FR')} membres</span>
                                    </div>
                                </div>
                            </div>
                            <a href="${link.href}" target="_blank" rel="noopener noreferrer" class="discord-join-button">Rejoindre</a>
                        </div>
                      `;
                      placeholder.outerHTML = cardHtml;

                  } catch (error) {
                      console.error('[Discord] Erreur lors de la création de l\'embed :', error);
                      const placeholder = document.getElementById(uniqueId);
                      if (placeholder) {
                          placeholder.outerHTML = `<div class="placeholder-embed discord-error-placeholder">Impossible de charger l'invitation.</div>`;
                      }
                  }
              }, 0);

              return placeholderContainer;
          }
        },
        {
          name: 'StackOverflow',
          selector: 'a[href*="stackoverflow.com/questions/"]',
          category: 'connect',
          connect: 'api.stackexchange.com',
          async createEmbedElement(link) {
              const questionIdMatch = link.href.match(/\/questions\/(\d+)/);
              if (!questionIdMatch || !questionIdMatch[1]) return null;

              const questionId = questionIdMatch[1];
              const uniqueId = `so-embed-${questionId}-${Math.random().toString(36).substring(2, 9)}`;

              const placeholder = document.createElement('div');
              placeholder.className = 'bloc-embed';
              placeholder.innerHTML = `<div id="${uniqueId}" class="so-embed-wrapper" style="padding: 20px; text-align: center;">Chargement de la réponse Stack Overflow...</div>`;

              (async () => {
                  try {
                      const apiUrl = `https://api.stackexchange.com/2.3/questions/${questionId}/answers?order=desc&sort=votes&site=stackoverflow&filter=withbody`;

                      const answersResponse = await LecteurMedia.compatibleHttpRequest({
                          method: 'GET',
                          url: apiUrl
                      });

                      if (answersResponse.status !== 200) {
                          throw new Error(`L'API Stack Exchange (réponses) a retourné le statut ${answersResponse.status}`);
                      }
                      const data = JSON.parse(answersResponse.responseText);

                      if (!data.items || data.items.length === 0) {
                          throw new Error("Aucune réponse trouvée pour cette question.");
                      }

                      // On cherche la réponse acceptée, sinon on prend la plus votée (la première de la liste)
                      const answer = data.items.find(item => item.is_accepted) || data.items[0];

                      const questionApiUrl = `https://api.stackexchange.com/2.3/questions/${questionId}?site=stackoverflow`;
                      const questionResponse = await LecteurMedia.compatibleHttpRequest({
                          method: 'GET',
                          url: questionApiUrl
                      });

                      if (questionResponse.status !== 200) {
                          throw new Error(`L'API Stack Exchange (question) a retourné le statut ${questionResponse.status}`);
                      }

                      const questionData = JSON.parse(questionResponse.responseText);
                      const questionTitle = questionData.items[0]?.title || "Question Stack Overflow";

                      const scoreClass = answer.score < 0 ? 'so-score-negative' : '';

                      const embedHTML = `
                          <div class="so-embed-wrapper">
                              <div class="so-question-header">
                                  <a href="${link.href}" target="_blank" rel="noopener noreferrer">${questionTitle}</a>
                              </div>
                              <div class="so-answer-body">${answer.body}</div>
                              <div class="so-footer">
                                  <div class="so-score ${scoreClass}">
                                      <svg aria-hidden="true" width="18" height="18" viewBox="0 0 18 18"><path d="M1 12h16L9 4l-8 8Z" fill="currentColor"></path></svg>
                                      <span>${answer.score.toLocaleString('fr-FR')}</span>
                                  </div>
                                  <div class="so-author">
                                      Réponse par ${answer.owner.display_name}
                                  </div>
                              </div>
                          </div>
                      `;

                      const targetElement = document.getElementById(uniqueId);
                      if (targetElement) {
                          targetElement.parentElement.innerHTML = embedHTML;
                      }

                  } catch (error) {
                      console.error('[StackOverflow] Erreur:', error);
                      const targetElement = document.getElementById(uniqueId);
                      if (targetElement) {
                          targetElement.textContent = "Impossible de charger la réponse.";
                      }
                  }
              })();

              return placeholder;
          }
        },
        {
          name: 'PDF',
          selector: 'a[href$=".pdf" i]',
          category: 'wildcard',
          async createEmbedElement(link) {
                const pdfUrl = link.href;

                try {
                    const response = await LecteurMedia.compatibleHttpRequest({
                        method: 'HEAD',
                        url: pdfUrl
                    });

                    if (response.status < 200 || response.status >= 300) {
                        throw new Error(`Le serveur a retourné le statut ${response.status} pour le fichier PDF.`);
                    }
                    const headers = response.responseHeaders;

                    const lowerCaseHeaders = headers.toLowerCase();
                    const xFrameOptions = lowerCaseHeaders.match(/x-frame-options:\s*(deny|sameorigin)/);
                    const csp = lowerCaseHeaders.match(/content-security-policy:.*frame-ancestors\s+('none'|'self')/);

                    if (xFrameOptions || csp) {
                        console.log(`[PDF Embed] Intégration bloquée pour ${pdfUrl} par les en-têtes du serveur.`);
                        return null;
                    }

                } catch (error) {
                    console.error(`[PDF Embed] Erreur réseau en vérifiant les en-têtes pour ${pdfUrl}:`, error);
                    return null;
                }

                const container = document.createElement('div');
                container.className = 'bloc-embed';
                container.innerHTML = `<iframe
                    src="${pdfUrl}"
                    class="iframe-embed iframe-pdf"
                    title="Lecteur PDF"
                    frameborder="0"
                    sandbox="allow-scripts allow-same-origin">
                </iframe>`;
                return container;
            }
        },
        {
          name: 'GenericMedia',
          selector: `
              a[href*=".jpg" i]:not([href*="noelshack.com"]), a[href*=".jpeg" i]:not([href*="noelshack.com"]),
              a[href*=".png" i]:not([href*="noelshack.com"]), a[href*=".gif" i]:not([href*="noelshack.com"]),
              a[href*=".webp" i]:not([href*="noelshack.com"]), a[href*=".bmp" i]:not([href*="noelshack.com"]),
              a[href*=".mp4" i]:not([href*="noelshack.com"]), a[href*=".webm" i]:not([href*="noelshack.com"]),
              a[href*=".mov" i]:not([href*="noelshack.com"]), a[href*=".ogg" i]:not([href*="noelshack.com"])
          `,
          category: 'base',
          createEmbedElement(link) {
              const container = document.createElement('div');
              container.className = 'bloc-embed';

              const handleError = () => {
                  const stickerUrl = 'https://risibank.fr/cache/medias/0/5/512/51206/thumb.png';
                  container.innerHTML = `
                      <div class="dead-link-sticker">
                          <img src="${stickerUrl}" alt="[Média supprimé]">
                          <span>[Média supprimé]</span>
                      </div>
                  `;
              };

              const handleResize = (element) => {
                  const isVideo = element.tagName === 'VIDEO';
                  const w = isVideo ? element.videoWidth : element.naturalWidth;
                  const h = isVideo ? element.videoHeight : element.naturalHeight;
                  if (h > w) element.classList.add('iframe-vertical-content');
              };

              const pathname = new URL(link.href).pathname;

              if (/\.(mp4|webm|mov|ogg)$/i.test(pathname)) {
                  const video = document.createElement('video');
                  video.src = link.href;
                  video.className = 'video-embed';
                  video.controls = true;
                  video.muted = true;
                  video.loop = true;
                  video.playsinline = true;
                  video.onloadedmetadata = () => handleResize(video);
                  video.onerror = handleError;
                  container.appendChild(video);
              } else {
                  const img = document.createElement('img');
                  img.src = link.href;
                  img.className = 'image-embed';
                  img.loading = 'lazy';
                  img.onload = () => handleResize(img);
                  img.onerror = handleError;
                  container.appendChild(img);
              }

              return container;
          }
        },
        {
          name: 'ArticlePreview',
          selector: 'a[href^="http"]:not([data-miniatweet-processed])',
          category: 'wildcard', 
          
          async createEmbedElement(link) {
              const href = link.href;
              const urlObj = new URL(href);

              const isHandledByOther = allProviders.some(p => {
                  if (p.name === 'ArticlePreview' || p.name === 'GenericMedia') return false;
                  return link.matches(p.selector);
              });
              const excludedDomains = [
                  'youtube.com', 'youtu.be', 'twitter.com', 'x.com', 'instagram.com',
                  'tiktok.com', 'vm.tiktok.com', 'streamable.com', 'webmshare.com',
                  'facebook.com', 'twitch.tv', 'vocaroo.com', 'voca.ro', 'reddit.com',
                  'flourish.studio', 'jeuxvideo.com', 'jvarchive.com', 'jvarchive.st',
                  'noelshack.com', 'spotify.com',  'drive.google.com', 'docs.google.com', 'google.com/maps', 'maps.app.goo.gl',
              ];
              if (isHandledByOther || excludedDomains.some(domain => urlObj.hostname.includes(domain)) || /\.(jpg|jpeg|png|gif|webp|bmp|mp4|webm|mov|ogg|pdf)$/i.test(href)) {
                  return null;
              }

              const createCard = (title, description, image, hostname) => {
                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `
                      <a href="${href}" class="article-preview-card" target="_blank" rel="noopener noreferrer" data-miniatweet-processed="true">
                          <div class="article-preview-image" style="background-image: url('${image}');"></div>
                          <div class="article-preview-content">
                              <div class="article-preview-title">${title}</div>
                              ${description ? `<div class="article-preview-description">${description}</div>` : ''}
                          </div>
                          <div class="article-preview-footer">
                              <span class="article-preview-sitename">${hostname.replace(/^www\./, '')}</span>
                          </div>
                      </a>
                  `;
                  return container;
              };

              try {
                  const cachedData = await CacheManager.get(href);
                  if (cachedData) {
                      return createCard(cachedData.title, cachedData.description, cachedData.image, urlObj.hostname);
                  }
              } catch (e) {}

              const skeletonContainer = document.createElement('div');
              skeletonContainer.className = 'bloc-embed lm-skeleton-wrapper';
              skeletonContainer.innerHTML = `
                <div class="article-preview-card skeleton-card">
                    <div class="skeleton-image"></div>
                    <div class="skeleton-content">
                        <div class="skeleton-line title"></div>
                        <div class="skeleton-line text"></div>
                    </div>
                </div>`;

              (async () => {
                  try {
                      const settings = await SettingsManager.getSettings();
                      const mode = settings.previewMode || 'proxy_fallback'; // proxy_fallback, proxy_only, direct

                      let dataFound = null;

                      const fetchViaProxy = async () => {
                          const workerUrl = `${baseUrl}/?url=${encodeURIComponent(href)}`;
                          // Timeout court pour le worker si on a un fallback, sinon un peu plus long
                          const timeoutVal = (mode === 'proxy_only') ? 4000 : 2000; 
                          
                          const response = await LecteurMedia.compatibleHttpRequest({
                              method: 'GET',
                              url: workerUrl,
                              timeout: timeoutVal
                          });
                          
                          if (response.status === 200) {
                              const data = JSON.parse(response.responseText);
                              if (!data.error && data.title && data.image) {
                                  return data;
                              }
                          }
                          throw new Error("Worker failed or invalid data");
                      };

                      const fetchDirect = async () => {
                          const response = await LecteurMedia.compatibleHttpRequest({
                              method: 'GET',
                              url: href,
                              timeout: 6000, // On laisse plus de temps en direct
                              headers: { 
                                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
                                'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8'
                              }
                          });

                          if (response.status >= 200 && response.status < 300) {
                              const parser = new DOMParser();
                              const doc = parser.parseFromString(response.responseText, 'text/html');
                              const getMeta = (prop) => doc.querySelector(`meta[property="${prop}"], meta[name="${prop}"]`)?.getAttribute('content')?.trim();

                              const title = getMeta('og:title') || getMeta('twitter:title') || doc.querySelector('title')?.textContent.trim();
                              const description = getMeta('og:description') || getMeta('twitter:description') || getMeta('description');
                              let imageUrl = getMeta('og:image') || getMeta('twitter:image');

                              if (imageUrl && !imageUrl.startsWith('http')) {
                                  try { imageUrl = new URL(imageUrl, href).href; } catch(e){}
                              }

                              if (title && imageUrl) {
                                  return { title, description, image: imageUrl };
                              }
                          }
                          throw new Error("Direct fetch failed or no metadata");
                      };
                    console.log("mode:", mode);
                      if (mode === 'direct') {
                          // Mode Direct uniquement
                          console.log(`[ArticlePreview] Tentative Directe pour ${href}`);
                          dataFound = await fetchDirect();

                      } else if (mode === 'proxy_only') {
                          // Mode Proxy uniquement
                          console.log(`[ArticlePreview] Tentative Proxy Unique pour ${href}`);
                          dataFound = await fetchViaProxy();

                      } else {
                          // Mode Proxy + Fallback (Défaut)
                          try {
                              console.log(`[ArticlePreview] Tentative Proxy pour ${href}`);
                              dataFound = await fetchViaProxy();
                          } catch (errProxy) {
                              console.warn(`[ArticlePreview] Proxy échoué, fallback direct pour ${href}`);
                              dataFound = await fetchDirect();
                          }
                      }

                      if (dataFound) {
                          await CacheManager.set(href, dataFound);
                          const finalCard = createCard(dataFound.title, dataFound.description, dataFound.image, urlObj.hostname);
                          
                          if (LecteurMedia.instance && LecteurMedia.instance.embedManager) {
                              LecteurMedia.instance.embedManager._makeEmbedCollapsible(finalCard, link);
                          }
                          skeletonContainer.replaceWith(finalCard);
                      } else {
                          skeletonContainer.remove();
                      }

                  } catch (globalError) {
                      console.error(`[ArticlePreview] Erreur finale pour ${href}:`, globalError);
                      skeletonContainer.remove();
                  }
              })();

              return skeletonContainer;
          }
        },
    ];


    // =========================================================================
    // == HELPERS
    // =========================================================================

    // Gère le redimensionnement dynamique des iframes en écoutant les événements `postMessage`
    const IframeResizeManager = {
        isInitialized: false,
        handlers: {},

        register: function(origin, handler) {
            this.handlers[origin] = handler;
        },

        init: function() {
            if (this.isInitialized) {
                return;
            }
            window.addEventListener('message', this._handleMessage.bind(this));
            this.isInitialized = true;
        },

        _handleMessage: function(event) {
            const handler = this.handlers[event.origin];
            if (handler) {
                handler.process(event);
            }
        }
    };

    // --- Définition des Handlers  ---
    const redditResizeHandler = {
        process: function(event) {
            let data;
            if (typeof event.data === 'string') {
                try { data = JSON.parse(event.data); } catch (e) { return; }
            } else if (typeof event.data === 'object' && event.data !== null) {
                data = event.data;
            } else {
                return;
            }

            if (data && data.type === 'resize.embed' && typeof data.data === 'number') {
                const height = data.data;
                this._resizeIframe(height, event.source);
            }
        },

        _resizeIframe: function(height, sourceWindow) {
            if (height <= 0 || !sourceWindow) return;
            const iframes = document.querySelectorAll('iframe.iframe-reddit');

            for (const iframe of iframes) {
                if (iframe.contentWindow === sourceWindow) {
                    iframe.style.height = `${height}px`;
                    break;
                }
            }
        }
    };

    IframeResizeManager.register('https://embed.reddit.com', redditResizeHandler);


    // =========================================================================
    // == GESTIONNAIRE DE PARAMÈTRES
    // =========================================================================

    const SettingsManager = {
        defaults: {
            startCollapsed: false,
            collapsibleEmbeds: true,
            disabledProviders: [],
            twitterFullHeight: false,
            embedPosition: 'below_line', // Options: 'after', 'before', 'replace', 'bottom', 'below_line'
            previewMode: 'direct' // Options: 'proxy_fallback', 'proxy_only', 'direct'
        },

         async _safeGetValue(key, defaultValue) {
            if (typeof GM_getValue === 'function') {
                return await GM_getValue(key, defaultValue);
            }
            return defaultValue;
        },

        // VERSION CORRIGÉE : Pareil pour GM_setValue
        async _safeSetValue(key, value) {
            if (typeof GM_setValue === 'function') {
                await GM_setValue(key, value);
            }
        },

        async getSettings(developerDefaults = {}) {
            const settings = {};

            settings.collapsibleEmbeds = await this._safeGetValue(
                'collapsibleEmbeds', 
                developerDefaults.collapsible ?? this.defaults.collapsibleEmbeds
            );

            settings.startCollapsed = await  this._safeGetValue('startCollapsed', this.defaults.startCollapsed);
            settings.disabledProviders = await  this._safeGetValue('disabledProviders', this.defaults.disabledProviders);
            settings.twitterFullHeight = await this._safeGetValue('twitterFullHeight', this.defaults.twitterFullHeight);
            settings.embedPosition = await this._safeGetValue('embedPosition', this.defaults.embedPosition);
            settings.previewMode = await this._safeGetValue('previewMode', this.defaults.previewMode);

            return settings;
        },

        async saveSettings(settings) {
            for (const key in settings) {
                await this._safeSetValue(key, settings[key]);
            }
        },

        registerSettingsMenu(providersList) {
            if (typeof GM_registerMenuCommand === 'function' && typeof GM_getValue === 'function' && typeof GM_setValue === 'function') {
                GM_registerMenuCommand('Configurer le Lecteur Média', () => openSettingsPanel(providersList));
            } else {
                console.warn('[Lecteur Media] Panneau de configuration désactivé (permissions GM_* manquantes).');
            }
        }
    };


    // =========================================================================
    // == PANNEAU DE CONFIGURATION (UI)
    // =========================================================================
    function openSettingsPanel(providersList) {
        if (document.getElementById('lm-settings-panel')) return;

        // Création des éléments
        const overlay = document.createElement('div');
        overlay.id = 'lm-settings-overlay';

        const panel = document.createElement('div');
        panel.id = 'lm-settings-panel';

        const styleElement = document.createElement('style');
        styleElement.id = 'lm-settings-style';
        
        styleElement.textContent = `
            :root {
                --lm-bg: #2a2d31;
                --lm-bg-secondary: #202225;
                --lm-text: #dcddde;
                --lm-text-muted: #72767d;
                --lm-border: #36393f;
                --lm-accent: #3ba55d;
                --lm-radius: 8px;
                --lm-shadow: 0 10px 30px rgba(0,0,0,0.4);
            }

            html.theme-light {
                --lm-bg: #f2f3f5;
                --lm-bg-secondary: #ffffff;
                --lm-text: #2e3338;
                --lm-text-muted: #5c626b;
                --lm-border: #e3e5e8;
                --lm-shadow: 0 10px 30px rgba(0,0,0,0.1);
            }

            #lm-settings-overlay {
                position: fixed; inset: 0; z-index: 2147483646;
                background-color: rgba(0, 0, 0, 0.7);
                backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
                opacity: 0;
                transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1);
            }

            #lm-settings-panel {
                position: fixed;
                top: 80px;
                left: 50%;
                transform: translateX(-50%) scale(0.95);
                max-height: calc(100vh - 100px);
                width: 90vw; max-width: 650px;
                background-color: var(--lm-bg); color: var(--lm-text);
                border-radius: var(--lm-radius); box-shadow: var(--lm-shadow);
                display: flex; flex-direction: column;
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
                opacity: 0;
                transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1), transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
                z-index: 2147483647;
            }

            #lm-settings-panel .panel-header {
                padding: 16px 20px; border-bottom: 1px solid var(--lm-border);
                display: flex; justify-content: space-between; align-items: center;
                flex-shrink: 0;
            }
            #lm-settings-panel .panel-header h2 { margin: 0; font-size: 16px; font-weight: 600; }

            #lm-settings-panel .panel-close-btn {
                background: transparent; 
                border: none; 
                cursor: pointer; 
                padding: 8px; 
                margin-right: -8px;
                border-radius: 50%;
                display: flex; align-items: center; justify-content: center;
                transition: background-color 0.2s;
            }
            #lm-settings-panel .panel-close-btn:hover { 
                background-color: rgba(127,127,127,0.15); 
            }

            .panel-close-icon {
                display: block;
                width: 24px; 
                height: 24px;
                background-size: contain;
                background-repeat: no-repeat;
                background-position: center;
            }

            /* Icône pour le thème SOMBRE (Croix blanche/grise) */
            html:not(.theme-light) .panel-close-icon {
                background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23dcddde' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");
            }

            /* Icône pour le thème CLAIR (Croix noire/grise) */
            html.theme-light .panel-close-icon {
                background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235c626b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");
            }

            #lm-settings-panel .panel-body {
                padding: 20px;
                overflow-y: auto;
                flex: 1 1 auto; 
                min-height: 0; 
            }
            #lm-settings-panel .panel-body::-webkit-scrollbar { width: 8px; }
            #lm-settings-panel .panel-body::-webkit-scrollbar-track { background: transparent; }
            #lm-settings-panel .panel-body::-webkit-scrollbar-thumb { background-color: var(--lm-border); border-radius: 4px; }
            #lm-settings-panel .panel-body::-webkit-scrollbar-thumb:hover { background-color: var(--lm-text-muted); }

            #lm-settings-panel .setting-group {
                background-color: var(--lm-bg-secondary);
                border: 1px solid var(--lm-border);
                border-radius: var(--lm-radius);
                overflow: hidden;
                margin-bottom: 24px;
            }
            #lm-settings-panel .setting-group:last-child { margin-bottom: 0; }
            
            #lm-settings-panel .setting-group-header {
                padding: 12px 16px;
                background-color: rgba(127,127,127,0.05);
                border-bottom: 1px solid var(--lm-border);
                display: flex; align-items: center; gap: 10px;
            }
            #lm-settings-panel .setting-group-header svg { width: 20px; height: 20px; color: var(--lm-text-muted); }
            #lm-settings-panel .setting-group-header h3 { margin: 0; font-size: 14px; font-weight: 600; }
            
            #lm-settings-panel .setting-group-content {
                padding: 16px;
                display: flex;
                flex-direction: column;
                gap: 16px;
            }

            #lm-settings-panel .setting-item {
                display: flex; justify-content: space-between; align-items: center;
                transition: opacity 0.2s;
                gap: 15px; /* Espace minimum entre texte et bouton sur desktop */
            }
            #lm-settings-panel .setting-item-label {
                display: flex; flex-direction: column; gap: 2px;
                padding-right: 0;
                flex: 1; /* Le texte prend toute la place dispo */
            }
            #lm-settings-panel .setting-item-label strong { font-size: 14px; font-weight: 500; color: var(--lm-text); }
            #lm-settings-panel .setting-item-label span { font-size: 12px; color: var(--lm-text-muted); }

            #lm-settings-panel .toggle-switch {
                --switch-width: 40px; --switch-height: 22px; --thumb-size: 16px; --track-padding: 3px;
                position: relative; display: inline-block; width: var(--switch-width); height: var(--switch-height); flex-shrink: 0;
            }
            #lm-settings-panel .toggle-switch input { opacity: 0; width: 0; height: 0; }
            #lm-settings-panel .slider {
                position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0;
                background-color: var(--lm-border); border-radius: var(--switch-height);
                transition: background-color .2s ease-in-out;
            }
            #lm-settings-panel .slider:before {
                position: absolute; content: "";
                height: var(--thumb-size); width: var(--thumb-size); left: var(--track-padding); bottom: var(--track-padding);
                background-color: white; border-radius: 50%;
                transition: transform .2s cubic-bezier(.25,.8,.25,1), box-shadow .2s;
                box-shadow: 0 1px 2px rgba(0,0,0,0.2);
            }
            #lm-settings-panel input:checked + .slider { background-color: var(--lm-accent); }
            #lm-settings-panel input:checked + .slider:before { transform: translateX(calc(var(--switch-width) - var(--thumb-size) - (var(--track-padding) * 2))); }
            
            #lm-settings-panel .providers-list {
                display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 16px;
            }
            #lm-settings-panel .provider-item {
                display: flex; align-items: center; background-color: var(--lm-bg);
                border: 1px solid var(--lm-border); border-radius: 6px; padding: 8px 12px;
                cursor: pointer; transition: border-color 0.2s, background-color 0.2s;
            }
            #lm-settings-panel .provider-item:hover { border-color: var(--lm-text-muted); }
            #lm-settings-panel .provider-item.checked { border-color: var(--lm-accent); background-color: rgba(59, 165, 93, 0.1); }
            #lm-settings-panel .provider-item input { display: none; }
            #lm-settings-panel .provider-item-icon { width: 20px; height: 20px; margin-right: 10px; background-color: #fff; padding: 2px; border-radius: 4px; }
            #lm-settings-panel .provider-item label { font-size: 14px; font-weight: 500; cursor: pointer; }

            #lm-settings-panel .panel-footer {
                padding: 10px 20px; border-top: 1px solid var(--lm-border);
                text-align: right; font-size: 12px; color: var(--lm-text-muted); flex-shrink: 0;
            }

            #lm-settings-panel .toast-indicator {
                position: absolute; bottom: 16px; left: 50%;
                transform: translateX(-50%) translateY(20px);
                padding: 8px 16px; background-color: var(--lm-accent); color: white;
                border-radius: 6px; font-size: 14px; font-weight: 500;
                opacity: 0; pointer-events: none;
                transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
                z-index: 10;
            }
            #lm-settings-panel .toast-indicator.show { opacity: 1; transform: translateX(-50%) translateY(0); }

           /* --- STYLES POUR LES SELECTS (Position + Preview) --- */
            #lm-position-select, 
            #lm-preview-mode-select,
            .lm-input-select {
                background: var(--lm-bg); 
                color: var(--lm-text); 
                border: 1px solid var(--lm-border); 
                padding: 8px; 
                border-radius: 4px; 
                outline: none;
                font-size: 14px;
                max-width: 200px; /* Limite sur PC */
                cursor: pointer;
                text-overflow: ellipsis; /* Coupe proprement si c'est encore trop long */
                white-space: nowrap;
                overflow: hidden;
            }

            /* --- RESPONSIVE MOBILE --- */
            @media (max-width: 600px) {
                #lm-settings-panel {
                    width: 95vw;
                    top: 20px;
                    max-height: calc(100vh - 40px);
                }
                
                #lm-settings-panel .setting-item {
                    flex-direction: column; /* On empile verticalement */
                    align-items: flex-start;
                    gap: 12px;
                }

                #lm-settings-panel .setting-item-label {
                    width: 100%;
                }
                
                /* Le toggle switch reste à droite pour l'ergonomie, ou à gauche, au choix. 
                   Ici on le met aligné à droite pour garder le style "switch" */
                #lm-settings-panel .toggle-switch {
                    align-self: flex-end;
                    margin-top: -10px; /* Petit ajustement */
                }

                /* On force les selects à prendre toute la largeur sur mobile */
                #lm-position-select, 
                #lm-preview-mode-select,
                .lm-input-select {
                    max-width: 100% !important;
                    width: 100% !important;
                    margin-top: 5px; /* Un peu d'espace si ça passe sous le label */
                }

                #lm-settings-panel .providers-list {
                    grid-template-columns: 1fr; /* Une seule colonne pour les providers */
                }
            }
        `;

        document.head.appendChild(styleElement);

        const providerIcons = {
            'AppleMusic': 'https://www.google.com/s2/favicons?domain=music.apple.com&sz=64',
            'Bandcamp': 'https://www.google.com/s2/favicons?domain=bandcamp.com&sz=64',
            'Bilibili': 'https://www.google.com/s2/favicons?domain=bilibili.com&sz=64',
            'Codepen': 'https://www.google.com/s2/favicons?domain=codepen.io&sz=64',
            'Coub': 'https://www.google.com/s2/favicons?domain=coub.com&sz=64',
            'Dailymotion': 'https://www.google.com/s2/favicons?domain=dailymotion.com&sz=64',
            'DeviantArt': 'https://www.google.com/s2/favicons?domain=deviantart.com&sz=64',
            'Discord': 'https://www.google.com/s2/favicons?domain=discord.com&sz=64',
            'DistroKid': 'https://www.google.com/s2/favicons?domain=distrokid.com&sz=64',
            'Facebook': 'https://www.google.com/s2/favicons?domain=facebook.com&sz=64',
            'Flickr': 'https://www.google.com/s2/favicons?domain=flickr.com&sz=64',
            'Flourish': 'https://www.google.com/s2/favicons?domain=flourish.studio&sz=64',
            'Giphy': 'https://www.google.com/s2/favicons?domain=giphy.com&sz=64',
            'Gofundme': 'https://www.google.com/s2/favicons?domain=gofundme.com&sz=64',
            'GoogleDrive': 'https://www.google.com/s2/favicons?domain=drive.google.com&sz=64',
            'GoogleMaps': 'https://www.google.com/s2/favicons?domain=maps.google.com&sz=64',
            'Gyazo': 'https://www.google.com/s2/favicons?domain=gyazo.com&sz=64',
            'ImageShack': 'https://www.google.com/s2/favicons?domain=imageshack.com&sz=64',
            'ImgBB': 'https://www.google.com/s2/favicons?domain=ibb.co&sz=64',
            'Imgur': 'https://www.google.com/s2/favicons?domain=imgur.com&sz=64',
            'Instagram': 'https://www.google.com/s2/favicons?domain=instagram.com&sz=64',
            'Koreus': 'https://www.google.com/s2/favicons?domain=koreus.com&sz=64',
            'Pastebin': 'https://www.google.com/s2/favicons?domain=pastebin.com&sz=64',
            'Pinterest': 'https://www.google.com/s2/favicons?domain=pinterest.com&sz=64',
            'Postimages': 'https://www.google.com/s2/favicons?domain=postimg.cc&sz=64',
            'Reddit': 'https://www.google.com/s2/favicons?domain=reddit.com&sz=64',
            'Sketchfab': 'https://www.google.com/s2/favicons?domain=sketchfab.com&sz=64',
            'SoundCloud': 'https://www.google.com/s2/favicons?domain=soundcloud.com&sz=64',
            'Spotify': 'https://www.google.com/s2/favicons?domain=spotify.com&sz=64',
            'StackOverflow': 'https://www.google.com/s2/favicons?domain=stackoverflow.com&sz=64',
            'Steam': 'https://www.google.com/s2/favicons?domain=steampowered.com&sz=64',
            'StrawPoll': 'https://www.google.com/s2/favicons?domain=strawpoll.com&sz=64',
            'Streamable': 'https://www.google.com/s2/favicons?domain=streamable.com&sz=64',
            'Telegram': 'https://www.google.com/s2/favicons?domain=telegram.org&sz=64',
            'Tenor': 'https://www.google.com/s2/favicons?domain=tenor.com&sz=64',
            'TikTok': 'https://www.google.com/s2/favicons?domain=tiktok.com&sz=64',
            'Twitch': 'https://www.google.com/s2/favicons?domain=twitch.tv&sz=64',
            'Twitter': 'https://www.google.com/s2/favicons?domain=x.com&sz=64',
            'Vimeo': 'https://www.google.com/s2/favicons?domain=vimeo.com&sz=64',
            'Webmshare': 'https://www.google.com/s2/favicons?domain=webmshare.com&sz=64',
            'YouTube': 'https://www.google.com/s2/favicons?domain=youtube.com&sz=64',
            'Zupimages': 'https://www.google.com/s2/favicons?domain=zupimages.net&sz=64'
        };
        const defaultIcon = 'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22currentColor%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3E%3Cpath d=%22M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.72%22/%3E%3Cpath d=%22M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.72-1.72%22/%3E%3C/svg%3E';

        SettingsManager.getSettings().then(settings => {
            panel.innerHTML = `
        <div class="panel-header">
            <h2>Paramètres du Lecteur Média</h2>
            <button class="panel-close-btn" title="Fermer">
                <span class="panel-close-icon"></span>
            </button>
        </div>
        <div class="panel-body">
            <div class="setting-group">
                <div class="setting-group-header">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"></path></svg>
                    <h3>Comportement Général</h3>
                </div>
                <div class="setting-group-content">
                    <div class="setting-item" data-setting-key="collapsibleEmbeds">
                        <div class="setting-item-label">
                            <strong>En-têtes réductibles</strong>
                            <span>Affiche une barre pour masquer/afficher les médias.</span>
                        </div>
                        <label class="toggle-switch"><input type="checkbox" ${settings.collapsibleEmbeds ? 'checked' : ''}><span class="slider"></span></label>
                    </div>
                    <div class="setting-item" data-setting-key="startCollapsed">
                        <div class="setting-item-label">
                            <strong>Réduire par défaut</strong>
                            <span>Les médias seront masqués au chargement de la page.</span>
                        </div>
                        <label class="toggle-switch"><input type="checkbox" ${settings.startCollapsed ? 'checked' : ''}><span class="slider"></span></label>
                    </div>
                    <div class="setting-item">
                        <div class="setting-item-label">
                            <strong>Position du lecteur</strong>
                            <span>Où placer le média par rapport au lien ?</span>
                        </div>
                        <select id="lm-position-select" style="background: var(--lm-bg); color: var(--lm-text); border: 1px solid var(--lm-border); padding: 6px; border-radius: 4px; outline: none;">
                            <option value="after">Juste après le lien</option>
                            <option value="before">Juste avant le lien</option>
                            <option value="below_line">Sous la ligne courante</option>
                            <option value="replace">Remplacer le lien</option>
                            <option value="bottom">Tout en bas du message</option>
                        </select>
                    </div>   
                        
                    <div class="setting-item">
                        <div class="setting-item-label">
                            <strong>Mode Prévisualisation</strong>
                            <span>Auto (Proxy+Direct), Proxy seul (0 popup), ou Direct (Rapide).</span>
                        </div>
                        <select id="lm-preview-mode-select" class="lm-input-select">
                            <option value="proxy_fallback">Fiable (Auto)</option>
                            <option value="proxy_only">Sans popup (Proxy)</option>
                            <option value="direct">Rapide (Direct)</option>
                        </select>
                    </div>
                </div>
            </div>

            <div class="setting-group">
                <div class="setting-group-header">
                    <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.602.75Zm-.86 13.028h1.36L4.323 2.145H2.865z"/></svg>
                    <h3>Options pour Twitter / X</h3>
                </div>
                <div class="setting-group-content">
                    <div class="setting-item" data-setting-key="twitterFullHeight">
                        <div class="setting-item-label">
                            <strong>Afficher les tweets en entier</strong>
                            <span>Désactive la hauteur limitée et la barre de défilement.</span>
                        </div>
                        <label class="toggle-switch"><input type="checkbox" ${settings.twitterFullHeight ? 'checked' : ''}><span class="slider"></span></label>
                    </div>
                </div>
            </div>

            <div class="setting-group">
                <div class="setting-group-header">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z"></path></svg>
                    <h3>Fournisseurs de contenu</h3>
                </div>
                <div class="setting-group-content providers-list">
                    ${providersList.map(p => {
                        const isChecked = !settings.disabledProviders.includes(p.name);
                        return `<div class="provider-item ${isChecked ? 'checked' : ''}" data-provider-name="${p.name}">
                                    <input type="checkbox" ${isChecked ? 'checked' : ''}>
                                    <img src="${providerIcons[p.name] || defaultIcon}" class="provider-item-icon" alt=""/>
                                    <label>${p.name}</label>
                                </div>`;
                    }).join('')}
                </div>
            </div>
        </div>
        <div class="panel-footer">
            Les modifications sont enregistrées automatiquement.
        </div>
        <div class="toast-indicator">Enregistré !</div>
    `;

            document.body.append(overlay, panel);

            setTimeout(() => {
                overlay.style.opacity = '1';
                panel.style.opacity = '1';
                panel.style.transform = 'translateX(-50%) scale(1)';
            }, 10);

            const closePanel = () => {
                overlay.style.opacity = '0';
                panel.style.opacity = '0';
                panel.style.transform = 'translateX(-50%) scale(0.95)';
                setTimeout(() => {
                    overlay.remove();
                    panel.remove();
                    styleElement.remove();
                }, 200);
            };
            panel.querySelector('.panel-close-btn').onclick = closePanel;
            overlay.addEventListener('click', (e) => {
                if (e.target === overlay) { closePanel(); }
            });
            
            const toast = panel.querySelector('.toast-indicator');
            let toastTimeout;
            const showToast = () => {
                clearTimeout(toastTimeout);
                toast.classList.add('show');
                toastTimeout = setTimeout(() => toast.classList.remove('show'), 1500);
            };
            
            const collapsibleToggle = panel.querySelector('[data-setting-key="collapsibleEmbeds"] input');
            const startCollapsedItem = panel.querySelector('[data-setting-key="startCollapsed"]');
            const updateDependencies = () => {
                if (collapsibleToggle.checked) {
                    startCollapsedItem.style.opacity = '1';
                    startCollapsedItem.style.pointerEvents = 'auto';
                } else {
                    startCollapsedItem.style.opacity = '0.5';
                    startCollapsedItem.style.pointerEvents = 'none';
                }
            };
            updateDependencies();

            const positionSelect = panel.querySelector('#lm-position-select');
            if (positionSelect) {
                positionSelect.value = settings.embedPosition;
                
                positionSelect.addEventListener('change', (e) => {
                    const newValue = e.target.value;
                    SettingsManager.saveSettings({ embedPosition: newValue }).then(showToast);
                });
            }

            const previewModeSelect = panel.querySelector('#lm-preview-mode-select');
            if (previewModeSelect) {
                previewModeSelect.value = settings.previewMode;
                
                previewModeSelect.addEventListener('change', (e) => {
                    const newValue = e.target.value;
                    SettingsManager.saveSettings({ previewMode: newValue }).then(showToast);
                });
            }

            panel.querySelectorAll('.setting-item').forEach(item => {
                const checkbox = item.querySelector('input[type="checkbox"]');
                if (!checkbox) return;
                
                item.addEventListener('click', (e) => {
                    if(e.target.closest('a')) return;
                    checkbox.checked = !checkbox.checked;
                    const key = item.dataset.settingKey;
                    SettingsManager.saveSettings({ [key]: checkbox.checked }).then(showToast);
                    if(key === 'collapsibleEmbeds') { updateDependencies(); }
                });
            });
            
            panel.querySelectorAll('.provider-item').forEach(item => {
                item.addEventListener('click', () => {
                    const checkbox = item.querySelector('input');
                    const providerName = item.dataset.providerName;
                    checkbox.checked = !checkbox.checked;
                    item.classList.toggle('checked');
                    
                    let currentDisabled = settings.disabledProviders || [];
                    if (checkbox.checked) {
                        currentDisabled = currentDisabled.filter(name => name !== providerName);
                    } else {
                        if (!currentDisabled.includes(providerName)) { currentDisabled.push(providerName); }
                    }
                    settings.disabledProviders = currentDisabled;
                    SettingsManager.saveSettings({ disabledProviders: currentDisabled }).then(showToast);
                });
            });
        });
    }

    // =========================================================================
    // == GESTIONNAIRE DE CACHE
    // =========================================================================
    const CacheManager = {
        EXPIRATION_MS: 24 * 60 * 60 * 1000, 

        _hash(str) {
            let hash = 5381;
            for (let i = 0; i < str.length; i++) {
                hash = ((hash << 5) + hash) + str.charCodeAt(i); 
            }
            return (hash >>> 0).toString(36) + str.length.toString(36);
        },

        async get(url) {
            const key = 'lm_cache_v3_' + this._hash(url); 
            const data = await GM_getValue(key, null);
            
            if (!data) return null;

            if (Date.now() > data.expires) {
                GM_deleteValue(key);
                return null;
            }
            return data.payload;
        },

        async set(url, payload) {
            const key = 'lm_cache_v3_' + this._hash(url);
            await GM_setValue(key, {
                payload: payload,
                expires: Date.now() + this.EXPIRATION_MS
            });
        }
    };

    //GM_registerMenuCommand('Configurer le Lecteur Média', () => openSettingsPanel(providers));

    // =========================================================================
    // == STYLES GLOBAUX
    // =========================================================================
        GM_addStyle(`
        .bloc-embed {
            margin: 1em 0;
            margin-top: 0;
            display: flex;
            justify-content: left;
        }
        .iframe-embed, .video-embed, .image-embed, .thumbnail-embed, .facebook-embed-placeholder {
            max-width: 550px;
            width: 100%;
            border-radius: 9px;
            border: none;
            display: block;
            background-color: #1c1c1c;
        }

        html:not(.theme-light) .facebook-embed-placeholder {
            border: 1px solid #444;
        }
        html.theme-light .facebook-embed-placeholder {
            background-color: #f0f2f5;
            border: 1px solid #ddd;
        }
        .iframe-twitter {
            height: 500px;
            background-color: transparent;
            transition: height 0.4s ease-in-out;
        }

        .iframe-giphy {
            aspect-ratio: 16 / 9;
            height: auto;
        }

        .iframe-streamable {
            height: auto;
        }

        .iframe-youtube, .iframe-streamable, .iframe-tiktok, .iframe-twitch, .iframe-vocaroo, .iframe-reddit, .iframe-giphy {
          max-height: 80vh;
        }
        .iframe-vertical-content {
            max-width: 320px;
            max-height: 65vh;
        }
        .iframe-youtube-short {
            aspect-ratio: 9 / 16;
            height: auto;
        }
        .iframe-youtube {
            aspect-ratio: 16 / 9;
            height: auto;
        }
        .youtube-facade-container {
            position: relative;
            display: block;
            aspect-ratio: 16 / 9;
            max-width: 550px;
            width: 100%;
        }
        .youtube-facade-overlay {
            position: absolute;
            top: 0; left: 0;
            width: 100%; height: 100%;
            cursor: pointer;
            background: transparent;
            z-index: 1;
        }
        .youtube-facade-container .iframe-youtube {
            position: absolute;
            top: 0; left: 0;
            width: 100%; height: 100%;
        }
        @media (max-width: 768px) {
            .iframe-youtube,
            .youtube-facade-container {
                aspect-ratio: 5 / 4;
            }
        }

        .iframe-tiktok {
            height: auto;
            aspect-ratio: 9 / 16.5;
            max-height: 75vh;
            background-color: #000;
        }
        .iframe-twitch {
                aspect-ratio: 16 / 9;
                height: auto;
        }
        .iframe-vocaroo {
            max-width: 300px;
            width: 100%;
            height: 60px;
        }
        .iframe-reddit {
            height: 500px;
            background-color: transparent;
            transition: height 0.3s ease-in-out;
        }
        .video-embed, .image-embed, .thumbnail-embed {
             height: auto;
             max-height: 80vh;
             object-fit: contain;
        }
        .placeholder-embed {
            padding: 20px;
            text-align: center;
            border-radius: 12px;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            font-size: 14px;
            line-height: 1.4;
        }

        .placeholder-embed a {
            text-decoration: none;
            font-weight: 500;
        }

        html:not(.theme-light) .placeholder-embed {
            background-color: #2a2a2e;
            border: 1px solid #444;
        }
        html:not(.theme-light) .placeholder-embed a {
            color: #b9bbbe;
        }

        html.theme-light .placeholder-embed {
            background-color: #f0f2f5;
            border: 1px solid #ddd;
        }
        html.theme-light .placeholder-embed a {
            color: #555;
        }
        .twitter-loading-placeholder {
            display: flex;
            align-items: center;
            justify-content: center;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            font-size: 14px;
            min-height: 120px;
        }

        html:not(.theme-light) .twitter-loading-placeholder {
            background-color: #1c1c1c;
            color: #b9bbbe;
        }

        html.theme-light .twitter-loading-placeholder {
            background-color: #f0f2f5;
            color: #555;
        }
        .tweet-unavailable-placeholder {
            display: flex;
            align-items: center;
            gap: 15px;
            text-align: left;
            padding: 15px 20px;
        }

        .tweet-unavailable-placeholder .icon-container {
            flex-shrink: 0;
        }

        .tweet-unavailable-placeholder .icon-container svg {
            width: 40px;
            height: 40px;
            opacity: 0.6;
        }

        .tweet-unavailable-placeholder .text-container {
            display: flex;
            flex-direction: column;
            gap: 4px;
        }

        .tweet-unavailable-placeholder strong {
            font-weight: 600;
            font-size: 15px;
        }
        html:not(.theme-light) .tweet-unavailable-placeholder strong {
            color: #e4e6eb;
        }
        html.theme-light .tweet-unavailable-placeholder strong {
            color: #050505;
        }

        .tweet-unavailable-placeholder .description {
            font-size: 13px;
        }
        html:not(.theme-light) .tweet-unavailable-placeholder .description {
            color: #b9bbbe;
        }
        html.theme-light .tweet-unavailable-placeholder .description {
            color: #65676b;
        }

        .tweet-unavailable-placeholder a {
            font-size: 13px;
            text-decoration: underline;
            opacity: 0.9;
        }
        .snapchat-embed-placeholder {
            max-width: 416px;
            min-height: 650px;
            background-color: #333;
            border-radius: 12px;
        }
        html.theme-light .snapchat-embed-placeholder {
            background-color: #e0e0e0;
        }
        .iframe-google-drive {
            height: 600px;
            max-height: 80vh;
            aspect-ratio: 4 / 3;
        }

        .iframe-google-slides {
            height: auto;
            aspect-ratio: 16 / 9;
        }
        .iframe-google-maps {
            aspect-ratio: 16 / 9;
            height: 450px;
            max-height: 75vh;
        }
        .dead-link-sticker {
            display: inline-flex;
            align-items: center;
            gap: 8px;
            font-size: 13px;
            font-family: Arial, sans-serif;
            color: #8c8c8c;
            background-color: #f0f2f5;
            border: 1px solid transparent;
            padding: 5px 10px;
            border-radius: 8px;
        }
        html:not(.theme-light) .dead-link-sticker {
            color: #b9bbbe;
            background-color: transparent;
            border: transparent;
        }
        .dead-link-sticker img {
            width: 50px;
            height: auto;
        }
        .iframe-flourish {
            height: 450px;
            max-height: 85vh;
            background-color: #ffffff;
        }
        .thumbnail-embed img { width: 100%; height: 100%; object-fit: cover; }
        .thumbnail-embed { position: relative; cursor: pointer; overflow: hidden; }
        .jvchat-content .bloc-embed { justify-content: flex-start; }
        .instagram-placeholder { min-height: 450px; max-width: 500px; width: calc(100% - 20px); margin: 1em auto; border-radius: 8px; }
        .article-preview-card {
            max-width: 550px;
            width: 100%;
            border-radius: 12px;
            border: 1px solid #444;
            display: flex;
            flex-direction: column;
            text-decoration: none;
            overflow: hidden;
            transition: background-color 0.2s ease;
        }
        html:not(.theme-light) .article-preview-card {
            background-color: #2a2a2e;
        }
        html:not(.theme-light) .article-preview-card:hover {
            background-color: #333338;
        }
        html.theme-light .article-preview-card {
            border: 1px solid #ddd;
            background-color: #f0f2f5;
        }
        html.theme-light .article-preview-card:hover {
            background-color: #e8eaf0;
        }
        .article-preview-image {
            width: 100%;
            aspect-ratio: 1.91 / 1;
            background-size: cover;
            background-position: center;
            border-bottom: 1px solid #444;
        }
        html.theme-light .article-preview-image {
            border-bottom: 1px solid #ddd;
        }
        .article-preview-content {
            padding: 12px 15px;
            display: flex;
            flex-direction: column;
            gap: 4px;
        }
        .article-preview-title {
            font-size: 16px;
            font-weight: bold;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
        }
        .article-preview-description {
            font-size: 14px;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            max-height: 5.4em; /* Limite la hauteur visible à ~3 lignes */
            overflow-y: auto;
            padding-right: 5px;
        }
        .article-preview-footer {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 4px 15px 12px;
        }
        .article-preview-favicon {
            width: 16px;
            height: 16px;
            border-radius: 3px;
        }
        .article-preview-footer .article-preview-sitename {
            font-size: 12px;
            font-family: Arial, sans-serif;
            text-transform: uppercase;
            line-height: 1;
        }
        html:not(.theme-light) .article-preview-footer .article-preview-sitename { color: #8c8c8c; }
        html.theme-light .article-preview-footer .article-preview-sitename { color: #65676b; }
        html:not(.theme-light) .article-preview-title { color: #e4e6eb; }
        html:not(.theme-light) .article-preview-description { color: #b9bbbe; }
        html.theme-light .article-preview-sitename { color: #65676b; }
        html.theme-light .article-preview-title { color: #050505; }
        html.theme-light .article-preview-description { color: #65676b; }
        .article-preview-card {
            line-height: normal;
        }
        .article-preview-description::-webkit-scrollbar {
            width: 8px;
        }
        html:not(.theme-light) .article-preview-description::-webkit-scrollbar-track {
            background: transparent;
        }
        html:not(.theme-light) .article-preview-description::-webkit-scrollbar-thumb {
            background-color: #4A4A4A;
            border-radius: 10px;
            border: 2px solid #2a2a2e;
        }
        html.theme-light .article-preview-description::-webkit-scrollbar-track {
            background: transparent;
        }
        html.theme-light .article-preview-description::-webkit-scrollbar-thumb {
            background-color: #C0C0C0;
            border-radius: 10px;
            border: 2px solid #f0f2f5;
        }

        .distrokid-embed-card {
            display: flex;
            align-items: center;
            max-width: 550px;
            width: 100%;
            border-radius: 12px;
            overflow: hidden;
            text-decoration: none;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            transition: background-color 0.2s ease;
        }
        html:not(.theme-light) .distrokid-embed-card {
            background-color: #2a2a2e;
            border: 1px solid #444;
        }
        html.theme-light .distrokid-embed-card {
            background-color: #f0f2f5;
            border: 1px solid #ddd;
        }
        html:not(.theme-light) .distrokid-embed-card:hover {
             background-color: #333338;
        }
        html.theme-light .distrokid-embed-card:hover {
            background-color: #e8eaf0;
        }

        .distrokid-album-art {
            width: 90px;
            height: 90px;
            flex-shrink: 0;
            background-size: cover;
            background-position: center;
        }

        .distrokid-content {
            flex-grow: 1;
            padding: 10px 15px;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            height: 90px;
            box-sizing: border-box;
        }

        .distrokid-title {
            font-size: 16px;
            font-weight: 600;
            display: -webkit-box;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        html:not(.theme-light) .distrokid-title { color: #e4e6eb; }
        html.theme-light .distrokid-title { color: #050505; }

        .distrokid-artist {
            font-size: 14px;
        }
        html:not(.theme-light) .distrokid-artist { color: #b9bbbe; }
        html.theme-light .distrokid-artist { color: #65676b; }

        .distrokid-content audio {
            width: 100%;
            height: 30px;
        }

      .tweet-loading-overlay {
          position: absolute;
          top: 0; left: 0;
          width: 100%;
          height: 100%;
          display: flex;
          justify-content: center;
          align-items: center;
          background: rgba(0, 0, 0, 0.7);
          color: white;
          font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
          font-size: 15px;
          font-weight: 500;
          z-index: 10;
          border-radius: 9px;
          backdrop-filter: blur(2px);
          -webkit-backdrop-filter: blur(2px);
      }
      html.theme-light .tweet-loading-overlay {
          background: rgba(255, 255, 255, 0.7);
          color: #0f1419;
      }
      .tweet-embed-wrapper {
          position: relative;
          display: block;
          line-height: 0;
      }

      .overlay-replies-button {
          position: absolute;
          bottom: 0;
          left: 0;
          right: 0;
          background: rgba(20, 23, 26, 0.85);
          backdrop-filter: blur(4px);
          -webkit-backdrop-filter: blur(4px);
          text-align: center;
          padding: 12px 0;
          cursor: pointer;
          border-bottom-left-radius: 9px;
          border-bottom-right-radius: 9px;
          transition: background-color 0.2s ease;
          display: block; /* Visible par défaut */
      }

      .tweet-embed-wrapper.showing-replies .overlay-replies-button {
          display: none;
      }
      .overlay-replies-button a {
          color: white;
          text-decoration: none;
          font-weight: 600;
          font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
          font-size: 14px;
      }

      html.theme-light .overlay-replies-button {
          background: rgba(255, 255, 255, 0.8);
      }
      html.theme-light .overlay-replies-button a {
          color: #0f1419;
      }
      .iframe-pdf {
          height: 600px;
          max-height: 80vh;
          aspect-ratio: 4 / 3;
      }

      .bloc-embed {
          display: flex;
          justify-content: left;
      }

      .embed-collapsible {
          display: flex;
          flex-direction: column;
          max-width: 550px;
          width: 100%;
      }

      .embed-header {
          display: flex;
          justify-content: space-between;
          align-items: center;
          padding: 8px 12px;
          border: 1px solid transparent;
          border-radius: 9px;
          order: -1;
          width: 100%;
          box-sizing: border-box;
          cursor: pointer;
          /* On ajoute 'margin' et 'border-radius' à la transition */
          transition: margin 0.3s ease-in-out, border-color 0.2s ease, border-radius 0.3s ease-in-out;
      }

      .embed-collapsible:not(.collapsed) .embed-header {
          margin-top: -9px;
          margin-bottom: -5px;
          border-radius: 0;
      }

      .embed-info {
          font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
          font-size: 12px;
          font-weight: 500;
          opacity: 0.5;
      }
      html:not(.theme-light) .embed-info { color: #b9bbbe; }
      html.theme-light .embed-info { color: #555; }


      @media (hover: hover) {

        }

      .toggle-embed-button {
            color: #b9bbbe;
        }
        .toggle-embed-button svg {
            width: 100%; height: 100%;
        }
        html.theme-light .toggle-embed-button {
            color: #555;
        }

      .media-content {
            display: grid;
            grid-template-rows: 1fr;
            transition: grid-template-rows 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;
            opacity: 1;
        }

        .media-content > div {
            overflow: hidden;
        }

        .embed-collapsible.collapsed .media-content {
            grid-template-rows: 0fr;
            opacity: 0.5;
        }

      .media-content > * {
          border-radius: 9px;
        }

      .embed-collapsible.collapsed .embed-header {
          border-radius: 9px;
          background-color: transparent;
          border-color: #505050;
      }
      html.theme-light .embed-collapsible.collapsed .embed-header {
          border-color: #e0e0e0;
      }

      .embed-collapsible.collapsed .embed-info {
          opacity: 0.7;
      }
      .embed-collapsible.collapsed .toggle-embed-button {
          opacity: 0.8;
      }
      .embed-collapsible.collapsed .embed-header:hover .toggle-embed-button,
      .embed-collapsible.collapsed .embed-header:hover .embed-info {
          opacity: 1;
      }

    .toggle-embed-button {
        background-color: transparent;
        border: none;
        border-radius: 5px;
        width: 22px;
        height: 22px;
        padding: 2px;
        opacity: 0.6;
        display: flex;
        align-items: center;
        justify-content: center;
        transition: background-color 0.2s ease-in-out, opacity 0.2s ease-in-out;
    }

    .icon-collapse, .icon-expand {
        display: block;
        width: 100%;
        height: 100%;
        background-repeat: no-repeat;
        background-position: center;
        background-size: contain;
    }

    html:not(.theme-light) .icon-collapse { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3e%3cpath d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z' fill='%23b9bbbe'/%3e%3c/svg%3e"); }
    html:not(.theme-light) .icon-expand { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3e%3cpath d='M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z' fill='%23b9bbbe'/%3e%3c/svg%3e"); }

    html.theme-light .icon-collapse { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3e%3cpath d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z' fill='%23555'/%3e%3c/svg%3e"); }
    html.theme-light .icon-expand { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3e%3cpath d='M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z' fill='%23555'/%3e%3c/svg%3e"); }

    .icon-collapse { display: block; }
    .icon-expand { display: none; }
    .embed-collapsible.collapsed .icon-collapse { display: none; }
    .embed-collapsible.collapsed .icon-expand { display: block; }

    .vxtwitter-embed {
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
        color: var(--text-bloc-principal, #d7d7d7);
        background-color: #16181C;
        border: 1px solid #38444d;
        border-radius: 12px;
        padding: 1rem;
        max-width: 550px;
        width: 100%;
        text-decoration: none;
        display: block;
        line-height: 1.4;
        overflow: hidden;
        max-height: min(85vh, 500px);
        overflow-y: auto;
        box-sizing: border-box;
    }
    .vxtwitter-embed.vxtwitter-full-height {
        max-height: none;
        overflow-y: visible;
    }
    .vxtwitter-embed::-webkit-scrollbar { width: 8px; }
    .vxtwitter-embed::-webkit-scrollbar-track { background: transparent; }
    .vxtwitter-embed::-webkit-scrollbar-thumb {
        background-color: #4A4A4A;
        border-radius: 10px;
        border: 2px solid #16181C;
    }
    html.theme-light .vxtwitter-embed {
        border-color: #cfd9de;
        color: #0f1419;
        background-color: #F7F9F9;
    }
    html.theme-light .vxtwitter-embed::-webkit-scrollbar-thumb {
        background-color: #C0C0C0;
        border: 2px solid #F7F9F9;
    }
    .vxtwitter-quoted-tweet, .parent-tweet-container {
        background-color: rgba(0, 0, 0, 0.1);
        border-radius: 12px;
    }
    html.theme-light .vxtwitter-quoted-tweet,
    html.theme-light .parent-tweet-container {
        background-color: rgba(0, 0, 0, 0.03);
    }
    .vxtwitter-header {
        display: flex;
        align-items: flex-start; /* Aligne l'avatar en haut du bloc d'infos */
        gap: 0.75rem;
        margin-bottom: 0.5rem;
    }
    .vxtwitter-header-left { display: flex; align-items: center; gap: 0.75rem; }
    .vxtwitter-header-date {
        color: #8899a6;
        font-size: 0.9em;
    }
    html.theme-light .vxtwitter-header-date { color: #536471; }
    .vxtwitter-avatar { width: 48px; height: 48px; border-radius: 9999px; }
    .vxtwitter-author {
        display: flex;
        align-items: baseline;
        gap: 0.3rem;
    }
    .vxtwitter-author-name {
        font-weight: bold;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    .vxtwitter-author-handle {
        color: #8899a6;
        font-size: 0.9em;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        flex-shrink: 0;
    }
     html.theme-light .vxtwitter-author-handle { color: #536471; }
    .vxtwitter-replying-to { color: #8899a6; font-size: 0.9em; margin-bottom: 0.5rem; }
    .vxtwitter-text { white-space: pre-wrap; word-wrap: break-word; font-size: 1.1em; }
    .vxtwitter-text a { color: #1d9bf0; text-decoration: none; }
    .vxtwitter-text a:hover { text-decoration: underline; }
    .vxtwitter-media-grid {
        display: grid;
        gap: 2px;
        margin-top: 0.75rem;
        border-radius: 12px;
        overflow: hidden;
    }
    .vxtwitter-media-grid[data-count="1"] { grid-template-columns: 1fr; }
    .vxtwitter-media-grid[data-count="2"] { grid-template-columns: 1fr 1fr; }
    .vxtwitter-media-grid[data-count="3"] { grid-template-columns: 1fr 1fr; }
    .vxtwitter-media-grid[data-count="4"] { grid-template-columns: 1fr 1fr; }
    .vxtwitter-media-grid[data-count="3"] .media-item:first-child { grid-row: span 2; }
    .vxtwitter-media-item {
        background-color: #000;
        display: flex;
        align-items: center;
        justify-content: center;
    }
    .vxtwitter-media-item a { display: block; }
    .vxtwitter-media-item img, .vxtwitter-media-item video {
        display: block;
        max-width: 100%;
        width: 100%;
        height: auto;
        object-fit: contain;
        max-height: 50vh;
    }
    .vxtwitter-quoted-tweet {
        margin-top: 0.75rem;
        border: 1px solid #38444d;
        padding: 0.75rem;
    }
    html.theme-light .vxtwitter-quoted-tweet { border-color: #cfd9de; }
    .vxtwitter-quoted-tweet .vxtwitter-header { margin-bottom: 0.25rem; }
    .vxtwitter-quoted-tweet .vxtwitter-avatar { width: 24px; height: 24px; }
    .vxtwitter-quoted-tweet .vxtwitter-author-name { font-size: 0.9em; }
    .vxtwitter-quoted-tweet .vxtwitter-author-handle { font-size: 0.8em; }
    .vxtwitter-quoted-tweet .vxtwitter-text { font-size: 0.9em; }

    .vxtwitter-footer {
        display: flex;
        justify-content: space-between;
        align-items: center;
        color: #8899a6;
        margin-top: 0.75rem;
        padding-top: 0.5rem;
        gap: 1rem;
    }
    html.theme-light .vxtwitter-footer { color: #536471; }

    .vxtwitter-stats-group {
      display: flex;
      gap: 1rem;
      flex-shrink: 1;
      min-width: 0;
    }

    .vxtwitter-stats-group.is-overflowing::after {
        content: '';
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        width: 20px;
        pointer-events: none;
        background: linear-gradient(to right, transparent, #16181C 90%);
    }
    html.theme-light .vxtwitter-stats-group.is-overflowing::after {
        background: linear-gradient(to right, transparent, #F7F9F9 90%);
    }

    .vxtwitter-stat {
        display: flex;
        align-items: center;
        gap: 0.3rem;
        font-size: 0.85em;
        white-space: nowrap;
    }

    .vxtwitter-poll {
        margin-top: 0.75rem;
        border: 1px solid #38444d;
        border-radius: 12px;
        padding: 8px;
        display: flex;
        flex-direction: column;
        gap: 8px;
    }
    html.theme-light .vxtwitter-poll {
        border-color: #cfd9de;
    }

    .vxtwitter-poll-option {
        position: relative;
        border-radius: 6px;
        overflow: hidden;
    }
    html:not(.theme-light) .vxtwitter-poll-option {
        color: #e4e6eb;
    }
    html.theme-light .vxtwitter-poll-option {
        color: #0f1419;
    }
    .vxtwitter-poll-text {
        position: relative;
        z-index: 2;
        padding: 8px 12px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        font-size: 0.95em;
        font-weight: 500;
    }
    .vxtwitter-poll-option.winner .vxtwitter-poll-option-name {
        font-weight: bold;
    }

    .vxtwitter-poll-bar {
        position: absolute;
        top: 0;
        left: 0;
        height: 100%;
        z-index: 1;
        background-color: rgba(29, 155, 240, 0.4);
        transition: width 0.5s ease-out;
    }

    .vxtwitter-poll-option.winner .vxtwitter-poll-bar {
        background-color: rgb(29, 155, 240);
    }

    html.theme-light .vxtwitter-poll-bar {
        background-color: rgba(29, 155, 240, 0.2);
    }
    html.theme-light .vxtwitter-poll-option.winner .vxtwitter-poll-bar {
        background-color: rgba(29, 155, 240, 0.4);
    }

    .vxtwitter-poll-footer {
        font-size: 0.85em;
        padding: 4px 4px 0;
    }
    html:not(.theme-light) .vxtwitter-poll-footer {
        color: #8899a6;
    }
    html.theme-light .vxtwitter-poll-footer {
        color: #536471;
    }
   .vxtwitter-community-note {
        margin-top: 0.75rem;
        border-radius: 12px;
        overflow: hidden;
        font-size: 0.9em;
    }
    html:not(.theme-light) .vxtwitter-community-note {
        border: 1px solid #454A4D;
        color: #E4E6EB;
    }
    html.theme-light .vxtwitter-community-note {
        border: 1px solid #CFD9DE;
        color: #0F1419;
    }

    .cn-header {
        display: flex;
        align-items: center;
        gap: 0.5rem;
        padding: 0.75rem;
    }
    html:not(.theme-light) .cn-header {
        background-color: #272B2D;
    }
    html.theme-light .cn-header {
        background-color: #EFF3F4;
    }

    .cn-title {
        font-weight: bold;
    }

    .cn-icon {
        display: inline-block;
        width: 1.25em;
        height: 1.25em;
        background-repeat: no-repeat;
        background-position: center;
        background-size: contain;
        flex-shrink: 0;
    }
    html:not(.theme-light) .cn-icon {
        background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23B1B8BE'%3e%3cpath d='M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z'%3e%3c/path%3e%3c/svg%3e");
    }
    html.theme-light .cn-icon {
        background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23536471'%3e%3cpath d='M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z'%3e%3c/path%3e%3c/svg%3e");
    }
    .cn-body {
        padding: 0.75rem;
    }

    .cn-text {
        line-height: 1.4;
        white-space: pre-wrap;
        word-wrap: break-word;
    }
    .cn-text a {
        color: #1d9bf0;
        text-decoration: none;
    }
    .cn-text a:hover {
        text-decoration: underline;
    }

    .cn-footer-link {
        display: block;
        margin-top: 0.75rem;
        font-size: 0.9em;
        text-decoration: none;
    }
    html:not(.theme-light) .cn-footer-link {
        color: #8899a6;
    }
    html.theme-light .cn-footer-link {
        color: #536471;
    }
    .cn-footer-link:hover {
        text-decoration: underline;
    }
    .vxtwitter-nitter-link {
        flex-shrink: 0;
        font-size: 0.85em;
        color: #8899a6;
        text-decoration: none;
        white-space: nowrap;
    }
    .vxtwitter-nitter-link:hover { text-decoration: underline; }
    html.theme-light .vxtwitter-nitter-link { color: #536471; }

    .vxtwitter-icon {
    display: block;
    width: 1.2em;
    height: 1.2em;
    background-repeat: no-repeat;
    background-position: center;
    background-size: contain;
    position: relative; /* Permet de déplacer l'élément */
    top: 1px;           /* Pousse l'icône de 1px vers le bas */
}
    /* -- Icônes Thème Sombre -- */
    html:not(.theme-light) .vx-icon-reply { background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='%238899a6' d='M19 4H5a2 2 0 0 0-2 2v15l3.467-2.6a2 2 0 0 1 1.2-.4H19a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z'/%3e%3c/svg%3e"); }
    html:not(.theme-light) .vx-icon-retweet { background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 52 52' xmlns='http://www.w3.org/2000/svg'%3e%3cg%3e%3cpath fill='%238899a6' d='M51.6 28.8l-2.1-2.1c-.6-.6-1.5-.6-2.1 0l-2.7 2.7C44 30.1 43 29.6 43 28.7V14v-2c0-2.2-1.8-4-4-4h-.6H24.5c-.8 0-1.5.7-1.5 1.5v3c0 .8.7 1.5 1.5 1.5h11c.8 0 1.5.7 1.5 1.5v13.2c0 .9-1.1 1.3-1.8.7l-2.6-2.6c-.6-.6-1.6-.6-2.1 0L28.4 29c-.6.6-.6 1.5 0 2.1l10.5 10.5c.6.6 1.5.6 2.1 0L51.6 31c.5-.6.5-1.6 0-2.2z'/%3e%3cpath fill='%238899a6' d='M27.5 38h-11c-.8 0-1.5-.7-1.5-1.5V23.3c0-.9 1.1-1.3 1.8-.7l2.6 2.6c.6.6 1.6.6 2.1 0l2.1-2.1c.6-.6.6-1.5 0-2.1L13.2 10.4c-.6-.6-1.5-.6-2.1 0L.4 21c-.6.6-.6 1.5 0 2.1l2.1 2.1c.6.6 1.5.6 2.1 0l2.7-2.7C7.9 21.9 9 22.3 9 23.2V38v2c0 2.2 1.9 4 4.1 4h.6h13.9c.8 0 1.5-.7 1.5-1.5v-3c0-.8-.7-1.5-1.5-1.5z'/%3e%3c/g%3e%3c/svg%3e"); }
    html:not(.theme-light) .vx-icon-like { background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='%238899a6' d='M31 11c0 11-14 18-15 18S1 22 1 11c0-4.4 3.6-8 8-8c3 0 5.6 1.7 7 4.1C17.4 4.7 20 3 23 3c4.4 0 8 3.6 8 8z'/%3e%3c/svg%3e"); }
    /* -- Icônes Thème Clair -- */
    html.theme-light .vx-icon-reply { background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='%23536471' d='M19 4H5a2 2 0 0 0-2 2v15l3.467-2.6a2 2 0 0 1 1.2-.4H19a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z'/%3e%3c/svg%3e"); }
    html.theme-light .vx-icon-retweet { background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 52 52' xmlns='http://www.w3.org/2000/svg'%3e%3cg%3e%3cpath fill='%23536471' d='M51.6 28.8l-2.1-2.1c-.6-.6-1.5-.6-2.1 0l-2.7 2.7C44 30.1 43 29.6 43 28.7V14v-2c0-2.2-1.8-4-4-4h-.6H24.5c-.8 0-1.5.7-1.5 1.5v3c0 .8.7 1.5 1.5 1.5h11c.8 0 1.5.7 1.5 1.5v13.2c0 .9-1.1 1.3-1.8.7l-2.6-2.6c-.6-.6-1.6-.6-2.1 0L28.4 29c-.6.6-.6 1.5 0 2.1l10.5 10.5c.6.6 1.5.6 2.1 0L51.6 31c.5-.6.5-1.6 0-2.2z'/%3e%3cpath fill='%23536471' d='M27.5 38h-11c-.8 0-1.5-.7-1.5-1.5V23.3c0-.9 1.1-1.3 1.8-.7l2.6 2.6c.6.6 1.6.6 2.1 0l2.1-2.1c.6-.6.6-1.5 0-2.1L13.2 10.4c-.6-.6-1.5-.6-2.1 0L.4 21c-.6.6-.6 1.5 0 2.1l2.1 2.1c.6.6 1.5.6 2.1 0l2.7-2.7C7.9 21.9 9 22.3 9 23.2V38v2c0 2.2 1.9 4 4.1 4h.6h13.9c.8 0 1.5-.7 1.5-1.5v-3c0-.8-.7-1.5-1.5-1.5z'/%3e%3c/g%3e%3c/svg%3e"); }
    html.theme-light .vx-icon-like { background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='%23536471' d='M31 11c0 11-14 18-15 18S1 22 1 11c0-4.4 3.6-8 8-8c3 0 5.6 1.7 7 4.1C17.4 4.7 20 3 23 3c4.4 0 8 3.6 8 8z'/%3e%3c/svg%3e"); }

    .parent-tweet-container {
        padding-left: 24px;
        border-left: 2px solid #38444d;
        margin-left: 24px;
        margin-bottom: 0.5rem;
        padding-bottom: 0.5rem;
        padding-right: 12px;
    }
    html.theme-light .parent-tweet-container {
        border-left-color: #cfd9de;
    }
    .vxtwitter-author-info {
        display: flex;
        flex-direction: column;
        line-height: 1.3;
        min-width: 0;
    }
    @media (max-width: 768px) {
        .vxtwitter-text {
            font-size: 1em;
        }
    }

    .vxtwitter-text-collapsible {
        max-height: 12em;
        overflow: hidden;
        position: relative;
        transition: max-height 0.3s ease-out;
    }
    .vxtwitter-text-collapsible:not(.vxtwitter-text-expanded)::after {
        content: '';
        position: absolute;
        bottom: 0;
        left: 0;
        right: 0;
        height: 3em;
        background: linear-gradient(to bottom, transparent, #16181C);
        pointer-events: none;
    }
    html.theme-light .vxtwitter-text-collapsible:not(.vxtwitter-text-expanded)::after {
        background: linear-gradient(to bottom, transparent, #F7F9F9);
    }
    .vxtwitter-text-expanded {
        max-height: 1500px;
    }

    .vxtwitter-show-more {
        display: inline-block;
        margin-top: 8px;
        padding: 6px 12px;
        font-size: 0.85em;
        font-weight: 500;
        border-radius: 15px;
        text-decoration: none;
        transition: background-color 0.2s ease;
        cursor: pointer;
    }
    html:not(.theme-light) .vxtwitter-show-more {
        background-color: #2a2a2e;
        color: #e4e6eb;
        border: 1px solid #38444d;
    }
    html:not(.theme-light) .vxtwitter-show-more:hover {
        background-color: #333338;
    }
    html.theme-light .vxtwitter-show-more {
        background-color: #e8eaf0;
        color: #0f1419;
        border: 1px solid #cfd9de;
    }
    html.theme-light .vxtwitter-show-more:hover {
        background-color: #dde0e4;
    }
    .vxtwitter-stats-group.is-rotating {
        position: relative;
        min-height: 1.2em;
    }
    .vxtwitter-stats-group.is-rotating > .vxtwitter-stat {
        position: absolute;
        top: 0;
        left: 0;
        opacity: 0;
        visibility: hidden;
        transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;
        pointer-events: none;
    }

    .vxtwitter-stats-group.is-rotating > .vxtwitter-stat.is-active {
        position: relative;
        opacity: 1;
        visibility: visible;
        pointer-events: auto;
}

    @keyframes lm-spin {
        to { transform: rotate(360deg); }
    }

    .lm-loader-container {
        position: absolute;
        top: 0; left: 0;
        width: 100%;
        height: 100%;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        gap: 12px;
        background: rgba(0, 0, 0, 0.7);
        z-index: 10;
        backdrop-filter: blur(2px);
        -webkit-backdrop-filter: blur(2px);
        transition: opacity 0.3s ease;
    }
    html.theme-light .lm-loader-container {
        background: rgba(255, 255, 255, 0.7);
    }

    .lm-spinner {
        width: 40px;
        height: 40px;
        border-radius: 50%;
        border-width: 4px;
        border-style: solid;
        animation: lm-spin 0.8s linear infinite;
    }
    html:not(.theme-light) .lm-spinner {
        border-color: rgba(255, 255, 255, 0.2);
        border-top-color: #fff;
    }
    html.theme-light .lm-spinner {
        border-color: rgba(0, 0, 0, 0.1);
        border-top-color: #333;
    }

    .lm-loader-text {
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
        font-size: 14px;
        font-weight: 500;
    }
    html:not(.theme-light) .lm-loader-text {
        color: #e4e6eb;
    }
    html.theme-light .lm-loader-text {
        color: #0f1419;
    }

      .discord-invite-card {
          font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
          max-width: 550px;
          width: 100%;
          border-radius: 12px;
          padding: 16px;
          display: flex;
          align-items: center;
          justify-content: space-between;
          box-sizing: border-box;
          line-height: 1.4;
      }
      .discord-header {
          display: flex;
          align-items: center;
          gap: 12px;
          overflow: hidden;
          flex-grow: 1;
          min-width: 0;
      }
      .discord-server-icon {
          width: 50px;
          height: 50px;
          border-radius: 15px;
          flex-shrink: 0;
          object-fit: cover;
      }
      .discord-server-info {
          display: flex;
          flex-direction: column;
          gap: 4px;
          overflow: hidden;
          flex-grow: 1;
          min-width: 0;
      }
      .discord-server-name {
          font-size: 16px;
          font-weight: 600;
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
          display: flex;
          align-items: center;
          gap: 6px;
      }
      .discord-badge {
          display: inline-flex;
          align-items: center;
          justify-content: center;
          flex-shrink: 0;
      }
      .discord-verified-badge { color: #23a55a; }
      .discord-partnered-badge { color: #5865f2; }

      .discord-member-counts {
          display: flex;
          align-items: center;
          font-size: 13px;
      }
      .discord-status-dot {
          width: 8px;
          height: 8px;
          border-radius: 50%;
          margin-right: 4px;
      }
      .discord-count {
          margin-right: 12px;
      }
      .discord-join-button {
          padding: 8px 16px;
          border-radius: 6px;
          font-weight: 600;
          font-size: 14px;
          text-decoration: none;
          flex-shrink: 0;
          transition: background-color 0.2s ease, transform 0.1s ease;
          margin-left: 12px;
      }
      .discord-join-button:active {
          transform: scale(0.97);
      }

      .discord-loading-placeholder, .discord-error-placeholder {
          display: flex;
          align-items: center;
          justify-content: center;
          font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
          font-size: 14px;
          height: 82px;
          box-sizing: border-box;
          border-radius: 12px;
      }

      /* -- Thème Sombre -- */
      html:not(.theme-light) .discord-invite-card {
          background-color: #2a2a2e;
          border: 1px solid #444;
      }
      html:not(.theme-light) .discord-server-name {
          color: #e4e6eb;
      }
      html:not(.theme-light) .discord-member-counts {
          color: #b9bbbe;
      }
      html:not(.theme-light) .discord-status-dot.discord-online {
          background-color: #23a55a;
      }
      html:not(.theme-light) .discord-status-dot.discord-offline {
          background-color: #80848e;
      }
      html:not(.theme-light) .discord-join-button {
          background-color: #404eed;
          color: #fff;
      }
      html:not(.theme-light) .discord-join-button:hover {
          background-color: #3642d3;
      }
      html:not(.theme-light) .discord-loading-placeholder,
      html:not(.theme-light) .discord-error-placeholder {
          background-color: #1c1c1c;
          color: #b9bbbe;
      }

      /* -- Thème Clair -- */
      html.theme-light .discord-invite-card {
          background-color: #f0f2f5;
          border: 1px solid #ddd;
      }
      html.theme-light .discord-server-name {
          color: #050505;
      }
      html.theme-light .discord-member-counts {
          color: #65676b;
      }
      html.theme-light .discord-status-dot.discord-online {
          background-color: #2dc770;
      }
      html.theme-light .discord-status-dot.discord-offline {
          background-color: #96989e;
      }
      html.theme-light .discord-join-button {
          background-color: #5865f2;
          color: #fff;
      }
      html.theme-light .discord-join-button:hover {
          background-color: #4a54d4;
      }
      html.theme-light .discord-loading-placeholder,
      html.theme-light .discord-error-placeholder {
          background-color: #f0f2f5;
          color: #555;
      }

      .embed-info-container {
          display: flex;
          align-items: center;
          gap: 8px;
          min-width: 0;
      }
      .embed-favicon {
          width: 16px;
          height: 16px;
          border-radius: 3px;
          flex-shrink: 0;
      }
      .embed-info {
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
      }
      /* -- Styles pour l'embed Stack Overflow -- */
      .so-embed-wrapper {
          font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
          border: 1px solid var(--border-bloc-principal, #4a4a4a);
          border-radius: 9px;
          max-width: 550px;
          width: 100%;
          background-color: var(--bg-bloc-content, #252525);
          line-height: 1.5;
          overflow: hidden;
      }
      html.theme-light .so-embed-wrapper {
          background-color: #f9f9f9;
      }

      .so-question-header {
          padding: 12px 15px;
          border-bottom: 1px solid var(--border-bloc-principal, #4a4a4a);
      }

      .so-question-header a {
          font-size: 16px;
          font-weight: 600;
          text-decoration: none;
          color: #3ca4ff;
      }
      .so-question-header a:hover {
          color: #69bfff;
      }
      html.theme-light .so-question-header a {
          color: #0077cc;
      }
      html.theme-light .so-question-header a:hover {
          color: #005999;
      }


      .so-answer-body {
          padding: 0 15px 15px 15px;
          font-size: 14px;
          max-height: 400px;
          overflow-y: auto;
          color: var(--text-bloc-principal, #d7d7d7);
      }
      html.theme-light .so-answer-body {
          color: #242729;
      }

      /* Styles pour les barres de défilement */
      .so-answer-body::-webkit-scrollbar { width: 8px; }
      .so-answer-body::-webkit-scrollbar-track { background: transparent; }
      .so-answer-body::-webkit-scrollbar-thumb {
          background-color: #4A4A4A;
          border-radius: 10px;
          border: 2px solid var(--bg-bloc-content, #252525);
      }
      html.theme-light .so-answer-body::-webkit-scrollbar-thumb {
          background-color: #C0C0C0;
          border: 2px solid #f9f9f9;
      }

      /* Styles pour le code */
      .so-answer-body pre {
          background-color: var(--bg-bloc-code, #1c1c1c);
          border: 1px solid var(--border-bloc-principal, #3a3a3a);
          border-radius: 6px;
          padding: 12px;
          white-space: pre-wrap;
          word-wrap: break-word;
          font-family: "Consolas", "Menlo", "Monaco", "Courier New", monospace;
          font-size: 13px;
      }
      html.theme-light .so-answer-body pre {
          background-color: #f0f2f5;
          border-color: #e0e0e0;
      }

      .so-answer-body code {
          background-color: rgba(135,131,120,0.15);
          color: #eb5757;
          padding: 2px 4px;
          border-radius: 4px;
          font-size: 0.9em;
      }
      html.theme-light .so-answer-body code {
          background-color: rgba(30,30,30,0.07);
          color: #c7254e;
      }
      .so-answer-body pre code {
          background: none;
          color: inherit;
          padding: 0;
      }


      .so-footer {
          display: flex;
          justify-content: space-between;
          align-items: center;
          padding: 10px 15px;
          background-color: rgba(0,0,0,0.15);
          border-top: 1px solid var(--border-bloc-principal, #4a4a4a);
          font-size: 13px;
      }
      html.theme-light .so-footer {
          background-color: rgba(0,0,0,0.03);
      }

      .so-score {
          display: flex;
          align-items: center;
          gap: 8px;
          font-weight: bold;
          color: #2f9a4c;
      }
      .so-score.so-score-negative {
          color: #d13c3c;
      }

      .so-author {
          color: var(--text-color-meta, #888);
          opacity: 0.8;
      }
      html.theme-light .so-author {
          color: #525960;
      }

      .lm-settings-button-modern {
          display: inline-flex;
          align-items: center;
          justify-content: center;
          gap: 8px;
          text-decoration: none;
          padding: 10px 15px;
          margin: 12px 1rem 4px 1rem; 
          border-radius: 8px;
          font-weight: 500;
          font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
          font-size: 14px;
          cursor: pointer;
          transition: background-color 0.2s ease, transform 0.1s ease;
      }
      .lm-settings-button-modern:active {
          transform: scale(0.98);
      }
      .lm-settings-button-modern svg {
          width: 18px;
          height: 18px;
      }

      html:not(.theme-light) .lm-settings-button-modern {
          background-color: #3a3f44;
          color: #e4e6eb;
          border: 1px solid #4a4a4f;
      }
      html:not(.theme-light) .lm-settings-button-modern:hover {
          background-color: #4b5157;
      }

      html.theme-light .lm-settings-button-modern {
          background-color: #e4e6eb;
          color: #050505;
          border: 1px solid #dcdfe2;
      }
      html.theme-light .lm-settings-button-modern:hover {
          background-color: #d8dbdf;
      }
      .lm-settings-menu-item {
          display: flex !important;
          align-items: center;
          gap: 10px;
          text-decoration: none;
          padding: 0.75rem 1rem;
          border-radius: 6px;
          font-weight: 500;
          font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
          font-size: 14px;
          cursor: pointer;
          transition: background-color 0.2s ease;
      }
      .lm-settings-menu-item .lm-settings-icon {
          width: 20px;
          height: 20px;
          flex-shrink: 0;
      }
      html:not(.theme-light) .lm-settings-menu-item {
          color: #dcddde;
      }
      html:not(.theme-light) .lm-settings-menu-item:hover {
          background-color: #3a3f44;
      }
      html.theme-light .lm-settings-menu-item {
          color: #050505;
      }
      html.theme-light .lm-settings-menu-item:hover {
          background-color: #f0f2f5;
      }

        @keyframes skeleton-pulse {
            0% { opacity: 0.6; }
            50% { opacity: 1; }
            100% { opacity: 0.6; }
        }

        .skeleton-card {
            height: 300px;
            background-color: #2a2a2e;
            border-color: #444;
        }
        html.theme-light .skeleton-card {
            background-color: #f0f2f5;
            border-color: #ddd;
        }

        .skeleton-image {
            width: 100%;
            height: 180px;
            background-color: #3a3a3a;
            animation: skeleton-pulse 1.5s infinite ease-in-out;
        }
        html.theme-light .skeleton-image { background-color: #dce0e6; }

        .skeleton-content { padding: 15px; display: flex; flex-direction: column; gap: 10px; }

        .skeleton-line {
            height: 14px;
            background-color: #3a3a3a;
            border-radius: 4px;
            animation: skeleton-pulse 1.5s infinite ease-in-out;
        }
        html.theme-light .skeleton-line { background-color: #dce0e6; }

        .skeleton-line.title { width: 70%; height: 20px; margin-bottom: 5px; }
        .skeleton-line.text { width: 90%; }

        /* Animation de pulsation */
        @keyframes skeleton-loading {
            0% { background-position: 100% 50%; }
            100% { background-position: 0 50%; }
        }

        .tweet-skeleton {
            padding: 1rem;
            border: 1px solid #38444d;
            border-radius: 12px;
            background: #16181C;
            max-width: 550px;
            width: 100%;
            box-sizing: border-box;
        }
        html.theme-light .tweet-skeleton {
            background: #F7F9F9;
            border-color: #cfd9de;
        }

        .sk-header { display: flex; gap: 10px; margin-bottom: 10px; }
        .sk-avatar { width: 48px; height: 48px; border-radius: 50%; background: #333; }
        .sk-meta { display: flex; flex-direction: column; gap: 6px; justify-content: center; }
        .sk-line { height: 10px; border-radius: 4px; background: linear-gradient(90deg, #333 25%, #444 37%, #333 63%); background-size: 400% 100%; animation: skeleton-loading 1.4s ease infinite; }
        .sk-line.short { width: 100px; }
        .sk-line.medium { width: 180px; }
        .sk-line.long { width: 100%; height: 60px; margin-top: 10px; }

        html.theme-light .sk-avatar, html.theme-light .sk-line {
            background: #e1e8ed;
            background: linear-gradient(90deg, #e1e8ed 25%, #f5f8fa 37%, #e1e8ed 63%);
            background-size: 400% 100%;
        }
    `);

    // =========================================================================
    // == HELPERS POUR LE LECTEUR TWITTER PERSONNALISÉ
    // =========================================================================

    function setupStatCarousel(statsGroupNode) {
        setTimeout(() => {
            const isOverflowing = statsGroupNode.scrollWidth > statsGroupNode.clientWidth;

            if (!isOverflowing) {
                return;
            }

            statsGroupNode.classList.add('is-rotating');
            const stats = statsGroupNode.querySelectorAll('.vxtwitter-stat');
            if (stats.length <= 1) return;

            let currentIndex = 0;
            const animationInterval = 3000;

            stats[currentIndex].classList.add('is-active');

            setInterval(() => {
                stats[currentIndex].classList.remove('is-active');
                currentIndex = (currentIndex + 1) % stats.length;
                stats[currentIndex].classList.add('is-active');
            }, animationInterval);

        }, 100);
    }

    /**
     * Gère le rendu des embeds Twitter personnalisés.
     */
    const VxTwitterRenderer = {
        TWEET_TRUNCATE_LENGTH: 350,

        _formatNumber(num) {
            if (num >= 1000000) return (num / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
            if (num >= 1000) return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
            return num;
        },

        _formatDate(epoch) {
            const date = new Date(epoch * 1000);
            return date.toLocaleDateString('fr-FR', { day: '2-digit', month: 'short', year: 'numeric' }) + ' à ' +
                   date.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
        },

        render(tweet, isQuoted = false) {
            if (!tweet || !tweet.user_screen_name) return '';
            const isLongTweet = tweet.text.length > this.TWEET_TRUNCATE_LENGTH && !isQuoted;

            const formattedText = tweet.text
                .replace(/\n/g, '<br>')
                .replace(/(https?:\/\/[^\s]+)/g, '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>');

            let pollHtml = '';
            if (tweet.pollData && tweet.pollData.options && tweet.pollData.options.length > 0) {
                const totalVotes = tweet.pollData.options.reduce((sum, option) => sum + option.votes, 0);
                const maxPercent = Math.max(...tweet.pollData.options.map(o => o.percent));
                pollHtml = `
                    <div class="vxtwitter-poll">
                        ${tweet.pollData.options.map(option => `
                            <div class="vxtwitter-poll-option ${option.percent === maxPercent ? 'winner' : ''}">
                                <div class="vxtwitter-poll-bar" style="width: ${option.percent}%;"></div>
                                <div class="vxtwitter-poll-text">
                                    <span class="vxtwitter-poll-option-name">${option.name}</span>
                                    <span class="vxtwitter-poll-option-percent">${option.percent.toFixed(1).replace('.', ',')}%</span>
                                </div>
                            </div>
                        `).join('')}
                        <div class="vxtwitter-poll-footer">
                            <span>${totalVotes.toLocaleString('fr-FR')} votes</span>
                        </div>
                    </div>
                `;
            }

         let communityNoteHtml = '';
            if (tweet.communityNote) {
                const formattedNote = tweet.communityNote
                    .replace(/x\.com\//g, 'https://x.com/')
                    .replace(/\n/g, '<br>')
                    .replace(/(https?:\/\/[^\s]+)/g, '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>');
                communityNoteHtml = `
                    <div class="vxtwitter-community-note">
                        <div class="cn-header">
                            <span class="cn-icon"></span>
                            <span class="cn-title">Les lecteurs ont ajouté du contexte</span>
                        </div>
                        <div class="cn-body">
                            <div class="cn-text">${formattedNote}</div>
                        </div>
                    </div>
                `;
            }

            let mediaHtml = '';
            if (tweet.media_extended && tweet.media_extended.length > 0) {
                mediaHtml = `<div class="vxtwitter-media-grid" data-count="${tweet.media_extended.length}">`;
                tweet.media_extended.forEach(media => {
                    mediaHtml += `<div class="vxtwitter-media-item">`;
                    if (media.type === 'video') {
                        mediaHtml += `<video src="${media.url}" poster="${media.thumbnail_url}" controls loop playsinline></video>`;
                    } else if (media.type === 'image') {
                        mediaHtml += `<a href="${media.url}" target="_blank" rel="noopener noreferrer"><img src="${media.url}" alt="Média intégré" loading="lazy"></a>`;
                    }
                    mediaHtml += `</div>`;
                });
                mediaHtml += `</div>`;
            }

            let qrtHtml = '';
            if (tweet.qrt) {
                qrtHtml = `<div class="vxtwitter-quoted-tweet">${this.render(tweet.qrt, true)}</div>`;
            }

            return `
                <div class="vxtwitter-header">
                    <img class="vxtwitter-avatar" src="${tweet.user_profile_image_url}" alt="Avatar de ${tweet.user_name}">
                    <div class="vxtwitter-author-info">
                        <div class="vxtwitter-author">
                            <span class="vxtwitter-author-name">${tweet.user_name}</span>
                            <span class="vxtwitter-author-handle">@${tweet.user_screen_name}</span>
                        </div>
                        ${!isQuoted ? `<div class="vxtwitter-header-date">${this._formatDate(tweet.date_epoch)}</div>` : ''}
                    </div>
                </div>
                ${tweet.replyingTo ? `<div class="vxtwitter-replying-to">En réponse à @${tweet.replyingTo}</div>` : ''}
                <div class="vxtwitter-text ${isLongTweet ? 'vxtwitter-text-collapsible' : ''}">${formattedText}</div>
                ${isLongTweet ? '<a href="#" class="vxtwitter-show-more">Afficher plus</a>' : ''}
                ${pollHtml}
                ${mediaHtml}
                ${qrtHtml}
                ${communityNoteHtml}
                ${!isQuoted ? `
                <div class="vxtwitter-footer">
                  <div class="vxtwitter-stats-group">

                      <span class="vxtwitter-stat">
                          <span class="vxtwitter-icon vx-icon-retweet"></span>
                          <span class="vxtwitter-stat-value">${this._formatNumber(tweet.retweets)}</span>
                      </span>
                      <span class="vxtwitter-stat">
                          <span class="vxtwitter-icon vx-icon-like"></span>
                          <span class="vxtwitter-stat-value">${this._formatNumber(tweet.likes)}</span>
                      </span>
                  </div>
                  <a href="#" class="vxtwitter-nitter-link">
                      Voir réponses (${this._formatNumber(tweet.replies)})
                  </a>
              </div>
                ` : ''}
            `;
        }
    };

    function waitForElement(selector, callback) {
        const element = document.querySelector(selector);
        if (element) {
            callback(element);
            return;
        }

        const observer = new MutationObserver((mutations, obs) => {
            const foundElement = document.querySelector(selector);
            if (foundElement) {
                obs.disconnect();
                callback(foundElement);
            }
        });

        observer.observe(document.documentElement, {
            childList: true,
            subtree: true
        });
    }

    // =========================================================================
    // == AJOUT DU BOUTON DE CONFIGURATION À L'INTERFACE JVC
    // =========================================================================
    function addSettingsButtonToHeader() {
        const menuSelector = '.headerAccount__dropdownContainer';
        const buttonId = 'lm-config-button';

        const injectButton = () => {
            const dropdownContainer = document.querySelector(menuSelector);
            if (!dropdownContainer || document.getElementById(buttonId)) {
                return false;
            }

            const contentSection = dropdownContainer.querySelector('.headerAccount__dropdownContainerContent');
            if (!contentSection) {
                return false;
            }

            const separator = document.createElement('hr');
            separator.className = 'headerAccount__dropdownSeparator';
            separator.style.margin = '0.5rem 0';

            const settingsLink = document.createElement('a');
            settingsLink.id = buttonId;
            settingsLink.href = '#';
            settingsLink.className = 'lm-settings-menu-item';

            settingsLink.innerHTML = `
                <svg class="lm-settings-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                    <path d="M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61-.25-1.17-.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19-.15-.24-.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18-.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22-.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"></path>
                </svg>
                <span>Configurer le Lecteur Média</span>
            `;

            settingsLink.addEventListener('click', (e) => {
                e.preventDefault();
                e.stopPropagation();
                openSettingsPanel(allProviders);
            });

            contentSection.appendChild(separator);
            contentSection.appendChild(settingsLink);

            return true;
        };

        if (injectButton()) return;
        const observer = new MutationObserver((mutations, obs) => {
            if (document.querySelector('.headerAccount__dropdownContainerContent')) {
                if (injectButton()) obs.disconnect();
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }

    class EmbedManager {
        constructor(initialProviders = [], config = {}) {
            this.providers = initialProviders;
            this.isDarkTheme = !document.documentElement.classList.contains('theme-light');
            this.config = config;

            this.embedCreatorObserver = new IntersectionObserver(async (entries, observer) => {
                for (const entry of entries) {
                    if (entry.isIntersecting) {
                        const link = entry.target;
                        observer.unobserve(link);

                        const providerName = link.dataset.providerName;
                        if (!providerName) continue;

                        const provider = this.providers.find(p => p.name === providerName);
                        if (!provider) continue;

                        try {
                            const embedElement = await provider.createEmbedElement(link, this.isDarkTheme);
                            if (embedElement) {
                                await this._makeEmbedCollapsible(embedElement, link);

                                const settings = await SettingsManager.getSettings(this.config);
                                this._insertEmbed(embedElement, link, settings.embedPosition);

                                if (provider.postProcess) {
                                    setTimeout(() => provider.postProcess(embedElement), 50);
                                }
                            }
                        } catch (e) {
                            console.error(`[${provider.name}] Erreur lors de la création différée:`, e);
                        }
                    }
                }
            }, { rootMargin: '400px 0px' });
        }

        /**
         * Gère l'insertion du lecteur dans le DOM selon la position choisie
         */
        _insertEmbed(embedElement, link, position = 'below_line') {
            switch (position) {
                case 'before':
                    link.before(embedElement);
                    break;

                case 'replace':
                    link.replaceWith(embedElement);
                    break;

                case 'bottom':
                    const container = link.closest('.txt-msg, .message-content, .jvchat-bloc-message');
                    if (container) {
                        container.appendChild(embedElement);
                    } else {
                        link.after(embedElement);
                    }
                    break;

                case 'below_line':
                    let currentNode = link;
                    let insertionPointFound = false;

                    while (currentNode && currentNode.nextSibling) {
                        currentNode = currentNode.nextSibling;

                        if (currentNode.nodeName === 'BR') {
                            currentNode.after(embedElement);
                            insertionPointFound = true;
                            break;
                        }

                        if (currentNode.nodeType === 1 && /^(DIV|P|BLOCKQUOTE|UL|OL|HR)$/.test(currentNode.nodeName)) {
                            const br = document.createElement('br');
                            currentNode.before(br);
                            br.after(embedElement);
                            insertionPointFound = true;
                            break;
                        }
                    }

                    if (!insertionPointFound) {
                        const br = document.createElement('br');
                        link.parentNode.appendChild(br);
                        link.parentNode.appendChild(embedElement);
                    }
                    break;

                case 'after':
                default:
                    link.after(embedElement);
                    break;
            }
        }

        processMessageElement(messageNode, enabledProviders) {
            enabledProviders.forEach(provider => {
                const links = messageNode.querySelectorAll(provider.selector);
                
                links.forEach(link => {
                    if (link.dataset.miniatweetProcessed) return;

                    link.dataset.miniatweetProcessed = 'true';
                    link.dataset.providerName = provider.name;
                    this.embedCreatorObserver.observe(link);
                });
            });
        }

        async  _makeEmbedCollapsible(blocEmbedElement, originalLink) {
            const settings = await SettingsManager.getSettings(this.config);
            if (!settings.collapsibleEmbeds) {
                return;
            }

            if (blocEmbedElement.querySelector('.iframe-vocaroo, .dead-link-sticker, .distrokid-embed-card')) {
                return;
            }

            function getDisplayNameFromHostname(hostname) {
                const parts = hostname.replace(/^(www\.|m\.|music\.|open\.)/, '').split('.');
                const n = parts.length;

                if (n < 2) {
                    return hostname.charAt(0).toUpperCase() + hostname.slice(1);
                }

                const genericTldsToHide = ['com', 'fr', 'org', 'net', 'io', 'gg', 'tv', 'app'];

                // Heuristique pour trouver le domaine enregistrable (ex: "bbc.co.uk", "jeuxvideo.com")
                let registrableDomainParts;
                if (n > 2 && parts[n - 2].length <= 3 && ['co', 'com', 'org', 'gov'].includes(parts[n - 2])) {
                    registrableDomainParts = parts.slice(-3);
                } else {
                    registrableDomainParts = parts.slice(-2);
                }

                const registrableDomain = registrableDomainParts.join('.');
                const tld = registrableDomainParts[registrableDomainParts.length - 1];
                let brandName = registrableDomainParts[0];

                if (!genericTldsToHide.includes(tld)) {
                    brandName = registrableDomain;
                }

                return brandName.charAt(0).toUpperCase() + brandName.slice(1);
            }

            let domain = 'Média';
            let faviconUrl = '';

            try {
                const url = new URL(originalLink.href);
                const cleanHostname = url.hostname.replace(/^www\./, '');

                const faviconDomainMap = {
                    'vm.tiktok.com': 'tiktok.com',
                    'youtu.be': 'youtube.com',
                    'voca.ro': 'vocaroo.com',
                    'gph.is': 'giphy.com',
                    'dai.ly': 'dailymotion.com',
                    'flic.kr': 'flickr.com',
                    'maps.app.goo.gl': 'google.com'
                };

                const faviconDomain = faviconDomainMap[cleanHostname] || cleanHostname;
                faviconUrl = `https://www.google.com/s2/favicons?domain=${faviconDomain}&sz=32`;
                domain = getDisplayNameFromHostname(url.hostname);

            } catch (e) {
                 console.error("[Collapsible Header] Erreur de parsing d'URL:", e);
            }

            const header = document.createElement('div');
            header.className = 'embed-header';

            header.innerHTML = `
                <div class="embed-info-container">
                    ${faviconUrl ? `<img src="${faviconUrl}" class="embed-favicon" alt="Favicon">` : ''}
                    <span class="embed-info">${domain}</span>
                </div>
                <button class="toggle-embed-button" title="Masquer/Afficher le média">
                    <span class="icon-collapse"></span>
                    <span class="icon-expand"></span>
                </button>
            `;

            const mediaContent = document.createElement('div');
            mediaContent.className = 'media-content';

            const innerWrapper = document.createElement('div');
            innerWrapper.append(...blocEmbedElement.childNodes);
            mediaContent.appendChild(innerWrapper);
            blocEmbedElement.classList.add('embed-collapsible');
            if (settings.startCollapsed) {
                blocEmbedElement.classList.add('collapsed');
            }
            blocEmbedElement.append(header, mediaContent);
            const infoText = header.querySelector('.embed-info');
            if (settings.startCollapsed) {
                infoText.textContent = `${domain} (cliquer pour afficher)`;
            }
            header.addEventListener('click', (e) => {
                if (e.target.tagName === 'A') return;
                e.preventDefault();
                e.stopPropagation();
                const isCollapsed = blocEmbedElement.classList.toggle('collapsed');
                infoText.textContent = isCollapsed ? `${domain} (cliquer pour afficher)` : domain;
            });
        }

        init() {
            const hostname = window.location.hostname;
            if (hostname === 'www.jeuxvideo.com') {
                waitForElement('.conteneur-messages-pagi', () => this.startObserving('.conteneur-messages-pagi', '[Lecteur Media] Actif sur JVC.'));
                waitForElement('#jvchat-main', () => this.startObserving('#jvchat-main', '[Lecteur Media] Actif sur JVChat.'));
            } else if (hostname === 'jvarchive.com' || hostname === 'jvarchive.st') {
                waitForElement('body', () => this.startObserving('body', '[Lecteur Media] Actif sur JVArchive.'));
            }
        }

        async startObserving(containerSelector, startMessage) {
            const containerNode = document.querySelector(containerSelector);
            if (!containerNode) return;

            console.log(startMessage);

            const settings = await SettingsManager.getSettings();
            const enabledProviders = this.providers.filter(p => !settings.disabledProviders.includes(p.name));
            
            // Les classes CSS qui identifient un message sur JVC / JVArchive
            const messageSelectors = '.txt-msg, .jvchat-bloc-message, .message-content';

            const initialMessages = containerNode.querySelectorAll(messageSelectors);
            initialMessages.forEach(msg => this.processMessageElement(msg, enabledProviders));

            const mutationObserver = new MutationObserver(mutations => {
                for (const mutation of mutations) {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if (node.matches && node.matches(messageSelectors)) {
                                this.processMessageElement(node, enabledProviders);
                            } 
                            else if (node.querySelectorAll) {
                                const internalMessages = node.querySelectorAll(messageSelectors);
                                internalMessages.forEach(msg => this.processMessageElement(msg, enabledProviders));
                            }
                        }
                    });
                }
            });

            mutationObserver.observe(containerNode, {
                childList: true,
                subtree: true, 
            });
        }
    }

    // =========================================================================
    // == API LECTEUR MEDIA
    // =========================================================================

    class LecteurMedia {
        static instance = null;

        /**
         * @param {Object} [options] Options de configuration pour l'instance.
         * @param {string|string[]} [options.providers='all'] Quels providers activer.
         *      Peut être 'all', 'base', 'connect', ou un tableau de noms de providers ['YouTube', 'Twitter'].
         */
        constructor(options = {}) {
            this.config = { logLevel: 'error' };
            let selectedProviders = [];
            const providerSelection = options.providers || 'all';

            if (providerSelection === 'all') {
                selectedProviders = allProviders;
            } else if (Array.isArray(providerSelection)) {
                selectedProviders = allProviders.filter(p => providerSelection.includes(p.name));
            } else {
                const categories = Array.isArray(providerSelection) ? providerSelection : [providerSelection];
                selectedProviders = allProviders.filter(p => categories.includes(p.category));
            }

            this.activeProviders = selectedProviders;
            this.embedManager = new EmbedManager(this.activeProviders, {
                collapsible: options.collapsible ?? true
            });
            
            if (!LecteurMedia.instance) {
                LecteurMedia.instance = this;
            }
        }

        // --- Méthodes de logging internes ---
        _logError(...args) {
            if (['error', 'info', 'debug'].includes(this.config.logLevel)) {
                console.error('[Lecteur Media]', ...args);
            }
        }
        _logInfo(...args) {
            if (['info', 'debug'].includes(this.config.logLevel)) {
                console.log('[Lecteur Media]', ...args);
            }
        }
        _logDebug(...args) {
            if (this.config.logLevel === 'debug') {
                console.log('[Lecteur Media DEBUG]', ...args);
            }
        }

         /**
         * Méthode statique pour les requêtes réseau compatibles.
         */
        static compatibleHttpRequest(options) {
            return new Promise((resolve, reject) => {
                const requestOptions = {
                    ...options,
                    onload: resolve,
                    onerror: (err) => reject(new Error(`Erreur réseau pour ${options.url}: ${err.statusText || 'Erreur inconnue'}`)),
                    ontimeout: () => reject(new Error(`Timeout pour la requête à ${options.url}`))
                };

                if (typeof GM !== 'undefined' && typeof GM.xmlHttpRequest === 'function') {
                    GM.xmlHttpRequest(requestOptions);
                } else if (typeof GM_xmlhttpRequest === 'function') {
                    GM_xmlhttpRequest(requestOptions);
                } else {
                    reject(new Error('Aucune fonction GM.xmlHttpRequest ou GM_xmlhttpRequest n\'est disponible.'));
                }
            });
        }

        // Lance plusieurs requêtes GM et annule les perdantes dès qu'une réussit
        static raceRequests(urls) {
            return new Promise((resolve, reject) => {
                console.log("[Lecteur Media] raceRequests: Démarrage des requêtes pour les URLs :", urls);
                const xhrFn = (typeof GM !== 'undefined' && typeof GM.xmlHttpRequest === 'function')
                    ? GM.xmlHttpRequest
                    : (typeof GM_xmlhttpRequest === 'function' ? GM_xmlhttpRequest : null);

                if (!xhrFn) {
                    console.log("[Lecteur Media] raceRequests: Aucune fonction GM de requête détectée.");
                    return reject(new Error("Aucune fonction de requête GM détectée (GM.xmlHttpRequest ou GM_xmlhttpRequest)."));
                }

                const requests = [];
                let settled = false;
                let errorCount = 0;
                
                urls.forEach(url => {
                    console.log("raceRequests: lancement de la requête vers", url); 
                    const req = xhrFn({
                        method: "GET",
                        url: url,
                        onload: (response) => {
                            if (settled) return;
                            if (response.status === 200) {
                                settled = true;
                                // On annule toutes les autres requêtes en cours !
                                requests.forEach(r => r !== req && r.abort && r.abort());
                                resolve(response);
                            } else {
                                errorCount++;
                                if (errorCount === urls.length) reject(new Error("Toutes les API ont échoué"));
                            }
                        },
                        onerror: () => {
                            if (settled) return;
                            errorCount++;
                            if (errorCount === urls.length) reject(new Error("Erreur réseau sur toutes les API"));
                        },
                        ontimeout: () => {
                            if (settled) return;
                            errorCount++;
                            if (errorCount === urls.length) reject(new Error("Timeout sur toutes les API"));
                        }
                    });
                    requests.push(req);
                });
            });
        }

        /**
         * Méthode publique principale de l'API pour traiter un noeud.
         * @param {HTMLElement|jQuery} node L'élément à analyser.
         */
        async _processNodeAsync(node) {
            this._logDebug('Logique asynchrone de processNode démarrée pour le noeud :', node);

            let elementNode = node;
            if (elementNode && typeof elementNode.get === 'function' && elementNode.length > 0) {
                elementNode = elementNode.get(0);
            }

            if (!elementNode || !(elementNode instanceof Element)) {
                this._logError('processNode a été appelé sans un élément valide.');
                throw new Error('processNode a reçu un noeud invalide.'); 
            }

            if (!this.settings) {
                this.settings = await SettingsManager.getSettings();
            }

            const enabledProviders = this.activeProviders.filter(p => !this.settings.disabledProviders.includes(p.name));
            this.embedManager.processMessageElement(elementNode, enabledProviders);
            
            this._logDebug(`Traitement manuel terminé pour le noeud.`);
        }

        /**
         * Méthode publique principale de l'API pour traiter un noeud.
         * @param {HTMLElement|jQuery} node L'élément à analyser.
         * @returns {boolean} `true` si le traitement a été lancé avec succès, `false` si l'entrée était invalide.
         */
        processNode(node) {
            let elementNode = node;
            if (elementNode && typeof elementNode.get === 'function' && elementNode.length > 0) {
                elementNode = elementNode.get(0);
            }

            if (!elementNode || !(elementNode instanceof Element)) {
                this._logError('processNode a été appelé sans un élément valide.');
                return false;
            }
-
            this._processNodeAsync(elementNode).catch(err => {
                this._logError('Une erreur inattendue est survenue en arrière-plan dans processNode :', err);
            });

            return true;
        }

        /**
         * Méthode pour lancer le script en mode autonome.
         */
        initStandalone() {
            this._logInfo('Script initialisé en mode autonome.');
            SettingsManager.registerSettingsMenu(this.activeProviders);

            const initializeFeatures = () => {
                addSettingsButtonToHeader();
                this.embedManager.init();
            };

            if (document.readyState === 'loading') {
                window.addEventListener('DOMContentLoaded', initializeFeatures);
            } else {
                initializeFeatures();
            }
        }

        /**
         * Valide la structure d'un objet provider.
         * @param {Object} provider L'objet provider à valider.
         * @returns {string[]} Un tableau de messages d'erreur. Le tableau est vide si le provider est valide.
         * @private
         */
        _validateProvider(provider) {
            const errors = [];
            if (!provider || typeof provider !== 'object') {
                return ['Le provider doit être un objet.'];
            }
            if (typeof provider.name !== 'string' || !provider.name.trim()) {
                errors.push('doit avoir une propriété "name" (string non vide).');
            }
            if (typeof provider.selector !== 'string' || !provider.selector.trim()) {
                errors.push('doit avoir une propriété "selector" (string non vide).');
            }
            if (typeof provider.createEmbedElement !== 'function') {
                errors.push('doit avoir une méthode "createEmbedElement".');
            }
            if (provider.hasOwnProperty('postProcess') && typeof provider.postProcess !== 'function') {
                errors.push('la propriété "postProcess" doit être une fonction si elle est définie.');
            }
            return errors;
        }

        /**
         * Permet d'ajouter un ou plusieurs providers à l'instance du lecteur
         * @param {LecteurMediaProvider|Array<LecteurMediaProvider>} newProviders
         */
        addProvider(newProviders) {
            const providersToAdd = Array.isArray(newProviders) ? newProviders : [newProviders];

            const validAndUniqueProviders = providersToAdd.filter(provider => {
                const validationErrors = this._validateProvider(provider);
                if (validationErrors.length > 0) {
                    console.warn(`[Lecteur Media] Tentative d'ajout d'un provider invalide. Ignoré. Erreurs : ${validationErrors.join('; ')}`, provider);
                    return false;
                }

                const alreadyExists = this.embedManager.providers.some(
                    existingProvider => existingProvider.name === provider.name
                );
                if (alreadyExists) {
                    console.warn(`[Lecteur Media] Le provider "${provider.name}" existe déjà. Ignoré.`);
                    return false;
                }

                return true;
            });

            if (validAndUniqueProviders.length > 0) {
                this.embedManager.providers.push(...validAndUniqueProviders);
                console.log(`[Lecteur Media] ${validAndUniqueProviders.length} provider(s) ajouté(s) : ${validAndUniqueProviders.map(p => p.name).join(', ')}.`);
            }
        }

        /**
         * Génère les directives @connect nécessaires pour une configuration de providers donnée.
         * C'est un outil pour les développeurs utilisant l'API.
         * @param {Object} [options] - Les mêmes options que le constructeur.
         * @param {string|string[]} [options.providers='all'] - La sélection de providers.
         * @returns {string[]} Un tableau de chaînes de caractères, chacune étant une ligne @connect prête à être copiée.
         */
        static getRequiredConnects(options = {}) {
            const providerSelection = options.providers || 'all';
            let selectedProviders = [];

            if (providerSelection === 'all') {
                selectedProviders = allProviders;
            } else if (Array.isArray(providerSelection) && providerSelection.every(item => typeof item === 'string' && !['base', 'connect', 'wildcard'].includes(item))) {
                // Si c'est un tableau de noms de providers
                selectedProviders = allProviders.filter(p => providerSelection.includes(p.name));
            } else {
                const categories = Array.isArray(providerSelection) ? providerSelection : [providerSelection];
                selectedProviders = allProviders.filter(p => categories.includes(p.category));
            }

            const connectDomains = new Set();

            selectedProviders.forEach(provider => {
                if (provider.category === 'wildcard') {
                    connectDomains.add('*');
                } else if (provider.category === 'connect' && provider.connect) {
                    if (Array.isArray(provider.connect)) {
                        provider.connect.forEach(domain => connectDomains.add(domain));
                    } else {
                        connectDomains.add(provider.connect);
                    }
                }
            });

            if (connectDomains.has('*')) {
                return ['// @connect      *'];
            }

            return Array.from(connectDomains).map(domain => `// @connect      ${domain}`);
        }
    }

    // =========================================================================
    // == EXPOSITION DE L'API
    // =========================================================================
    window.LecteurMedia = LecteurMedia;
    window.LecteurMedia.compatibleHttpRequest = LecteurMedia.compatibleHttpRequest;
    window.LecteurMedia.AllProviders = allProviders ;
    window.LecteurMedia.getRequiredConnects = LecteurMedia.getRequiredConnects;

    const lecteurMediaInstance = new LecteurMedia();
    window.lecteurMediaJVC = {
        version: '1.3.2',
        setLogLevel: (level) => {
            if (['none', 'error', 'info', 'debug'].includes(level)) {
                lecteurMediaInstance.config.logLevel = level;
            }
        },
        processNode: lecteurMediaInstance.processNode.bind(lecteurMediaInstance)
    };
    
    })();
}