IMDb-Lists-Highlighter

highlights movie titles, series titles, and people from your lists

// ==UserScript==
// @name          IMDb-Lists-Highlighter
// @author        tronix42
// @copyright     2025, tronix42
// @copyright     2008-2024, Ricardo Mendonca Ferreira (original script - IMDb 'My Movies' enhancer)
// @namespace     http://example.com/
// @version       1.2
// @description   highlights movie titles, series titles, and people from your lists
// @include       *://*.imdb.com/*
// @grant         none
// @run-at        document-idle
// @license       GPL-3.0-or-later
// ==/UserScript==
//
// --------------------------------------------------------------------
//
// Thanks to AltoRetrato and his work with the great "IMDb 'My Movies' enhancer" Userscript.
// https://openuserjs.org/scripts/AltoRetrato/IMDb_My_Movies_enhancer
//
// This userscript highlights movie titles, series titles, and people from your lists. This way, you can immediately see which
// movies or series you've already seen or have on your watchlist while browsing IMDb. If you have lists of your
// favorite actors/actresses, you can see them highlighted in the calendar when they appear in a new film.
//
// This all works so far, with a small limitation. Custom lists and watchlist working fine. Unfortunately,
// the ratings list and check-in list don't work via automatic import. You have to take a detour for that.
// You can either manually import the ratings CSV file (which you downloaded previously) or create a custom list
// and add all rated films to it, which you then import via the script.
//
// A "Configure List" button will appear on the list page. All recognized lists will then be in the configuration,
// where you can assign each list a unique color. If you simply check the box next to the list WITHOUT uploading
// a CSV file, the lists will be imported automatically (as mentioned, this unfortunately doesn't work for
// ratings or check-ins). If you check the box AND upload a CSV file, the import will be done manually.
//
// As soon as you click Start Import, all lists to be imported will be displayed, along with a progress circle.
// When the import of a list is complete, the number of imported entries will be displayed next to it.
// After the import is finished, reload the page, and all imported entries should be highlighted.

// All custom colors will be saved. You don't have to import all lists at once. Nothing will be lost if you import
// another list later. Clear Data deletes all data!
//
//
// History:
// --------
// 2025.05.26  [1.2] Added Tooltip, couple other changes
// 2025.05.19  [1.1] Added Fallback for GM_addStyle (Greasemonkey)
// 2025.05.12  [1.0] Public Release
// --------------------------------------------------------------------

(function() {
    'use strict';
    // === Tooltip-CSS ===
    addStyle(`
    .imdb-tooltip {
      position: absolute;
      background: rgba(0,0,0,0.8);
      color: #fff;
      padding: 4px 8px;
      border-radius: 4px;
      font-size: 12px;
      pointer-events: none;
      z-index: 10000;
      white-space: normal;
  line-height: 1.4;
  font-family: system-ui, -apple-system, BlinkMacSystemFont,
               "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif;

    }
    `);

    // === Tooltip-Config ===
    let showTooltips = true;
    try {
        const v = localStorage.getItem('showTooltips');
        if (v !== null) showTooltips = JSON.parse(v);
    } catch (e) {}

    function addStyle(css) {
        const style = document.createElement('style');
        style.textContent = css;
        document.head.appendChild(style);
    }

    // Persistent People-Map
    let namesToColor = {};
    try {
        namesToColor = JSON.parse(localStorage.getItem('namesToColor') || '{}');
    } catch (e) {
        namesToColor = {};
    }

    let myLists = [],
        listOrder = [];
    let progressModal = null;
    let progressItems = [];

    function getCurrentUser() {
        const el = document.querySelector('[data-testid="user-menu-toggle-name"]') ||
            document.querySelector('.navbar__user-menu-toggle__name') ||
            document.querySelector('#nbpersonalize strong');
        return el ? el.textContent.trim() : null;
    }

    function getStorageUser() {
        for (let i = 0; i < localStorage.length; i++) {
            const k = localStorage.key(i);
            if (k.startsWith('myMovies-')) return k.slice(9);
        }
        return null;
    }

    function getUserId() {
        const link = document.querySelector('a[href*="/user/ur"]');
        if (link) {
            const m = link.href.match(/\/user\/(ur\d+)\//);
            if (m) return m[1];
        }
        return null;
    }

    const user = getCurrentUser() || getStorageUser();
    // Check language Regex
    const pathParts = window.location.pathname.split('/');
    const langSegment = pathParts[1];
    const langRegex = /^[a-z]{2}(?:-[A-Z]{2})?$/i;
    const countryPath = langRegex.test(langSegment) ? `/${langSegment}` : '';
    if (!user) return;

    // 1) Load lists from LocalStorage
    const listsLoaded = loadLists();

    // 2) Config-Button (Lists URL)
    if (/^\/(?:[a-z]{2}(?:-[a-z]{2})?\/)?user\/ur\d+\/lists/i.test(location.pathname)) {
        // (a) Load Database
        let savedListsMap = {};
        if (listsLoaded) {
            myLists.forEach(l => {
                savedListsMap[l.id] = {
                    ids: JSON.parse(JSON.stringify(l.ids)),
                    names: l.names ? JSON.parse(JSON.stringify(l.names)) : {},
                    color: l.color,
                    selected: l.selected
                };
            });
        }

        // (b) Show all Lists
        collectLists();
        addConfigButton();

        // (c) Overwrite Data
        Object.entries(savedListsMap).forEach(([id, data]) => {
            const lst = myLists.find(x => x.id === id);
            if (lst) {
                lst.ids = data.ids;
                lst.names = data.names;
                lst.color = data.color;
                lst.selected = data.selected;
            }
        });

        // Stylesheet Lists
        if (/^\/(?:[a-z]{2}(?:-[A-Z]{2})?\/)?user\/ur\d+\/lists/.test(location.pathname)) {
            const cssRules = [];
            myLists.forEach(list => {
                const color = list.color;
                Object.keys(list.ids).forEach(code36 => {
                    const num = parseInt(code36, 36);
                    cssRules.push(
                        `a[href*="/title/tt${num}/?ref_"] {` +
                        ` color: ${color} !important; font-weight: bold !important; }`
                    );
                });
            });
            addStyle(cssRules.join('\n'));
            highlightLinks();
        }
    }

    // 3) CSS and Search-Highlight

    const isHome = location.pathname === '/';
    const isCalendar = /^\/(?:[a-z]{2}(?:-[a-z]{2})?\/)?calendar/i.test(location.pathname);

    // A) Stylesheet on all sites beside calendar
    if (!isCalendar) {
        let css = '';
        myLists.forEach(list => {
            const color = list.color;
            Object.keys(list.ids).forEach(code36 => {
                const num = parseInt(code36, 36);
                const idPadded = String(num).padStart(7, '0');
                // Title-Links
                css += `
a[href^="/title/tt${num}/?ref_="],
a[href^="https://www.imdb.com/title/tt${num}/?ref_="] {
  color: ${color} !important;
  font-weight: bold !important;
}
`;
                // People-Links
                css += `
a[href^="/name/nm${idPadded}/?ref_="],
a[href^="https://www.imdb.com/name/nm${idPadded}/?ref_="] {
  color: ${color} !important;
  font-weight: bold !important;
}
`;
            });
        });
        addStyle(css);
    }

    // B) JS-Highlight
    highlightTitle();
    highlightLinks();

    // C) Observer & Autocomplete-Dropdowns
    let moTimeout;
    const linkObserver = new MutationObserver(() => {
        clearTimeout(moTimeout);
        moTimeout = setTimeout(() => {
            highlightTitle();
            highlightLinks();
        }, 100);
    });

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

    window.addEventListener('beforeunload', () => {
        linkObserver.disconnect();
    });

    // D) Calendar Highlighting
    if (isCalendar) {
        highlightTitle();
        highlightLinks();
        highlightCalendarPeople();
        let calTimeout;
        const calObserver = new MutationObserver(() => {
            clearTimeout(calTimeout);
            calTimeout = setTimeout(() => {
                highlightCalendarPeople();
            }, 100);
        });
        calObserver.observe(document.body, { childList: true, subtree: true });
        window.addEventListener('beforeunload', () => calObserver.disconnect());
    }

    function collectLists() {
        let savedColors = {};
        if (listsLoaded) {
            savedColors = myLists.reduce((map, l) => {
                map[l.id] = l.color;
                return map;
            }, {});
        }
        const customColors = {
            "Your Watchlist": "DarkGoldenRod",
            "Your Ratings": "Green"
        };
        const defaultColor = 'Red';
        myLists = [];
        listOrder = [];
        const seen = new Set();
        [
            ["Your Watchlist", "watchlist"],
            ["Your Ratings", "ratings"],
            ["Your check-ins", "checkins"]
        ].forEach(([name, id], i) => {
            myLists.push({
                name,
                id,
                color: savedColors[id] || customColors[name] || defaultColor,
                ids: {},
                names: {},
                selected: false,
                csvFile: null,
                importMode: 'auto'
            });
            listOrder.push(i);
            seen.add(id);
        });
        document.querySelectorAll('a[href*="/list/ls"]').forEach(a => {
            const m = a.href.match(/\/list\/(ls\d+)/);
            if (!m) return;
            const id = m[1];
            if (seen.has(id)) return;
            seen.add(id);
            const raw = a.getAttribute('aria-label') || a.title || a.textContent.trim();
            const name = raw
                // remove all Prefix-Keywords for list names
                .replace(/^.*\b(?:for|für|para|pour|per|de)\b\s*/i, '')
                .replace(/\s*(?:के लिए सूची का पेज देखें)$/i, '')
                .trim();
            myLists.push({
                name,
                id,
                color: savedColors[id] || defaultColor,
                ids: {},
                names: {},
                selected: false,
                csvFile: null,
                importMode: 'auto'
            });
            listOrder.push(myLists.length - 1);
        });
    }

    function addConfigButton() {
        const h1 = document.querySelector('h1');
        if (!h1) return;
        const btn = document.createElement('button');
        btn.textContent = 'Configure lists';
        btn.style.margin = '0 10px';
        btn.onclick = openConfig;
        h1.parentNode.insertBefore(btn, h1.nextSibling);
    }

    function openConfig() {
        // 1) Toggle-Switch CSS
        if (!document.getElementById('imdb-toggle-style')) {
            const style = document.createElement('style');
            style.id = 'imdb-toggle-style';
            style.textContent = `
.switch {
  position: relative;
  display: inline-block;
  width: 50px;
  height: 24px;
  margin: 0 8px;
  vertical-align: middle;
}
.switch input {
  opacity: 0;
  width: 0;
  height: 0;
}
.slider {
  position: absolute;
  cursor: pointer;
  inset: 0;
  background-color: #ccc;
  transition: .4s;
  border-radius: 24px;
}
.slider:before {
  position: absolute;
  content: "";
  height: 20px;
  width: 20px;
  left: 2px;
  bottom: 2px;
  background-color: white;
  transition: .4s;
  border-radius: 50%;
}
.switch input:checked + .slider {
  background-color: #4CAF50;
}
.switch input:checked + .slider:before {
  transform: translateX(26px);
}
        `;
            document.head.appendChild(style);
        }

        // 2) load saved settings
        let savedListsMap = {};
        if (listsLoaded) {
            myLists.forEach(l => {
                savedListsMap[l.id] = {
                    ids: JSON.parse(JSON.stringify(l.ids)),
                    color: l.color,
                    selected: l.selected,
                    importMode: l.importMode || 'auto'
                };
            });
        }

        // 3) load lists
        collectLists();

        // 3) Modal & Box
        const modal = document.createElement('div');
        modal.id = 'imdb-config-modal';
        modal.style = 'position:fixed;top:0;left:0;width:100%;height:100%;' +
            'background:rgba(0,0,0,0.5);display:flex;' +
            'align-items:center;justify-content:center;';
        const box = document.createElement('div');
        box.style = 'background:#fff;padding:20px;max-height:80%;overflow:auto;' +
            'min-width:600px;';

        // 4) Header
        const header = document.createElement('div');
        header.style = 'display:flex;align-items:center;justify-content:flex-start;margin-bottom:8px;font-weight:bold;';
        const hChk = document.createElement('span');
        hChk.style = 'width:1px;';
        const hLists = document.createElement('span');
        hLists.textContent = 'Select List(s):';
        hLists.style = 'margin-right:80px;';
        const hColor = document.createElement('span');
        hColor.textContent = 'Color - HEX/Name:';
        hColor.style = 'margin-right:40px;';
        const hMode = document.createElement('span');
        hMode.textContent = 'Import Mode:';
        hMode.style = 'margin-right:130px;';
        const hStatus = document.createElement('span');
        hStatus.textContent = 'Status:';
        hStatus.style = 'margin-right:8px;';
        header.append(hChk, hLists, hColor, hMode, hStatus);
        box.appendChild(header);

        // 5) Header-Content
        myLists.forEach((lst, i) => {
            // use saved data
            const sav = savedListsMap[lst.id];
            if (sav) {
                lst.ids = sav.ids;
                lst.color = sav.color;
                lst.selected = sav.selected;
                lst.importMode = sav.importMode;
            }

            // load importMode-Persistence
            const savedModes = JSON.parse(localStorage.getItem('imdbListsImportModes') || '[]');
            const modesMap = savedModes.reduce((m, o) => (m[o.id] = o.importMode, m), {});
            myLists.forEach(lst => {
                if (modesMap[lst.id] != null) {
                    lst.importMode = modesMap[lst.id];
                }
            });
            // —–
            const row = document.createElement('div');
            row.style = 'margin:5px 0; display:flex; align-items:center;';

            // a) Lists checkboxes
            const chk = document.createElement('input');
            chk.type = 'checkbox';
            chk.className = 'list-select';
            chk.style = 'margin-right:8px;';
            chk.checked = lst.selected;
            chk.defaultChecked = lst.selected;
            chk.onchange = e => {
                lst.selected = e.target.checked;
                if (!lst.selected) lst.csvFile = null;
            };

            // b) Lists label
            const lbl = document.createElement('span');
            lbl.textContent = ' ' + lst.name + ' ';
            lbl.style = 'display:inline-block;width:145px;line-height:20px;margin-right:8px;cursor:default;font-weight:normal;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';

            if (Object.keys(lst.ids).length > 0) {
                lbl.style.color = lst.color;
                lbl.style.fontWeight = 'bold';
            }

            // c) Hidden File-Input for CSV import
            const fileInput = document.createElement('input');
            fileInput.type = 'file';
            fileInput.accept = '.csv';
            fileInput.style.display = 'none';
            fileInput.onchange = e => {
                lst.csvFile = e.target.files[0];
                chooseBtn.textContent = 'Ready!';
            };

            // d) CSV-Button
            const chooseBtn = document.createElement('button');
            chooseBtn.type = 'button';
            chooseBtn.textContent = 'Select CSV';
            chooseBtn.style = 'width:90px;height:24px;margin-left:8px;margin-right:15px;background-color:#ffcc00;';
            chooseBtn.addEventListener('click', () => fileInput.click());

            // e) Color-Picker
            const col = document.createElement('input');
            col.type = 'color';

            col.value = nameToHex(lst.color);
            col.style = 'width:35px;height:25px;margin-left:2px;margin-right:20px;';
            col.oninput = e => {
                lst.color = e.target.value;
                txt.value = e.target.value;
                if (Object.keys(lst.ids).length > 0) {
                    lbl.style.color = lst.color;
                    lbl.style.fontWeight = 'bold';
                }
            };

            // f) Text-Input for HEX/String
            const txt = document.createElement('input');
            txt.type = 'text';
            txt.value = lst.color.toLowerCase();
            txt.placeholder = '#Hex or Name';
            txt.style = 'width:100px;margin-left:auto;margin-right:8px;';
            txt.oninput = e => {
                const v = e.target.value.trim().toLowerCase();
                lst.color = v;
                if (/^#([0-9A-Fa-f]{6})$/.test(v)) {
                    col.value = v;
                } else {
                    try { col.value = nameToHex(v); } catch {}
                }
                if (Object.keys(lst.ids).length > 0) {
                    lbl.style.color = lst.color;
                    lbl.style.fontWeight = 'bold';
                }
            };

            // g) Toggle-Switch for all List (not Ratings & Check-ins)
            const switchLabel = document.createElement('label');
            switchLabel.className = 'switch';
            switchLabel.style.marginLeft = '10px';
            const switchInput = document.createElement('input');
            switchInput.type = 'checkbox';
            // Ratings & Check-ins only manual
            if (lst.id === 'ratings' || lst.id === 'checkins') {
                switchInput.checked = false;
            } else {
                switchInput.checked = lst.importMode === 'auto';
            }
            const slider = document.createElement('span');
            slider.className = 'slider';
            switchLabel.append(switchInput, slider);

            const modeText = document.createElement('span');
            modeText.textContent = switchInput.checked ? 'Auto' : 'Manual';
            modeText.style = 'margin-right:8px;';
            modeText.style.display = 'inline-block';
            modeText.style.width = '50px';
            modeText.style.minWidth = '50px';
            modeText.style.maxWidth = '50px';
            modeText.style.overflow = 'hidden';
            modeText.style.whiteSpace = 'nowrap';

            // show CSV-Button on Manual-Mode & for Ratings/Check-ins
            if (lst.id === 'ratings' || lst.id === 'checkins') {
                chooseBtn.style.visibility = 'visible';
            } else {
                chooseBtn.style.visibility = lst.importMode === 'manual' ? 'visible' : 'hidden';
            }

            // Special-Handler for Ratings & Check-ins
            if (lst.id === 'ratings' || lst.id === 'checkins') {
                switchInput.addEventListener('click', e => {
                    e.preventDefault();
                    alert('Only CSV Import possible for Your Ratings-List and Check-Ins-List!');
                    // take care Toggle is only on manual
                    switchInput.checked = false;
                    modeText.textContent = 'Manual';
                });
            } else {
                // Toggle behaviour for all other lists
                switchInput.onchange = () => {
                    lst.importMode = switchInput.checked ? 'auto' : 'manual';
                    modeText.textContent = switchInput.checked ? 'Auto' : 'Manual';
                    modeText.style.display = 'inline-block';
                    modeText.style.width = '50px';
                    modeText.style.minWidth = '50px';
                    modeText.style.maxWidth = '50px';
                    modeText.style.overflow = 'hidden';
                    modeText.style.whiteSpace = 'nowrap';
                    chooseBtn.style.visibility = lst.importMode === 'manual' ? 'visible' : 'hidden';

                };
            }

            // h) Import-Count
            const countInput = document.createElement('input');
            countInput.type = 'text';
            countInput.readOnly = true;
            const count = lst.ids ? Object.keys(lst.ids).length : 0;
            countInput.value = count;
            countInput.dataset.listIdx = i;
            countInput.style = 'width:60px; box-sizing:border-box; text-align:right;';
            const wrapper = document.createElement('div');
            wrapper.style = 'position:relative; width:60px; margin-left:auto; box-sizing:border-box;';
            wrapper.appendChild(countInput);

            row.append(chk, lbl, txt, col, switchLabel, modeText, fileInput, chooseBtn, wrapper);
            box.appendChild(row);

        });

        // 6) Button Start Import, Clear Data, Close, Tooltip
        const imp = document.createElement('button');
        imp.id = 'start-import-btn';
        imp.type = 'button';
        imp.textContent = 'Start Import';
        imp.style = 'margin:10px;';
        imp.onclick = (e) => {
            e.preventDefault();
            // deactivate Buttons Start import, Clear Data during Import
            document.getElementById('start-import-btn').disabled = true;
            document.getElementById('start-import-btn').style.opacity = '0.5';
            document.getElementById('start-import-btn').style.pointerEvents = 'none';
            document.getElementById('clear-data-btn').disabled = true;
            document.getElementById('clear-data-btn').style.opacity = '0.5';
            document.getElementById('clear-data-btn').style.pointerEvents = 'none';

            // --- deactivate Lists checkboxes during Import
            document.querySelectorAll('input.list-select[type="checkbox"]').forEach(cb => {
                cb.disabled = true;
                cb.style.opacity = '0.5';
                cb.style.pointerEvents = 'none';
            });

            // --- deactivate color pocker during Import
            document.querySelectorAll('input[type="color"]').forEach(cp => {
                cp.disabled = true;
                cp.style.opacity = '0.5';
                cp.style.pointerEvents = 'none';
            });

            // --- deactivate toggle switch during Import
            document.querySelectorAll('.switch input[type="checkbox"]').forEach(sw => {
                sw.disabled = true;
                sw.style.opacity = '0.5';
                sw.style.pointerEvents = 'none';
            });

            // --- deactivate fileinput during Import
            document.querySelectorAll('input[type="file"]').forEach(fi => {
                fi.disabled = true;
                fi.style.opacity = '0.5';
                fi.style.pointerEvents = 'none';
            });

            // start Import & show progress circle
            startImport();
        };

        const clr = document.createElement('button');
        clr.textContent = 'Clear Data';
        clr.id = 'clear-data-btn';
        clr.style = 'margin:10px;';
        clr.onclick = () => {
            // deactive Buttons
            document.getElementById('start-import-btn').disabled = true;
            document.getElementById('start-import-btn').style.opacity = '0.5';
            document.getElementById('start-import-btn').style.pointerEvents = 'none';
            document.getElementById('clear-data-btn').disabled = true;
            document.getElementById('clear-data-btn').style.opacity = '0.5';
            document.getElementById('clear-data-btn').style.pointerEvents = 'none';

            eraseData();
            alert('Data cleared');
            document.body.removeChild(modal);
        };

        const cxl = document.createElement('button');
        cxl.textContent = 'Close';
        cxl.style = 'margin:10px;';
        cxl.onclick = () => {
            // 1) UI: unmarkd all lists checkboxes
            document
                .querySelectorAll('#imdb-config-modal input.list-select[type="checkbox"]')
                .forEach(cb => cb.checked = false);

            // 2) reset Lists-Data
            myLists.forEach(lst => {
                lst.selected = false;
                lst.csvFile = null;
            });

            // 3) close config-lists-menu
            if (modal && modal.parentNode) {
                document.body.removeChild(modal);
            }
        };

        const btnRow = document.createElement('div');
        btnRow.style = 'margin:4px 0; display:flex; align-items:center;';

        // Bottom left button "Start Import", "Clear Data", "Close" order
        btnRow.append(imp, clr, cxl);

        // Bottom right Tooltip-Checkbox
        const tooltipRow = document.createElement('div');
        tooltipRow.style = 'display:flex; align-items:center; margin-left:auto;';

        const tooltipChk = document.createElement('input');
        tooltipChk.type = 'checkbox';
        tooltipChk.checked = showTooltips;
        tooltipChk.onchange = e => {
            showTooltips = e.target.checked;
            saveTooltipSetting(e.target.checked);
        };

        const tooltipLbl = document.createElement('label');
        tooltipLbl.textContent = 'Show Tooltips';
        tooltipLbl.style = 'margin-left:6px;';

        tooltipRow.append(tooltipChk, tooltipLbl);
        btnRow.appendChild(tooltipRow);

        box.appendChild(btnRow);
        modal.appendChild(box);
        document.body.appendChild(modal);
    }


    // startImport: CSV import vs. automatic import
    function startImport() {
        if (!document.getElementById('spinStyleInline')) {
            const spinStyle = document.createElement('style');
            spinStyle.id = 'spinStyleInline';
            spinStyle.textContent = `
    @keyframes spin {
      0%   { transform: rotate(0deg); }
      100% { transform: rotate(360deg); }
    }
  `;
            document.head.appendChild(spinStyle);
        }
        const tasks = [];
        for (let i = 0; i < myLists.length; i++) {
            const l = myLists[i];
            if (!l.selected) continue;

            // Ratings & Check-ins: only Manual-Import via CSV
            if (l.id === 'ratings' || l.id === 'checkins') {
                if (l.csvFile) {
                    tasks.push({ type: 'csv', idx: i });
                } else {
                    alert('No CSV-File selected!');
                    // === re-enable UI after alert===
                    ['start-import-btn', 'clear-data-btn'].forEach(id => {
                        const btn = document.getElementById(id);
                        if (btn) {
                            btn.disabled = false;
                            btn.style.opacity = '';
                            btn.style.pointerEvents = '';
                        }
                    });
                    document.querySelectorAll('input.list-select[type="checkbox"]').forEach(cb => {
                        cb.disabled = false;
                        cb.style.opacity = '';
                        cb.style.pointerEvents = '';
                    });
                    document.querySelectorAll('input[type="color"]').forEach(cp => {
                        cp.disabled = false;
                        cp.style.opacity = '';
                        cp.style.pointerEvents = '';
                    });
                    document.querySelectorAll('.switch input[type="checkbox"]').forEach(sw => {
                        sw.disabled = false;
                        sw.style.opacity = '';
                        sw.style.pointerEvents = '';
                    });
                    document.querySelectorAll('input[type="file"]').forEach(fi => {
                        fi.disabled = false;
                        fi.style.opacity = '';
                        fi.style.pointerEvents = '';
                    });
                    return false;
                }
                continue;
            }

            // all other lists
            if (l.importMode === 'manual') {
                if (l.csvFile) {
                    tasks.push({ type: 'csv', idx: i });
                } else {
                    alert('No CSV-File selected!');
                    // === re-enable UI after alert===
                    ['start-import-btn', 'clear-data-btn'].forEach(id => {
                        const btn = document.getElementById(id);
                        if (btn) {
                            btn.disabled = false;
                            btn.style.opacity = '';
                            btn.style.pointerEvents = '';
                        }
                    });
                    document.querySelectorAll('input.list-select[type="checkbox"]').forEach(cb => {
                        cb.disabled = false;
                        cb.style.opacity = '';
                        cb.style.pointerEvents = '';
                    });
                    document.querySelectorAll('input[type="color"]').forEach(cp => {
                        cp.disabled = false;
                        cp.style.opacity = '';
                        cp.style.pointerEvents = '';
                    });
                    document.querySelectorAll('.switch input[type="checkbox"]').forEach(sw => {
                        sw.disabled = false;
                        sw.style.opacity = '';
                        sw.style.pointerEvents = '';
                    });
                    document.querySelectorAll('input[type="file"]').forEach(fi => {
                        fi.disabled = false;
                        fi.style.opacity = '';
                        fi.style.pointerEvents = '';
                    });
                    return false;
                }
            } else {
                tasks.push({ type: 'auto', idx: i });
            }
        }

        if (!tasks.length) {
            alert('No List(s) selected!');
            // === re-enable UI after alert===
            // Buttons
            ['start-import-btn', 'clear-data-btn'].forEach(id => {
                const btn = document.getElementById(id);
                if (btn) {
                    btn.disabled = false;
                    btn.style.opacity = '';
                    btn.style.pointerEvents = '';
                }
            });
            // Lists-Checkboxes
            document.querySelectorAll('input.list-select[type="checkbox"]').forEach(cb => {
                cb.disabled = false;
                cb.style.opacity = '';
                cb.style.pointerEvents = '';
            });
            // Color-Picker
            document.querySelectorAll('input[type="color"]').forEach(cp => {
                cp.disabled = false;
                cp.style.opacity = '';
                cp.style.pointerEvents = '';
            });
            // Toggle-Switches
            document.querySelectorAll('.switch input[type="checkbox"]').forEach(sw => {
                sw.disabled = false;
                sw.style.opacity = '';
                sw.style.pointerEvents = '';
            });
            // CSV-File-Inputs
            document.querySelectorAll('input[type="file"]').forEach(fi => {
                fi.disabled = false;
                fi.style.opacity = '';
                fi.style.pointerEvents = '';
            });
            return false;

        }

        // === clear all old data bevor import list
        tasks.forEach(({ idx }) => {
            myLists[idx].ids = {};
            updateListProgress(idx, 0);
        });

        // === progress circle
        tasks.forEach(({ idx }) => {
            const input = document.querySelector(`input[data-list-idx="${idx}"]`);
            if (input) {
                input.value = '';
                const wrapper = input.parentNode;

                // create circle
                const spinner = document.createElement('div');
                spinner.className = 'item-spinner-inline';
                spinner.dataset.listIdx = idx;
                spinner.style = [
                    'position:absolute',
                    'top:0',
                    'bottom:0',
                    'left:0',
                    'right:0',
                    'margin:auto',
                    'pointer-events:none',
                    'width:16px',
                    'height:16px',
                    'border:4px solid #ccc',
                    'border-top:4px solid #3498db',
                    'border-radius:50%',
                    'animation:spin 1s linear infinite'
                ].join(';');

                wrapper.appendChild(spinner);
            }
        });

        let rem = tasks.length;
        tasks.forEach(({ type, idx }) => {
            if (type === 'csv') {
                importCsv(idx, () => {
                    updateListProgress(idx, Object.keys(myLists[idx].ids).length);
                    if (--rem === 0) {
                        myLists.forEach(l => l.selected = false);
                        saveLists();
                        finishProgress();
                    }
                });
            } else {
                downloadList(idx, () => {
                    const cnt = Object.keys(myLists[idx].ids).length;
                    updateListProgress(idx, cnt);
                    if (--rem === 0) {
                        myLists.forEach(l => l.selected = false);
                        saveLists();
                        finishProgress();
                    }
                });
            }
        });
        return true;
    }


    function importCsv(idx, cb) {
        const lst = myLists[idx];
        lst.ids = {};
        lst.names = {};

        const reader = new FileReader();
        reader.onload = e => {
            const lines = e.target.result.split(/\r?\n/);

            for (const line of lines) {
                if (!line.trim()) continue;

                let m;

                // 1a) extrac movies-IDs (tt1234567)
                m = line.match(/tt(\d+)/i);
                if (m) {
                    const code36 = parseInt(m[1], 10).toString(36);
                    lst.ids[code36] = {};
                }

                // 1b) extrac people-IDs (nm1234567) + Name
                m = line.match(/nm(\d+).*?,\s*"([^"]+)"/i);
                if (m) {
                    const code36 = parseInt(m[1], 10).toString(36);
                    const personName = m[2].trim();

                    lst.ids[code36] = {};
                    lst.names[personName] = code36;

                    // put in localStorage.namesToColor
                    let ntc = {};
                    try {
                        ntc = JSON.parse(localStorage.getItem('namesToColor') || '{}');
                    } catch (_) {}
                    ntc[personName] = lst.color;
                    localStorage.setItem('namesToColor', JSON.stringify(ntc));
                }
            }

            saveLists();

            try {
                namesToColor = JSON.parse(localStorage.getItem('namesToColor') || '{}');
            } catch (_) {}

            cb();
        };

        reader.onerror = () => {
            console.error("CSV-Import-Error:", reader.error);
            cb();
        };

        reader.readAsText(lst.csvFile);
    }


    function downloadList(idx, cb) {
        const lst = myLists[idx];
        lst.ids = lst.ids || {};
        lst.names = lst.names || {};
        // Watchlist-Auto-import
        if (lst.id === 'watchlist') {
            // Basis-URL + language regex
            const BASE = `${window.location.origin}${countryPath}/user/${getUserId()}/watchlist/`;
            let page = 1,
                seen = new Set();
            (async function fetchPage() {
                const iframe = document.createElement('iframe');
                iframe.style.display = 'none';
                // Detail-View + Pageing
                iframe.src = `${BASE}?view=detail&page=${page}`;
                document.body.appendChild(iframe);

                await new Promise(r => iframe.onload = r);
                await new Promise(r => setTimeout(r, 2000)); // wait to load all entries

                const doc = iframe.contentDocument;
                const sel1 = Array.from(doc.querySelectorAll('a.ipc-title-link-wrapper[href*="/title/tt"]'));
                const sel2 = Array.from(doc.querySelectorAll('.lister-item-header a[href*="/title/tt"]'));
                const anchors = sel1.length ? sel1 : sel2;
                let newFound = false;
                anchors.forEach(a => {
                    const m = a.href.match(/tt(\d+)\//);
                    if (!m) return;
                    const code36 = parseInt(m[1], 10).toString(36);
                    if (!seen.has(code36)) {
                        seen.add(code36);
                        lst.ids[code36] = {};
                        newFound = true;
                    }
                });

                document.body.removeChild(iframe);
                // load next page if at least one new item found
                if (newFound) {
                    page++;
                    fetchPage();
                } else {
                    cb();
                }
            })();
            return;
        }

        // Custom-lists-Auto-import
        (async () => {
            const base = `https://www.imdb.com/list/${lst.id}/?mode=detail`;
            let page = 1;
            let isPeople = null;

            while (true) {
                const resp = await fetch(`${base}&page=${page}`, { credentials: 'same-origin' });
                const html = await resp.text();
                const d = new DOMParser().parseFromString(html, 'text/html');
                const sc = d.querySelector('script[type="application/ld+json"]');
                if (!sc) break;

                let data;
                try { data = JSON.parse(sc.textContent); } catch (err) { break; }

                if (page === 1) {
                    const first = data.itemListElement[0];
                    isPeople = (first['@type'] === 'Person') ||
                        (first.item && first.item['@type'] === 'Person');
                }

                const items = data.itemListElement || [];
                if (!items.length) break;

                items.forEach(e => {
                    const l = e.url || e['@id'] || (e.item && (e.item.url || e.item['@id'])) || '';
                    const re = isPeople ? /name\/nm(\d+)\// : /tt(\d+)\//;
                    const m = l.match(re);
                    if (!m) return;
                    const code36 = parseInt(m[1], 10).toString(36);
                    lst.ids[code36] = {};
                    if (e.item && e.item.name) {
                        const name = e.item.name.trim();
                        lst.names[name] = code36;
                        let namesToColor = {};
                        try {
                            namesToColor = JSON.parse(localStorage.getItem('namesToColor') || '{}');
                        } catch (e) {}
                        namesToColor[name] = lst.color;
                        localStorage.setItem('namesToColor', JSON.stringify(namesToColor));
                    }
                });

                if (items.length < 250) break;
                page++;
            }

            cb();
        })();
    }


    function updateListProgress(listIdx, count) {
        const spinner = document.querySelector(`.item-spinner-inline[data-list-idx="${listIdx}"]`);
        if (spinner) {
            spinner.remove();
        } else {}

        const countInput = document.querySelector(`input[data-list-idx="${listIdx}"]`);
        if (countInput) {
            countInput.value = count;
        } else {
            console.error(`Count-Input für listIdx=${listIdx} nicht gefunden!`);
        }
    }


    function finishProgress() {
        // 1) close Progress-circle
        if (progressModal && progressModal.parentNode) {
            document.body.removeChild(progressModal);
        }

        // 2) UI: unmark all Lists-Checkboxes
        const cfg = document.getElementById('imdb-config-modal');
        if (cfg) {
            cfg
                .querySelectorAll('input.list-select[type="checkbox"]')
                .forEach(cb => cb.checked = false);
            // 3) UI: default CSV-Button
            cfg.querySelectorAll('input[type="file"]').forEach(fileInput => {
                fileInput.value = null;
                const chooseBtn = fileInput.nextSibling;
                if (chooseBtn && chooseBtn.tagName === 'BUTTON') {
                    chooseBtn.textContent = 'Select CSV';
                }

            });
        }

        // 4) reset-lists
        myLists.forEach(lst => {
            lst.selected = false;
            lst.csvFile = null;
        });

        // 5) activate Button "Start import", "Clear Data"
        ['start-import-btn', 'clear-data-btn'].forEach(id => {
            const btn = document.getElementById(id);
            if (btn) {
                btn.disabled = false;
                btn.style.opacity = '';
                btn.style.pointerEvents = '';
            }
        });
        // 6) unmark all Lists-Checkboxes
        document.querySelectorAll('input.list-select[type="checkbox"]').forEach(cb => {
            cb.checked = false;
            cb.disabled = false;
            cb.style.opacity = '';
            cb.style.pointerEvents = '';
        });

        // 7) activate Color-Picke<<<<<r
        document.querySelectorAll('input[type="color"]').forEach(cp => {
            cp.disabled = false;
            cp.style.opacity = '';
            cp.style.pointerEvents = '';
        });

        // 8) activate Toggle-Switch
        document.querySelectorAll('.switch input[type="checkbox"]').forEach(sw => {
            sw.disabled = false;
            sw.style.opacity = '';
            sw.style.pointerEvents = '';
        });

        // 9) activate File-Inputs
        document.querySelectorAll('input[type="file"]').forEach(fi => {
            fi.disabled = false;
            fi.style.opacity = '';
            fi.style.pointerEvents = '';
        });
        // 10) save importMode
        const modesToSave = myLists.map(lst => ({
            id: lst.id,
            importMode: lst.importMode
        }));
        localStorage.setItem(
            'imdbListsImportModes',
            JSON.stringify(modesToSave)
        );
        alert('Import Done!');
    }


    function eraseData() {
        localStorage.removeItem('myMovies-' + user);
        localStorage.removeItem('namesToColor');
        localStorage.removeItem('imdbListsImportModes');
        namesToColor = {};
        window.location.reload();
    }

    function saveLists() {
        localStorage.setItem('myMovies-' + user, JSON.stringify({
            myLists,
            listOrder
        }));
    }

    function loadLists() {
        const d = localStorage.getItem('myMovies-' + user);
        if (!d) return false;
        const o = JSON.parse(d);
        myLists = o.myLists;
        listOrder = o.listOrder;
        return true;
    }

    function highlightTitle() {
        let m = location.href.match(/tt(\d+)\//);
        if (m) {
            const c = movieColor(parseInt(m[1], 10).toString(36));
            if (c) document.querySelectorAll('h1').forEach(h => h.style.color = c);
        }
        m = location.href.match(/name\/nm(\d+)\//);
        if (m) {
            const c = movieColor(parseInt(m[1], 10).toString(36));
            if (c) document.querySelectorAll('h1').forEach(h => h.style.color = c);
        }
    }

    function highlightLinks() {
        document.querySelectorAll('a[href*="/title/tt"]').forEach(a => {
            const m = a.href.match(
                /^https?:\/\/(?:www\.)?imdb\.com\/(?:[a-z]{2}(?:-[a-z]{2})?\/)?title\/tt(\d+)\/\?ref_=[^/]+/i
            );
            if (!m) return;
            const code36 = parseInt(m[1], 10).toString(36);
            const c = movieColor(code36);
            if (c) {
                a.style.color = c;
                a.style.fontWeight = 'bold';
            }
        });
        const peopleLinkRe = /^https:\/\/www\.imdb\.com\/(?:[a-z]{2}(?:-[a-z]{2})?\/)?name\/nm(\d+)\/\?ref_=[^&#]+$/i;

        document.querySelectorAll('a[href]').forEach(a => {
            const href = a.href;
            const m = peopleLinkRe.exec(href);
            if (!m) return;
            const code36 = parseInt(m[1], 10).toString(36);
            const c = movieColor(code36);
            if (c) {
                a.style.color = c;
                a.style.fontWeight = 'bold';
            }
        });

        document
            .querySelectorAll('li[id^="react-autowhatever-navSuggestionSearch"]')
            .forEach(li => {
                let link = li.querySelector('a[href*="/title/tt"]');
                if (!link) link = li.querySelector('[data-testid="search-result--link"]');
                if (!link || !link.href) return;
                const m = link.href.match(
                    /^https?:\/\/(?:www\.)?imdb\.com\/title\/tt(\d+)\/\?ref_=[^/]+/i
                );
                if (!m) return;
                const code36 = parseInt(m[1], 10).toString(36);
                const c = movieColor(code36);
                if (!c) return;
                const titleSpan =
                    li.querySelector('.searchResult__constTitle') ||
                    li.querySelector('span');
                if (titleSpan) {
                    titleSpan.style.color = c;
                    titleSpan.style.fontWeight = 'bold';
                }
            });
        document
            .querySelectorAll('li[id^="react-autowhatever-navSuggestionSearch"]')
            .forEach(li => {
                let link = li.querySelector('a[href*="/name/nm"]');
                if (!link) link = li.querySelector('[data-testid="search-result--link"]');
                if (!link || !link.href) return;
                const m = link.href.match(
                    /^https?:\/\/(?:www\.)?imdb\.com\/name\/nm(\d+)\/\?ref_=[^/]+/i
                );
                if (!m) return;
                const code36 = parseInt(m[1], 10).toString(36);
                const c = movieColor(code36);
                if (!c) return;
                const nameSpan =
                    li.querySelector('.searchResult__actorName') ||
                    li.querySelector('.searchResult__constTitle') ||
                    li.querySelector('span');
                if (nameSpan) {
                    nameSpan.style.color = c;
                    nameSpan.style.fontWeight = 'bold';
                }
            });
        if (/^\/([a-z]{2}(-[a-z]{2})?)?\/?$/i.test(location.pathname)) {
            document.querySelectorAll('a[href*="/name/nm"]').forEach(link => {
                const match = link.href.match(/\/name\/nm(\d+)\//);
                if (match) {
                    const code36 = parseInt(match[1], 10).toString(36);
                    const color = movieColor(code36);
                    if (color) {
                        link.style.color = color;
                        link.style.fontWeight = 'bold';
                        if (link.parentElement) {
                            link.parentElement.style.color = color;
                            link.parentElement.style.fontWeight = 'bold';
                        }
                    }
                }
            });
            document.querySelectorAll('div').forEach(div => {
                const name = div.textContent.trim();
                if (!/^[A-Za-zÄÖÜäöüß\-'. ]{2,}$/.test(name)) return;
                if (namesToColor[name]) {
                    div.style.color = namesToColor[name];
                    div.style.fontWeight = 'bold';
                }
            });
        }
        // === Tooltip-Listener
        if (showTooltips) {
            document
                .querySelectorAll('a[href*="/title/tt"], a[href*="/name/nm"]')
                .forEach(a => {
                    const m = a.href.match(/tt(\d+)\//) || a.href.match(/name\/nm(\d+)\//);
                    if (!m) return;
                    const code36 = parseInt(m[1], 10).toString(36);
                    if (!movieColor(code36)) return;

                    if (!a.dataset.tooltipListener) {
                        a.dataset.tooltipListener = '1';
                        a.addEventListener('mouseenter', () => showTooltip(a, code36));
                        a.addEventListener('mouseleave', hideTooltip);
                    }

                });

        }
    }

    function highlightCalendarPeople() {
        document
            .querySelectorAll('.ipc-inline-list__item span.ipc-metadata-list-summary-item__li')
            .forEach(span => {
                const name = span.textContent.trim();
                const color = namesToColor[name];
                if (color) {
                    span.style.color = color;
                    span.style.fontWeight = 'bold';
                }
            });
    }

    function movieColor(code) {
        for (const i of listOrder)
            if (myLists[i].ids[code]) return myLists[i].color;
        return '';
    }

    function nameToHex(name) {
        const ctx = document.createElement('canvas').getContext('2d');
        ctx.fillStyle = name;
        return ctx.fillStyle;
    }

    // === Tooltip-function ===
    let currentTooltip = null;

    function showTooltip(elem, code36) {
        const lists = myLists
            .filter(l => l.ids[code36])
            .map(l => l.name);
        if (!lists.length) return;

        const tip = document.createElement('div');
        tip.className = 'imdb-tooltip';
        tip.innerHTML = '<strong>In List(s):</strong><br>' +
            lists.map(l => `&bull; ${l}`).join('<br>');
        document.body.appendChild(tip);

        const rect = elem.getBoundingClientRect();
        tip.style.top = (window.scrollY + rect.bottom + 4) + 'px';
        tip.style.left = (window.scrollX + rect.left) + 'px';

        currentTooltip = tip;
    }

    function hideTooltip() {
        if (currentTooltip) {
            document.body.removeChild(currentTooltip);
            currentTooltip = null;
        }
    }

})();