Greasy Fork is available in English.

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();

})();