AMC → Rotten Tomatoes + IMDb + Letterboxd Links

Add Rotten Tomatoes, IMDb, and Letterboxd links/icons next to movie titles on amctheatres site

// ==UserScript==
// @name         AMC → Rotten Tomatoes + IMDb + Letterboxd Links
// @namespace    http://tampermonkey.net/
// @version      1.01
// @description  Add Rotten Tomatoes, IMDb, and Letterboxd links/icons next to  movie titles on amctheatres site
// @author       Chatgpt and me
// @match        *://www.amctheatres.com/movie-theatres/*
// @match        *://www.amctheatres.com/movies/*
// @grant        none
// @license      MIT
// ==/UserScript==
(function () {
    'use strict';

    function cleanTitle(title) {
        let clean = title.trim();
        // General removals
        clean = clean.replace(/\bQ&A.*$/i, "");
        clean = clean.split("/")[0];
        clean = clean.replace(/[-–]\s*studio ghibli fest\s*\d{4}/gi, "");
        clean = clean.replace(/\bstudio ghibli fest\s*\d{4}/gi, "");
        clean = clean.replace(/\bstudio ghibli fest\b/gi, "");
        clean = clean.replace(/[-–]\s*\d+(st|nd|rd|th)?\s+anniversary\b/gi, "");
        clean = clean.replace(/\b\d+(st|nd|rd|th)?\s+anniversary\b/gi, "");
        clean = clean.replace(/\bunrated\b/gi, "");
        clean = clean.replace(/\b4k\b/gi, "");
        clean = clean.replace(/\b3d\b/gi, "");
        clean = clean.replace(/\bfathom\s*\d{4}\b/gi, "");
        clean = clean.replace(/\bfathom\b/gi, "");
        clean = clean.replace(/\bopening night fan event\b/gi, "");
        clean = clean.replace(/\bopening night\b/gi, "");
        clean = clean.replace(/\bfan event\b/gi, "");
        clean = clean.replace(/\bspecial screening\b/gi, "");
        clean = clean.replace(/\bdouble feature\b/gi, "");
        clean = clean.replace(/\([^)]*\)/g, "");
        // Targeted cleanups
        const cutPhrases = [
            " - Opening Weekend Event",
            "Private Theatre",
            "Early Access",
            "Sneak Peak",
            "IMAX",
            "Sensory Friendly Screening"
        ];
        for (let phrase of cutPhrases) {
            const regex = new RegExp(`[:\\-]?\\s*${phrase}.*$`, "i");
            clean = clean.replace(regex, "");
        }
        return clean.trim();
    }

    // Rotten Tomatoes slug
    function makeRtSlug(title) {
        let clean = cleanTitle(title).toLowerCase();
        clean = clean.replace(/&/g, " and ");
        clean = clean.replace(/[':;!?,.\-–]/g, "");
        clean = clean.replace(/\s+/g, "_");
        clean = clean.replace(/^_+|_+$/g, "");
        return clean.trim();
    }

    // Letterboxd slug
    function makeLbSlug(title) {
        let clean = cleanTitle(title)
            .toLowerCase()
            .normalize("NFD").replace(/[\u0300-\u036f]/g, ""); // remove accents
        clean = clean.replace(/&/g, " ");               // drop ampersands (treat as space)
        clean = clean.replace(/['":;!?,.]/g, "");       // remove punctuation
        clean = clean.replace(/[-–]/g, " ");            // treat dashes as spaces
        clean = clean.replace(/\s+/g, "-");             // spaces -> hyphens
        clean = clean.replace(/-+/g, "-");              // collapse multiple hyphens
        clean = clean.replace(/^-+|-+$/g, "");          // trim leading/trailing hyphens
        return clean.trim();
    }

    function addLinks() {
        const movieTitleEls = document.querySelectorAll("h2.font-bold, h3.font-bold, .md\\:text-2xl.font-bold");

        movieTitleEls.forEach(movieTitleEl => {
            const rawTitle = movieTitleEl.textContent.trim();
            if (!rawTitle || /amc/i.test(rawTitle)) return;
            if (movieTitleEl.querySelector(".rt-btn, .imdb-btn, .lb-btn")) return;

            // Rotten Tomatoes
            const rtSlug = makeRtSlug(rawTitle);
            const rtUrl = `https://www.rottentomatoes.com/m/${rtSlug}`;
            const rtLink = document.createElement("a");
            rtLink.href = rtUrl;
            rtLink.target = "_blank";
            rtLink.title = "View on Rotten Tomatoes";
            rtLink.className = "rt-btn";
            rtLink.style.cssText = `
                display:inline-flex;align-items:center;justify-content:center;
                background-color:#d92323;color:white;font-size:15px;font-weight:bold;
                text-decoration:none;border-radius:50%;width:24.5px;height:24.5px;
                margin-left:4px;vertical-align:middle;box-shadow:0 1px 3px rgba(0,0,0,0.2);
                cursor:pointer;
            `;
            rtLink.textContent = "🍅";

            // IMDb
            const imdbQuery = encodeURIComponent(cleanTitle(rawTitle));
            const imdbUrl = `https://www.imdb.com/find/?q=${imdbQuery}`;
            const imdbLink = document.createElement("a");
            imdbLink.href = imdbUrl;
            imdbLink.target = "_blank";
            imdbLink.title = "Search on IMDb";
            imdbLink.className = "imdb-btn";
            imdbLink.style.cssText = `
                display:inline-flex;align-items:center;justify-content:center;
                background-color:#f5c518;color:black;font-size:12px;font-weight:bold;
                text-decoration:none;border-radius:3px;width:32px;height:24.5px;
                margin-left:4px;vertical-align:middle;box-shadow:0 1px 3px rgba(0,0,0,0.2);
                cursor:pointer;font-family:Arial, sans-serif;
            `;
            imdbLink.textContent = "IMDb";

            // Letterboxd
            const lbSlug = makeLbSlug(rawTitle);
            const lbUrl = `https://letterboxd.com/film/${lbSlug}/`;
            const lbLink = document.createElement("a");
            lbLink.href = lbUrl;
            lbLink.target = "_blank";
            lbLink.title = "View on Letterboxd";
            lbLink.className = "lb-btn";
            lbLink.style.cssText = `
                display:inline-flex;align-items:center;justify-content:center;
                text-decoration:none;margin-left:4px;vertical-align:middle;cursor:pointer;
            `;
            lbLink.innerHTML = `
                <div style="display:flex;">
                    <div style="
                        width:24.5px;height:24.5px;border-radius:50%;background:#ff8000;
                        display:flex;align-items:center;justify-content:center;
                        font-size:12px;font-weight:bold;color:white;font-family:Arial, sans-serif;
                        box-shadow:0 1px 3px rgba(0,0,0,0.2);margin-right:-5px;">
                        L
                    </div>
                    <div style="
                        width:24.5px;height:24.5px;border-radius:50%;background:#00e054;
                        display:flex;align-items:center;justify-content:center;
                        font-size:12px;font-weight:bold;color:black;font-family:Arial, sans-serif;
                        box-shadow:0 1px 3px rgba(0,0,0,0.2);margin-right:-5px;">
                        T
                    </div>
                    <div style="
                        width:24.5px;height:24.5px;border-radius:50%;background:#00bfff;
                        display:flex;align-items:center;justify-content:center;
                        font-size:12px;font-weight:bold;color:black;font-family:Arial, sans-serif;
                        box-shadow:0 1px 3px rgba(0,0,0,0.2);">
                        B
                    </div>
                </div>
            `;

            movieTitleEl.appendChild(rtLink);
            movieTitleEl.appendChild(imdbLink);
            movieTitleEl.appendChild(lbLink);
        });
    }

    addLinks();
    const observer = new MutationObserver(addLinks);
    observer.observe(document.body, { childList: true, subtree: true });
})();