// ==UserScript== // @name TheMovieDB Toast // @namespace https://github.com/Tetrax-10 // @version 1.2 // @description Displays toast notifications on TheMovieDB // @author Tetrax-10 // @icon https://www.themoviedb.org/favicon.ico // @match *://*.themoviedb.org/* // @run-at document-start // @grant GM_addStyle // ==/UserScript== ;(async () => { // Inject CSS for this userscript try { GM_addStyle(` #toast-container { position: fixed; bottom: 30px; width: 100%; display: flex; justify-content: center; align-items: flex-end; pointer-events: none; z-index: 9999; } .toast { position: absolute; bottom: 0; background-color: #333; color: #fff; padding: 12px 20px; border-radius: 8px; font-size: 15px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); max-width: 80vw; white-space: pre-wrap; word-wrap: break-word; text-align: center; pointer-events: all; opacity: 0; transform: translateY(20px); transition: opacity 0.4s ease, transform 0.4s ease; } .toast.visible { opacity: 1; transform: translateY(0); } `) } catch (e) { console.error("❌ [Toast] Failed to inject CSS:", e) } // Ensure our namespace exists on the unsafeWindow unsafeWindow.TmdbAdvScp = unsafeWindow.TmdbAdvScp || {} function hideAndRemoveToast(el) { if (!el || !el.parentElement) return if (el.dataset.hiding === "1") return el.dataset.hiding = "1" el.classList.remove("visible") // Fallback removal after 600ms let fallback = setTimeout(() => { try { if (el.parentElement) el.parentElement.removeChild(el) } catch (e) {} }, 600) function onTransitionEnd(ev) { if (ev.target !== el) return if (ev.propertyName !== "opacity" && ev.propertyName !== "transform") return clearTimeout(fallback) el.removeEventListener("transitionend", onTransitionEnd) try { if (el.parentElement) el.parentElement.removeChild(el) } catch (e) {} } el.addEventListener("transitionend", onTransitionEnd) } unsafeWindow.TmdbAdvScp.toast = (message, { duration = 5000, update } = {}) => { try { const container = document.getElementById("toast-container") if (!container) { console.error("❌ [Toast] Toast container not found") return } // Hide previous toast but let animation overlap for smoother transition const existing = container.querySelector(".toast") if (existing) { hideAndRemoveToast(existing) } // Create the toast element const toast = document.createElement("div") toast.className = "toast" toast.innerHTML = message container.appendChild(toast) // Trigger show requestAnimationFrame(() => { void toast.offsetWidth toast.classList.add("visible") }) if (update) { // returns controls and never auto-hides return { update: (newText) => { if (toast && toast.parentElement) { toast.innerHTML = newText } }, remove: () => hideAndRemoveToast(toast), } } // Normal auto-hide behavior setTimeout(() => { hideAndRemoveToast(toast) }, duration) } catch (e) { console.error("❌ [Toast] Error displaying toast:", e) } } // Wait until the exists before injecting the toast container while (!document?.body) { await new Promise((resolve) => setTimeout(resolve, 10)) } // Create and append the toast container to the body try { if (!document.getElementById("toast-container")) { const toastContainer = document.createElement("div") toastContainer.id = "toast-container" document.body.appendChild(toastContainer) } } catch (e) { console.error("❌ [Toast] Failed to create or append toast container:", e) } })()