LecteurMedia API Library

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

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greasyfork.org/scripts/554422/1688102/LecteurMedia%20API%20Library.js

if (typeof window.LecteurMedia === 'undefined') {
    (function() {
        'use strict';
    
    // =========================================================================
    // == 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/"]:not([data-miniatweet-processed])',
            scriptPromise: null,
            category: 'base',
            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) {
                this.loadScript();

                try {
                    let permalink = new URL(link.href);
                    permalink.pathname = permalink.pathname.replace('/reels/', '/reel/');
                    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';

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

                    container.appendChild(blockquote);
                    return container;

                } catch (e) {
                    console.error("URL Instagram invalide:", link.href, e);
                    return null;
                }
            },
            postProcess(element) {
                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 uniqueId = `vxtweet-${tweetId}-${Math.random().toString(36).substring(2, 9)}`;

                const placeholderContainer = document.createElement('div');
                placeholderContainer.className = 'bloc-embed';
                placeholderContainer.innerHTML = `<div id="${uniqueId}" class="iframe-embed twitter-loading-placeholder">Chargement du Tweet...</div>`;

                (async () => {
                    try {
                        const response = await LecteurMedia.compatibleHttpRequest({
                            method: "GET",
                            url: `https://api.vxtwitter.com/${screenName}/status/${tweetId}`
                        });

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

                        let finalContentElement;

                        if (response.status === 200) {
                            const tweet = JSON.parse(response.responseText);
                            let parentTweetHtml = '';

                            if (tweet.replyingToStatus) {
                                try {
                                    const parentResponse = await LecteurMedia.compatibleHttpRequest({
                                        method: 'GET',
                                        url: `https://api.vxtwitter.com/${tweet.replyingTo}/status/${tweet.replyingToStatus}`
                                    });
                                    if (parentResponse.status === 200) {
                                        const parentTweet = JSON.parse(parentResponse.responseText);
                                        parentTweetHtml = `<div class="parent-tweet-container">${VxTwitterRenderer.render(parentTweet, true)}</div>`;
                                    }
                                } catch (e) {
                                    console.error('[vxTwitter] Erreur lors de la récupération du tweet parent:', e);
                                }
                            }

                            const mainTweetHtml = VxTwitterRenderer.render(tweet, false);
                            const tweetEmbed = document.createElement('div');
                            tweetEmbed.className = 'vxtwitter-embed';
                            tweetEmbed.innerHTML = parentTweetHtml + mainTweetHtml;
                            finalContentElement = tweetEmbed;

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

                            const nitterButton = finalContentElement.querySelector('.vxtwitter-nitter-link');
                            if (nitterButton) {
                                nitterButton.addEventListener('click', (e) => {
                                    e.preventDefault();
                                    const collapsibleContainer = e.target.closest('.embed-collapsible');
                                    if (!collapsibleContainer) return;

                                    const mediaContent = collapsibleContainer.querySelector('.media-content');
                                    const customTweetEmbed = mediaContent.querySelector('.vxtwitter-embed');
                                    if (!mediaContent || !customTweetEmbed) return;

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

                                    const loader = document.createElement('div');
                                    loader.className = 'lm-loader-container';
                                    loader.innerHTML = `<div class="lm-spinner"></div><div class="lm-loader-text">Chargement des commentaires...</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.setAttribute('scrolling', 'yes');
                                    iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups');

                                    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';
                                    };

                                    customTweetEmbed.replaceWith(iframe);
                                    const header = collapsibleContainer.querySelector('.embed-header');
                                    if (header) {
                                        header.style.cssText = "transition: opacity 0.3s, height 0.3s, padding 0.3s, margin 0.3s; opacity: 0; height: 0; padding: 0; margin: 0;";
                                    }
                                });
                            }
                        } else {
                            finalContentElement = document.createElement('div');
                            finalContentElement.className = 'placeholder-embed tweet-unavailable-placeholder';
                            finalContentElement.innerHTML = `
                                <div class="icon-container">
                                    <svg xmlns="http://www.w3.org/2000/svg" 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>Ce Tweet n'est plus disponible</strong>
                                    <span class="description">Le lien est peut-être rompu ou le Tweet a été supprimé.</span>
                                    <a href="${link.href}" target="_blank" rel="noopener noreferrer">Consulter le lien d'origine</a>
                                </div>`;
                        }

                        if (finalContentElement) {
                            placeholderToReplace.replaceWith(finalContentElement);
                            const statsGroup = finalContentElement.querySelector('.vxtwitter-stats-group');
                            if (statsGroup) {
                                setupStatCarousel(statsGroup);
                            }
                        }
                    } catch (error) {
                        // --- Gestion de toutes les erreurs (réseau, timeout, parsing JSON) ---
                        console.error('[vxTwitter] Échec du chargement du tweet :', error);
                        const placeholderToReplace = document.getElementById(uniqueId);
                        if (placeholderToReplace) {
                            const errorElement = document.createElement('div');
                            errorElement.className = 'placeholder-embed';
                            errorElement.innerHTML = `Impossible de charger le Tweet.`;
                            placeholderToReplace.replaceWith(errorElement);
                        }
                    }
                })();

                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: 'wildcard',
          async createEmbedElement(link) {
              const stickerUrl = 'https://risibank.fr/cache/medias/0/5/512/51206/thumb.png';
              const handleMediaResize = (mediaElement) => {
                  const isVideo = mediaElement.tagName === 'VIDEO';
                  const width = isVideo ? mediaElement.videoWidth : mediaElement.naturalWidth;
                  const height = isVideo ? mediaElement.videoHeight : mediaElement.naturalHeight;

                  if (width > 0 && height > width) {
                      mediaElement.classList.add('iframe-vertical-content');
                  }
              };

              const createDeadLinkSticker = () => {
                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `
                      <div class="dead-link-sticker">
                          <img src="${stickerUrl}" alt="[Média supprimé]">
                          <span>[Média supprimé]</span>
                      </div>
                  `;
                  return container;
              };

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

                  if (response.status < 200 || response.status >= 300) {
                      throw new Error(`Statut HTTP: ${response.status}`);
                  }

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

                  const handleError = (element) => {
                      const parentBloc = element.closest('.bloc-embed');
                      if (parentBloc) {
                          parentBloc.replaceWith(createDeadLinkSticker());
                      }
                  };

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

                  if (/\.(jpg|jpeg|png|gif|webp|bmp)$/i.test(pathname)) {
                      const img = document.createElement('img');
                      img.src = link.href;
                      img.className = 'image-embed';
                      img.alt = 'Image intégrée';
                      img.loading = 'lazy';
                      img.addEventListener('load', () => handleMediaResize(img), { once: true });
                      img.onerror = () => handleError(img);
                      container.appendChild(img);
                  } else 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.autoplay = true;
                      video.muted = true;
                      video.loop = true;
                      video.playsinline = true;
                      video.addEventListener('loadedmetadata', () => handleMediaResize(video), { once: true });
                      video.onerror = () => handleError(video);
                      container.appendChild(video);
                  }

                  if (container.hasChildNodes()) {
                      return container;
                  }
                  return null;

              } catch (error) {
                  console.warn(`[GenericMedia] Lien mort détecté pour ${link.href}. Raison: ${error.message}`);
                  return createDeadLinkSticker();
              }
          }
        },
        {
          name: 'ArticlePreview',
          selector: 'a[href^="http"]:not([data-miniatweet-processed])',
          category: 'wildcard',
          async createEmbedElement(link) {
              const href = link.href;
              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',
              ];

              try {
                  const url = new URL(href);

                  if (excludedDomains.some(domain => url.hostname.includes(domain)) || /\.(jpg|jpeg|png|gif|webp|bmp|mp4|webm|mov|ogg)$/i.test(href)) {
                      return null;
                  }

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

                  if (response.status < 200 || response.status >= 300) {
                      lecteurMediaInstance._logDebug(`[ArticlePreview] Impossible de récupérer ${href}, statut: ${response.status}`);
                      return null;
                  }

                  const htmlContent = response.responseText;
                  const parser = new DOMParser();
                  const doc = parser.parseFromString(htmlContent, 'text/html');

                  const getMeta = (prop) => doc.querySelector(`meta[property="${prop}"], meta[name="${prop}"]`)?.getAttribute('content')?.trim();

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

                  if (!title || !imageUrl) {
                      return null;
                  }

                  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('${imageUrl}');"></div>
                          <div class="article-preview-content">
                              <div class="article-preview-title">${title}</div>
                              ${description ? `<div class="article-preview-description">${description}</div>` : ''}
                          </div>
                      </a>
                  `;

                  return container;

              } catch (error) {
                  lecteurMediaInstance._logInfo(`[ArticlePreview] Impossible de récupérer ${href}: ${error.message}`);
                  return null;
              }
          }
        },
    ];


    // =========================================================================
    // == 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: []
        },

         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);

            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;

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

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

    const panelStyle = `
        #lm-settings-overlay {
            position: fixed; inset: 0; z-index: 9998;
            background-color: rgba(0, 0, 0, 0.7); backdrop-filter: blur(4px);
            opacity: 0; transition: opacity 0.2s ease-in-out;
        }
        #lm-settings-panel {
            position: fixed; top: 50%; left: 50%; z-index: 9999;
            transform: translate(-50%, -50%) scale(0.95);
            width: 90vw; max-width: 600px; max-height: 85vh;
            background-color: var(--bg-bloc-principal, #303030);
            color: var(--text-bloc-principal, #d7d7d7);
            border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.3);
            display: flex; flex-direction: column;
            opacity: 0; transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
        }
        html.theme-light #lm-settings-panel {
            background-color: #f7f7f7; color: #333;
        }
        #lm-settings-panel .panel-header {
            padding: 15px 20px; border-bottom: 1px solid var(--border-bloc-principal, #4a4a4a);
            display: flex; justify-content: space-between; align-items: center;
        }
        html.theme-light #lm-settings-panel .panel-header { border-bottom-color: #e0e0e0; }
        #lm-settings-panel .panel-header h2 { margin: 0; font-size: 18px; }
        #lm-settings-panel .panel-close-btn {
            font-size: 24px; font-weight: bold; color: #888; cursor: pointer;
            line-height: 1; transition: color 0.2s; user-select: none;
        }
        #lm-settings-panel .panel-close-btn:hover { color: var(--text-bloc-principal, #fff); }
        #lm-settings-panel .panel-body { padding: 20px; overflow-y: auto; }
        #lm-settings-panel .setting-group { margin-bottom: 25px; }
        #lm-settings-panel .setting-group h3 { margin-top: 0; margin-bottom: 15px; font-size: 16px; border-bottom: 1px solid var(--border-bloc-principal, #4a4a4a); padding-bottom: 8px; }
        html.theme-light #lm-settings-panel .setting-group h3 { border-bottom-color: #e0e0e0; }
        #lm-settings-panel .setting-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; transition: opacity 0.2s; }
        #lm-settings-panel .setting-item label { font-size: 14px; }
        #lm-settings-panel .toggle-switch { position: relative; display: inline-block; width: 44px; height: 24px; }
        #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: #555; transition: .4s; border-radius: 24px;
        }
        html.theme-light #lm-settings-panel .slider { background-color: #ccc; }
        #lm-settings-panel .slider:before {
            position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px;
            background-color: white; transition: .4s; border-radius: 50%;
        }
        #lm-settings-panel input:checked + .slider { background-color: #2ecc71; }
        #lm-settings-panel input:checked + .slider:before { transform: translateX(20px); }
        #lm-settings-panel .providers-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
        #lm-settings-panel .provider-item { display: flex; align-items: center; }
        #lm-settings-panel .provider-item input { margin-right: 8px; }
        #lm-settings-panel .panel-footer {
            padding: 15px 20px; border-top: 1px solid var(--border-bloc-principal, #4a4a4a);
            text-align: right;
        }
        html.theme-light #lm-settings-panel .panel-footer { border-top-color: #e0e0e0; }
        #lm-settings-panel .save-btn {
            background-color: #2ecc71; color: white; border: none; padding: 10px 20px;
            border-radius: 6px; font-size: 14px; font-weight: bold; cursor: pointer;
            transition: background-color 0.2s, transform 0.1s;
        }
        #lm-settings-panel .save-btn:hover { background-color: #27ae60; }
        #lm-settings-panel .save-btn:active { transform: scale(0.98); }
    `;
    const styleElement = document.createElement('style');
    styleElement.textContent = panelStyle;
    document.head.appendChild(styleElement);

    SettingsManager.getSettings().then(settings => {
        panel.innerHTML = `
            <div class="panel-header">
                <h2>Paramètres du Lecteur Média</h2>
                <span class="panel-close-btn" title="Fermer">&times;</span>
            </div>
            <div class="panel-body">
                <div class="setting-group">
                    <h3>Comportement Général</h3>
                    <div class="setting-item">
                        <label for="collapsibleEmbeds">Activer les en-têtes réductibles</label>
                        <label class="toggle-switch">
                            <input type="checkbox" id="collapsibleEmbeds" ${settings.collapsibleEmbeds ? 'checked' : ''}>
                            <span class="slider"></span>
                        </label>
                    </div>
                    <div class="setting-item">
                        <label for="startCollapsed">Réduire les médias par défaut</label>
                        <label class="toggle-switch">
                            <input type="checkbox" id="startCollapsed" ${settings.startCollapsed ? 'checked' : ''}>
                            <span class="slider"></span>
                        </label>
                    </div>
                </div>
                <div class="setting-group">
                    <h3>Fournisseurs de contenu</h3>
                    <div class="providers-list">
                        ${providersList.map(p => `
                            <div class="provider-item">
                                <input type="checkbox" id="provider-${p.name}" value="${p.name}" ${!settings.disabledProviders.includes(p.name) ? 'checked' : ''}>
                                <label for="provider-${p.name}">${p.name}</label>
                            </div>
                        `).join('')}
                    </div>
                </div>
            </div>
            <div class="panel-footer">
                <button class="save-btn">Enregistrer</button>
            </div>
        `;

        document.body.append(overlay, panel);

        const collapsibleToggle = panel.querySelector('#collapsibleEmbeds');
        const startCollapsedItem = panel.querySelector('#startCollapsed').closest('.setting-item');

        const updateDependencies = () => {
            if (collapsibleToggle.checked) {
                startCollapsedItem.style.opacity = '1';
                startCollapsedItem.style.pointerEvents = 'auto';
                startCollapsedItem.querySelector('input').disabled = false;
            } else {
                startCollapsedItem.style.opacity = '0.5';
                startCollapsedItem.style.pointerEvents = 'none';
                startCollapsedItem.querySelector('input').disabled = true;
            }
        };
        collapsibleToggle.addEventListener('change', updateDependencies);
        updateDependencies(); 

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

        const closePanel = () => {
            overlay.style.opacity = '0';
            panel.style.opacity = '0';
            panel.style.transform = 'translate(-50%, -50%) scale(0.95)';
            setTimeout(() => {
                overlay.remove();
                panel.remove();
                styleElement.remove();
            }, 150);
        };

        panel.querySelector('.panel-close-btn').onclick = closePanel;
        overlay.onclick = closePanel;

        panel.querySelector('.save-btn').onclick = () => {
            const startCollapsedInput = panel.querySelector('#startCollapsed');
            const collapsibleEmbedsInput = panel.querySelector('#collapsibleEmbeds');

            const newSettings = {
                startCollapsed: startCollapsedInput ? startCollapsedInput.checked : false,
                collapsibleEmbeds: collapsibleEmbedsInput ? collapsibleEmbedsInput.checked : false,
                disabledProviders: providersList
                    .filter(p => {
                        const providerCheckbox = panel.querySelector(`#provider-${p.name}`);
                        return providerCheckbox && !providerCheckbox.checked;
                    })
                    .map(p => p.name)
            };

            SettingsManager.saveSettings(newSettings).then(() => {
                const saveBtn = panel.querySelector('.save-btn');
                saveBtn.textContent = 'Enregistré !';
                saveBtn.style.backgroundColor = '#27ae60';
                setTimeout(() => {
                    saveBtn.textContent = 'Enregistrer';
                    saveBtn.style.backgroundColor = '#2ecc71';
                    closePanel();
                }, 1000);
            });
        };
    });
}

    //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: inline-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 {
          transition: max-height 0.4s ease-out, opacity 0.3s ease-in-out, visibility 0.4s, height 0.4s ease-in-out;
          max-height: 2000px;
          opacity: 1;
          overflow: hidden;
          visibility: visible;
          line-height: 0;
          width: 100%;
      }
      .media-content > * {
          border-radius: 9px;
      }

      .embed-collapsible.collapsed .media-content {
          max-height: 0;
          opacity: 0;
          visibility: hidden;
      }

      .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::-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;
      }
    `);

    // =========================================================================
    // == 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
        });
    }

    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);
                                link.after(embedElement);

                                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' });
        }

        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';
            mediaContent.append(...blocEmbedElement.childNodes);
            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', '[MiniaTweetPRO+] Observateur du forum JVC activé.'));
                waitForElement('#jvchat-main', () => this.startObserving('#jvchat-main', '[MiniaTweetPRO+] Observateur de JVChat activé.'));
            } else if (hostname === 'jvarchive.com' || hostname === 'jvarchive.st') {
                waitForElement('body', () => this.startObserving('body', '[MiniaTweetPRO+] Observateur de JVArchive activé.'));
            }
        }

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

            const messageSelector = '.txt-msg, .jvchat-bloc-message, .message-content';

            const settings = await SettingsManager.getSettings();
            const enabledProviders = this.providers.filter(p => !settings.disabledProviders.includes(p.name));

            const scanAndDelegate = (scope) => {
                const messageSelector = '.txt-msg, .jvchat-bloc-message, .message-content';
                const messageElements = [];
                if (scope.nodeType === Node.ELEMENT_NODE) {
                    if (scope.matches(messageSelector)) {
                        messageElements.push(scope);
                    }
                    messageElements.push(...scope.querySelectorAll(messageSelector));
                }

                messageElements.forEach(messageElement => {
                    enabledProviders.forEach(provider => {
                        messageElement.querySelectorAll(provider.selector).forEach(link => {
                            if (link.dataset.miniatweetProcessed) return;

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

            scanAndDelegate(targetNode);

            const mutationObserver = new MutationObserver(mutations => {
                for (const mutation of mutations) {
                    for (const node of mutation.addedNodes) {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            scanAndDelegate(node);
                        }
                    }

                    if (mutation.type === 'childList' && mutation.target.nodeType === Node.ELEMENT_NODE) {
                        scanAndDelegate(mutation.target);
                    }
                }
            });

            mutationObserver.observe(targetNode, {
                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.'));
                }
            });
        }

        /**
         * 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.'); // On lance une erreur pour le catch
            }

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

            const enabledProviders = this.activeProviders.filter(p => !this.settings.disabledProviders.includes(p.name));
            let processedLinks = 0;

            for (const provider of enabledProviders) {
                const links = elementNode.querySelectorAll(provider.selector);
                for (const link of links) {
                    if (link.dataset.miniatweetProcessed) continue;
                    
                    link.dataset.miniatweetProcessed = 'true';
                    link.dataset.providerName = provider.name;
                    this.embedManager.embedCreatorObserver.observe(link);
                    processedLinks++;
                }
            }
            this._logDebug(`${processedLinks} lien(s) mis en observation.`);
        }

        /**
         * 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);

            if (document.readyState === 'loading') {
                window.addEventListener('DOMContentLoaded', () => this.embedManager.init());
            } else {
                this.embedManager.init();
            }
        }

        /**
         * 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)
    };
    
    })();
}