WARDROBE

Гардероб + библиотека костюмов Catwar. ИНСТРУКЦИЯ И ОБРАТНАЯ СВЯЗЬ: https://catwar.su/sniff1186155 | https://catwar.net/sniff1186155

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

// ==UserScript==
// @name         WARDROBE
// @namespace    http://tampermonkey.net/
// @version      1.1.3
// @description  Гардероб + библиотека костюмов Catwar. ИНСТРУКЦИЯ И ОБРАТНАЯ СВЯЗЬ: https://catwar.su/sniff1186155 | https://catwar.net/sniff1186155
// @author       RESSOR
// @match        http*://*.catwar.net/rabbit*
// @match        http*://*.catwar.su/rabbit*
// @match        http*://*.catwar.net/settings_costumes*
// @match        http*://*.catwar.su/settings_costumes*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=catwar.su
// @license      MIT
// @grant        none
// ==/UserScript==

const QUICK_CATEGORIES = [
    { name: "МИФИЧЕСКИЕ ЗВЕРИ", id: 12025 }, { name: "ЕГИПЕТ", id: 12053 },
    { name: "ТЁМНОЕ ФЭНТЕЗИ", id: 12119 },   { name: "ГОБЛИНКОР", id: 12335 },
    { name: "САМОЦВЕТЫ", id: 12389 },         { name: "МОРСКИЕ ГЛУБИНЫ", id: 12427 },
    { name: "ДОЛИНА ГРЁЗ", id: 12468 },       { name: "ОТТЕПЕЛЬ", id: 12524 },
    { name: "КОСМОС", id: 12604 },            { name: "СТИМПАНК", id: 12967 },
    { name: "ЯПОНИЯ", id: 13571 },            { name: "ДЖУНГЛИ", id: 13742 },
    { name: "ПИРАТЫ", id: 100000 },           { name: "ПРАЗДНИК УРОЖАЯ", id: 100494 },
    { name: "МАСКАРАД", id: 100634 },         { name: "ДИКИЙ ЗАПАД", id: 100929 },
    { name: "ЗИМНЯЯ СКАЗКА", id: 101431 },    { name: "ТЁМНЫЕ ПРОРОЧЕСТВА", id: 101970 },
    { name: "ПЕРВОБЫТНЫЙ МИР", id: 102899 },  { name: "МГНОВЕНИЯ ВЕСНЫ", id: 103309 },
    { name: "ПУТЕШЕСТВИЕ В КОСМОС", id: 103644 }, { name: "КИБЕРПАНК", id: 103813 },
    { name: "В ПОКОЯХ ЛЕСА", id: 104273 }
];

const THEMES = {
    dark: {
        gold: '#a29b8b', goldDim: '#7a6230', bg: '#1d1c19', panel: '#2d2b28',
        glass: 'rgba(255,255,255,0.03)', border: '#3c3932', borderHi: 'rgba(201,168,76,0.7)',
        text: '#e8dfc8', muted: '#998e7c', red: '#8b2020', shadow: 'rgba(0,0,0,.6)',
        labelBg: 'rgba(0,0,0,.65)', thumbHoverBg: 'rgba(201,168,76,0.06)',
    },
    light: {
        gold: '#695d45', goldDim: '#c9a84c', bg: '#d7d3c8', panel: '#cbc5b8',
        glass: 'rgba(0,0,0,0.03)', border: '#b4ab9b', borderHi: 'rgba(154,110,26,0.75)',
        text: '#2c2416', muted: '#5e5645', red: '#b03030', shadow: 'rgba(0,0,0,.18)',
        labelBg: '#dfd8cf', thumbHoverBg: 'rgba(154,110,26,0.07)',
    }
};

let currentTheme = localStorage.getItem('wd-theme') || 'dark';
let C = { ...THEMES[currentTheme] };
let DEFAULT_MODEL_URL = '';
let layerCounter = 0;
let pendingUrl = { model: null, costume: null };
let searchStartID = 1;
let searchItemsPerPage = 40;
let activeLayers = [];

function applyTheme(theme) {
    currentTheme = theme;
    C = { ...THEMES[theme] };
    localStorage.setItem('wd-theme', theme);
    rebuildPanel();
}

function injectStyles() {
    const style = document.createElement('style');
    style.id = 'wd-style';
    style.textContent = `
        @import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600&family=Jost:wght@300;400&display=swap');
        #try-on-panel-wrapper * { font-family: 'Jost', sans-serif; }
        #try-on-panel-wrapper h2, #try-on-panel-wrapper .wd-title { font-family: 'Cinzel', serif; }
        #try-on-panel-wrapper input:focus { border-color: ${C.gold} !important; outline: none; }
        #try-on-panel-wrapper button:hover { border-color: ${C.gold} !important; color: ${C.gold} !important; }
        .sortable-ghost { opacity: .4; background: ${C.glass}; border-radius: 2px; }
        @keyframes wd-fade-in { from { opacity:0; transform:translateY(-6px); } to { opacity:1; transform:translateY(0); } }
        #try-on-panel-content { animation: wd-fade-in .25s ease; }
        #wd-theme-toggle {
            border: 2px solid ${C.gold}; color: ${C.gold};
            font-size: 14px; cursor: pointer; padding: 3px 7px; border-radius: 50px;
            transition: border-color .15s, color .15s; line-height: 1.4; flex-shrink: 0;
        }
        #wd-theme-toggle:hover { border-color: ${C.gold} !important; color: ${C.gold} !important; background-color: ${C.bg} !important; }
        @media (max-width: 768px) {
            .w-flex { flex-direction: column !important; align-items: stretch !important; }
            #control-col { margin-right: 0 !important; margin-bottom: 20px !important; }
            .s-ctrl { flex-direction: column !important; gap: 8px !important; }
            .s-ctrl > * { width: 100% !important; box-sizing: border-box; }
        }
    `;
    document.head.appendChild(style);
}

const thumbCSS = (url, h) =>
    `width:100%;height:${h};background:url('${url}') center/contain no-repeat;` +
    `cursor:pointer;border:1px solid ${C.border};border-radius:2px;box-sizing:border-box;` +
    `transition:border-color .15s,background-color .15s;`;

function loaderHTML(type, title, withRestore) {
    const inputS = `width:100%;padding:5px 8px;background:${C.bg};color:${C.text};border:1px solid ${C.border};` +
        `border-radius:2px;font-size:16px;box-sizing:border-box;outline:none;font-family:inherit;`;
    const btnS = col => `width:100%;padding:5px 8px;background:${col};color:${C.text};border:1px solid ${C.border};` +
        `border-radius:2px;cursor:pointer;font-size:16px;letter-spacing:.06em;font-family:inherit;transition:border-color .15s,color .15s;`;
    return `
        <div style="margin-bottom:6px;order:${type === 'model' ? 3 : 4};">
            <div id="${type}-loader-header" style="display:flex;justify-content:space-between;align-items:center;cursor:pointer;padding:6px 0;border-top:1px solid ${C.border};">
                <span style="font-size:12px;font-weight:bolder;letter-spacing:.12em;color:${C.muted};text-transform:uppercase;">${title}</span>
                <button id="${type}-loader-toggle-btn" style="background:none;border:none;color:${C.gold};font-size:12px;cursor:pointer;padding:0;">▸</button>
            </div>
            <div id="${type}-loader-content" style="display:none;padding:6px 0;">
                <input type="text" id="${type}-url-input" placeholder="URL изображения" style="${inputS} margin-bottom:5px;">
                <input type="file" id="${type}-file-input" style="display:none;" accept="image/png,image/jpeg">
                <button id="${type}-select-file-btn" style="${btnS(C.glass)} margin-bottom:4px;">выбрать файл</button>
                ${withRestore ? `<button id="restore-model-btn" style="${btnS(C.glass)}">вернуть оригинал</button>` : ''}
            </div>
        </div>`;
}

function buildPanelInnerHTML(modelSrc) {
    const quickBtns = QUICK_CATEGORIES.map(cat =>
        `<button onclick="document.getElementById('search-start-id-input').value='${cat.id}';document.getElementById('search-range-btn').click();"
            style="padding:3px 8px;font-size:14px;letter-spacing:.08em;background:${C.glass};color:${C.muted};border:1px solid ${C.border};border-radius:2px;cursor:pointer;transition:border-color .15s,color .15s;font-family:inherit;">
            ${cat.name}</button>`
    ).join('');

    return `
        <div style="position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,${C.gold},transparent);"></div>
        <div id="main-panel-header" style="display:flex;align-items:center;cursor:pointer;padding:14px 0;gap:12px;">
            <button id="main-panel-toggle-btn" style="background:none;border:none;color:${C.gold};font-size:16px;cursor:pointer;padding:0;line-height:1;transition:transform .2s;">▸</button>
            <h2 class="wd-title" style="font-size:13px;margin:0;color:${C.gold};letter-spacing:.2em;font-weight:600;">ПРИМЕРКА КОСТЮМОВ</h2>
            <div style="flex-grow:1;height:1px;background:linear-gradient(90deg,${C.border},transparent);margin-left:8px;"></div>
            <button id="wd-theme-toggle" title="${currentTheme === 'dark' ? 'Светлая тема' : 'Тёмная тема'}">${currentTheme === 'dark' ? '☀' : '☾'}</button>
        </div>
        <div id="try-on-panel-content" style="display:none;flex-direction:column;padding-bottom:20px;">
            <div class="w-flex" style="display:flex;align-items:flex-start;gap:24px;">
                <div id="control-col" style="display:flex;flex-direction:column;align-items:stretch;flex-shrink:0;width:190px;">
                    <div style="text-align:center;margin-bottom:16px;">
                        <div style="font-size:12px;letter-spacing:.18em;color:${C.muted};margin-bottom:35px;text-transform:uppercase;">предпросмотр</div>
                        <div class="try-on-container" style="position:relative;width:100px;height:150px;margin:0 auto 20px;transform:scale(1.4);transform-origin:center;">
                            <img id="player-model" src="${modelSrc}" style="position:absolute;top:0;left:0;width:100%;height:100%;object-fit:contain;z-index:1;">
                        </div>
                    </div>
                    <div style="display:flex;flex-direction:column;">
                        ${loaderHTML('model', 'Заменить модель', true)}
                        ${loaderHTML('costume', 'Загрузить костюм', false)}
                    </div>
                    <div style="margin-top:10px;">
                        <div style="font-size:12px;letter-spacing:.18em;color:${C.muted};margin-bottom:6px;text-transform:uppercase;">слои</div>
                        <div id="try-on-controller-panel" style="max-height:320px;overflow-y:auto;padding:4px;background:${C.bg};border:1px solid ${C.border};border-radius:3px;">
                            <p style="font-style:italic;color:${C.muted};font-size:13px;text-align:center;padding:10px 0;margin:0;">нажмите на костюмы</p>
                        </div>
                    </div>
                </div>
                <div style="flex-grow:1;min-width:0;">
                    <div style="font-size:12px;letter-spacing:.18em;color:${C.muted};margin-bottom:8px;text-transform:uppercase;">костюмы на странице</div>
                    <div id="try-on-thumbnails" style="display:grid;grid-template-columns:repeat(1,1fr);gap:3px;padding:8px;background:${C.bg};border:1px solid ${C.border};border-radius:3px;width:100%;box-sizing:border-box;"></div>
                </div>
            </div>
            <div style="margin-top:20px;border-top:4px solid ${C.border};padding-top:14px;">
                <div id="costume-search-header" style="display:flex;align-items:center;background:linear-gradient(to right,${C.border} 0%,transparent 100%);padding:10px;font-weight:600;border-radius:15px;cursor:pointer;gap:10px;margin-bottom:4px;">
                    <button id="costume-search-toggle-btn" style="background:none;border:none;color:${C.gold};font-size:14px;cursor:pointer;padding:0;line-height:1;">▸</button>
                    <span class="wd-title" style="font-size:13px;letter-spacing:.2em;color:${C.gold};">ПОИСК КОСТЮМОВ</span>
                </div>
                <div id="costume-search-content" style="display:none;padding-top:12px;">
                    <div id="quick-panel" style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:12px;padding:8px;background:${C.bg};border:1px solid ${C.border};border-radius:3px;">
                        ${quickBtns}
                    </div>
                    <div class="s-ctrl" style="display:flex;align-items:center;gap:8px;margin-bottom:10px;flex-wrap:wrap;">
                        <span style="font-size:12px;letter-spacing:.08em;color:${C.muted};white-space:nowrap;">ID от:</span>
                        <input type="text" id="search-start-id-input" placeholder="число"
                            style="width:80px;padding:5px 8px;background:${C.bg};color:${C.text};border:1px solid ${C.border};border-radius:2px;font-size:12px;font-family:inherit;box-sizing:border-box;">
                        <button id="search-range-btn"
                            style="padding:5px 14px;background:${C.glass};color:${C.text};border:1px solid ${C.border};border-radius:2px;cursor:pointer;font-size:10px;letter-spacing:.1em;font-family:inherit;transition:border-color .15s,color .15s;">
                            НАЙТИ</button>
                    </div>
                    <div class="s-ctrl" style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;padding:6px 10px;background:${C.bg};border:1px solid ${C.border};border-radius:3px;">
                        <button id="prev-page-btn" style="background:none;border:none;color:${C.muted};font-size:35px;cursor:pointer;padding:0;transition:color .15s;line-height:1;">&#8592;</button>
                        <span id="current-id-display" style="font-size:12px;letter-spacing:.1em;color:${C.muted};">1 — 40</span>
                        <button id="next-page-btn" style="background:none;border:none;color:${C.muted};font-size:35px;cursor:pointer;padding:0;transition:color .15s;line-height:1;">&#8594;</button>
                    </div>
                    <div id="costume-search-thumbnails" style="display:grid;grid-template-columns:repeat(1,1fr);gap:4px;padding:8px;background:${C.bg};border:1px solid ${C.border};border-radius:3px;width:100%;box-sizing:border-box;"></div>
                </div>
            </div>
        </div>`;
}

function gridCols(panelId, minW, gap, fallback) {
    const el = document.getElementById(panelId);
    if (!el) return 1;
    return Math.max(1, Math.floor(((el.clientWidth || el.offsetWidth || fallback) + gap) / (minW + gap)));
}

function updateLayerOrder() {
    const layers = document.getElementById('try-on-controller-panel')?.querySelectorAll('.costume-controller');
    if (!layers) return;
    layers.forEach((ctrl, i) => {
        const img = document.getElementById(ctrl.dataset.layerId);
        if (img) img.style.zIndex = 1000 + (layers.length - i) * 10;
    });
}

function removeLayer(id) {
    document.getElementById(id)?.remove();
    document.querySelector(`.costume-controller[data-layer-id="${id}"]`)?.remove();
    activeLayers = activeLayers.filter(l => l.id !== id);
    updateLayerOrder();
    const panel = document.getElementById('try-on-controller-panel');
    if (panel?.children.length === 0)
        panel.innerHTML = `<p style="font-style:italic;color:${C.muted};font-size:13px;text-align:center;padding:10px 0;">нажмите на костюмы</p>`;
}

function changeModel(url) {
    const img = document.getElementById('player-model');
    if (!img || !url) return;
    img.src = url;
    document.getElementById('model-url-input').value = '';
    document.getElementById('model-file-input').value = '';
    pendingUrl.model = null;
}

function renderCostumeLayer(id, url, container, panel) {
    panel.querySelector('p')?.remove();

    const img = document.createElement('img');
    img.id = id;
    img.src = url;
    img.style.cssText = `position:absolute;top:0;left:0;width:100%;height:100%;object-fit:contain;z-index:100;`;
    container.appendChild(img);

    const costumeID = url.match(/costume\/(\d+)\.png/)?.[1] ?? '—';
    const ctrl = document.createElement('div');
    ctrl.className = 'costume-controller';
    ctrl.dataset.layerId = id;
    ctrl.style.cssText =
        `display:flex;align-items:center;gap:6px;border:1px solid ${C.border};border-radius:3px;` +
        `padding:5px 6px;margin-bottom:4px;background:${C.glass};font-size:12px;cursor:move;transition:border-color .15s;`;
    ctrl.innerHTML = `
        <div style="width:24px;height:24px;flex-shrink:0;background:url('${url}') center/contain no-repeat;border:1px solid ${C.border};border-radius:2px;"></div>
        <span style="flex-grow:1;color:${C.text};letter-spacing:.04em;">ID ${costumeID}</span>
        <button class="remove-layer-btn" data-layer-id="${id}"
            style="background:${C.red};color:#fff;border:none;width:18px;height:18px;border-radius:2px;cursor:pointer;font-size:12px;line-height:1;flex-shrink:0;">✕</button>
    `;
    ctrl.addEventListener('mouseenter', () => ctrl.style.borderColor = C.goldDim);
    ctrl.addEventListener('mouseleave', () => ctrl.style.borderColor = C.border);
    ctrl.querySelector('.remove-layer-btn').addEventListener('click', e => { e.stopPropagation(); removeLayer(id); });
    panel.prepend(ctrl);
}

function addCostumeLayer(url) {
    if (!url || url.includes('/cw3/composited/')) return;
    const id = `costume-layer-${++layerCounter}`;
    const container = document.querySelector('#try-on-panel-content .try-on-container');
    const panel = document.getElementById('try-on-controller-panel');
    if (!container || !panel) return;
    activeLayers.unshift({ id, url });
    renderCostumeLayer(id, url, container, panel);
    updateLayerOrder();
}

function handleFileSelect(e, type) {
    const file = e.target.files[0];
    if (!file || !['image/png', 'image/jpeg'].includes(file.type)) return;
    const reader = new FileReader();
    reader.onload = ev => { pendingUrl[type] = ev.target.result; handleLoad(type); };
    reader.readAsDataURL(file);
}

function handleLoad(type) {
    const url = pendingUrl[type] || document.getElementById(`${type}-url-input`)?.value;
    if (!url) return;
    if (type === 'model') {
        changeModel(url);
    } else {
        addCostumeLayer(url);
        pendingUrl.costume = null;
        document.getElementById('costume-file-input').value = '';
        document.getElementById('costume-url-input').value = '';
    }
}

function togglePanel(id) {
    const content = document.getElementById(`${id}-content`);
    const arrow = document.getElementById(`${id}-toggle-btn`);
    if (!content || !arrow) return;
    const open = content.style.display === 'none' || content.style.display === '';
    content.style.display = open ? 'block' : 'none';
    arrow.textContent = open ? '▾' : '▸';
    if (open && id === 'costume-search' && !document.getElementById('costume-search-thumbnails').children.length)
        updateSearchDisplay(1);
}

function updateSearchDisplay(startId) {
    const panel = document.getElementById('costume-search-thumbnails');
    if (!panel) return;
    const cols = gridCols('costume-search-thumbnails', 100, 4, 600);
    searchItemsPerPage = cols * 5;
    searchStartID = Math.max(1, startId);
    panel.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
    panel.innerHTML = '';

    for (let i = 0; i < searchItemsPerPage; i++) {
        const costumeID = searchStartID + i;
        const url = `/cw3/cats/0/costume/${costumeID}.png`;
        const thumb = document.createElement('div');
        thumb.style.cssText = thumbCSS(url, '150px') + `position:relative;background-color:${C.glass};overflow:hidden;`;

        const label = document.createElement('div');
        label.style.cssText =
            `position:absolute;bottom:0;left:0;width:100%;background:${C.labelBg};` +
            `color:${C.muted};font-size:12px;text-align:center;padding:2px 0;letter-spacing:.05em;`;
        label.textContent = costumeID;
        thumb.appendChild(label);

        thumb.addEventListener('click', () => addCostumeLayer(url));
        thumb.addEventListener('mouseenter', () => { thumb.style.borderColor = C.gold; thumb.style.backgroundColor = C.thumbHoverBg; });
        thumb.addEventListener('mouseleave', () => { thumb.style.borderColor = C.border; thumb.style.backgroundColor = C.glass; });
        panel.appendChild(thumb);
    }

    document.getElementById('current-id-display').textContent =
        `${searchStartID} — ${searchStartID + searchItemsPerPage - 1}`;
}

function handleSearchRange() {
    const raw = parseInt(document.getElementById('search-start-id-input').value.replace(/\D/g, ''), 10) || 1;
    updateSearchDisplay(Math.max(1, Math.floor((raw - 1) / searchItemsPerPage) * searchItemsPerPage + 1));
}

function navigateSearch(dir) {
    updateSearchDisplay(Math.max(1, searchStartID + dir * searchItemsPerPage));
}

function buildPageGrid(costumeUrls) {
    const panel = document.getElementById('try-on-thumbnails');
    if (!panel) return;
    const cols = gridCols('try-on-thumbnails', 50, 3, 400);
    panel.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
    panel.innerHTML = '';

    costumeUrls.forEach(url => {
        const thumb = document.createElement('div');
        thumb.style.cssText = thumbCSS(url, '75px') + `background-color:${C.glass};`;
        thumb.addEventListener('click', () => addCostumeLayer(url));
        thumb.addEventListener('mouseenter', () => { thumb.style.borderColor = C.gold; thumb.style.backgroundColor = C.thumbHoverBg; });
        thumb.addEventListener('mouseleave', () => { thumb.style.borderColor = C.border; thumb.style.backgroundColor = C.glass; });
        panel.appendChild(thumb);
    });

    const rem = costumeUrls.length % cols;
    if (rem) for (let i = 0; i < cols - rem; i++) {
        const ghost = document.createElement('div');
        ghost.style.cssText = `width:100%;height:75px;box-sizing:border-box;`;
        panel.appendChild(ghost);
    }
}

function bindPanelEvents(costumeUrls) {
    document.getElementById('model-loader-header').addEventListener('click', () => togglePanel('model-loader'));
    document.getElementById('costume-loader-header').addEventListener('click', () => togglePanel('costume-loader'));
    document.getElementById('costume-search-header').addEventListener('click', () => togglePanel('costume-search'));
    document.getElementById('restore-model-btn').addEventListener('click', () => changeModel(DEFAULT_MODEL_URL));
    document.getElementById('search-range-btn').addEventListener('click', handleSearchRange);
    document.getElementById('prev-page-btn').addEventListener('click', () => navigateSearch(-1));
    document.getElementById('next-page-btn').addEventListener('click', () => navigateSearch(1));

    const navBtnHover = id => {
        const el = document.getElementById(id);
        el.addEventListener('mouseenter', () => el.style.color = C.gold);
        el.addEventListener('mouseleave', () => el.style.color = C.muted);
    };
    navBtnHover('prev-page-btn');
    navBtnHover('next-page-btn');

    ['model', 'costume'].forEach(type => {
        document.getElementById(`${type}-select-file-btn`).addEventListener('click', () => document.getElementById(`${type}-file-input`).click());
        document.getElementById(`${type}-file-input`).addEventListener('change', e => handleFileSelect(e, type));
        document.getElementById(`${type}-url-input`).addEventListener('keydown', e => { if (e.key === 'Enter') handleLoad(type); });
    });

    document.getElementById('main-panel-header').addEventListener('click', e => {
        if (e.target.closest('#wd-theme-toggle')) return;
        const content = document.getElementById('try-on-panel-content');
        const wrapper = document.getElementById('try-on-panel-wrapper');
        const toggleBtn = document.getElementById('main-panel-toggle-btn');
        const open = content.style.display === 'none';
        content.style.display = open ? 'flex' : 'none';
        toggleBtn.textContent = open ? '▾' : '▸';
        toggleBtn.style.transform = open ? 'rotate(0deg)' : '';
        wrapper.style.paddingBottom = open ? '20px' : '0';
    });

    document.getElementById('wd-theme-toggle').addEventListener('click', e => {
        e.stopPropagation();
        applyTheme(currentTheme === 'dark' ? 'light' : 'dark');
    });

    requestAnimationFrame(() => buildPageGrid(costumeUrls));

    new ResizeObserver(() => {
        buildPageGrid(costumeUrls);
        const sp = document.getElementById('costume-search-thumbnails');
        if (sp?.children.length) updateSearchDisplay(searchStartID);
    }).observe(document.getElementById('try-on-thumbnails'));

    const initSortable = () => new Sortable(document.getElementById('try-on-controller-panel'), {
        animation: 150, ghostClass: 'sortable-ghost', onEnd: updateLayerOrder,
    });
    if (window.Sortable) {
        initSortable();
    } else if (!document.querySelector('script[src*="sortablejs"]')) {
        const sortableScript = document.createElement('script');
        sortableScript.src = 'https://cdn.jsdelivr.net/npm/[email protected]/Sortable.min.js';
        sortableScript.onload = initSortable;
        document.head.appendChild(sortableScript);
    }
}

function rebuildPanel() {
    const wrapper = document.getElementById('try-on-panel-wrapper');
    if (!wrapper) return;

    const wasOpen     = document.getElementById('try-on-panel-content')?.style.display !== 'none';
    const searchOpen  = document.getElementById('costume-search-content')?.style.display !== 'none';
    const modelOpen   = document.getElementById('model-loader-content')?.style.display !== 'none';
    const costumeOpen = document.getElementById('costume-loader-content')?.style.display !== 'none';
    const modelSrc    = document.getElementById('player-model')?.src ?? DEFAULT_MODEL_URL;

    document.getElementById('wd-style')?.remove();
    injectStyles();

    wrapper.style.cssText =
        `border:1px solid ${C.border};border-radius:4px;background:${C.panel};` +
        `backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);` +
        `padding:0 20px;margin:20px auto;width:90%;max-width:1300px;color:${C.text};` +
        `position:relative;overflow:hidden;`;
    wrapper.innerHTML = buildPanelInnerHTML(modelSrc);

    const costumeUrls = [];
    document.querySelectorAll('#main button div[style*="background-image: url"]').forEach(icon => {
        const m = window.getComputedStyle(icon).backgroundImage.match(/url\(['"]?(.*?)['"]?\)/);
        if (m?.[1]?.includes('/cw3/cats/')) costumeUrls.push(m[1]);
    });
    bindPanelEvents(costumeUrls);

    if (wasOpen) {
        document.getElementById('try-on-panel-content').style.display = 'flex';
        document.getElementById('main-panel-toggle-btn').textContent = '▾';
        wrapper.style.paddingBottom = '20px';
    }
    if (searchOpen) {
        document.getElementById('costume-search-content').style.display = 'block';
        document.getElementById('costume-search-toggle-btn').textContent = '▾';
        updateSearchDisplay(searchStartID);
    }
    ['model', 'costume'].forEach(t => {
        if (t === 'model' ? modelOpen : costumeOpen)
            document.getElementById(`${t}-loader-content`).style.display = 'block';
    });

    if (activeLayers.length) {
        const container = document.querySelector('#try-on-panel-content .try-on-container');
        const panel = document.getElementById('try-on-controller-panel');
        [...activeLayers].reverse().forEach(({ id, url }) => renderCostumeLayer(id, url, container, panel));
        updateLayerOrder();
    }
}

function extractModelUrl(firstDiv) {
    if (firstDiv) {
        const attr = firstDiv.getAttribute('style') || '';
        const m = attr.match(/url\(['"]?(.*?)['"]?\)/);
        if (m?.[1]) return m[1];
        const cm = window.getComputedStyle(firstDiv).backgroundImage.match(/url\(['"]?(.*?)['"]?\)/);
        if (cm?.[1]) return cm[1];
    }
    for (const el of document.querySelectorAll('div[style*="/cw3/composited/"]')) {
        const m = el.getAttribute('style').match(/url\(['"]?(.*?)['"]?\)/);
        if (m?.[1]) return m[1];
    }
    const comp = document.querySelector('div[style*="composited"]');
    if (comp) {
        const m = window.getComputedStyle(comp).backgroundImage.match(/url\(['"]?(.*?)['"]?\)/);
        if (m?.[1]) return m[1];
    }
    return '';
}

function injectPanel(anchorEl, firstDivOrPosition, costumeSourceSelector) {
    let insertPosition, firstDiv;
    if (typeof firstDivOrPosition === 'string') {
        insertPosition = firstDivOrPosition;
        firstDiv = null;
    } else {
        insertPosition = 'afterend';
        firstDiv = firstDivOrPosition;
        costumeSourceSelector = 'div[style*="background-image: url"]';
    }

    const url = extractModelUrl(firstDiv);
    if (url) DEFAULT_MODEL_URL = url;

    injectStyles();

    const wrapper = document.createElement('div');
    wrapper.id = 'try-on-panel-wrapper';
    wrapper.style.cssText =
        `border:1px solid ${C.border};border-radius:4px;background:${C.panel};` +
        `backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);` +
        `padding:0 20px;margin:20px auto;width:90%;max-width:1300px;color:${C.text};` +
        `position:relative;overflow:hidden;`;

    if (insertPosition === 'afterend') {
        anchorEl.insertAdjacentElement('afterend', document.createElement('hr'));
        anchorEl.insertAdjacentElement('afterend', wrapper);
    } else {
        anchorEl.insertAdjacentHTML(insertPosition, wrapper.outerHTML + '<hr>');
    }

    document.getElementById('try-on-panel-wrapper').innerHTML = buildPanelInnerHTML(DEFAULT_MODEL_URL);

    const costumeUrls = [];
    document.querySelectorAll(costumeSourceSelector).forEach(icon => {
        const m = window.getComputedStyle(icon).backgroundImage.match(/url\(['"]?(.*?)['"]?\)/);
        if (m?.[1]?.includes('/cw3/cats/')) costumeUrls.push(m[1]);
    });

    bindPanelEvents(costumeUrls);
}

(function () {
    const isSettingsCostumes = /\/settings_costumes/.test(location.pathname);

    if (isSettingsCostumes) {
        const tryInsert = () => {
            if (document.getElementById('try-on-panel-wrapper')) return true;
            const col3 = document.querySelector('div[data-v-5fa27571][class*="col-3"]');
            if (!col3) return false;
            const firstDiv =
                col3.querySelector('[class="first"]') ||
                col3.querySelector('[class*="first"]') ||
                document.querySelector('[data-v-59afe5e8][class="first"]') ||
                document.querySelector('[class="first"]');
            if (!firstDiv) return false;
            injectPanel(col3, firstDiv);
            return true;
        };

        if (!tryInsert()) {
            const observer = new MutationObserver(() => { if (tryInsert()) observer.disconnect(); });
            observer.observe(document.body, { childList: true, subtree: true });
        }
    } else {
        const mainDiv = document.getElementById('main');
        if (!mainDiv) return;
        injectPanel(mainDiv, 'beforebegin', '#main button div[style*="background-image: url"]');
    }
})();