Twitter/X Timeline Sync

Tracks and syncs your last reading position on Twitter/X, with manual and automatic options. Ideal for keeping track of new posts without losing your place.

// ==UserScript==
// @name              Twitter/X Timeline Sync
// @description       Tracks and syncs your last reading position on Twitter/X, with manual and automatic options. Ideal for keeping track of new posts without losing your place.
// @description:de    Verfolgt und synchronisiert Ihre letzte Leseposition auf Twitter/X, mit manuellen und automatischen Optionen. Perfekt, um neue Beiträge im Blick zu behalten, ohne die aktuelle Position zu verlieren.
// @description:es    Rastrea y sincroniza tu última posición de lectura en Twitter/X, con opciones manuales y automáticas. Ideal para mantener el seguimiento de las publicaciones nuevas sin perder tu posición.
// @description:fr    Suit et synchronise votre dernière position de lecture sur Twitter/X, avec des options manuelles et automatiques. Idéal pour suivre les nouveaux posts sans perdre votre place actuelle.
// @description:zh-CN 跟踪并同步您在 Twitter/X 上的最后阅读位置,提供手动和自动选项。完美解决在查看新帖子时不丢失当前位置的问题。
// @description:ru    Отслеживает и синхронизирует вашу последнюю позицию чтения на Twitter/X с ручными и автоматическими опциями. Идеально подходит для просмотра новых постов без потери текущей позиции。
// @description:ja    Twitter/X での最後の読み取り位置を追跡して同期します。手動および自動オプションを提供します。新しい投稿を見逃さずに現在の位置を維持するのに最適です。
// @description:pt-BR Rastrea e sincroniza sua última posição de leitura no Twitter/X, com opções manuais e automáticas. Perfeito para acompanhar novos posts sem perder sua posição atual。
// @description:hi    Twitter/X पर आपकी अंतिम पठन स्थिति को ट्रैक और सिंक करता है, मैनुअल और स्वचालित विकल्पों के साथ। नई पोस्ट देखते समय अपनी वर्तमान स्थिति को खोए बिना इसे ट्रैक करें।
// @description:ar    يتتبع ويزامن آخر موضع قراءة لك على Twitter/X، مع خيارات يدوية وتلقائية. مثالي لتتبع المشاركات الجديدة دون فقدان موضعك الحالي。
// @description:it    Traccia e sincronizza la tua ultima posizione di lettura su Twitter/X, con opzioni manuali e automatiche. Ideale per tenere traccia dei nuovi post senza perdere la posizione attuale。
// @description:ko    Twitter/X에서 마지막 읽기 위치를 추적하고 동기화합니다. 수동 및 자동 옵션 포함. 새로운 게시물을 확인하면서 현재 위치를 잃지 않도록 이상적입니다。
// @icon              https://x.com/favicon.ico
// @namespace         http://tampermonkey.net/
// @version           2025.6.2.3
// @author            Copiis
// @license           MIT
// @match             https://x.com/home
// @grant             GM_setValue
// @grant             GM_getValue
// @grant             GM_registerMenuCommand
// ==/UserScript==
//                    If you find this script useful and would like to support my work, consider making a small donation!
//                    Bitcoin (BTC): bc1quc5mkudlwwkktzhvzw5u2nruxyepef957p68r7
//                    PayPal: https://www.paypal.com/paypalme/Coopiis?country.x=DE&locale.x=de_DE

(function () {
    'use strict';

    let lastReadPost = null;
    let isAutoScrolling = false;
    let isSearching = false;
    let isScriptActivated = false;
    let currentPost = null;
    let lastHighlightedPost = null;
    const downloadedPosts = new Set();

    function loadLastReadPost(callback) {
        try {
            const storedPost = GM_getValue("lastReadPost", null);
            if (storedPost) {
                const parsedPost = JSON.parse(storedPost);
                console.log("🛠️ DEBUG: Geladene Leseposition:", parsedPost);
                callback(parsedPost);
            } else {
                console.log("⏹️ Keine gespeicherte Leseposition gefunden.");
                callback(null);
            }
        } catch (err) {
            console.error("❌ Fehler beim Laden der Leseposition:", err);
            callback(null);
        }
    }

    function saveLastReadPost(post) {
        if (!post || !post.timestamp || !post.authorHandler) {
            console.log("❌ Ungültige Leseposition, Speicherung abgebrochen:", post);
            return;
        }

        let attempts = 0;
        const maxAttempts = 3;

        function trySave() {
            try {
                const postData = JSON.stringify(post);
                GM_setValue("lastReadPost", postData);
                console.log("💾 Leseposition erfolgreich mit GM_setValue gespeichert:", postData);
                localStorage.setItem("lastReadPost", postData);
                console.log("💾 Fallback in localStorage gespeichert:", localStorage.getItem("lastReadPost"));
            } catch (err) {
                attempts++;
                console.error(`❌ Fehler beim Speichern der Leseposition (Versuch ${attempts}/${maxAttempts}):`, err);
                if (attempts < maxAttempts) {
                    console.log("🔄 Wiederhole Speicherversuch...");
                    setTimeout(trySave, 1000);
                } else {
                    console.error("❌ Maximale Speicherversuche erreicht. Fallback auf localStorage.");
                    localStorage.setItem("lastReadPost", JSON.stringify(post));
                    promptManualFallback(post);
                }
            }
        }

        trySave();
    }

    function promptManualFallback(data) {
        const content = JSON.stringify(data);
        const message = `📝 Neue Leseposition: ${content}\nBitte speichere dies manuell, da der Speichervorgang fehlschlug.`;
        showPopup(message, 10000);
        console.log("📝 Bitte manuell speichern:", content);
    }

    function downloadLastReadPost() {
        try {
            if (!currentPost || !currentPost.timestamp || !currentPost.authorHandler) {
                console.warn("⚠️ Keine gültige Leseposition zum Speichern:", currentPost);
                showPopup("❌ Keine gültige Leseposition zum Downloaden.", 5000);
                return;
            }

            const postKey = `${currentPost.timestamp}-${currentPost.authorHandler}`;
            if (downloadedPosts.has(postKey)) {
                console.log("⏹️ Leseposition bereits heruntergeladen:", postKey);
                showPopup("ℹ️ Diese Leseposition wurde bereits heruntergeladen.", 5000);
                return;
            }

            console.log("🛠️ DEBUG: Starte manuellen Download-Prozess für Leseposition:", currentPost);

            const date = new Date(currentPost.timestamp);
            const year = date.getFullYear();
            const month = String(date.getMonth() + 1).padStart(2, "0");
            const day = String(date.getDate()).padStart(2, "0");
            const hour = String(date.getHours()).padStart(2, "0");
            const minute = String(date.getMinutes()).padStart(2, "0");
            const second = String(date.getSeconds()).padStart(2, "0");
            const fileName = `${year}${month}${day}_${hour}${minute}${second}-${currentPost.authorHandler}.json`;

            console.log("📄 Generierter Dateiname:", fileName);

            const fileContent = JSON.stringify(currentPost, null, 2);
            const blob = new Blob([fileContent], { type: "application/json" });
            const url = URL.createObjectURL(blob);
            const a = document.createElement("a");
            a.href = url;
            a.download = fileName;
            a.style.display = "none";
            document.body.appendChild(a);
            console.log("🔗 Download-Element erstellt:", a);

            try {
                a.click();
                console.log(`💾 Leseposition als Datei gespeichert: ${fileName}`);
                showPopup(`✅ Leseposition als ${fileName} heruntergeladen.`, 5000);
                downloadedPosts.add(postKey);
            } catch (clickErr) {
                console.error("❌ Fehler beim Auslösen des Downloads:", clickErr);
                try {
                    navigator.clipboard.writeText(fileContent).then(() => {
                        console.log("📋 Leseposition in Zwischenablage kopiert.");
                        showPopup(`❌ Download fehlgeschlagen. Leseposition wurde in die Zwischenablage kopiert. Bitte manuell in eine .json-Datei einfügen.`, 10000);
                        downloadedPosts.add(postKey);
                    }).catch(clipErr => {
                        console.error("❌ Fehler beim Kopieren in die Zwischenablage:", clipErr);
                        showPopup("❌ Download und Kopieren fehlgeschlagen. Bitte manuell speichern.", 10000);
                        promptManualFallback(currentPost);
                    });
                } catch (clipErr) {
                    console.error("❌ Fehler beim Zugriff auf die Zwischenablage:", clipErr);
                    showPopup("❌ Download und Kopieren fehlgeschlagen. Bitte manuell speichern.", 10000);
                    promptManualFallback(currentPost);
                }
            }

            setTimeout(() => {
                try {
                    document.body.removeChild(a);
                    URL.revokeObjectURL(url);
                    console.log("🧹 Download-Element entfernt und URL freigegeben.");
                } catch (cleanupErr) {
                    console.error("❌ Fehler beim Aufräumen:", cleanupErr);
                }
            }, 3000);
        } catch (err) {
            console.error("❌ Fehler beim Speichern der Datei:", err);
            showPopup("❌ Fehler beim Speichern der Datei.", 5000);
            promptManualFallback(currentPost);
        }
    }

    function loadNewestLastReadPost() {
        return new Promise(resolve => {
            loadLastReadPost(storedPost => {
                if (storedPost && storedPost.timestamp && storedPost.authorHandler) {
                    lastReadPost = storedPost;
                    console.log("✅ Leseposition geladen:", lastReadPost);
                } else {
                    const localPost = JSON.parse(localStorage.getItem("lastReadPost") || "{}");
                    if (localPost && localPost.timestamp && localPost.authorHandler) {
                        lastReadPost = localPost;
                        console.log("✅ Leseposition aus localStorage:", lastReadPost);
                    } else {
                        console.warn("⚠️ Keine Leseposition gefunden.");
                        showPopup("ℹ️ Scrolle, um eine Leseposition zu setzen.");
                    }
                }
                resolve();
            });
        });
    }

    async function initializeScript() {
        console.log("🔧 Lade Leseposition...");
        try {
            await loadNewestLastReadPost();
            console.log("✅ Initialisierung erfolgreich.");

            window.addEventListener("scroll", () => {
                if (!isScriptActivated) {
                    isScriptActivated = true;
                    console.log("🛠️ DEBUG: Skript durch Scrollen aktiviert.");
                    observeForNewPosts();
                }

                if (isAutoScrolling || isSearching) {
                    console.log("⏹️ Scroll-Ereignis ignoriert.");
                    return;
                }
                markTopVisiblePost(true);
            }, { passive: true });
        } catch (err) {
            console.error("❌ Fehler bei der Initialisierung:", err);
            showPopup("❌ Fehler beim Laden des Skripts.");
        }
    }

    function initializeWhenDOMReady() {
        if (!window.location.href.includes("/home")) {
            console.log("🚫 Skript deaktiviert: Nicht auf der Home-Seite.");
            return;
        }
        console.log("🚀 Initialisiere Skript...");

        const observer = new MutationObserver((mutations, obs) => {
            if (document.body) {
                obs.disconnect();
                initializeScript().then(() => {
                    createButtons();
                    startPeriodicSave();
                }).catch(err => {
                    console.error("❌ Fehler bei der Initialisierung:", err);
                    showPopup("❌ Fehler beim Starten des Skripts.");
                });
            }
        });
        observer.observe(document.documentElement, { childList: true, subtree: true });
    }

    window.addEventListener("load", initializeWhenDOMReady);

    function loadLastReadPostFromFile() {
        try {
            const input = document.createElement("input");
            input.type = "file";
            input.accept = ".json";
            input.style.display = "none";
            document.body.appendChild(input);

            input.addEventListener("change", (event) => {
                const file = event.target.files[0];
                if (!file) {
                    console.warn("⚠️ Keine Datei ausgewählt.");
                    showPopup("❌ Bitte wähle eine JSON-Datei aus.", 5000);
                    document.body.removeChild(input);
                    return;
                }

                const reader = new FileReader();
                reader.onload = (e) => {
                    try {
                        const data = JSON.parse(e.target.result);
                        if (data.timestamp && data.authorHandler) {
                            lastReadPost = data;
                            saveLastReadPost(data);
                            console.log("✅ Leseposition aus Datei geladen:", lastReadPost);
                            showPopup("✅ Leseposition erfolgreich geladen!", 3000);
                            startRefinedSearchForLastReadPost();
                        } else {
                            console.warn("⚠️ Ungültige Leseposition in der Datei:", data);
                            showPopup("❌ Ungültige Leseposition in der Datei.", 5000);
                        }
                    } catch (err) {
                        console.error("❌ Fehler beim Lesen der Datei:", err);
                        showPopup("❌ Fehler beim Lesen der Datei.", 5000);
                    }
                    document.body.removeChild(input);
                };
                reader.readAsText(file);
            });

            input.click();
        } catch (err) {
            console.error("❌ Fehler beim Öffnen des Datei-Dialogs:", err);
            showPopup("❌ Fehler beim Öffnen des Datei-Dialogs.", 5000);
        }
    }

    function startPeriodicSave() {
        setInterval(() => {
            if (lastReadPost && isScriptActivated) {
                loadLastReadPost(existingPost => {
                    if (!existingPost || new Date(lastReadPost.timestamp) > new Date(existingPost.timestamp) ||
                        (lastReadPost.timestamp === existingPost.timestamp && lastReadPost.authorHandler !== existingPost.authorHandler)) {
                        saveLastReadPost(lastReadPost);
                        console.log("💾 Periodische Speicherung: Neue Leseposition gespeichert:", lastReadPost);
                    } else {
                        console.log("⏹️ Periodische Speicherung übersprungen: Leseposition nicht neuer oder identisch.");
                    }
                });
            }
        }, 30000);
    }

    function markTopVisiblePost(save = true) {
        const topPost = getTopVisiblePost();
        if (!topPost) {
            console.log("❌ Kein sichtbarer Beitrag.");
            return;
        }

        const postTimestamp = getPostTimestamp(topPost);
        const postAuthorHandler = getPostAuthorHandler(topPost);

        if (postTimestamp && postAuthorHandler) {
            const newPost = { timestamp: postTimestamp, authorHandler: postAuthorHandler };
            console.log("🛠️ DEBUG: Versuche, Leseposition zu speichern:", newPost);

            if (lastHighlightedPost && lastHighlightedPost !== topPost) {
                lastHighlightedPost.style.boxShadow = "none";
            }
            topPost.style.boxShadow = "0 0 20px 10px rgba(255, 215, 0, 0.9)";
            lastHighlightedPost = topPost;
            currentPost = newPost;

            if (save && isScriptActivated) {
                loadLastReadPost(existingPost => {
                    console.log("🛠️ DEBUG: markTopVisiblePost - newPost:", newPost, "existingPost:", existingPost);
                    if (!existingPost ||
                        new Date(postTimestamp) > new Date(existingPost.timestamp) ||
                        (postTimestamp === existingPost.timestamp && postAuthorHandler !== existingPost.authorHandler)) {
                        lastReadPost = newPost;
                        console.log("💾 Neue Leseposition gesetzt:", lastReadPost);
                        saveLastReadPost(lastReadPost);
                    } else {
                        console.log("⏹️ Interne Speicherung übersprungen: Leseposition nicht neuer oder identisch.");
                    }
                });
            }
        }
    }

    function getTopVisiblePost() {
        const posts = Array.from(document.querySelectorAll("article"));
        return posts.find(post => {
            const rect = post.getBoundingClientRect();
            return rect.top >= 0 && rect.bottom > 0;
        });
    }

    function getPostTimestamp(post) {
        const timeElement = post.querySelector("time");
        return timeElement ? timeElement.getAttribute("datetime") : null;
    }

    function getPostAuthorHandler(post) {
        const handlerElement = post.querySelector('[role="link"][href*="/"]');
        return handlerElement ? handlerElement.getAttribute("href").slice(1) : null;
    }

    function startRefinedSearchForLastReadPost() {
        if (!isScriptActivated) {
            console.log("⏹️ Suche abgebrochen: Skript nicht aktiviert.");
            showPopup("ℹ️ Bitte scrollen oder Lupe klicken.");
            return;
        }

        loadLastReadPost(storedData => {
            if (!storedData) {
                const localData = JSON.parse(localStorage.getItem("lastReadPost") || "{}");
                if (localData && localData.timestamp && localData.authorHandler) {
                    lastReadPost = localData;
                } else {
                    console.log("❌ Keine Leseposition gefunden.");
                    showPopup("❌ Keine Leseposition vorhanden.");
                    return;
                }
            } else {
                lastReadPost = storedData;
            }

            if (!lastReadPost.timestamp || !lastReadPost.authorHandler) {
                console.log("❌ Ungültige Leseposition:", lastReadPost);
                showPopup("❌ Ungültige Leseposition.");
                return;
            }

            console.log("🔍 Starte Suche:", lastReadPost);
            const popup = createSearchPopup();

            let direction = 1;
            let scrollAmount = 2000;
            let previousScrollY = -1;
            let searchAttempts = 0;
            const maxAttempts = 50;

            function handleSpaceKey(event) {
                if (event.code === "Space") {
                    console.log("⏹️ Suche gestoppt.");
                    isSearching = false;
                    popup.remove();
                    window.removeEventListener("keydown", handleSpaceKey);
                }
            }

            window.addEventListener("keydown", handleSpaceKey);

            const search = () => {
                if (!isSearching || searchAttempts >= maxAttempts) {
                    console.log("⏹️ Suche beendet: Max Versuche oder abgebrochen.");
                    isSearching = false;
                    popup.remove();
                    window.removeEventListener("keydown", handleSpaceKey);
                    return;
                }

                const visiblePosts = getVisiblePosts();
                const comparison = compareVisiblePostsToLastReadPost(visiblePosts);

                if (comparison === "match") {
                    const matchedPost = findPostByData(lastReadPost);
                    if (matchedPost) {
                        console.log("🎯 Beitrag gefunden:", lastReadPost);
                        isAutoScrolling = true;
                        scrollToPostWithHighlight(matchedPost);
                        isSearching = false;
                        popup.remove();
                        window.removeEventListener("keydown", handleSpaceKey);
                        return;
                    }
                } else if (comparison === "older") {
                    direction = -1;
                } else if (comparison === "newer") {
                    direction = 1;
                }

                if (window.scrollY === previousScrollY) {
                    scrollAmount = Math.max(scrollAmount / 2, 500);
                    direction = -direction;
                } else {
                    scrollAmount = Math.min(scrollAmount * 1.5, 3000);
                }

                previousScrollY = window.scrollY;
                searchAttempts++;

                requestAnimationFrame(() => {
                    window.scrollBy({
                        top: direction * scrollAmount,
                        behavior: "smooth"
                    });
                    setTimeout(search, 1000);
                });
            };

            isSearching = true;
            search();
        });
    }

    function createSearchPopup() {
        const popup = document.createElement("div");
        popup.style.position = "fixed";
        popup.style.bottom = "20px";
        popup.style.left = "50%";
        popup.style.transform = "translateX(-50%)";
        popup.style.backgroundColor = "rgba(0, 0, 0, 0.9)";
        popup.style.color = "#ffffff";
        popup.style.padding = "10px 20px";
        popup.style.borderRadius = "8px";
        popup.style.fontSize = "14px";
        popup.style.boxShadow = "0 0 10px rgba(255, 255, 255, 0.8)";
        popup.style.zIndex = "10000";
        popup.textContent = "🔍 Searching... Press SPACE to cancel.";
        document.body.appendChild(popup);
        return popup;
    }

    function compareVisiblePostsToLastReadPost(posts, customPosition = lastReadPost) {
        const validPosts = posts.filter(post => post.timestamp && post.authorHandler);

        if (validPosts.length === 0) {
            console.log("⚠️ Keine sichtbaren Beiträge.");
            return null;
        }

        const lastReadTime = new Date(customPosition.timestamp);

        const allOlder = validPosts.every(post => new Date(post.timestamp) < lastReadTime);
        const allNewer = validPosts.every(post => new Date(post.timestamp) > lastReadTime);

        if (validPosts.some(post => post.timestamp === customPosition.timestamp && post.authorHandler === customPosition.authorHandler)) {
            return "match";
        } else if (allOlder) {
            return "older";
        } else if (allNewer) {
            return "newer";
        } else {
            return "mixed";
        }
    }

    function scrollToPostWithHighlight(post) {
        if (!post) {
            console.log("❌ Kein Beitrag zum Scrollen.");
            return;
        }

        isAutoScrolling = true;

        if (lastHighlightedPost && lastHighlightedPost !== post) {
            lastHighlightedPost.style.boxShadow = "none";
        }
        post.style.boxShadow = "0 0 20px 10px rgba(255, 215, 0, 0.9)";
        lastHighlightedPost = post;

        post.scrollIntoView({ behavior: "smooth", block: "center" });

        setTimeout(() => {
            isAutoScrolling = false;
            console.log("✅ Beitrag zentriert.");
        }, 1000);
    }

    function getVisiblePosts() {
        const posts = Array.from(document.querySelectorAll("article"));
        return posts.map(post => ({
            element: post,
            timestamp: getPostTimestamp(post),
            authorHandler: getPostAuthorHandler(post),
        }));
    }

    function observeForNewPosts() {
        let isProcessingIndicator = false;

        const observer = new MutationObserver(() => {
            if (!isScriptActivated) {
                console.log("⏹️ Beobachtung abgebrochen: Skript nicht aktiviert.");
                return;
            }

            if (window.scrollY <= 1 && !isSearching && !isProcessingIndicator && lastReadPost) {
                const newPostsIndicator = getNewPostsIndicator();
                if (newPostsIndicator) {
                    console.log("🆕 Neue Beiträge erkannt.");
                    isProcessingIndicator = true;
                    clickNewPostsIndicator(newPostsIndicator);
                    setTimeout(() => {
                        startRefinedSearchForLastReadPost();
                        isProcessingIndicator = false;
                    }, 2000);
                }
            }
        });

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

    function getNewPostsIndicator() {
        const buttons = document.querySelectorAll('button[role="button"]');
        for (const button of buttons) {
            const span = button.querySelector('span');
            if (span) {
                const textContent = span.textContent || '';
                const postIndicatorPattern = /^\d+\s*(neue|new)?\s*(Post|Posts|Beitrag|Beiträge|Tweet|Tweets|Publicación|Publications|投稿|게시물|пост|постов|mensagem|mensagens|مشاركة|مشاركات)\b/i;
                if (postIndicatorPattern.test(textContent)) {
                    if (!button.dataset.processed) {
                        console.log(`🆕 Indikator gefunden: ${textContent}`);
                        button.dataset.processed = 'true';
                        return button;
                    }
                }
            }
        }
        console.log("ℹ️ Kein Beitragsindikator gefunden.");
        return null;
    }

    function clickNewPostsIndicator(indicator) {
        if (!indicator) {
            console.log("⚠️ Kein Indikator gefunden.");
            return;
        }

        console.log("✅ Klicke auf Indikator...");
        try {
            indicator.click();
            console.log("✅ Indikator geklickt.");
        } catch (err) {
            console.error("❌ Fehler beim Klicken:", err);
        }
    }

    function findPostByData(data) {
        const posts = Array.from(document.querySelectorAll("article"));
        return posts.find(post => {
            const postTimestamp = getPostTimestamp(post);
            const authorHandler = getPostAuthorHandler(post);
            return postTimestamp === data.timestamp && authorHandler === data.authorHandler;
        });
    }

    function createButtons() {
        setTimeout(() => {
            try {
                if (!document.body) {
                    console.warn("⚠️ document.body nicht verfügbar.");
                    return;
                }

                const buttonContainer = document.createElement("div");
                buttonContainer.style.position = "fixed";
                buttonContainer.style.top = "10px";
                buttonContainer.style.left = "10px";
                buttonContainer.style.zIndex = "10000";
                buttonContainer.style.display = "flex";
                buttonContainer.style.flexDirection = "column";
                buttonContainer.style.alignItems = "flex-start";
                buttonContainer.style.visibility = "visible";

                const buttonsConfig = [
                    {
                        icon: "🔍",
                        title: "Start manual search",
                        onClick: () => {
                            console.log("🔍 Manuelle Suche gestartet.");
                            if (!isScriptActivated) {
                                isScriptActivated = true;
                                console.log("🛠️ DEBUG: Skript durch Lupen-Klick aktiviert.");
                                observeForNewPosts();
                            }
                            startRefinedSearchForLastReadPost();
                        },
                    },
                    {
                        icon: "📂",
                        title: "Load last read position from file",
                        onClick: () => {
                            console.log("📂 Lade Leseposition aus Datei...");
                            loadLastReadPostFromFile();
                        },
                    },
                    {
                        icon: "💾",
                        title: "Download current read position",
                        onClick: () => {
                            console.log("💾 Starte manuellen Download der Leseposition...");
                            downloadLastReadPost();
                        },
                    },
                ];

                buttonsConfig.forEach(({ icon, title, onClick }) => {
                    const button = createButton(icon, title, onClick);
                    buttonContainer.appendChild(button);
                });

                document.body.appendChild(buttonContainer);
                console.log("🛠️ DEBUG: Button-Container erstellt:", buttonContainer);
            } catch (err) {
                console.error("❌ Fehler beim Erstellen der Buttons:", err);
                showPopup("❌ Fehler beim Anzeigen der Buttons.");
            }
        }, 2000);
    }

    function createButton(icon, title, onClick) {
        const button = document.createElement("div");
        button.style.width = "27px";
        button.style.height = "27px";
        button.style.backgroundColor = "rgba(0, 0, 0, 0.9)";
        button.style.color = "#ffffff";
        button.style.borderRadius = "50%";
        button.style.display = "flex";
        button.style.justifyContent = "center";
        button.style.alignItems = "center";
        button.style.cursor = "pointer";
        button.style.fontSize = "14px";
        button.style.boxShadow = "0 0 8px rgba(255, 255, 255, 0.5)";
        button.style.transition = "transform 0.2s, box-shadow 0.3s";
        button.style.zIndex = "10001";
        button.style.marginBottom = "8px";
        button.textContent = icon;
        button.title = title;

        button.addEventListener("click", () => {
            button.style.boxShadow = "0 0 15px rgba(255, 255, 255, 0.8)";
            button.style.transform = "scale(0.9)";
            setTimeout(() => {
                button.style.boxShadow = "0 0 8px rgba(255, 255, 255, 0.5)";
                button.style.transform = "scale(1)";
            }, 300);
            onClick();
        });

        return button;
    }

    function showPopup(message, duration = 3000) {
        const popup = document.createElement("div");
        popup.style.position = "fixed";
        popup.style.top = "20px";
        popup.style.left = "50%";
        popup.style.transform = "translateX(-50%)";
        popup.style.backgroundColor = "rgba(0, 0, 0, 0.9)";
        popup.style.color = "#ffffff";
        popup.style.padding = "10px 20px";
        popup.style.borderRadius = "8px";
        popup.style.fontSize = "14px";
        popup.style.boxShadow = "0 0 10px rgba(255, 255, 255, 0.8)";
        popup.style.zIndex = "10000";
        popup.style.maxWidth = "500px";
        popup.style.whiteSpace = "pre-wrap";
        popup.textContent = message;

        document.body.appendChild(popup);

        setTimeout(() => {
            popup.remove();
        }, duration);
    }
})();