- // ==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-04-15
- // @author Copiis
- // @license MIT
- // @match https://x.com/home
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_download
- // ==/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 () {
- let lastReadPost = null;
- let isAutoScrolling = false;
- let isSearching = false;
- let isTabFocused = true;
- let downloadTriggered = false;
- let lastDownloadedPost = null;
-
- window.onload = async () => {
- if (!window.location.href.includes("/home")) {
- console.log("🚫 Skript deaktiviert: Nicht auf der Home-Seite.");
- return;
- }
- console.log("🚀 Seite vollständig geladen. Initialisiere Skript...");
- await loadNewestLastReadPost();
- await initializeScript();
- createButtons();
- };
-
- window.addEventListener("blur", async () => {
- isTabFocused = false;
- console.log("🌐 Tab nicht mehr fokussiert.");
- if (lastReadPost && !downloadTriggered) {
- // Prüfe, ob sich die aktuelle Leseposition von der zuletzt heruntergeladenen unterscheidet
- if (
- !lastDownloadedPost ||
- lastDownloadedPost.timestamp !== lastReadPost.timestamp ||
- lastDownloadedPost.authorHandler !== lastReadPost.authorHandler
- ) {
- downloadTriggered = true;
- console.log("📥 Speichere und lade aktuelle Leseposition herunter...");
- await saveLastReadPostToFile(); // Speichert lokal
- await downloadLastReadPost(); // Löst den Download aus
- lastDownloadedPost = { ...lastReadPost }; // Aktualisiere die zuletzt heruntergeladene Position
- downloadTriggered = false;
- } else {
- console.log("⏹️ Leseposition ist identisch mit der zuletzt heruntergeladenen. Download übersprungen.");
- }
- }
- });
-
- window.addEventListener("focus", () => {
- isTabFocused = true;
- downloadTriggered = false;
- console.log("🟢 Tab wieder fokussiert.");
- });
-
- async function initializeScript() {
- console.log("🔧 Lade Leseposition...");
- await loadLastReadPostFromFile();
- observeForNewPosts();
-
- window.addEventListener("scroll", () => {
- if (isAutoScrolling || isSearching) {
- console.log("⏹️ Scroll-Ereignis ignoriert (automatischer Modus aktiv).");
- return;
- }
- markTopVisiblePost(true);
- }, { passive: true }); // Passiver Listener
- }
-
- async function downloadLastReadPost() {
- if (!lastReadPost || !lastReadPost.timestamp || !lastReadPost.authorHandler) {
- console.warn("⚠️ Keine gültige Leseposition zum Herunterladen.");
- return;
- }
-
- // Prüfe, ob die Leseposition bereits heruntergeladen wurde
- if (
- lastDownloadedPost &&
- lastDownloadedPost.timestamp === lastReadPost.timestamp &&
- lastDownloadedPost.authorHandler === lastReadPost.authorHandler
- ) {
- console.log("⏹️ Leseposition ist identisch mit der zuletzt heruntergeladenen. Download übersprungen.");
- return;
- }
-
- try {
- const data = JSON.stringify(lastReadPost, null, 2);
- const sanitizedHandler = lastReadPost.authorHandler.replace(/[^a-zA-Z0-9-_]/g, "");
- const timestamp = new Date(lastReadPost.timestamp).toISOString().replace(/[:.-]/g, "_");
- const fileName = `${timestamp}_${sanitizedHandler}.json`;
-
- GM_download({
- url: `data:application/json;charset=utf-8,${encodeURIComponent(data)}`,
- name: fileName,
- onload: () => {
- console.log(`✅ Leseposition erfolgreich heruntergeladen: ${fileName}`);
- // Aktualisiere lastDownloadedPost nach erfolgreichem Download
- lastDownloadedPost = { ...lastReadPost };
- },
- onerror: (err) => console.error("❌ Fehler beim Herunterladen der Leseposition:", err),
- });
- } catch (error) {
- console.error("❌ Fehler beim Herunterladen der Leseposition:", error);
- }
- }
-
- async function loadNewestLastReadPost() {
- try {
- const localData = GM_getValue("lastReadPost", null);
- if (localData) {
- lastReadPost = JSON.parse(localData);
- console.log("✅ Lokale Leseposition beim Start geladen:", lastReadPost);
- } else {
- console.warn("⚠️ Keine gespeicherte Leseposition gefunden.");
- }
- } catch (err) {
- console.error("❌ Fehler beim Laden der neuesten Leseposition:", err);
- }
- }
-
- async function loadLastReadPostFromFile() {
- try {
- const data = GM_getValue("lastReadPost", null);
- if (data) {
- lastReadPost = JSON.parse(data);
- console.log("✅ Leseposition erfolgreich geladen:", lastReadPost);
- } else {
- console.warn("⚠️ Keine gespeicherte Leseposition gefunden.");
- }
- } catch (err) {
- console.error("❌ Fehler beim Laden der Leseposition:", err);
- }
- }
-
- function markTopVisiblePost(save = true) {
- const topPost = getTopVisiblePost();
- if (!topPost) {
- console.log("❌ Kein oberster sichtbarer Beitrag gefunden.");
- return;
- }
-
- const postTimestamp = getPostTimestamp(topPost);
- const authorHandler = getPostAuthorHandler(topPost);
-
- if (postTimestamp && authorHandler) {
- const newPost = { timestamp: postTimestamp, authorHandler };
- const existingData = GM_getValue("lastReadPost", null);
- const existingPost = existingData ? JSON.parse(existingData) : null;
-
- if (save && (!existingPost || new Date(postTimestamp) > new Date(existingPost.timestamp))) {
- lastReadPost = newPost;
- console.log("💾 Neue Leseposition erkannt und aktualisiert:", lastReadPost);
- if (isTabFocused) {
- saveLastReadPostToFile(); // Speichert nur lokal, kein Download
- }
- }
- }
- }
-
- 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() {
- const storedData = GM_getValue("lastReadPost", null);
- if (!storedData) {
- console.log("❌ Keine gespeicherte Leseposition gefunden.");
- showPopup("❌ Keine gespeicherte Leseposition vorhanden.");
- return;
- }
-
- try {
- lastReadPost = JSON.parse(storedData);
- if (!lastReadPost.timestamp || !lastReadPost.authorHandler) {
- console.log("❌ Gespeicherte Leseposition ist ungültig:", lastReadPost);
- showPopup("❌ Ungültige gespeicherte Leseposition.");
- return;
- }
- } catch (err) {
- console.error("❌ Fehler beim Parsen der gespeicherten Leseposition:", err);
- showPopup("❌ Fehler bei der gespeicherten Leseposition.");
- return;
- }
-
- console.log("🔍 Starte verfeinerte Suche mit gespeicherter Position:", lastReadPost);
- const popup = createSearchPopup();
-
- let direction = 1;
- let scrollAmount = 2000;
- let previousScrollY = -1;
- let searchAttempts = 0;
- const maxAttempts = 50; // Begrenze die maximale Anzahl an Suchversuchen
-
- function handleSpaceKey(event) {
- if (event.code === "Space") {
- console.log("⏹️ Suche manuell gestoppt.");
- isSearching = false;
- popup.remove();
- window.removeEventListener("keydown", handleSpaceKey);
- }
- }
-
- window.addEventListener("keydown", handleSpaceKey);
-
- const search = () => {
- if (!isSearching || searchAttempts >= maxAttempts) {
- console.log("⏹️ Suche beendet: Maximale Versuche erreicht 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++;
-
- // Verwende requestAnimationFrame mit längerer Verzögerung
- requestAnimationFrame(() => {
- window.scrollBy({
- top: direction * scrollAmount,
- behavior: "smooth"
- });
- setTimeout(search, 1000); // Erhöhte Wartezeit
- });
- };
-
- isSearching = true;
- search();
- }
-
- function startRefinedSearchForLastReadPostWithPosition(position) {
- if (!position || !position.timestamp || !position.authorHandler) {
- console.log("❌ Ungültige Leseposition für Suche:", position);
- showPopup("❌ Ungültige Leseposition.");
- return;
- }
-
- console.log("🔍 Starte verfeinerte Suche mit temporärer Position:", position);
- const popup = createSearchPopup();
-
- let direction = 1;
- let scrollAmount = 2000;
- let previousScrollY = -1;
-
- function handleSpaceKey(event) {
- if (event.code === "Space") {
- console.log("⏹️ Suche manuell gestoppt.");
- isSearching = false;
- popup.remove();
- window.removeEventListener("keydown", handleSpaceKey);
- }
- }
-
- window.addEventListener("keydown", handleSpaceKey);
-
- const search = () => {
- if (!isSearching) {
- popup.remove();
- return;
- }
-
- const visiblePosts = getVisiblePosts();
- const comparison = compareVisiblePostsToLastReadPost(visiblePosts, position);
-
- if (comparison === "match") {
- const matchedPost = findPostByData(position);
- if (matchedPost) {
- console.log("🎯 Beitrag gefunden:", position);
- 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;
-
- window.scrollBy(0, direction * scrollAmount);
-
- setTimeout(search, 300);
- };
-
- 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 gefunden.");
- 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 gefunden.");
- return;
- }
-
- isAutoScrolling = true;
-
- // Entferne bestehende Stile und setze durchgehendes Leuchten
- post.style.outline = "none";
- post.style.boxShadow = "0 0 20px 10px rgba(255, 215, 0, 0.9)"; // Durchgehendes Leuchten
- post.style.animation = "none"; // Entferne die Animation
-
- // Entferne das @keyframes glow, falls es existiert
- const existingStyle = document.querySelector('#glowStyle');
- if (existingStyle) {
- existingStyle.remove();
- }
-
- post.scrollIntoView({ behavior: "smooth", block: "center" });
-
- // Entferne das Highlight bei der ersten Scrollaktion
- const removeHighlightOnScroll = () => {
- if (!isAutoScrolling) {
- post.style.boxShadow = "none";
- console.log("✅ Highlight entfernt nach manuellem Scroll.");
- window.removeEventListener("scroll", removeHighlightOnScroll);
- }
- };
-
- setTimeout(() => {
- isAutoScrolling = false;
- window.addEventListener("scroll", removeHighlightOnScroll);
- console.log("✅ Beitrag zentriert, warte auf manuellen Scroll.");
- }, 1000);
- }
-
- function getVisiblePosts() {
- const posts = Array.from(document.querySelectorAll("article"));
- return posts.map(post => ({
- element: post,
- timestamp: getPostTimestamp(post),
- authorHandler: getPostAuthorHandler(post),
- }));
- }
-
- async function saveLastReadPostToFile() {
- try {
- if (!lastReadPost || !lastReadPost.timestamp || !lastReadPost.authorHandler) {
- console.warn("⚠️ Keine gültige Leseposition vorhanden. Speichern übersprungen.");
- return;
- }
-
- const existingData = GM_getValue("lastReadPost", null);
- if (existingData) {
- const existingPost = JSON.parse(existingData);
- if (
- existingPost.timestamp === lastReadPost.timestamp &&
- existingPost.authorHandler === lastReadPost.authorHandler
- ) {
- console.log("⏹️ Lesestelle ist identisch mit der gespeicherten. Speichern übersprungen.");
- return;
- }
- }
-
- GM_setValue("lastReadPost", JSON.stringify(lastReadPost));
- console.log("💾 Leseposition erfolgreich lokal gespeichert:", lastReadPost);
- } catch (err) {
- console.error("❌ Fehler beim Speichern der Leseposition:", err);
- }
- }
-
- async function deleteOldReadingPositions(handler) {
- console.log(`🗑️ Ältere Lesestellen für den Handler "${handler}" werden simuliert entfernt.`);
- }
-
- function observeForNewPosts() {
- let isProcessingIndicator = false; // Flag, um Mehrfachverarbeitung zu verhindern
-
- const observer = new MutationObserver(() => {
- if (window.scrollY <= 1 && !isSearching && !isProcessingIndicator && lastReadPost) {
- const newPostsIndicator = getNewPostsIndicator();
- if (newPostsIndicator) {
- console.log("🆕 Neue Beiträge erkannt. Starte automatische Suche...");
- isProcessingIndicator = true;
- clickNewPostsIndicator(newPostsIndicator);
- setTimeout(() => {
- startRefinedSearchForLastReadPost();
- isProcessingIndicator = false;
- }, 2000); // Erhöhter Timeout für stabileres Laden
- }
- }
- });
-
- observer.observe(document.body, {
- childList: true,
- subtree: true,
- });
- }
-
- function getNewPostsIndicator() {
- // Suche nach Buttons, die den Indikator enthalten könnten
- const buttons = document.querySelectorAll('button[role="button"]');
- for (const button of buttons) {
- // Suche nach dem span mit dem Textmuster innerhalb des Buttons
- const span = button.querySelector('span.r-poiln3');
- if (span) {
- const textContent = span.textContent || '';
- // Prüfe auf eine Zahl gefolgt von einer Variation von "Posts anzeigen" (international)
- 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(`🆕 Neuer Beitrags-Indikator gefunden mit Text: "${textContent}"`);
- button.dataset.processed = 'true'; // Markiere als verarbeitet
- return button;
- }
- }
- }
- }
- console.log("ℹ️ Kein neuer Beitragsindikator gefunden.");
- return null;
- }
-
- function clickNewPostsIndicator(indicator) {
- if (!indicator) {
- console.log("⚠️ Kein neuer Beitragsindikator gefunden.");
- return;
- }
-
- console.log("✅ Klicke auf neuen Beitragsindikator...");
- try {
- indicator.click();
- console.log("✅ Neuer Beitragsindikator erfolgreich geklickt.");
- } catch (err) {
- console.error("❌ Fehler beim Klicken auf den Beitragsindikator:", 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() {
- const buttonContainer = document.createElement("div");
- buttonContainer.style.position = "fixed";
- buttonContainer.style.top = "50%";
- buttonContainer.style.left = "3px";
- buttonContainer.style.transform = "translateY(-50%)";
- buttonContainer.style.display = "flex";
- buttonContainer.style.flexDirection = "column";
- buttonContainer.style.gap = "3px";
- buttonContainer.style.zIndex = "10000";
-
- const buttonsConfig = [
- {
- icon: "📂",
- title: "Load saved position",
- onClick: async () => {
- await importLastReadPost();
- },
- },
- {
- icon: "🔍",
- title: "Start manual search",
- onClick: () => {
- console.log("🔍 Manuelle Suche gestartet.");
- startRefinedSearchForLastReadPost();
- },
- },
- ];
-
- buttonsConfig.forEach(({ icon, title, onClick }) => {
- const button = createButton(icon, title, onClick);
- buttonContainer.appendChild(button);
- });
-
- document.body.appendChild(buttonContainer);
- }
-
- function createButton(icon, title, onClick) {
- const button = document.createElement("div");
- button.style.width = "36px";
- button.style.height = "36px";
- 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 = "18px";
- button.style.boxShadow = "inset 0 0 10px rgba(255, 255, 255, 0.5)";
- button.style.transition = "transform 0.2s, box-shadow 0.3s";
- button.textContent = icon;
- button.title = title;
-
- button.addEventListener("click", () => {
- button.style.boxShadow = "inset 0 0 20px rgba(255, 255, 255, 0.8)";
- button.style.transform = "scale(0.9)";
- setTimeout(() => {
- button.style.boxShadow = "inset 0 0 10px rgba(255, 255, 255, 0.5)";
- button.style.transform = "scale(1)";
- }, 300);
- onClick();
- });
-
- button.addEventListener("mouseenter", () => {
- button.style.boxShadow = "inset 0 0 15px rgba(255, 255, 255, 0.7)";
- button.style.transform = "scale(1.1)";
- });
-
- button.addEventListener("mouseleave", () => {
- button.style.boxShadow = "inset 0 0 10px rgba(255, 255, 255, 0.5)";
- button.style.transform = "scale(1)";
- });
-
- return button;
- }
-
- async function importLastReadPost() {
- const input = document.createElement("input");
- input.type = "file";
- input.accept = "application/json";
- input.style.display = "none";
-
- input.addEventListener("change", async (event) => {
- const file = event.target.files[0];
- if (file) {
- const reader = new FileReader();
- reader.onload = async () => {
- try {
- const importedData = JSON.parse(reader.result);
- if (importedData.timestamp && importedData.authorHandler) {
- console.log("✅ Importierte Leseposition geladen (wird nicht intern gespeichert):", importedData);
- showPopup("✅ Position geladen. Suche startet...");
-
- const tempPosition = importedData;
- const matchedPost = findPostByData(tempPosition);
- if (matchedPost) {
- scrollToPostWithHighlight(matchedPost);
- } else {
- startRefinedSearchForLastReadPostWithPosition(tempPosition);
- }
- } else {
- throw new Error("Ungültige Position");
- }
- } catch (error) {
- console.error("❌ Fehler beim Importieren der Leseposition:", error);
- showPopup("❌ Fehler: Ungültige Position.");
- }
- };
- reader.readAsText(file);
- }
- });
-
- document.body.appendChild(input);
- input.click();
- document.body.removeChild(input);
- }
-
- function showPopup(message) {
- const popup = document.createElement("div");
- popup.style.position = "fixed";
- popup.style.bottom = "20px";
- popup.style.right = "20px";
- 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 = message;
-
- document.body.appendChild(popup);
-
- setTimeout(() => {
- popup.remove();
- }, 3000);
- }
- })();