Card Helper AStars | AnimeStars | ASStars

card helper

目前為 2025-05-24 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Card Helper AStars | AnimeStars | ASStars
// @namespace    animestars.org
// @version      7.10
// @description  card helper
// @author       bmr
// @match        https://astars.club/*
// @match        https://asstars.club/*
// @match        https://asstars1.astars.club/*
// @match        https://animestars.org/*
// @match        https://as1.astars.club/*
// @match        https://asstars.tv/*
// @license      MIT
// @grant        none
// ==/UserScript==

const DELAY = 50;

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

let cardCounter = 0;
const cardClasses = '.remelt__inventory-item, .lootbox__card, .anime-cards__item, .trade__inventory-item, .trade__main-item, .card-filter-list__card, .deck__item, .history__body-item, .history__body-item, .card-pack__card';

let currentNotification = {
    element: null,
    id: null,
    type: null,
    timeoutId: null
};

function displayNotification(id, message, type = 'temporary', options = {}) {
    if (window.location.pathname.includes('/pm/') || window.location.pathname.includes('emotions.php') || window.location.pathname.includes('/messages/')) {
        return;
    }

    const { total, current, isSuccess = true, duration = 3500, sticky = false } = options;
    if (currentNotification.element && currentNotification.id !== id) {
        if (currentNotification.timeoutId) clearTimeout(currentNotification.timeoutId);
        if (currentNotification.element.parentNode) {
            currentNotification.element.remove();
        }
        currentNotification.element = null;
        currentNotification.id = null;
    }

    let notificationElement = currentNotification.element;

    if (!notificationElement || currentNotification.id !== id || (currentNotification.type === 'progress' && type !== 'progress')) {
        if (notificationElement && notificationElement.parentNode) {
             notificationElement.remove();
        }
        notificationElement = document.createElement('div');
        notificationElement.className = 'card-helper-status-notification';
        document.body.appendChild(notificationElement);
        currentNotification.element = notificationElement;
        currentNotification.id = id;
    }

    currentNotification.type = type;
    let iconHtml = '';
    if (type === 'progress') {
        iconHtml = '<div class="card-helper-spinner"></div>';
        if (total !== undefined && current !== undefined) {
            let progressText = total === 'неизвестно' ?
            `Обработано ${current}` : `${current}/${total}`;
            message = `${message} ${progressText}`;
        }
    } else if (type === 'completion' || type === 'temporary') {
        const iconClass = isSuccess ?
        'card-helper-checkmark' : 'card-helper-crossmark';
        const iconChar = isSuccess ? '✔' : '✖';
        iconHtml = `<span class="${iconClass}">${iconChar}</span>`;
    }

    notificationElement.innerHTML = `
        <div class="ch-status-icon-container">${iconHtml}</div>
        <span class="card-helper-status-text">${message}</span>
    `;
    requestAnimationFrame(() => {
        notificationElement.classList.add('show');
    });
    if (currentNotification.timeoutId) {
        clearTimeout(currentNotification.timeoutId);
        currentNotification.timeoutId = null;
    }

    if (!sticky && (type === 'completion' || type === 'temporary')) {
        currentNotification.timeoutId = setTimeout(() => {
            hideCurrentNotification(id);
        }, duration);
    }
}

function updateNotificationProgress(id, messagePrefix, current, total) {
    if (currentNotification.id === id && currentNotification.type === 'progress') {
        const textElement = currentNotification.element.querySelector('.card-helper-status-text');
        let progressText = total === 'неизвестно' ? `Обработано ${current}` : `${current}/${total}`;
        const fullMessage = `${messagePrefix} ${progressText}`;
        if (textElement && textElement.textContent !== fullMessage) {
            textElement.textContent = fullMessage;
        }
    } else {
        displayNotification(id, messagePrefix, 'progress', { current, total, sticky: true });
    }
}

function completeProgressNotification(id, message, isSuccess = true, duration = 3500) {
    displayNotification(id, message, 'completion', { isSuccess, duration });
}

function showTemporaryMessage(id, message, isSuccess = true, duration = 3500) {
    displayNotification(id, message, 'temporary', { isSuccess, duration });
}

function hideCurrentNotification(idToHide) {
    if (currentNotification.element && currentNotification.id === idToHide) {
        const element = currentNotification.element;
        element.classList.remove('show');
        if (currentNotification.timeoutId) {
            clearTimeout(currentNotification.timeoutId);
            currentNotification.timeoutId = null;
        }
        setTimeout(() => {
            if (element.parentNode) {
                element.remove();
            }
            if (currentNotification.element === element) {
                currentNotification.element = null;
                currentNotification.id = null;
                currentNotification.type = null;
            }
        }, 400);
    }
}

function getCurrentDomain() {
    const hostname = window.location.hostname;
    const protocol = window.location.protocol;
    return `${protocol}//${hostname}`;
}

async function loadCard(cardId) {
    const cacheKey = 'cardId: ' + cardId;
    let card = await getCard(cacheKey) ?? {};
    if (Object.keys(card).length) {
        return card;
    }
    const currentDomain = getCurrentDomain();
    let popularityCount = 0, needCount = 0, tradeCount = 0, rankText = '';
    try {
        await sleep(DELAY);
        const mainCardPageResponse = await fetch(`${currentDomain}/cards/${cardId}/users/`);
        if (mainCardPageResponse.ok) {
            const mainCardPageHtml = await mainCardPageResponse.text();
            const mainCardPageDoc = new DOMParser().parseFromString(mainCardPageHtml, 'text/html');
            rankText = mainCardPageDoc.querySelector('.ncard__rank')?.innerText.replace('Редкость\n','').trim() || '';
            popularityCount = parseInt(mainCardPageDoc.querySelector('#owners-count')?.textContent.trim(), 10) || 0;
            needCount = parseInt(mainCardPageDoc.querySelector('#owners-need')?.textContent.trim(), 10) || 0;
            tradeCount = parseInt(mainCardPageDoc.querySelector('#owners-trade')?.textContent.trim(), 10) || 0;
        } else {
            console.error(`Failed to fetch card page ${currentDomain}/cards/${cardId}/users/. Status: ${mainCardPageResponse.status}`);
        }
    } catch (error) {
        console.error(`Error in loadCard for cardId ${cardId}:`, error);
    }
    card = { popularityCount, needCount, tradeCount, rankText };
    await cacheCard(cacheKey, card);
    return card;
}

async function updateCardInfo(cardId, element) {
    if (!cardId || !element) return;
    try {
        const cardData = await loadCard(cardId);
        const oldStats = element.querySelector('.card-stats');
        if (oldStats) oldStats.remove();
        const stats = document.createElement('div');
        stats.className = 'card-stats';
        stats.innerHTML = `
            <span title="Редкость: ${cardData.rankText || 'N/A'} | Владельцев"><i class="fas fa-users"></i> ${cardData.popularityCount}</span>
            <span title="Хотят получить"><i class="fas fa-heart"></i> ${cardData.needCount}</span>
            <span title="Готовы обменять"><i class="fas fa-sync-alt"></i> ${cardData.tradeCount}</span>
        `;
        element.appendChild(stats);
    } catch (error) {
        console.error("Error in updateCardInfo for cardId " + cardId + ":", error);
    }
}

function clearMarkFromCards() { cleanByClass('div-marked'); }
function removeAllLinkIcons() { cleanByClass('link-icon'); }
function cleanByClass(className) { document.querySelectorAll('.' + className).forEach(item => item.remove()); }

function getCardsOnPage() {
    return Array.from(document.querySelectorAll(cardClasses)).filter(cardEl => cardEl.offsetParent !== null);
}

async function processCards() {
    if (isCardRemeltPage()) {
        const storedData = JSON.parse(localStorage.getItem('animeCardsData')) || {};
        if (Object.keys(storedData).length < 1) { await readyRemeltCards(); return; }
    }
    removeMatchingWatchlistItems(); removeAllLinkIcons(); clearMarkFromCards();
    const cardsOnPage = getCardsOnPage();
    let totalCardsToProcess = cardsOnPage.length;
    if (!totalCardsToProcess) return;

    const buttonId = 'processCards';
    startAnimation(buttonId);
    displayNotification(buttonId, 'Проверка спроса:', 'progress', { current: 0, total: totalCardsToProcess, sticky: true });
    let processedCardCount = 0;

    for (const cardElement of cardsOnPage) {
        if (cardElement.classList.contains('trade__inventory-item--lock') || cardElement.classList.contains('remelt__inventory-item--lock')) continue;
        cardElement.classList.add('processing-card');
        let cardId = await getCardId(cardElement);
        if (cardId) {
            await updateCardInfo(cardId, cardElement);
        }
        processedCardCount++;
        updateNotificationProgress(buttonId, 'Проверено карт:', processedCardCount, totalCardsToProcess);
        cardElement.classList.remove('processing-card');
        if (cardElement.classList.contains('lootbox__card')) cardElement.addEventListener('click', removeAllLinkIcons);
    }
    completeProgressNotification(buttonId, 'Проверка спроса завершена', true);
    stopAnimation(buttonId);
}

function removeMatchingWatchlistItems() {
    const watchlistItems = document.querySelectorAll('.watchlist__item');
    if (watchlistItems.length == 0) return;
    let initialCount = watchlistItems.length;
    watchlistItems.forEach(item => {
        const episodesText = item.querySelector('.watchlist__episodes')?.textContent.trim();
        if (episodesText) {
            const matches = episodesText.match(/[\d]+/g);
            if (matches) {
                const currentEpisode = parseInt(matches[0], 10);
                const totalEpisodes = parseInt(matches.length === 4 ? matches[3] : matches[1], 10);
                if (currentEpisode === totalEpisodes) item.remove();
            }
        }
    });
    let currentCount = document.querySelectorAll('.watchlist__item').length;
    if (initialCount > currentCount) {
        showTemporaryMessage('watchlistUpdate', `Из списка удалены просмотренные аниме. Осталось: ${currentCount}`, true);
    }
}

function startAnimation(id) {
    const buttonElement = document.getElementById(id);
    if (buttonElement) {
        buttonElement.classList.add('is-working');
        buttonElement.style.animationPlayState = 'paused';
        const iconElement = buttonElement.querySelector('span[class*="fa-"]');
        if (iconElement) {
            iconElement.style.animation = 'pulseIcon 1s ease-in-out infinite';
        }
    }
}

function stopAnimation(id) {
    const buttonElement = document.getElementById(id);
    if (buttonElement) {
        buttonElement.classList.remove('is-working');
        if (!buttonElement.matches(':hover')) {
             buttonElement.style.animationPlayState = 'running';
        }
        const iconElement = buttonElement.querySelector('span[class*="fa-"]');
        if (iconElement) {
            iconElement.style.animation = '';
        }
    }
}

function getButton(id, iconClassFASuffix, percent, tooltipText, clickFunction) {
    const wrapper = document.createElement('div');
    wrapper.style.position = 'fixed';
    wrapper.style.top = percent + '%';
    wrapper.style.right = '1%';
    wrapper.style.zIndex = '1000';

    const buttonElement = document.createElement('button');
    buttonElement.id = id;
    buttonElement.classList.add('anim-interactive-button');

    const icon = document.createElement('span');
    icon.className = 'fal fa-' + iconClassFASuffix;
    buttonElement.appendChild(icon);

    let tooltipTimeout;
    const tooltip = document.createElement('div');
    tooltip.className = 'anim-button-tooltip';
    tooltip.textContent = tooltipText;

    buttonElement.addEventListener('mouseenter', () => {
        tooltip.style.opacity = '1';
        tooltip.style.transform = 'translateY(-50%) translateX(0px)';
        buttonElement.style.animationPlayState = 'paused';
        if (tooltipTimeout) clearTimeout(tooltipTimeout);
    });

    buttonElement.addEventListener('mouseleave', () => {
        tooltip.style.opacity = '0';
        tooltip.style.transform = 'translateY(-50%) translateX(10px)';
        if (!buttonElement.classList.contains('is-working')) {
            buttonElement.style.animationPlayState = 'running';
        }
    });

    buttonElement.addEventListener('click', (e) => {
        e.stopPropagation();
        clickFunction(e);
        if (window.innerWidth <= 768) {
            tooltip.style.opacity = '1';
            tooltip.style.transform = 'translateY(-50%) translateX(0px)';
            if (tooltipTimeout) clearTimeout(tooltipTimeout);
            tooltipTimeout = setTimeout(() => {
                tooltip.style.opacity = '0';
                tooltip.style.transform = 'translateY(-50%) translateX(10px)';
            }, 1500);
        }
    });

    wrapper.appendChild(buttonElement);
    wrapper.appendChild(tooltip);
    return wrapper;
}

function addUpdateButton() {
    if (window.location.pathname.includes('/pm/') || window.location.pathname.includes('emotions.php') || window.frameElement) return;
    if (!document.getElementById('processCards')) {
        document.body.appendChild(getButton('processCards', 'star', 37, 'Узнать спрос', processCards));
    }
    if (isMyCardPage() && !document.getElementById('readyToCharge')) {
        document.body.appendChild(getButton('readyToCharge', 'handshake', 50, 'Отметить всё как "Готов обменять"', readyToCharge));
    }
    if (isCardRemeltPage() && !document.getElementById('readyRemeltCards')) {
        document.body.appendChild(getButton('readyRemeltCards', 'yin-yang', 50, 'Кешировать карточки', readyRemeltCards));
    }
    if (!document.getElementById('clearCacheButton')) {
        document.body.appendChild(getButton('clearCacheButton', 'trash', 63, 'Очистить кеш карт', clearCardCache));
    }
}

function isMyCardPage() { return (/^\/user\/(.*)\/cards(\/page\/\d+\/)?/).test(window.location.pathname); }
function isCardRemeltPage() { return (/^\/cards_remelt\//).test(window.location.pathname); }

async function readyRemeltCards() {
    const buttonId = 'readyRemeltCards';
    const notificationId = 'remeltCache';
    showTemporaryMessage(notificationId, 'Запрос на кеширование всех карт...', true, 2000);
    const linkElement = document.querySelector('a.button.button--left-icon.mr-3');
    const href = linkElement ? linkElement.href : null;
    if (!href) { showTemporaryMessage(notificationId, 'Не найдена ссылка для начала кеширования.', false); return; }
    removeMatchingWatchlistItems(); removeAllLinkIcons(); clearMarkFromCards();
    startAnimation(buttonId);
    await scrapeAllPages(href);
    stopAnimation(buttonId);
}

async function scrapeAllPages(firstPageHref) {
    const notificationId = 'scrapeAllPages';
    try {
        const response = await fetch(firstPageHref);
        if (!response.ok) throw new Error(`Ошибка HTTP: ${response.status}`);
        const firstPageHtml = await response.text();
        const firstPageDoc = new DOMParser().parseFromString(firstPageHtml, 'text/html');
        const pagination = firstPageDoc.querySelector('#pagination');
        let storedData = JSON.parse(localStorage.getItem('animeCardsData')) || {};
        let totalCardsInProfile = -1;
        const countCurrentlyCachedCards = () => Object.values(storedData).reduce((sum, userCards) => sum + (Array.isArray(userCards) ? userCards.length : 0), 0);
        let processedCardsCount = 0;
        const titleElement = firstPageDoc.querySelector('h1.secondary-title.text-center');
        if (titleElement) { const match = titleElement.textContent.match(/\((\d+)\s*шт\.\)/); if (match) totalCardsInProfile = parseInt(match[1], 10); }
        if (totalCardsInProfile !== -1) {
            let currentTotalCached = countCurrentlyCachedCards();
            if (totalCardsInProfile === currentTotalCached && currentTotalCached > 0) {
                showTemporaryMessage(notificationId, 'Кеш карточек уже актуален.', true);
                await processCards(); return;
            }
        }
        displayNotification(notificationId, 'Кеширование страниц:', 'progress', { current: countCurrentlyCachedCards(), total: totalCardsInProfile > 0 ? totalCardsInProfile : 'неизвестно', sticky: true });
        async function processBatchInStorage(doc) {
            const cardsElements = doc.querySelectorAll('.anime-cards__item');
            let newCardsThisBatch = 0;
            for (let i = 0; i < cardsElements.length; i += 10) {
                const cardGroup = Array.from(cardsElements).slice(i, i + 10);
                for (const cardEl of cardGroup) {
                    const cardId = cardEl.getAttribute('data-id'), ownerId = cardEl.getAttribute('data-owner-id');
                    if (!ownerId || !cardId) continue;
                    const ownerKey = 'o_' + ownerId;
                    if (!storedData[ownerKey]) storedData[ownerKey] = [];
                    if (!storedData[ownerKey].find(c => c.cardId === cardId && c.ownerId === ownerId)) {
                        storedData[ownerKey].push({ cardId, name: cardEl.getAttribute('data-name'), rank: cardEl.getAttribute('data-rank'), animeLink: cardEl.getAttribute('data-anime-link'), image: cardEl.getAttribute('data-image'), ownerId });
                        newCardsThisBatch++;
                    }
                }
                await sleep(10);
            }
            if (newCardsThisBatch > 0)  processedCardsCount += newCardsThisBatch;
            updateNotificationProgress(notificationId, 'Кешировано карт:', countCurrentlyCachedCards(), totalCardsInProfile > 0 ? totalCardsInProfile : 'неизвестно');
        }
        await processBatchInStorage(firstPageDoc);
        if (pagination) {
            const pageLinks = Array.from(pagination.querySelectorAll('a[href*="page/"]'));
            let lastPageNumber = 1;
            if (pageLinks.length > 0) {
                const numbers = pageLinks.map(a => (a.getAttribute('href')?.match(/page\/(\d+)/) || [])[1]).map(n => parseInt(n, 10)).filter(n => n > 0);
                if (numbers.length > 0) lastPageNumber = Math.max(...numbers);
                else { const lastLinkText = pagination.querySelector('a:last-of-type:not(.pagination__item--next)'); if(lastLinkText) lastPageNumber = parseInt(lastLinkText.textContent.trim(), 10) || 1; }
            }
            if (lastPageNumber > 1) {
                const parser = new DOMParser();
                let basePageUrl = firstPageHref.replace(/\/page\/\d+(\/)?$/, '').replace(/\/$/, '');
                for (let i = 2; i <= lastPageNumber; i++) {
                    const pageUrl = `${basePageUrl}/page/${i}/`;
                    const pageHTML = await (async (url) => { try { const r = await fetch(url); return r.ok ? await r.text() : null; } catch (e) { console.error(`Workspace error ${url}:`, e); return null; } })(pageUrl);
                    if (pageHTML) await processBatchInStorage(parser.parseFromString(pageHTML, 'text/html'));
                    await sleep(1000 + Math.random() * 1500);
                    if (i % 5 === 0) localStorage.setItem('animeCardsData', JSON.stringify(storedData));
                }
            }
        }
        localStorage.setItem('animeCardsData', JSON.stringify(storedData));
        completeProgressNotification(notificationId, 'Кеширование завершено. Всего в кеше: ' + countCurrentlyCachedCards(), true);
        await processCards();
    } catch (error) {
        console.error("Ошибка в scrapeAllPages:", error);
        completeProgressNotification(notificationId, 'Ошибка кеширования страниц.', false);
    }
}

async function getCardId(cardElement) {
    let cardId = cardElement.getAttribute('card-id') || cardElement.getAttribute('data-card-id') || cardElement.getAttribute('data-id');
    const href = cardElement.getAttribute('href');
    if (href) { const cardIdMatch = href.match(/\/cards\/(\d+)\/users\//); if (cardIdMatch) cardId = cardIdMatch[1]; }
    if (cardId) { const cardByOwner = await getFirstCardByOwner(cardId); if (cardByOwner?.cardId) cardId = cardByOwner.cardId; }
    return cardId;
}

async function getFirstCardByOwner(ownerId) {
    const storedData = JSON.parse(localStorage.getItem('animeCardsData')) || {};
    const key = 'o_' + ownerId;
    return storedData[key]?.[0] || null;
}

async function readyToCharge() {
    const buttonId = 'readyToCharge';
    displayNotification(buttonId, 'Подготовка к отметке карт...', 'progress', {sticky: true});
    let cardsOnPage = getCardsOnPage();
    if (!cardsOnPage || cardsOnPage.length === 0) { completeProgressNotification(buttonId, 'Карты на странице не найдены.', false); return; }
    const cardsToProcess = cardsOnPage.filter(cardEl => !cardEl.classList.contains('trade__inventory-item--lock'));
    const totalCardsToProcess = cardsToProcess.length;
    if (totalCardsToProcess === 0) { completeProgressNotification(buttonId, 'Нет карт для отметки.', false); return; }
    updateNotificationProgress(buttonId, 'Отмечаем карт:', 0, totalCardsToProcess);
    startAnimation(buttonId); clearMarkFromCards(); cardCounter = 0;
    let successfullyProcessedCount = 0, attemptedToProcessCount = 0;
    for (const cardElement of cardsToProcess) {
        cardElement.classList.add('charging-card');
        let idToSend = cardElement.getAttribute('data-owner-id') || await getCardId(cardElement);
        attemptedToProcessCount++;
        if (idToSend) {
            await sleep(1000 + Math.random() * 500);
            try { if (await readyToChargeCard(idToSend)) successfullyProcessedCount++; }
            catch (error) { console.error("Ошибка при отметке карты " + idToSend + ":", error); }
        }
        updateNotificationProgress(buttonId, 'Обработано карт:', attemptedToProcessCount, totalCardsToProcess);
        cardElement.classList.remove('charging-card');
    }
    completeProgressNotification(buttonId, `Отправлено на обмен ${cardCounter} из ${successfullyProcessedCount} (${attemptedToProcessCount} попыток).`, true, 5000);
    stopAnimation(buttonId);
}

async function readyToChargeCard(card_id_to_send) {
    try {
        await sleep(DELAY * 2 + Math.random() * DELAY);
        const data = await $.ajax({ url: "/engine/ajax/controller.php?mod=trade_ajax", type: "post", data: { action: "propose_add", type: 1, card_id: card_id_to_send, user_hash: dle_login_hash }, dataType: "json", cache: false });
        if (data?.error) {
            if (data.error === 'Слишком часто, подождите пару секунд и повторите действие') {
                await sleep(2500 + Math.random() * 1000); return await readyToChargeCard(card_id_to_send);
            }
            console.warn(`Ошибка от сервера (карта ${card_id_to_send}): ${data.error}`); return false;
        }
        if (data?.status == "added") { cardCounter++; return true; }
        if (data?.status == "deleted") { await sleep(1000); return await readyToChargeCard(card_id_to_send); }
        console.warn(`Неожиданный ответ от сервера для карты ${card_id_to_send}:`, data); return false;
    } catch (e) {
        console.error(`readyToChargeCard AJAX/исключение (ID ${card_id_to_send}):`, e.statusText || e.message || e);
        return false;
    }
}

const style = document.createElement('style');
style.textContent = `
@keyframes glowEffect {0% { box-shadow: 0 0 5px #6c5ce7; } 50% { box-shadow: 0 0 20px #6c5ce7; } 100% { box-shadow: 0 0 5px #6c5ce7; }}
@keyframes glowChargeEffect {0% { box-shadow: 0 0 7px #4CAF50; } 50% { box-shadow: 0 0 25px #4CAF50; } 100% { box-shadow: 0 0 7px #4CAF50; }}
@keyframes fadeInUp {from {opacity: 0; transform: translateY(10px);} to {opacity: 1; transform: translateY(0);}}
@keyframes breatheShadowInteractive { 0% { box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); transform: scale(1); } 50% { box-shadow: 0 5px 15px rgba(108, 92, 231, 0.4); transform: scale(1.02); } 100% { box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); transform: scale(1); } }
@keyframes pulseWorkingBorderInteractive { 0% { box-shadow: 0 0 0 0px rgba(86, 200, 239, 0.7), 0 3px 8px rgba(0,0,0,0.25); } 70% { box-shadow: 0 0 0 10px rgba(86, 200, 239, 0), 0 5px 12px rgba(0,0,0,0.3); } 100% { box-shadow: 0 0 0 0px rgba(86, 200, 239, 0), 0 3px 8px rgba(0,0,0,0.25); } }
@keyframes pulseIcon { 0% { transform: scale(1) rotate(0deg); opacity: 1; } 50% { transform: scale(1.2) rotate(0deg); opacity: 0.7; } 100% { transform: scale(1) rotate(0deg); opacity: 1; } }
@keyframes cardHelperSpin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }

.processing-card {position: relative;}
.processing-card img {position: relative; z-index: 2;}
.processing-card::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; max-height: calc(100% - 30px); border-radius: 8px; z-index: 1; animation: glowEffect 1.5s infinite; pointer-events: none; }
.charging-card {position: relative;}
.charging-card img {position: relative; z-index: 2;}
.charging-card::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; max-height: calc(100% - 30px); border-radius: 8px; z-index: 1; animation: glowChargeEffect 1.5s infinite; pointer-events: none; }

.card-stats {position: relative; background: linear-gradient(45deg, #6c5ce7, #a367dc); padding: 8px; color: white; font-size: 12px; margin-top: 5px; border-radius: 5px; display: flex; justify-content: space-between; align-items: center; text-shadow: 1px 1px 2px rgba(0,0,0,0.3); animation: fadeInUp 0.3s ease; z-index: 0 !important;}
.history__inner {max-width: 1200px !important; margin: 0 auto !important; padding: 15px !important;}
.history__item {background: rgba(108, 92, 231, 0.05) !important; border-radius: 10px !important; padding: 20px !important; margin-bottom: 20px !important;}
.history__body {display: flex !important; flex-wrap: wrap !important; gap: 15px !important; padding: 15px !important; border-radius: 8px !important;}
.history__body--gained {background: rgba(46, 213, 115, 0.1) !important; margin-bottom: 10px !important;}
.history__body--lost {background: rgba(255, 71, 87, 0.1) !important;}
@media screen and (min-width: 769px) {.history__body-item {width: 120px !important; height: auto !important; transition: transform 0.2s !important;} .history__body-item img {width: 120px !important; height: auto !important; border-radius: 8px !important; box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important;}}
.history__body-item:hover {transform: scale(1.05) !important; z-index: 2 !important;}
.card-stats span {display: flex; align-items: center; gap: 4px;}
.card-stats span i {font-size: 14px;}
.lootbox__card {position: relative !important; transform: scale(0.85) !important; margin-top: -15px !important; margin-bottom: 35px !important;}
.lootbox__card .card-stats {position: absolute !important; bottom: -35px !important; left: 0 !important; right: 0 !important; margin: 0; padding: 8px !important; border-radius: 5px; z-index: 9999 !important; background: linear-gradient(45deg, #6c5ce7, #a367dc) !important; font-size: 16px !important; width: 100% !important; transform: none !important; text-rendering: optimizeLegibility !important; -webkit-font-smoothing: antialiased !important;}
.lootbox__card .card-stats span {color: white !important; text-shadow: 1px 1px 2px rgba(0,0,0,0.3) !important; padding: 0 8px !important; flex: 1; text-align: center; font-weight: 500 !important;}
.lootbox__card .card-stats i {color: white !important; font-size: 16px !important; margin-right: 4px;}
.lootbox__list {gap: 25px !important; padding-bottom: 20px !important;}

@media screen and (max-width: 768px) {
    .history__body-item, .history__body-item img {width: 100px !important;}
    .processing-card::before, .charging-card::before {top: -1px !important; left: -1px !important; right: -1px !important; bottom: -1px !important; opacity: 0.5 !important;}
    div[style*="position: fixed"][style*="right: 1%"] { transform: scale(0.9); transform-origin: bottom right; }
    .anim-interactive-button { width: 40px !important; height: 40px !important; }
    .anim-interactive-button span[class*="fa-"] { font-size: 18px !important; }
    .anim-button-tooltip { font-size: 11px !important; padding: 5px 8px !important; }
    .card-stats {font-size: 10px !important; padding: 4px !important;} .card-stats span i {font-size: 12px !important;}
    .remelt__inventory-list {grid-template-columns: repeat(2, 1fr) !important; gap: 10px !important;}
    .remelt__inventory-item {width: 100% !important; margin: 0 !important;} .remelt__inventory-item img {width: 100% !important; height: auto !important;}
    .remelt__inventory-item .card-stats {width: 100% !important; margin-top: 4px !important;}
    .lootbox__card {transform: scale(0.8) !important; margin-top: -20px !important; margin-bottom: 30px !important;}
}

.anim-interactive-button { background-color: #6c5ce7; color: #fff; border: none; border-radius: 50%; width: 45px; height: 45px; padding: 0; cursor: pointer; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275); display: flex; justify-content: center; align-items: center; animation: breatheShadowInteractive 2.5s infinite ease-in-out; outline: none; position: relative; }
.anim-interactive-button span[class*="fa-"] { display: inline-block; font-size: 20px; transition: transform 0.25s ease-out; }
.anim-interactive-button:hover { background-color: #5f51e3; transform: scale(1.12) translateY(-3px); box-shadow: 0 7px 18px rgba(0, 0, 0, 0.25); }
.anim-interactive-button:hover span[class*="fa-"] { transform: rotate(18deg); }
.anim-interactive-button:active { background-color: #5245c9; transform: scale(0.93) translateY(0px); box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); transition-duration: 0.08s; }
.anim-interactive-button:active span[class*="fa-"] { transform: rotate(-8deg) scale(0.88); }
.anim-interactive-button.is-working { animation: pulseWorkingBorderInteractive 1s infinite ease-in-out, breatheShadowInteractive 2.5s infinite ease-in-out paused !important; }
.anim-interactive-button.is-working:hover { transform: scale(1.05) translateY(-1px); }

.anim-button-tooltip { position: absolute; right: calc(100% + 10px); top: 50%; transform: translateY(-50%) translateX(10px); background-color: #2d3436; color: #fff; padding: 8px 12px; border-radius: 4px; font-size: 14px; opacity: 0; transition: opacity 0.25s ease, transform 0.25s ease; white-space: nowrap; z-index: 1001; pointer-events: none; }

.card-helper-status-notification { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background-color: #3e444c; color: #f0f0f0; padding: 10px 18px; border-radius: 6px; font-size: 14px; font-family: Arial, sans-serif; z-index: 2147483647; display: flex; align-items: center; box-shadow: 0 2px 6px rgba(0,0,0,0.25); opacity: 0; transition: opacity 0.4s ease, bottom 0.4s ease; max-width: 380px; min-width: 280px; box-sizing: border-box; }
.card-helper-status-notification.show { opacity: 1; bottom: 30px; }
.ch-status-icon-container { margin-right: 10px; display: flex; align-items: center; height: 18px; }
.card-helper-spinner { width: 16px; height: 16px; border: 2px solid #666; border-top: 2px solid #ddd; border-radius: 50%; animation: cardHelperSpin 0.8s linear infinite; }
.card-helper-checkmark, .card-helper-crossmark { font-size: 18px; line-height: 1; }
.card-helper-checkmark { color: #76c779; } .card-helper-crossmark { color: #e57373; }
.card-helper-status-text { white-space: normal; text-align: left; line-height: 1.3; }
`;
document.head.appendChild(style);

function clearIcons() {
    $('.card-notification:first')?.click();
}

function autoRepeatCheck() {
    clearIcons();
    checkGiftCard(document);
    const volumeButton = document.querySelector('.adv_volume.volume_on');
    if (volumeButton) {
        volumeButton.click();
    }
}

async function checkGiftCard(doc) {
    const button = doc.querySelector('#gift-icon');
    if (!button) return;
    const giftCode = button.getAttribute('data-code');
    if (!giftCode) return;
    try {
        const response = await fetch('/engine/ajax/controller.php?mod=gift_code_game', {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams({ code: giftCode, user_hash: dle_login_hash })
        });
        const data = await response.json();
        if (data.status === 'ok') {
            showTemporaryMessage('giftStatus', data.text, true);
            button.remove();
        } else if (data.text) {
            showTemporaryMessage('giftStatus', data.text, false);
        }
    } catch (error) {
        console.error("Error checking gift card:", error);
        showTemporaryMessage('giftError', "Ошибка проверки гифт карты.", false);
    }
}

async function startPing() {
    const userHash = window.dle_login_hash;
    if (!userHash) return;
    try {
        await sleep(DELAY * 3);
        await $.ajax({
            url: "/engine/ajax/controller.php?mod=user_count_timer",
            type: "post", data: { user_hash: userHash }, dataType: "json", cache: false
        });
    } catch (e) {
        console.error("Error in startPing:", e.statusText || e.message || e);
    }
}

async function checkNewCard() {
    let userHash = window.dle_login_hash;
    if (!userHash) {
        setTimeout(() => { userHash = window.dle_login_hash; if (userHash) checkNewCard(); }, 2000);
        return;
    }
    const localStorageKey = 'checkCardStopped' + userHash;
    const currentHourMarker = new Date().toISOString().slice(0, 13);
    if (localStorage.getItem(localStorageKey) === currentHourMarker) return;
    try {
        await sleep(DELAY * 3);
        const data = await $.ajax({
            url: "/engine/ajax/controller.php?mod=reward_card",
            type: "post", data: { action: "check_reward", user_hash: userHash }, dataType: "json", cache: false
        });
        if (data.stop_reward === "yes") {
            localStorage.setItem(localStorageKey, currentHourMarker);
            return;
        }
        if (data?.cards?.owner_id) {
            if (data.cards.name) {
                showTemporaryMessage('newCardReceived', 'Получена новая карта: ' + data.cards.name, true, 5000);
            }
            await $.ajax({
                url: "/engine/ajax/controller.php?mod=cards_ajax",
                type: "post", data: { action: "take_card", owner_id: data.cards.owner_id, user_hash: userHash }, dataType: "json", cache: false
            });
        }
    } catch (e) {
        console.error("Error in checkNewCard:", e.statusText || e.message || e);
    }
}

async function setCache(key, data, baseTtlInSeconds = 86400) {
    const jitterPercent = 0.10;
    const jitter = Math.round(baseTtlInSeconds * jitterPercent * (Math.random() * 2 - 1));
    const finalTtlInSeconds = baseTtlInSeconds + jitter;
    const expires = Date.now() + finalTtlInSeconds * 1000;
    const cacheData = { data, expires };
    try {
        localStorage.setItem(key, JSON.stringify(cacheData));
    } catch (e) {
        console.error("Ошибка при записи в localStorage (возможно, переполнен):", e);
        showTemporaryMessage('localStorageError', 'Ошибка записи в localStorage.', false);
    }
}

async function getCache(key) {
    const cacheDataJSON = localStorage.getItem(key);
    if (!cacheDataJSON) return null;
    try {
        const cacheData = JSON.parse(cacheDataJSON);
        if (!cacheData || typeof cacheData !== 'object' || !cacheData.expires || !('data' in cacheData) || Date.now() > cacheData.expires) {
             localStorage.removeItem(key); return null;
        }
        return cacheData.data;
    } catch (e) {
        localStorage.removeItem(key); return null;
    }
}

async function cacheCard(key, data) { await setCache(key, data); }
async function getCard(key) { return await getCache(key); }

function clearCardCache() {
    let clearedCount = 0, animeCardsDataCleared = false;
    Object.keys(localStorage).forEach(key => {
        if (key.startsWith('cardId: ')) {
            try { const parsed = JSON.parse(localStorage.getItem(key)); if (parsed?.data && parsed.expires) { localStorage.removeItem(key); clearedCount++; } } catch (e) {}
        } else if (key === 'animeCardsData') { localStorage.removeItem(key); animeCardsDataCleared = true; }
    });
    showTemporaryMessage('cacheCleared', `Очищено ${clearedCount} карт. ${animeCardsDataCleared ? "Общий кеш очищен." : ""}`, true);
}

(function() {
    'use strict';
    function initializeScript() {
        if (typeof $ === 'undefined') { console.error("jQuery не найден."); showTemporaryMessage('jQueryError', 'jQuery не найден!', false, 10000); }
        if (typeof dle_login_hash === 'undefined') console.warn("dle_login_hash не определена.");
        addUpdateButton();
        setInterval(autoRepeatCheck, 2000); setInterval(startPing, 31000); setInterval(checkNewCard, 10000);
        $('#tg-banner')?.remove(); try { localStorage.setItem('notify18', 'closed'); localStorage.setItem('hideTelegramAs', 'true'); } catch (e) {}
    }
    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initializeScript);
    else initializeScript();
})();