Nexus Web Mods Collection

Create Web collections of mods, share them via URL, and view them Groups by game!

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         Nexus Web Mods Collection
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Create Web collections of mods, share them via URL, and view them Groups by game!
// @author       6969RandomGuy6969
// @match        https://www.nexusmods.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=nexusmods.com
// @grant        GM_setClipboard
// @grant        GM_xmlhttpRequest
// @connect      is.gd
// @run-at       document-start
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    const STORAGE_KEY = 'nx_saved_collection';
    const PARAMS = new URLSearchParams(window.location.search);
    const IS_WICKED_TAB = PARAMS.get('collection_view') === 'true';

    // Core Helpers
    const parseUrl = u => { const m = (u||'').match(/nexusmods\.com\/([^\/]+)\/mods\/(\d+)/i); return m ? { game: m[1], modId: m[2] } : null; };
    const store = {
        get: () => { try { return JSON.parse(localStorage[STORAGE_KEY] || '[]') } catch { return [] } },
        set: d => { try { localStorage[STORAGE_KEY] = JSON.stringify(d); return 1 } catch { return 0 } },
        clr: () => delete localStorage[STORAGE_KEY]
    };
    const share = {
        encode: v => btoa(unescape(encodeURIComponent(JSON.stringify(v.map(m => [m.game, m.modId, m.title, (m.thumb||'').replace('https://','').replace('www.nexusmods.com/','NX_').replace('staticdelivery.nexusmods.com/','SD_'), m.author]))))),
        decode: c => {
            try {
                return JSON.parse(decodeURIComponent(escape(atob(c)))).map(m => ({ game: m[0], gameTitle: fmtGame(m[0]), modId: m[1], url: `https://www.nexusmods.com/${m[0]}/mods/${m[1]}`, title: m[2], thumb: m[3] ? 'https://' + m[3].replace('NX_','www.nexusmods.com/').replace('SD_','staticdelivery.nexusmods.com/') : 'https://www.nexusmods.com/assets/images/default/placeholder.png', author: m[4] }));
            } catch { return null; }
        },
        getUrl: c => `${location.origin}/?collection_view=true&share=${encodeURIComponent(c)}&t=${Date.now()}`
    };

    // Formatting Game Names (Deduplicated globally)
    const fmtGame = n => {
        const d = { "marvelsspiderman2":"Marvel's Spider-Man 2", "readyornot":"Ready or Not", "cyberpunk2077":"Cyberpunk 2077", "fallout4":"Fallout 4", "stardewvalley":"Stardew Valley", "skyrimspecialedition":"Skyrim Special Edition", "baldursgate3":"Baldur's Gate 3", "witcher3":"The Witcher 3", "palworld":"Palworld", "lethalcompany":"Lethal Company", "helldivers2":"Helldivers 2" };
        let f = d[(n||'').toLowerCase()];
        if (!f) f = (n||'').replace(/([a-z])([A-Z0-9])/g,'$1 $2').replace(/([0-9])([a-zA-Z])/g,'$1 $2').replace(/_/g,' ').replace(/\b\w/g,s=>s.toUpperCase());
        return f ? f.trim() : n;
    };

    let state = { sel: store.get(), vis: false };

    const showToast = (m, err=false) => {
        let t = document.getElementById('nx-toast');
        if(!t){ t = document.createElement('div'); t.id = 'nx-toast'; t.style.cssText = 'position:fixed;bottom:30px;left:50%;transform:translateX(-50%) translateY(100px);background:#222;color:#fff;padding:12px 24px;border-radius:30px;font-size:14px;font-weight:600;z-index:2147483647;box-shadow:0 10px 30px rgba(0,0,0,.5);border:1px solid rgba(255,255,255,.1);transition:transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.4s;opacity:0;pointer-events:none;'; document.body.appendChild(t); }
        t.style.borderLeft = err ? '4px solid #dc3545' : '4px solid #da8e35'; t.textContent = m;
        t.style.opacity = '1'; t.style.transform = 'translateX(-50%) translateY(0)';
        clearTimeout(t.tmr); t.tmr = setTimeout(() => { t.style.opacity = '0'; t.style.transform = 'translateX(-50%) translateY(100px)'; }, 3000);
    };

    const showConfirm = (m, cb) => {
        let c = document.getElementById('nx-confirm');
        if(!c){ c = document.createElement('div'); c.id = 'nx-confirm'; c.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.8);backdrop-filter:blur(5px);z-index:2147483647;display:none;align-items:center;justify-content:center;padding:20px'; c.innerHTML = `<div style="background:#1f1f1f;border:1px solid rgba(255,255,255,.1);padding:30px;border-radius:12px;width:100%;max-width:400px;text-align:center;box-shadow:0 24px 64px rgba(0,0,0,.5)"><h3 style="margin-bottom:24px;color:#fff;font-size:18px;font-weight:600" id="nx-c-msg"></h3><div style="display:flex;gap:12px;justify-content:center"><button id="nx-c-no" style="flex:1;padding:12px;border-radius:8px;cursor:pointer;font-weight:600;border:none;background:#444;color:#fff;transition:0.2s">Cancel</button><button id="nx-c-yes" style="flex:1;padding:12px;border-radius:8px;cursor:pointer;font-weight:600;border:none;background:#dc3545;color:#fff;transition:0.2s">Remove</button></div></div>`; document.body.appendChild(c);
            c.querySelector('#nx-c-no').onmouseover = function(){ this.style.background='#555'; }; c.querySelector('#nx-c-no').onmouseout = function(){ this.style.background='#444'; };
            c.querySelector('#nx-c-yes').onmouseover = function(){ this.style.background='#c82333'; }; c.querySelector('#nx-c-yes').onmouseout = function(){ this.style.background='#dc3545'; };
        }
        c.querySelector('#nx-c-msg').textContent = m; c.style.display = 'flex';
        const hide = () => c.style.display = 'none';
        c.onclick = e => { if (e.target === c) hide(); };
        c.querySelector('#nx-c-no').onclick = hide;
        c.querySelector('#nx-c-yes').onclick = () => { hide(); cb(); };
    };

    // Find mod index (Deduplicated safe matching)
    const getModIdx = u => {
        const pu = parseUrl(u);
        return pu ? state.sel.findIndex(v => { const p = parseUrl(v.url); return p && p.modId===pu.modId && p.game===pu.game; }) : state.sel.findIndex(v => v.url === u);
    };

    // Toggle Mod function (Deduplicated core logic)
    const toggleMod = (url, title, thumb, author, parsed) => {
        const idx = getModIdx(url);
        if (idx > -1) state.sel.splice(idx, 1);
        else state.sel.push({ url, title, thumb, author, game: parsed.game, gameTitle: fmtGame(parsed.game), modId: parsed.modId });
        store.set(state.sel);
        return idx === -1;
    };

    // --- WICKED TAB GRID VIEWER ---
    if (IS_WICKED_TAB) {
        const s = document.createElement('style');
        s.textContent = `
            html { background: #2a2a2a !important; } body > *:not(#nx-wicked-app):not(#share-modal):not(#nx-confirm):not(#nx-toast):not(style):not(script) { display: none !important; opacity: 0 !important; }
            #nx-wicked-app { animation: nxFadeIn 0.35s cubic-bezier(0,0,0.2,1) forwards; } @keyframes nxFadeIn { 0% { opacity: 0; transform: scale(0.98); } 100% { opacity: 1; transform: scale(1); } }
            *{margin:0;padding:0;box-sizing:border-box} body{background:#2a2a2a!important;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,sans-serif;overflow-y:auto;color:#e0e0e0;font-size:14px;line-height:1.6}
            ::-webkit-scrollbar{width:8px} ::-webkit-scrollbar-track{background:#1a1a1a} ::-webkit-scrollbar-thumb{background:#444;border-radius:4px} ::-webkit-scrollbar-thumb:hover{background:#666}
            #grid-header{background:rgba(25,25,25,.95);backdrop-filter:blur(10px);border-bottom:1px solid rgba(255,255,255,.05);padding:16px 24px;display:flex;align-items:center;box-shadow:0 4px 20px rgba(0,0,0,.3);position:relative;z-index:100;flex-shrink:0;}
            .grid-logo{height:32px;margin-right:16px} #grid-header h2{font-size:20px;font-weight:600;display:flex;align-items:center;gap:12px;margin:0;letter-spacing:.5px;color:#fff}
            .item-count{font-size:13px;font-weight:400;color:#da8e35;background:rgba(218,142,53,.15);padding:4px 10px;border-radius:20px}
            .grid-header-actions{margin-left:auto;display:flex;gap:8px} .grid-header-btn{background:rgba(255,255,255,.05);color:rgba(255,255,255,.8);border:1px solid rgba(255,255,255,.1);padding:8px 16px;border-radius:8px;cursor:pointer;font-size:13px;font-weight:500;transition:all .2s;outline:none;}
            .grid-header-btn:hover{background:rgba(255,255,255,.1);color:#fff} #share-tab-btn{border-color:rgba(218,142,53,.3);color:rgba(218,142,53,.9)} #share-tab-btn:hover{background:rgba(218,142,53,.15);border-color:rgba(218,142,53,.6);color:#da8e35}
            .game-section{padding:24px 32px} .game-title{font-size:24px;font-weight:300;color:#fff;margin-bottom:16px;padding-bottom:8px;border-bottom:2px solid rgba(255,255,255,.05);text-transform:capitalize;display:flex;align-items:center;gap:12px;}
            .game-title .game-badge{font-size:12px;background:#444;color:#fff;padding:4px 8px;border-radius:4px;font-weight:600;}
            #grid-viewer-container{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:20px;}
            .mod-grid-item{background:#1f1f1f;border:1px solid rgba(255,255,255,.05);border-radius:12px;overflow:hidden;transition:transform .2s,box-shadow .2s;position:relative;display:flex;flex-direction:column;}
            .mod-grid-item:hover{transform:translateY(-4px);box-shadow:0 8px 24px rgba(0,0,0,.4);border-color:rgba(218,142,53,.4);}
            .mod-thumb-wrap{position:relative;width:100%;aspect-ratio:16/9;background:#111;overflow:hidden;cursor:pointer;} .mod-thumb{width:100%;height:100%;object-fit:cover;display:block;transition:transform .4s ease;}
            .mod-grid-item:hover .mod-thumb{transform:scale(1.05)} .mod-action-btns{position:absolute;top:10px;right:10px;display:flex;gap:6px;opacity:0;transition:opacity .2s;z-index:4;} .mod-grid-item:hover .mod-action-btns{opacity:1}
            .mod-action-btn{background:rgba(0,0,0,.7);backdrop-filter:blur(4px);color:#fff;border:1px solid rgba(255,255,255,.2);padding:6px 12px;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600;transition:all .2s;}
            .mod-action-btn.remove:hover{background:rgba(220,53,69,.8);border-color:#dc3545} .mod-info{padding:16px;flex:1;display:flex;flex-direction:column}
            .mod-title{font-size:15px;font-weight:600;color:#fff;text-decoration:none;line-height:1.4;margin-bottom:8px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;} .mod-title:hover{color:#da8e35} .mod-author{font-size:12px;color:rgba(255,255,255,.5);margin-top:auto;}
            #share-modal{position:fixed;inset:0;background:rgba(0,0,0,.8);backdrop-filter:blur(5px);z-index:10000;display:none;align-items:center;justify-content:center;padding:20px} #share-modal.visible{display:flex}
            .share-content{background:#1f1f1f;border:1px solid rgba(255,255,255,.1);padding:30px;border-radius:12px;width:100%;max-width:600px;box-shadow:0 24px 64px rgba(0,0,0,.5)}
            .share-content h3{margin-bottom:16px;color:#fff;font-size:18px;font-weight:600} .share-content textarea{width:100%;background:#111;color:#da8e35;border:1px solid rgba(255,255,255,.1);padding:12px;font-family:monospace;font-size:12px;border-radius:8px;resize:vertical;min-height:100px;margin-bottom:16px;outline:none;word-break:break-all}
            .share-content textarea:focus{border-color:rgba(218,142,53,.5)} .share-btns{display:flex;gap:12px} .share-btns button{flex:1;padding:12px;border-radius:8px;cursor:pointer;font-size:14px;font-weight:600;border:none;transition:all .2s;outline:none;color:#fff}
            #copy-url-btn{background:#da8e35} #copy-url-btn:hover{background:#bf7a29} #close-share-btn{background:#444} #close-share-btn:hover{background:#555}
        `;
        (document.head || document.documentElement).appendChild(s);

        const buildGrid = () => {
            window.stop();
            const sc = PARAMS.get('share');
            let mods = sc ? share.decode(sc) || store.get() : store.get();
            if (sc && !store.get().length && mods.length) store.set(mods);

            document.title = "Mod Collection - Nexus Mods";
            const app = document.createElement('div');
            app.id = 'nx-wicked-app';
            app.style.cssText = 'position:fixed;inset:0;width:100vw;height:100vh;z-index:2147483647;background:#2a2a2a;display:flex;flex-direction:column;overflow:hidden;';

            if (!mods.length) {
                app.style.overflowY = 'auto';
                app.innerHTML = '<div style="padding:100px 20px;text-align:center;color:#fff;max-width:500px;margin:0 auto"><div style="font-size:60px;margin-bottom:20px">📦</div><h2 style="font-size:24px;font-weight:600;margin-bottom:12px">Your collection is empty</h2><p style="font-size:15px;color:rgba(255,255,255,.5);margin-bottom:30px;line-height:1.6">Go back to Nexus Mods and click "Add to Web Collection" on any mod page to build your selection.</p><button id="close-empty" style="padding:12px 28px;background:#da8e35;color:#fff;border:none;cursor:pointer;border-radius:8px;font-size:15px;font-weight:600">Close Tab</button></div>';
                const ef = () => { if(document.body && !document.getElementById('nx-wicked-app')){ document.body.innerHTML=''; document.body.appendChild(app); document.body.style.overflow='hidden'; } };
                ef(); setInterval(ef, 100);
                return app.querySelector('#close-empty').onclick = () => window.close();
            }

            app.innerHTML = `
                <div id="grid-header"><img src="https://next.nexusmods.com/assets/images/default/logo.svg" alt="Nexus Mods" class="grid-logo"><h2>Web Collection <span class="item-count">${mods.length} mods</span></h2>
                <div class="grid-header-actions"><button class="grid-header-btn" id="share-tab-btn">Share Collection</button><button class="grid-header-btn" id="close-grid-btn">Close</button></div></div>
                <div id="wrapper" style="flex:1;overflow-y:auto;position:relative;padding-bottom:50px;"></div>`;

            app.querySelector('#close-grid-btn').onclick = () => window.close();
            app.querySelector('#share-tab-btn').onclick = () => {
                const target = document.getElementById('share-code');
                const btn = document.getElementById('share-tab-btn');
                if (!target) return;
                const longU = share.getUrl(share.encode(store.get()));

                if (typeof GM_xmlhttpRequest !== 'undefined') {
                    btn.textContent = 'Generating...'; btn.style.opacity = '0.5'; btn.style.pointerEvents = 'none';
                    GM_xmlhttpRequest({
                        method: "GET", url: `https://is.gd/create.php?format=simple&url=${encodeURIComponent(longU)}`,
                        onload: r => {
                            btn.textContent = 'Share Collection'; btn.style.opacity = '1'; btn.style.pointerEvents = 'auto';
                            target.value = (r.status === 200 && r.responseText.includes('is.gd')) ? r.responseText.trim() : longU;
                            document.getElementById('share-modal').classList.add('visible');
                        },
                        onerror: () => {
                            btn.textContent = 'Share Collection'; btn.style.opacity = '1'; btn.style.pointerEvents = 'auto';
                            target.value = longU; document.getElementById('share-modal').classList.add('visible');
                        }
                    });
                } else {
                    target.value = longU;
                    document.getElementById('share-modal').classList.add('visible');
                }
            };

            const m = document.createElement('div'); m.id = 'share-modal';
            m.innerHTML = `<div class="share-content"><h3>Share Your Mod Collection</h3><textarea id="share-code" readonly></textarea><div class="share-btns"><button id="copy-url-btn">Copy Link</button><button id="close-share-btn">Close</button></div></div>`;
            app.appendChild(m);
            m.onclick = e => { if (e.target === m) m.classList.remove('visible'); };
            m.querySelector('#close-share-btn').onclick = () => m.classList.remove('visible');
            m.querySelector('#copy-url-btn').onclick = () => {
                try { GM_setClipboard ? GM_setClipboard(navigator.clipboard.writeText(document.getElementById('share-code').value)) : navigator.clipboard.writeText(document.getElementById('share-code').value); } catch { document.getElementById('share-code').select(); document.execCommand('copy'); }
                showToast('Collection link copied to clipboard!');
            };

            const wrap = app.querySelector('#wrapper');
            const grp = mods.reduce((a, m) => { (a[m.game || 'unknown'] = a[m.game || 'unknown'] || []).push(m); return a; }, {});

            Object.keys(grp).sort().forEach(gk => {
                const sec = document.createElement('div'); sec.className = 'game-section';
                const gTitle = document.createElement('h2'); gTitle.className = 'game-title';
                gTitle.innerHTML = `${grp[gk][0].gameTitle || fmtGame(gk)} <span class="game-badge">${grp[gk].length} Mods</span>`;
                const grid = document.createElement('div'); grid.id = 'grid-viewer-container';

                grp[gk].forEach(mod => {
                    const i = document.createElement('div'); i.className = 'mod-grid-item';
                    i.innerHTML = `<div class="mod-thumb-wrap"><img class="mod-thumb" src="${mod.thumb||'https://www.nexusmods.com/assets/images/default/placeholder.png'}" onerror="this.src='https://www.nexusmods.com/assets/images/default/placeholder.png'"><div class="mod-action-btns"><button class="mod-action-btn remove">Remove</button></div></div><div class="mod-info"><a href="${mod.url}" target="_blank" class="mod-title">${mod.title}</a>${mod.author && mod.author !== 'Unknown' ? `<div class="mod-author">by ${mod.author}</div>`:''}</div>`;
                    i.querySelector('.remove').onclick = e => {
                        e.stopPropagation();
                        showConfirm('Remove this mod from web collection?', () => { store.set(store.get().filter(x=>x.url!==mod.url)); location.reload(); });
                    };
                    i.querySelector('.mod-thumb-wrap').onclick = () => window.open(mod.url, '_blank');
                    grid.appendChild(i);
                });
                sec.append(gTitle, grid); wrap.appendChild(sec);
            });

            const ef = () => { if (document.body && !document.getElementById('nx-wicked-app')) { document.body.innerHTML=''; document.body.style.overflow='hidden'; document.body.appendChild(app); } };
            ef(); setInterval(ef, 100);
        };

        return document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', buildGrid) : buildGrid();
    }


    // --- NEXUS MODS INTEGRATION ---
    const initNexus = () => {
        if (document.getElementById('nx-control-panel')) return;

        document.head.insertAdjacentHTML('beforeend', `<style>.nx-header-count{display:inline-block;} .nx-toggle-badge{position:absolute;top:-5px;right:-10px;background:#da8e35;color:#fff;padding:4px 8px;border-radius:12px;font-size:11px;font-weight:700;min-width:20px;text-align:center;box-shadow:0 2px 8px rgba(218,142,53,.4);cursor:pointer;z-index:1000;display:none} .nx-toggle-badge:hover{transform:scale(1.1)} #nx-btn-open:hover{background:#bf7a29!important} #nx-btn-clear:hover{background:rgba(255,255,255,0.05)!important}</style>`);

        const toggle = () => {
            state.vis = !state.vis;
            ['nx-control-panel', 'nx-control-backdrop'].forEach(id => {
                const el = document.getElementById(id);
                if(el){ el.style.visibility = state.vis?'visible':'hidden'; el.style.opacity = state.vis?'1':'0'; if(id==='nx-control-panel') el.style.transform = state.vis?'translateY(0)':'translateY(-10px)'; }
            });
            document.body.classList.toggle('nx-active', state.vis);
        };

        const updBadge = () => {
            const c = state.sel.length;
            document.querySelectorAll('.nx-header-count').forEach(el => { el.textContent = c; el.style.display = c > 0 ? 'inline-block' : 'none'; });
            const b = document.querySelector('.nx-toggle-badge'); if (b) { b.textContent = c; b.style.display = c > 0 ? 'block' : 'none'; }
            const pc = document.getElementById('nx-count'); if (pc) pc.textContent = c;
        };

        const checkHeader = () => {
            if (document.getElementById('nx-header-btn') || document.querySelector('.nx-toggle-badge')) return;
            const nav = document.querySelector('[data-e2eid="desktop-header"] .flex.h-full');
            if (nav) {
                const btn = document.createElement('div'); btn.id = 'nx-header-btn'; btn.className = 'flex h-full'; btn.setAttribute('data-headlessui-state', '');
                btn.innerHTML = `<button class="flex h-full items-center justify-center gap-x-1 px-2 cursor-pointer lg:px-3 hover:bg-surface-low" type="button"><span class="typography-body-md" style="color:#da8e35;font-weight:600">Web Collection</span><span class="nx-header-count" style="background:#da8e35;color:#fff;font-size:11px;padding:2px 6px;border-radius:10px;font-weight:bold;margin-left:4px">${state.sel.length}</span></button>`;
                btn.querySelector('button').onclick = e => { e.preventDefault(); e.stopPropagation(); toggle(); };
                return nav.appendChild(btn);
            }
            const logo = document.querySelector('.nexus-logo') || document.querySelector('header a[href="/"]');
            if (logo) {
                const lWrap = document.createElement('div'); lWrap.style.cssText = 'position:relative;display:inline-block';
                logo.parentNode.insertBefore(lWrap, logo); lWrap.appendChild(logo);
                const bdg = document.createElement('div'); bdg.className = 'nx-toggle-badge'; bdg.textContent = state.sel.length;
                bdg.onclick = e => { e.preventDefault(); e.stopPropagation(); toggle(); }; lWrap.appendChild(bdg);
            }
        };

        const checkPanel = () => {
            if (document.getElementById('nx-control-panel')) return;
            const bd = document.createElement('div'); bd.id = 'nx-control-backdrop'; bd.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:49;backdrop-filter:blur(4px);visibility:hidden;opacity:0;transition:visibility 0.3s, opacity 0.3s ease;';
            bd.onclick = toggle; document.body.appendChild(bd);

            const p = document.createElement('div'); p.id = 'nx-control-panel'; p.style.cssText = 'position:fixed;left:0;right:0;top:56px;width:100%;background:var(--color-surface-low, #212121);box-shadow:0 4px 12px rgba(0,0,0,0.5);z-index:50;border-bottom:1px solid var(--color-stroke-subdued, #333);margin-top:-1px;outline:none;box-sizing:border-box;visibility:hidden;opacity:0;transform:translateY(-10px);transition:visibility 0.3s, opacity 0.3s ease, transform 0.3s ease;';
            p.innerHTML = `<div style="display:flex;justify-content:center;gap:32px;padding:48px 32px 56px 32px;box-sizing:border-box;width:100%;max-width:1200px;margin:0 auto;flex-wrap:wrap;"><div style="display:flex;flex-direction:column;max-width:672px;padding:0 32px;flex:1;min-width:300px;box-sizing:border-box;"><h6 style="font-size:22px;font-weight:700;color:var(--color-neutral-strong, #e5e7eb);margin:0 0 8px 0;line-height:1.2">Web Collection Manager</h6><p style="font-size:16px;color:var(--color-neutral-subdued, #9ca3af);margin:0 0 24px 0;line-height:1.6">Manage your temporary mod selections before importing or sharing them. Visit any Mod Page and click Add to Web Collection to quickly build your selection list.</p><div style="display:flex;gap:16px;flex-wrap:wrap;"><button id="nx-btn-open" class="nxm-button" style="background:#da8e35;border:1px solid #da8e35;color:#fff;padding:12px 24px;border-radius:6px;font-weight:600;cursor:pointer;transition:all 0.2s;font-size:14px">View Web Collection</button><button id="nx-btn-clear" class="nxm-button" style="background:transparent;border:1px solid var(--color-stroke-subdued, #374151);color:var(--color-neutral-strong, #e5e7eb);padding:12px 24px;border-radius:6px;font-weight:600;cursor:pointer;transition:all 0.2s;font-size:14px">Clear Selection</button></div></div><div style="width:100%;max-width:240px;align-self:flex-start;border-radius:8px;border:1px solid var(--color-stroke-subdued, #374151);padding:16px;background:var(--color-surface-base, #18181b);box-sizing:border-box;margin-top:10px;"><h6 style="font-size:14px;font-weight:600;color:var(--color-neutral-moderate, #d1d5db);margin:0 0 6px 0;line-height:1.2">COLLECTION STATUS</h6><p style="font-size:14px;color:var(--color-neutral-subdued, #9ca3af);margin:0 0 16px 0;line-height:1.4">You have <strong id="nx-count" style="color:#da8e35;font-size:18px">${state.sel.length}</strong> mods selected.</p><p style="font-size:12px;color:var(--color-neutral-subdued, #9ca3af);margin:0;padding:10px;background:rgba(255,255,255,0.05);border-left:3px solid #da8e35;border-radius:4px;line-height:1.4">Shortcut: Press <strong style="color:var(--color-neutral-strong, #fff);">W</strong> at any time to quickly toggle this dashboard.</p></div></div>`;
            document.body.appendChild(p);

            p.querySelector('#nx-btn-open').onclick = () => { if (!state.sel.length) return showToast('No mods selected!', true); try { window.open(`${location.origin}/?collection_view=true&t=${Date.now()}`, '_blank'); } catch { location.href = `${location.origin}/?collection_view=true&t=${Date.now()}`; } };
            p.querySelector('#nx-btn-clear').onclick = () => { state.sel=[]; store.clr(); updBadge(); pMod(); showToast('Selection cleared'); };
        };

        const pMod = () => {
            const ul = document.querySelector('ul.modactions'); if (!ul) return;
            const parsed = parseUrl(location.href); if (!parsed) return;
            const isSel = getModIdx(location.href) > -1;

            let li = document.getElementById('nx-modpage-action');
            if (!li) {
                li = document.createElement('li'); li.id = 'nx-modpage-action';
                li.innerHTML = `<div class="nx-custom-modpage-btn" tabindex="0" style="cursor:pointer;display:inline-flex;align-items:center;justify-content:center;height:35px;padding:0 14px;border:1px solid #da8e35;color:#da8e35;gap:6px;border-radius:3px;font-weight:700;text-transform:uppercase;font-size:11px;transition:all .2s;"><svg viewBox="0 0 24 24" class="icon" style="width:1.2rem;height:1.2rem;"><path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z" style="fill: currentColor;"></path></svg><span class="flex-label"></span></div>`;
                li.querySelector('.nx-custom-modpage-btn').addEventListener('click', e => {
                    e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();

                    const titleEl = document.querySelector('h1') || document.querySelector('#pagetitle h1');
                    const imgEl = document.querySelector('meta[property="og:image"]') || document.querySelector('#image-carousel img');

                    const t = (document.title.split(' at ')[0] || titleEl?.textContent || '').trim() || 'Unknown Mod';
                    const u = imgEl?.content || imgEl?.src || '';

                    const authorEl = document.querySelector('#sideheader .author-name a, .author, .uploader') || Array.from(document.querySelectorAll('a[href*="/users/"], a[href*="/profile/"]')).find(a => !a.querySelector('img') && !a.textContent.includes('Log in') && !a.href.includes('my-profile'));
                    const a = authorEl ? authorEl.textContent.replace(/^by\s+/i, '').trim() : 'Unknown';

                    toggleMod(location.href, t, u, a, parsed);
                    updBadge(); pMod();
                }, true);
                const t = document.querySelector('li[id^="action-track-"]');
                t ? ul.insertBefore(li, t) : ul.appendChild(li);
            }
            const btn = li.querySelector('.nx-custom-modpage-btn');
            btn.classList.toggle('is-active', isSel);
            btn.style.background = isSel ? 'rgba(218,142,53,0.15)' : 'transparent';
            li.querySelector('.flex-label').textContent = isSel ? 'Remove from Web Collection' : 'Add to Web Collection';
        };

        const pDrop = () => {
            document.querySelectorAll('.nxm-dropdown-items').forEach(menu => {
                if (menu.dataset.nxPending || menu.querySelector('.nx-added-dropdown')) return;
                menu.dataset.nxPending = '1';

                const tBtn = document.getElementById(menu.getAttribute('aria-labelledby')||''); if (!tBtn) return;

                let c = tBtn, mL = null;
                for (let i = 0; i < 8 && c; i++) {
                    c = c.parentElement;
                    const aTags = Array.from(c.querySelectorAll('a[href*="/mods/"]')).filter(a => !a.href.includes('?tab=') && !a.href.includes('&') && !a.href.includes('#'));
                    if (aTags.length) { mL = aTags[0]; break; }
                }
                if (!mL) return;

                const parsed = parseUrl(mL.href); if (!parsed) return;

                const allLinks = Array.from(document.querySelectorAll(`a[href*="/mods/${parsed.modId}"]`));
                const titleLink = allLinks.find(a => a.textContent.trim().length > 1 && !a.querySelector('img'));
                const title = titleLink ? titleLink.textContent.trim() : 'Unknown Mod';

                const imgLink = allLinks.find(a => a.querySelector('img'));
                const imgNode = imgLink ? imgLink.querySelector('img') : null;
                const thumb = imgNode?.src || 'https://www.nexusmods.com/assets/images/default/placeholder.png';

                let author = 'Unknown';
                if (titleLink) {
                    let tile = titleLink.closest('article, li, [class*="tile"], [class*="card"]');
                    if (!tile) {
                        tile = titleLink;
                        for (let i = 0; i < 6 && tile; i++) {
                            tile = tile.parentElement;
                            if (tile && Array.from(tile.querySelectorAll('a[href*="/mods/"]')).length >= 2) break;
                        }
                    }
                    if (tile) {
                        const aEl = Array.from(tile.querySelectorAll('a[href*="/users/"], a[href*="/profile/"]')).find(a => a.textContent.trim() && !a.querySelector('img') && !a.textContent.includes('Log in') && !a.href.includes('my-profile'));
                        if (aEl) author = aEl.textContent.trim().replace(/^\s*by\s+/i, '');
                        else {
                            const byTxt = Array.from(tile.querySelectorAll('span, p, div')).find(e => e.textContent.trim().toLowerCase().startsWith('by '));
                            if (byTxt) author = byTxt.textContent.trim().replace(/^\s*by\s+/i, '');
                        }
                    }
                }

                const btn = document.createElement('button'); btn.className = 'nxm-dropdown-item nx-added-dropdown'; btn.role = 'menuitem';

                const updateBtnUI = () => {
                    const sel = getModIdx(mL.href) > -1;
                    btn.innerHTML = `<svg viewBox="0 0 24 24" role="presentation" class="nxm-dropdown-item-icon"><path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z" style="fill: ${sel?'#da8e35':'currentcolor'}"></path></svg><span class="nxm-dropdown-item-label" style="color:${sel?'#da8e35':'inherit'};font-weight:${sel?'700':'500'}">${sel?'Remove from Web Collection':'Web Collection'}</span>`;
                };

                btn.onclick = e => {
                    e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
                    toggleMod(mL.href, title, thumb, author, parsed);
                    updBadge(); updateBtnUI(); pMod();
                };
                updateBtnUI();
                setTimeout(() => {
                    if (!menu.querySelector('.nx-added-dropdown')) menu.insertBefore(btn, menu.firstChild);
                    delete menu.dataset.nxPending;
                }, 10);
            });
        };

        document.addEventListener('keydown', e => {
            if (e.ctrlKey || e.metaKey || ['INPUT','TEXTAREA','SELECT'].includes(document.activeElement?.tagName) || document.activeElement?.isContentEditable) return;
            if (e.key.toLowerCase()==='g' && state.sel.length) { e.preventDefault(); try { window.open(`${location.origin}/?collection_view=true&t=${Date.now()}`, '_blank'); } catch { location.href = `${location.origin}/?collection_view=true&t=${Date.now()}`; } }
            if (e.key.toLowerCase()==='w') { e.preventDefault(); toggle(); }
            if (e.key==='Escape' && state.vis) { e.preventDefault(); toggle(); }
        });

        checkHeader(); checkPanel(); pMod(); updBadge();

        let tmt = null;
        new MutationObserver(() => {
            checkHeader(); checkPanel(); pDrop();
            if (tmt) return;
            tmt = setTimeout(() => { tmt = null; pMod(); }, 300);
        }).observe(document.body, { childList: true, subtree: true });
    };

    document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', initNexus) : initNexus();

})();