Greasy Fork镜像 支持简体中文。

Disconnect Tools

Advanced Tools For VRChat Website

// ==UserScript==
// @name         Disconnect Tools
// @namespace    http://tampermonkey.net/
// @license      MIT
// @version      2025-04-03v0.9.3
// @description  Advanced Tools For VRChat Website
// @author       Disconnect3301
// @match        https://*.vrchat.com/*
// @grant        GM_addStyle
// @grant        GM_cookie
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @run-at       document-idle
// ==/UserScript==

'use strict';

let savedAvatars = GM_getValue('savedAvatars', []) || [];
let sortSettings = GM_getValue('sortSettings', { sortBy: 'default', isReversed: false });
let isCustomFavoritesMenuOpen = false;

let homeContentElement = null;
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;

const addMetod = {
    current : 'current',
    ID : 'ID'
}
let authorName = null;
let authorId = null;
let created_at = null;
let description = null;
let avatarID = null;
let imageUrl = null;
let avatarName = null;
let releaseStatus = null;
let updated_at = null;
let version = null;

let userId = null;
let authCookie = null;

GM_addStyle(`
    #notification-container {
        position: fixed;
        top: 20px;
        right: 20px;
        z-index: 3050;
        display: flex;
        flex-direction: column-reverse;
        align-items: flex-end;
        gap: 10px;
        pointer-events: none;
        transition: all .15s ease-in-out;
    }
    .notification {
        position: relative;
        min-width: 280px;
        max-width: 400px;
        width: auto;
        padding: 18px 35px 18px 20px;
        border-radius: 12px;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
        background: linear-gradient(135deg, #2d2d2d, #242424);
        color: #e0e0e0;
        font-family: 'Segoe UI', system-ui, sans-serif;
        font-size: 14px;
        line-height: 1.5;
        cursor: pointer;
        transform: translateY(-20px);
        opacity: 0;
        transition: all 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55);
        pointer-events: auto;
        overflow: hidden;
        border: 1px solid transparent;
        display: flex;
        flex-direction: row;
        align-items: center;
        gap: 10px;
    }
    .notification.show {
        transform: translateY(0);
        opacity: 1;
    }
    .notification.hide {
        height: 0 !important; /* Схлопываем высоту */
        padding: 0 !important; /* Убираем отступы */
        margin: 0 !important; /* Убираем внешние отступы */
        opacity: 0;
        transform: translateY(-20px);
        border-radius: 0;
        box-shadow: none;
        pointer-events: none;
    }
    .notification.info {
        border-left: 5px solid #2196F3;
        background-color: #2196F31A;
    }
    .notification.success {
        border-left: 5px solid #4CAF50;
        background-color: #4CAF501A;
    }
    .notification.warning {
        border-left: 5px solid #FFA726;
        background-color: #FFA7261A;
    }
    .notification.error {
        border-left: 5px solid #F44336;
        background-color: #F443361A;
    }
    .status-icon {
        font-size: 20px;
        opacity: 0.9;
    }
    .message {
        flex-grow: 1;
        overflow: hidden;
        text-overflow: ellipsis;
        word-wrap: break-word;
    }
    .progress-bar {
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%; /* Занимает всю ширину */
        height: 3px; /* Фиксированная высота */
        background: linear-gradient(90deg, #ddd, #bbb);
        transform-origin: left;
        transform: scaleX(1); /* Начальное состояние */
        transition: transform 0.1s linear;
    }
    .notification:hover {
        transform: scale(1.02) translateY(-2px);
        box-shadow: 0 8px 18px rgba(0, 0, 0, 0.2);
    }
    @media (max-width: 480px) {
        .notification {
            width: 90vw;
            min-width: unset;
        }
    }
`);

GM_addStyle(`
    .btn-custom {
        --bs-btn-font-family: ;
        --bs-btn-font-weight: normal;
        --bs-btn-line-height: 1.25;
        --bs-btn-color: var(--bs-body-color);
        --bs-btn-bg: transparent;
        --bs-btn-border-width: 1px;
        --bs-btn-border-color: transparent;
        --bs-btn-hover-border-color: transparent;
        --bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(10, 10, 13, 0.075);
        --bs-btn-disabled-opacity: 0.65;
        --bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);
        display: inline-block;
        padding: var(--bs-btn-padding-y) var(--bs-btn-padding-x);
        font-family: var(--bs-btn-font-family);
        font-size: var(--bs-btn-font-size);
        font-weight: var(--bs-btn-font-weight);
        line-height: var(--bs-btn-line-height);
        color: var(--bs-btn-color);
        text-align: center;
        vertical-align: middle;
        cursor: pointer;
        user-select: none;
        border: var(--bs-btn-border-width) solid var(--bs-btn-border-color);
        border-radius: var(--bs-btn-border-radius);
        background-color: var(--bs-btn-bg);
        background-image: var(--bs-gradient);
        transition: all .15s ease-in-out;
        --bs-btn-padding-y: 0.5rem;
        --bs-btn-padding-x: 1rem;
        --bs-btn-font-size: 1.25rem;
        --bs-btn-border-radius: 0.3rem;
        position: relative;
        flex: 1 1 auto;
        width: 100%;
        margin-top: calc(1px* -1);
        border-top-left-radius: 0;
        border-top-right-radius: 0;
        border-bottom-right-radius: 0;
        border-bottom-left-radius: 0;
    }
    .btn-custom:active {
        color: var(--bs-btn-active-color);
        background-color: var(--bs-btn-active-bg);
        background-image: none;
        border-color: var(--bs-btn-active-border-color);
        z-index: 1;
    }
    .btn-custom:focus-visible {
        color: var(--bs-btn-hover-color);
        background-color: var(--bs-btn-hover-bg);
        background-image: var(--bs-gradient);
        border-color: var(--bs-btn-hover-border-color);
        outline: 0;
        box-shadow: var(--bs-btn-focus-box-shadow);
    }
    .btn-custom:hover {
        color: var(--bs-btn-hover-color);
        text-decoration: none;
        background-color: var(--bs-btn-hover-bg);
    }
    .css-yjay0l-custom {
        background: rgb(7, 36, 43);
        border: 2px solid rgb(5, 60, 72);
        color: rgb(106, 227, 249);
        display: flex;
        flex-direction: row;
        place-content: start space-between;
        -webkit-box-align: center;
        align-items: center;
        -webkit-box-pack: justify;
        height: 45px;
        border-radius: 8px !important;
        box-shadow: none !important;
        padding: 0px 10px !important;
    }
    .css-yjay0l-custom:hover {
        background: rgb(7, 52, 63);
        border-color: rgb(8, 108, 132);
        transform: scale(1.1);
    }
    .css-yjay0l-custom:active {
        color: var(--bs-btn-active-color);
        background-color: var(--bs-btn-active-bg);
        background-image: none;
        border-color: var(--bs-btn-active-border-color);
        z-index: 1;
    }

    .css-1vrq36y-custom {
        border: 2px solid rgb(6, 75, 92);
        border-radius: 4px;
        background: rgb(6, 75, 92);
        color: rgb(106, 227, 249);
        padding: 5px;
        box-sizing: border-box;
        flex: 1 1 0%;
        outline: none !important;
        transition: all .15s ease-in-out;
    }
    .css-1vrq36y-custom.syncButton {
        width: 35px;
        height: 35px;
        padding: 0px;
    }
    .css-1vrq36y-custom:hover {
        border-color: rgb(8, 108, 132);
    }

    .avatarCardToggles {
        border: 2px solid rgb(6, 75, 92);
        border-radius: 4px;
        background: rgb(6, 75, 92);
        color: rgb(106, 227, 249);
        padding: 5px;
        box-sizing: border-box;
        outline: none !important;
        transition: all .15s ease-in-out;
    }
    .avatarCardToggles:hover {
        border-color: rgb(8, 108, 132);
    }
    .avatarCardToggles.Remove {
        border: 2px solid rgb(6, 75, 92);
        border-radius: 4px;
        background: rgba(255, 0, 0, 0.1); !important
        color: rgb(106, 227, 249);
        padding: 5px;
        box-sizing: border-box;
        outline: none !important;
        font-weight: bold;
    }
    .avatarCardToggles.Remove:hover {
        background: rgb(5, 25, 29) !important;
    }
`);

GM_addStyle(`
    .css-14ngdq4-custom {
        display: flex;
        background: rgb(54, 54, 54);
        border-radius: 4px;
        padding: 0.25rem 0.75rem;
        -webkit-box-align: center;
        align-items: center;
        font-weight: bold;
        font-size: 1.1rem;
        color: rgb(230, 230, 230);
    }
    .css-zjik7-custom {
        display: flex;
        align-items: center;
        justify-content: space-between;
    }
    .AvatarNameSelection {
        display: flex;
        align-items: center;
        justify-content: space-between;
        height: 20px;
    }
    .avatarCardTogglesSelection {
        display: flex;
        flex-wrap: wrap;
        justify-content: space-between;
        align-content: center;
        flex-direction: row;
        margin-top: 10px;
    }
    .css-qcqlg7-custom {
        display: flex;
        margin-bottom: 0.8rem;
        color: rgb(255, 255, 255);
        text-decoration: none;
        flex-direction: column;
        border-radius: 8px;
        background-color: rgb(24, 27, 31);
        transition: border-color 0.2s ease-in-out;
        overflow: visible;
    }
    .css-1kj6np9-custom {
        padding-top: 75%;
        height: 0px;
        overflow: hidden;
        border-radius: 8px;
        position: relative;
        display: flex;
        flex-shrink: 0;
        margin-bottom: 0.5rem;
    }
    .avatarCardImage {
        width: 100%;
        height: 100%;
        top: 0px;
        left: 0px;
        position: absolute;
        z-index: 0;
        border: 4px solid #656565a8;
    }
    .css-1brgsnm-custom {
        display: flex;
        flex-direction: column;
        padding: 0.9rem;
        background-color: rgb(37, 42, 48);
        border-color: rgb(37, 42, 48);
        border-style: solid;
        border-width: 3px 3px 0px;
        border-radius: 8px 8px 0px 0px;
    }
    .css-1106r7n-custom {
        display: flex;
        flex-direction: column;
        position: absolute;
        width: 100%;
        height: 100%;
        top: 0px;
        left: 0px;
        -webkit-box-align: center;
        align-items: center;
        -webkit-box-pack: end;
        justify-content: flex-end;
        z-index: 1;
        border-radius: 8px;
        border: 4px solid rgba(255, 255, 255, 0.184);
    }
    .css-1il99ht-custom {
        display: grid;
        grid-template-columns: 20px 1fr 1fr;
        gap: 0.25rem 1rem;
        -webkit-box-align: center;
        align-items: center;
        color: rgb(115, 115, 114);
    }
    .css-13sdljk-custom {
        position: relative;
        overflow: hidden;
        border-radius: 4px;
        display: flex;
    }
    .css-13sdljk-2-custom {
        display: flex;
        align-items: center;
        padding: 0.5rem 0.5rem;
    }
    .css-kfjcvw-custom {
        display: flex;
        flex-direction: column;
        padding: 0.9rem;
        border-style: solid;
        background-color: rgb(24, 27, 31);
        border-color: rgb(24, 27, 31);
        border-width: 0px 3px 3px;
        border-radius: 0px 0px 8px 8px;
    }
    .svg-icon {
        color: rgb(84, 181, 197);
        font-size: 20px;
        text-align: center;
        opacity: 1;
        transition: opacity 0.2s ease-in-out;
    }
    .css-1grfcoa-custom {
        display: flex;
        font-weight: bold;
        font-size: 0.85rem;
    }
    .css-so1s8h-custom {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        font-size: 0.85rem;
        white-space: nowrap;
    }
    .css-so1s8h-custom.Excellent {
        color: rgb(81, 255, 0);
    }
    .css-so1s8h-custom.Good {
        color: rgb(0, 255, 55);
    }
    .css-so1s8h-custom.Medium {
        color: rgb(255, 162, 41);
    }
    .css-so1s8h-custom.Poor {
        color: rgb(255, 84, 41);
    }
    .css-so1s8h-custom.VeryPoor {
        color: rgb(255, 0, 0);
    }
    .css-w9ziq0-custom {
        display: flex;
        margin-bottom: 0px;
        -webkit-box-align: center;
        align-items: center;
        height: 50px;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .css-1fttcpj-custom {
        display: flex;
        flex-direction: column;
    }
    .css-1bcpvc0-custom {
        display: flex;
        width: 100%;
        min-width: 10%;
        padding: 0.5rem 0.75rem;
        font-size: 1rem;
        line-height: 1.25;
        background: rgb(5, 25, 29);
        border: 2px solid rgb(5, 60, 72);
        border-radius: 4px;
        color: rgb(106, 227, 249);
        box-shadow: none;
        transition: 250ms ease-in-out;
        outline: none !important;
    }
    .css-1alc1xs-custom {
        padding: 0px;
        margin: 0px;
        color: rgb(14, 155, 177);
        outline: none !important;
    }
    .css-1alc1xs-custom:hover {
        color: rgb(9, 93, 106);
        text-decoration: none;
    }
    .css-1yw163h-custom {
        font-size: 1.2em;
        margin-top: 0.25rem;
        word-break: break-all;
        text-align: left;
        margin-bottom: 0px;
        color: rgb(255, 255, 255);
    }
    .css-1yw163h-custom:hover {
        color: #1fd1ed;
        text-decoration: none;
    }
    .realiseStatus {
        display: flex;
        border: 1px solid var(--bs-primary);
        border-radius: 4px;
        background-color: rgba(31, 209, 237, 0);
        color: var(--bs-primary);
        padding: 2px 10px;
        margin: 2px 5px 0px 5px;
        transition: background-color 0.2s ease-in-out;
        font-size: 0.85rem;
        outline: none !important;
        cursor: default;
    }
    .realiseStatus.private{
        color: rgb(238, 84, 84);
        border-color: rgb(238, 84, 84);
    }
    .platform {
        z-index: 2;
        display: flex;
        flex-direction: row;
        gap: 0.25rem;
        border: 4px solid #505153;
        border-left-width: 0px;
        border-bottom-width: 0px;
        border-bottom-left-radius: 0.5rem;
        border-top-right-radius: 0.5rem;
        
        padding: 0.5rem;
        transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
        --tw-backdrop-saturate: saturate(2);
        
        --tw-backdrop-blur: blur(8px) !important;
        
        backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia) !important;
        transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1) !important;
        transition-duration: 150ms !important;
        top: 0px !important;
        right: 0px !important;
        position: absolute !important;
    }
    .sync {
        z-index: 2;
        display: flex;
        flex-direction: row;
        gap: 0.25rem;
        border: 4px solid #505153;
        border-right-width: 0px;
        border-bottom-width: 0px;
        // border-bottom-right-radius: 0.5rem;
        // border-top-left-radius: 0.5rem;
        
        padding: 0.2rem;
        // transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
        // --tw-backdrop-saturate: saturate(2);
        
        // --tw-backdrop-blur: blur(8px) !important;
        
        // backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia) !important;
        transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1) !important;
        transition-duration: 150ms !important;
        top: 0px !important;
        left: 0px !important;
        position: absolute !important;
    }
    .me-1-custom{
        margin-left: 0.25rem;
    }
    @keyframes modalEnter {
        0% { opacity: 0; transform: scale(1.15); }
        100% { opacity: 1; transform: scale(1); }
    }
    @keyframes modalExit {
        0% { opacity: 1; transform: scale(1); }
        100% { opacity: 0; transform: scale(1.15); }
    }
    .modal-overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.7);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 2000;
        backdrop-filter: blur(4px);
        animation: modalEnter 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    }
    .modal-overlay.exit {
        animation: modalExit 0.2s ease-out forwards;
    }
    #avatar-search-modal .modal-content {
        background: rgb(24, 27, 31);
        color: #e0e0e0;
        padding: 1.5rem;
        border-radius: 8px;
        box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
        width: 600px;
        max-height: 80vh;
        overflow-y: auto;
        border: 2px solid rgb(37, 42, 48);
        position: relative;
    }
    #avatar-search-modal h3 {
        margin: 0 0 1.25rem 0;
        font-size: 1.5rem;
        color: rgb(106, 227, 249);
        text-align: center;
        font-weight: 500;
    }
    #search-results {
        max-height: 400px;
        overflow-y: auto;
        padding: 1rem;
        border: 2px solid rgb(5, 60, 72);
        border-radius: 8px;
        background-color: rgb(24, 27, 31);

        &::-webkit-scrollbar {
            width: 8px;
        }

        &::-webkit-scrollbar-thumb {
            background-color: rgb(6, 75, 92);
            border-radius: 4px;
        }

        &::-webkit-scrollbar-track {
            background-color: rgb(37, 42, 48);
            border-radius: 4px;
        }
    }
    #avatar-search-modal #search-results {
        display: flex;
        flex-direction: column;
        align-items: center;
        max-height: 400px;
        overflow-y: auto;
        padding: 0.5rem 0;
        border-top: 1px solid rgb(5, 60, 72);
        border-bottom: 1px solid rgb(5, 60, 72);
        transition: all 0.2s ease-in-out;
    }
    .search-result-card {
        display: flex;
        align-items: center;
        background: rgb(37, 42, 48);
        padding: 0.75rem;
        border-radius: 4px;
        transition: all 0.2s ease-in-out;
    }
    .search-result-card:hover {
        background: rgb(10, 57, 69);
    }
    .search-result-card img {
        width: 65px;
        height: 50px;
        border-radius: 5px;
        margin-right: 10px;
        object-fit: cover;
        transition: all 0.2s ease-in-out;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12);
    }
    .search-result-card img:hover {
        transform: scale(2.5) translate(20%, 20%);
        box-shadow: 0 8px 10px rgb(0, 0, 0);
    }
    .search-result-card .info {
        flex-grow: 1;
    }
    .search-result-card h4 {
        margin: 0;
        font-size: 1rem;
        color: rgb(106, 227, 249);
    }
    .search-result-card p {
        margin: 0.25rem 0 0 0;
        font-size: 0.9rem;
        color: rgb(160, 160, 160);
    }
    .search-result-card button {
        padding: 0.5rem 1rem;
        border: 2px solid rgb(6, 75, 92);
        border-radius: 4px;
        background: rgb(6, 75, 92);
        color: #e0e0e0;
        font-size: 0.9rem;
        cursor: pointer;
        transition: all 0.2s ease-in-out;
        max-width: 150px;
        min-width: 150px;
    }
    .search-result-card button:hover {
        background: rgb(8, 108, 132);
        border-color: rgb(8, 108, 132);
        transform: scale(1.05);
    }
    #avatar-search-modal #close-modal-btn {
        display: block;
        margin: 1.5rem auto 0;
        padding: 0.35rem 1.5rem;
        border: 2px solid rgb(6, 75, 92);
        border-radius: 4px;
        background: rgb(6, 75, 92);
        color: #e0e0e0;
        font-size: 1rem;
        cursor: pointer;
        transition: all 0.2s ease-in-out;
    }
    #avatar-search-modal #close-modal-btn:hover {
        background: rgba(8, 108, 132, 0.25);
        border-color: rgb(8, 108, 132);
        transform: scale(1.05);
    }
    #pagination-container {
        display: flex;
        justify-content: center;
        align-items: center;
        margin-bottom: 1rem;
    }
    #pagination-container button {
        margin: 0 5px;
    }
`);

GM_addStyle(`
    .sync-modal-overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.7);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 2000;
        backdrop-filter: blur(4px);
        animation: modalEnter 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    }

    .sync-modal-overlay.exit {
        animation: modalExit 0.2s ease-out forwards;
    }

    .modal-sync {
        background: rgb(24, 27, 31);
        color: #e0e0e0;
        padding: 2rem;
        border-radius: 8px;
        box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
        width: 600px;
        max-height: 80vh;
        overflow-y: auto;
        border: 2px solid rgb(37, 42, 48);
        position: relative;
    }

    .modal-sync-title {
        margin: 0 0 1rem 0;
        font-size: 1.5rem;
        color: rgb(106, 227, 249);
        text-align: center;
        font-weight: 500;
    }

    #sync-changes-list {
        max-height: 470px;
        overflow-y: auto;
        padding: 10px;
        // margin: 1rem 0;
        // padding: 1rem;
        // background: rgb(7, 36, 43);
        // border-radius: 4px;
    }

    .sync-change-item {
        display: flex;
        align-items: center;
        padding: 0.75rem 1rem;
        border-radius: 6px;
        background: linear-gradient(135deg, rgba(20, 40, 50, 0.8), rgba(10, 25, 30, 0.8));
        box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
        margin-bottom: 0.5rem;
        transition: all 0.3s ease-in-out;
        border: 2px solid transparent;
        border-color: rgb(0, 40, 55);
    }

    .sync-change-item:hover {
        background: linear-gradient(135deg, rgba(25, 50, 60, 0.9), rgba(15, 35, 40, 0.9));
        border-color: rgba(106, 227, 249, 0.5);
        transform: scale(1.03);
    }

    .sync-change-label {
        font-size: 1rem;
        color: rgb(140, 230, 250);
        font-weight: bold;
        // margin-left: 0.5rem;
        // text-transform: capitalize;
        // letter-spacing: 0.5px;
        width: 100%;
    }

    .sync-change-checkbox {
        appearance: none;
        width: 1.5rem;
        height: 1.5rem;
        border-radius: 4px;
        background: rgb(10, 30, 35);
        border: 2px solid rgb(15, 50, 60);
        outline: none;
        cursor: pointer;
        transition: all 0.3s ease-in-out;
        position: relative;
        margin-right: 0.75rem;
    }
    .sync-change-checkbox:checked {
        // background: linear-gradient(135deg, rgb(106, 227, 249), rgb(30, 150, 170));
        background: rgb(90, 255, 60);
        border-color: rgb(255, 255, 255);
        box-shadow: 0 0 8px rgb(255, 255, 255);
    }


    .sync-field-name {
        font-size: 1rem;
        color: rgb(160, 180, 200);
        font-weight: bold;
        // margin-right: 1rem;
        // text-transform: uppercase;
        letter-spacing: 1px;
    }

    .sync-field-value {
        font-size: 0.9rem;
        color: rgb(180, 200, 220);
        word-break: break-word;
        line-height: 1.4;
        max-width: 200px;
        // white-space: nowrap;
        // overflow: hidden;
        // text-overflow: ellipsis;
        transition: all 0.2s ease-in-out;
    }

    // .sync-field-value:hover {
    //     color: rgb(140, 230, 250);
    //     text-decoration: underline;
    // }

    .modal-sync-buttons {
        display: flex;
        justify-content: flex-end;
        gap: 1rem;
        margin-top: 1rem;
    }

    #sync-apply-changes,
    #sync-cancel-changes {
        padding: 0.4rem 1.2rem;
        border: 2px solid transparent;
        border-radius: 4px;
        cursor: pointer;
        font-weight: 500;
        transition: all 0.2s ease-in-out;
        background-color: rgb(6, 75, 92);
        color: rgb(106, 227, 249);
    }

    #sync-apply-changes:hover {
        background-color: rgb(8, 108, 132);
        border-color: rgb(45, 162, 183);
        // transform: scale(1.05);
    }

    #sync-cancel-changes {
        background-color: rgb(7, 36, 43);
        color: rgb(238, 84, 84);
        border: 3px solid rgb(5, 60, 72);
    }

    #sync-cancel-changes:hover {
        background-color: rgb(5, 25, 29);
        border-color: rgb(21, 46, 63);
        // transform: scale(1.05);
    }

    @keyframes modalEnter {
        0% { opacity: 0; transform: scale(1.15); }
        100% { opacity: 1; transform: scale(1); }
    }

    @keyframes modalExit {
        0% { opacity: 1; transform: scale(1); }
        100% { opacity: 0; transform: scale(1.15); }
    }

    #sync-changes-list::-webkit-scrollbar {
        width: 8px;
    }

    #sync-changes-list::-webkit-scrollbar-track {
        background: rgb(7, 36, 43);
    }

    #sync-changes-list::-webkit-scrollbar-thumb {
        background: rgba(106, 227, 249, 0.5);
        border-radius: 4px;
    }
    .sync-change-checkbox-container {
        display: flex;
        align-items: center;
    }
    .sync-change-arrow {
        margin-top: auto;
        margin-bottom: auto;
    }
    .sync-image {
        width: 150px;
        // height: 150px;
        border-radius: 10px;
        transition: all 0.2s ease-in-out;
    }
    .sync-image:hover {
        transform: scale(1.4) translateY(-12px);
    }
`);

window.onload = async function () {
    try {
        showNotification('Initializing...', 'info');
        await Get_ID_And_Cookie();
        handleUrlChange(window.location.href);
        Button_CustomFavorites();
        showNotification('Everything is fine!', 'success');
    } catch (error) {
		showNotification(`Error while Initializing: ${error}`, 'error');
    }
};

async function Get_ID_And_Cookie() {
    try {
        authCookie = await getAuthCookieValue();
        const { responseText } = await SendRequest('GET', `https://api.vrchat.cloud/api/1/auth/user`, authCookie);
        const data = JSON.parse(responseText);
        userId = data.id;
    } catch (error) {
        showNotification(`Error while Get_ID_And_Cookie: ${error}`, 'error');
    }
}

history.pushState = function(state, title, url) {
    originalPushState.apply(history, arguments);
    handleUrlChange(url || window.location.href);
};

history.replaceState = function(state, title, url) {
    originalReplaceState.apply(history, arguments);
    handleUrlChange(url || window.location.href);
};

window.addEventListener('popstate', () => {
    handleUrlChange(window.location.href);
});

async function handleUrlChange(url) {
    try {
        const path = new URL(url, window.location.origin).pathname;
        showNotification(`Path: ${path}`, 'info');
    
        if (path === '/home/custom-favorites' && !isCustomFavoritesMenuOpen) {
            homeContentElement = await awaitForElement('.home-content');
            const firstChild = homeContentElement.firstElementChild;
            if (firstChild === null) {
                openCustomFavorites();
            } else {
                firstChild.style.display = 'none';
                openCustomFavorites();
            }
        } else if (path !== '/home/custom-favorites' && isCustomFavoritesMenuOpen) {
            homeContentElement = await awaitForElement('.home-content');
            const oldCreatedWindow = document.getElementById('custom-favorites-window');
            if (oldCreatedWindow) {
                oldCreatedWindow.remove();
            }
            if (homeContentElement.firstElementChild) {
                homeContentElement.firstElementChild.style.display = 'block';
                isCustomFavoritesMenuOpen = false;
            } else {
                showNotification('Home content element not found, trying to open again', 'error');
                handleUrlChange(url);
            }
        }
    } catch (error) {
        showNotification(
            `Error while handling URL change: ${error.message}\nStack trace: ${error.stack}`,
            'error'
        );
    }
}

async function awaitForElement(selector, timeout = 5000) {
    try {
        await awaitForLoadingToDisappear();

        const element = document.querySelector(selector);
        if (element) return element;

        return new Promise((resolve, reject) => {
            let interval, timeoutId;

            const checkElement = () => {
                const element = document.querySelector(selector);
                if (element) {
                    clearInterval(interval);
                    clearTimeout(timeoutId);
                    resolve(element);
                }
            };

            interval = setInterval(checkElement, 100);

            timeoutId = setTimeout(() => {
                clearInterval(interval);
                showNotification(`Timed out waiting for ${selector}`, 'error');
                reject(new Error(`Timed out waiting for ${selector}`));
            }, timeout);
        });
    } catch (error) {
        showNotification(`Error while waiting for ${selector}: ${error.message}`, 'error');
        throw error;
    }
}

async function awaitForLoadingToDisappear(maxWaitTime = 1000) {
    return new Promise((resolve) => {
        const startTime = Date.now();
        let timeoutId;

        const checkLoading = () => {
            const loadingElement = document.querySelector('[aria-label="Loading"]');
            
            if (!loadingElement) {
                clearTimeout(timeoutId);
                resolve();
            } else if (Date.now() - startTime > maxWaitTime) {
                clearTimeout(timeoutId);
                resolve();
            } else {
                timeoutId = setTimeout(checkLoading, 100);
            }
        };
        
        checkLoading();
    });
}

function showNotification(message, type = 'info', duration = 3000) {
    if (!document.getElementById('notification-container')) {
        const container = document.createElement('div');
        container.id = 'notification-container';
        document.body.appendChild(container);
    }
    const container = document.getElementById('notification-container');
    if (type === 'error') {
        console.error(message);
        duration = 0;
    }

    const notification = document.createElement('div');
    notification.className = `notification ${type}`;
    notification.innerHTML = `
        <div class="status-icon">${getIcon(type)}</div>
        <div class="message">${message}</div>
        <div class="progress-bar"></div>
    `;

    container.prepend(notification);

    enforceNotificationLimit(container);

    setTimeout(() => notification.classList.add('show'), 50);

    const progressBar = notification.querySelector('.progress-bar');
    if (duration > 0) {
        progressBar.style.transitionDuration = `${duration}ms`;
        requestAnimationFrame(() => {
            progressBar.style.transform = 'scaleX(0)';
        });
    } else {
        progressBar.style.display = 'none';
    }

    function close() {
        notification.classList.remove('show');
        notification.classList.add('hide');

        notification.addEventListener('transitionend', () => {
            notification.remove();
        }, { once: true });
    }

    notification.addEventListener('click', close);

    if (duration > 0) {
        setTimeout(close, duration);
    }
}

function enforceNotificationLimit(container) {
    const notifications = Array.from(container.children);
    const maxNotifications = 10;

    if (notifications.length > maxNotifications) {
        const excessCount = notifications.length - maxNotifications;
        for (let i = 0; i < excessCount; i++) {
            const oldNotification = notifications[i];
            oldNotification.classList.remove('show');
            oldNotification.classList.add('hide');
            oldNotification.addEventListener('transitionend', () => {
                oldNotification.remove();
            }, { once: true });
        }
    }
}

function getIcon(type) {
    const icons = {
        info: 'ℹ️',
        success: '✅',
        warning: '⚠️',
        error: '❌'
    };
    return icons[type] || '';
}

async function Button_CustomFavorites() {
    const button = document.createElement('a');
	button.id = 'VRChat_ButtonList';
	button.classList.add('btn-custom', 'css-yjay0l-custom');
    button.title = "Open Custom Favorite Avatars";

    const leftIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    leftIcon.setAttribute('viewBox', '0 0 60 60');
    leftIcon.setAttribute('width', '20');
    leftIcon.setAttribute('height', '20');
    leftIcon.setAttribute('fill', 'none');
    leftIcon.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
    leftIcon.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
    button.appendChild(leftIcon);

    const linearGradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
    linearGradient.setAttribute('id', 'paint0_linear_203_10527');
    linearGradient.setAttribute('gradientUnits', 'userSpaceOnUse');
    linearGradient.setAttribute('x1', '30');
    linearGradient.setAttribute('x2', '30');
    linearGradient.setAttribute('y1', '7');
    linearGradient.setAttribute('y2', '53');
    leftIcon.appendChild(linearGradient);

    const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
    stop1.setAttribute('offset', '0');
    stop1.setAttribute('stop-color', '#ce9ffc');
    linearGradient.appendChild(stop1);

    const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
    stop2.setAttribute('offset', '1');
    stop2.setAttribute('stop-color', '#7367f0');
    linearGradient.appendChild(stop2);

    const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
    g.setAttribute('fill', 'url(#paint0_linear_203_10527)');
    leftIcon.appendChild(g);

    const path1 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    path1.setAttribute('d', 'm14 44c-.803 0-1.557-.313-2.122-.88l-10.999-10.999c-.566-.564-.879-1.318-.879-2.121s.313-1.557.88-2.122l10.999-10.999c.564-.566 1.318-.879 2.121-.879 1.654 0 3 1.346 3 3 0 .803-.313 1.557-.88 2.122l-8.878 8.878 8.879 8.879c.567.564.879 1.318.879 2.121 0 1.654-1.346 3-3 3z');
    path1.setAttribute('fill', 'url(#paint0_linear_203_10527)');
    g.appendChild(path1);

    const path2 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    path2.setAttribute('d', 'm46 45c-1.654 0-3-1.346-3-3 0-.803.313-1.557.88-2.122l8.878-8.878-8.879-8.879c-.566-.566-.879-1.32-.879-2.121 0-1.654 1.346-3 3-3 .803 0 1.557.313 2.122.88l10.999 10.999c.567.566.879 1.32.879 2.121 0 .803-.313 1.557-.88 2.122l-10.999 10.999c-.564.567-1.318.879-2.121.879z');
    path2.setAttribute('fill', 'url(#paint0_linear_203_10527)');
    g.appendChild(path2);

    const path3 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    path3.setAttribute('d', 'm21 53c-1.654 0-3-1.346-3-3 0-.398.078-.787.23-1.155l18.001-40.002c.47-1.12 1.557-1.843 2.769-1.843 1.654 0 3 1.346 3 3 0 .398-.078.787-.23 1.155l-18.001 40.002c-.47 1.12-1.557 1.843-2.769 1.843z');
    path3.setAttribute('fill', 'url(#paint0_linear_203_10527)');
    g.appendChild(path3);

    const textDiv = document.createElement('div');
    textDiv.textContent = "Custom Favorites";
    button.appendChild(textDiv);

    const rightIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    rightIcon.setAttribute('aria-hidden', 'true');
    rightIcon.setAttribute('focusable', 'false');
    rightIcon.setAttribute('data-prefix', 'fas');
    rightIcon.setAttribute('data-icon', 'angle-right');
    rightIcon.classList.add('svg-inline--fa', 'fa-angle-right', 'css-1efeorg', 'e9fqopp0');
    rightIcon.setAttribute('role', 'presentation');
    rightIcon.setAttribute('viewBox', '0 0 320 512');
    rightIcon.innerHTML = '<path fill="currentColor" d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"></path>';
    button.appendChild(rightIcon);

    const targetElement = await awaitForElement('div[role="group"].w-100.css-1bfow8s.btn-group-lg.btn-group-vertical');
    showNotification(`targetElement: ${targetElement}`, 'info');
	targetElement.appendChild(button);
	targetElement.insertBefore(button, targetElement.firstChild);

    button.addEventListener('click', async () => {
        history.pushState({ page: 'custom' }, "Custom Page", "/home/custom-favorites");
        document.title = 'Custom Favorites - VRChat';
	});
}

async function openCustomFavorites() {
    try {
        const HomeContent = homeContentElement;
    
    	const MainWindow = document.createElement('div');
    	MainWindow.id = 'custom-favorites-window';
        MainWindow.classList.add('pb-5', 'css-1fttcpj-custom');
        HomeContent.appendChild(MainWindow);
    
    	const Header = document.createElement('div');
        Header.classList.add('css-zjik7-custom');
        MainWindow.appendChild(Header);
    
    	const HeaderText = document.createElement('h2');
    	HeaderText.classList.add('css-w9ziq0-custom');
    	HeaderText.textContent = "Custom Favorites";
    	Header.appendChild(HeaderText);
    
    	const SearchBar = document.createElement('div');
        SearchBar.classList.add('css-zjik7-custom');
        MainWindow.appendChild(SearchBar);

    	const UserInput = document.createElement('input');
        UserInput.type = 'text';
        UserInput.setAttribute('aria-label', 'Search Favorites');
        UserInput.placeholder = 'Search Avatars';
        UserInput.classList.add('css-1bcpvc0-custom');
        UserInput.value = '';
        UserInput.addEventListener('input', () => {
            const query = UserInput.value.trim().toLowerCase();
            filterAvatars(query);
        });
        SearchBar.appendChild(UserInput);

        const sortContainer = document.createElement('div');
        sortContainer.classList.add('d-flex', 'align-items-center', 'gap-2');
        sortContainer.style.flex = 'none';

        const searchInDBButton = document.createElement('button');
        searchInDBButton.textContent = 'Search in Database';
        searchInDBButton.classList.add('px-3', 'css-1vrq36y-custom');
        searchInDBButton.style.flex = 'none';
        searchInDBButton.style.marginLeft = '10px';
        sortContainer.appendChild(searchInDBButton);

        searchInDBButton.addEventListener('click', () => {
            const query = UserInput.value.trim();
            showAvatarSearchModal(query || undefined);
        });
        
        const sortLabel = document.createElement('span');
        sortLabel.textContent = 'Sorting By:';
        sortLabel.style.color = '#888';
        sortLabel.style.marginLeft = '0.5rem';
        sortLabel.style.marginRight = '0.5rem';
        
        const sortSelect = document.createElement('select');
        sortSelect.classList.add('css-1bcpvc0-custom');
        sortSelect.style.width = '220px';
        sortSelect.innerHTML = `
            <option value="default">Date Added</option>
            <option value="lastUpdated">Last Updated</option>
            <option value="name">Name</option>
            <option value="performance">Performance</option>
        `;
        
        const reverseButton = document.createElement('img');
        reverseButton.src = 'https://img.icons8.com/?size=28&id=Ne3MRho4pubZ&format=png&color=6ae3f9';
        reverseButton.alt = 'Reverse Icon';
        reverseButton.classList.add('css-1vrq36y-custom');
        reverseButton.style.cursor = 'pointer';
        
        sortContainer.append(sortLabel, sortSelect, reverseButton);
        SearchBar.append(UserInput, sortContainer);

        let currentSort = sortSettings.sortBy;
        let isReversed = sortSettings.isReversed;

        sortSelect.addEventListener('change', () => {
            currentSort = sortSelect.value;
            sortAvatars(currentSort, isReversed);
            saveSortSettings(currentSort, isReversed);
        });

        reverseButton.addEventListener('click', () => {
            isReversed = !isReversed;
            sortAvatars(currentSort, isReversed);
            if (isReversed) {
                reverseButton.src = 'https://img.icons8.com/?size=28&id=r1k2t6YcvxL1&format=png&color=6ae3f9';
            } else {
                reverseButton.src = 'https://img.icons8.com/?size=28&id=Ne3MRho4pubZ&format=png&color=6ae3f9';
            }
            saveSortSettings(currentSort, isReversed);
        });

        sortSelect.value = currentSort;
        reverseButton.src = isReversed 
            ? 'https://img.icons8.com/?size=28&id=r1k2t6YcvxL1&format=png&color=6ae3f9'
            : 'https://img.icons8.com/?size=28&id=Ne3MRho4pubZ&format=png&color=6ae3f9';
    
    	const DisplayFunctions = document.createElement('div');
        DisplayFunctions.classList.add('css-zjik7-custom');
        MainWindow.appendChild(DisplayFunctions);
    
    	const NamedList = document.createElement('div');
    	NamedList.classList.add('align-items-center', 'css-zjik7-custom');
    	DisplayFunctions.appendChild(NamedList);
    
    	const NameText = document.createElement('h2');
    	NameText.classList.add('css-w9ziq0-custom');
    	NameText.textContent = "Total Avatars";
    
    	const Counter = document.createElement('div');
    	Counter.classList.add('ms-2', 'css-14ngdq4-custom');
    
        const innerDiv1 = document.createElement('div');
        innerDiv1.classList.add('counter');
        innerDiv1.textContent = '0';
    
        const innerDiv2 = document.createElement('div');
        innerDiv2.classList.add('mx-1');
        innerDiv2.textContent = '/';
    
        const innerDiv3 = document.createElement('div');
        innerDiv3.textContent = '∞';
        Counter.append(innerDiv1, innerDiv2, innerDiv3);
        
    	NamedList.append(NameText, Counter);
    
    	const ButtonsSelection = document.createElement('div');
    	ButtonsSelection.classList.add('align-items-center', 'justify-content-center', 'justify-content-md-end', 'flex-column', 'flex-md-row', 'flex-1', 'css-zjik7-custom');
    	DisplayFunctions.appendChild(ButtonsSelection);
    
    	const Option1 = document.createElement('div');
    	Option1.classList.add('css-13sdljk-custom');
    	ButtonsSelection.appendChild(Option1);
    
    	const SaveCurrentAvatar = document.createElement('button');
    	SaveCurrentAvatar.textContent = 'Save Current Avatar';
    	SaveCurrentAvatar.classList.add('px-3', 'me-1-custom', 'css-1vrq36y-custom');
    	Option1.appendChild(SaveCurrentAvatar);
    	SaveCurrentAvatar.addEventListener('click', async (e) => {
        	e.preventDefault();
    		AddAvatar(addMetod.current);
    	});
    
    	const Option2 = document.createElement('div');
    	Option2.classList.add('css-13sdljk-custom');
    	ButtonsSelection.appendChild(Option2);
    
    	const SaveByID = document.createElement('button');
    	SaveByID.textContent = 'Save By ID';
    	SaveByID.classList.add('px-3', 'me-1-custom', 'css-1vrq36y-custom');
    	Option2.appendChild(SaveByID);
    	SaveByID.addEventListener('click', async (e) => {
        	e.preventDefault();
            showSaveByIDModal();
    	});

    	const AvatarsWindow = document.createElement('div');
    	AvatarsWindow.id = 'custom-avatars';
        AvatarsWindow.classList.add('tw-grid', 'tw-grid-cols-1', 'sm:tw-grid-cols-2', 'lg:tw-grid-cols-3', '3xl:tw-grid-cols-4', 'tw-grid-flow-row', 'tw-gap-4');
        MainWindow.appendChild(AvatarsWindow);
    
        savedAvatars = GM_getValue('savedAvatars', []);
        let delayBetweenCards = 50;
        let avatarCreationPromises = savedAvatars.map((avatar, index) => {
            return new Promise(resolve => {
                setTimeout(() => {
                    createAvatarCard(avatar);
                    resolve();
                }, index * delayBetweenCards);
            });
        });
        await Promise.all(avatarCreationPromises);

        updateCounter();
        sortAvatars(currentSort, isReversed);
        isCustomFavoritesMenuOpen = true;
    } catch (error) {
        showNotification(`Error Message: ${error}`, 'error');
    }
}

function parseCustomDate(dateString) {
    if (!dateString) return new Date(0);
    const dateParts = dateString.split(', ');
    if (dateParts.length === 2) {
        const [date, time] = dateParts;
        const [day, month, year] = date.split('.').map(Number);
        const [hours, minutes, seconds] = time.split(':').map(Number);
        return new Date(year, month - 1, day, hours, minutes, seconds);
    } else if (dateString.includes('.')) {
        const [day, month, year] = dateString.split('.').map(Number);
        return new Date(year, month - 1, day);
    } else if (dateString.includes('-')) {
        return new Date(dateString);
    }
    return new Date(dateString);
}

function sortAvatars(sortBy, reverse) {
    const avatarsContainer = document.getElementById('custom-avatars');
    const avatars = Array.from(avatarsContainer.children);

    avatars.sort((a, b) => {
        const avatarA = savedAvatars.find(av => av.avatarID === a.dataset.avatarId);
        const avatarB = savedAvatars.find(av => av.avatarID === b.dataset.avatarId);
        if (!avatarA || !avatarB) return 0;

        let comparison = 0;

        switch (sortBy) {
            case 'default':
                const dateA = parseCustomDate(avatarA.dateAdded);
                const dateB = parseCustomDate(avatarB.dateAdded);
                comparison = dateB - dateA;
                break;

            case 'lastUpdated':
                const updatedA = parseCustomDate(avatarA.updated_at);
                const updatedB = parseCustomDate(avatarB.updated_at);
                comparison = updatedB - updatedA;
                break;

            case 'name':
                comparison = avatarA.avatarName.localeCompare(avatarB.avatarName);
                break;
            
            case 'performance':
                const order = { 'Excellent': 5, 'Good': 4, 'Medium': 3, 'Poor': 2, 'VeryPoor': 1, 'None': 0 };
                comparison = (order[avatarB.PC_Performance] || 0) - (order[avatarA.PC_Performance] || 0);
                break;
        }

        return reverse ? -comparison : comparison;
    });

    avatarsContainer.innerHTML = '';
    avatars.forEach(avatar => avatarsContainer.appendChild(avatar));
}

function saveSortSettings(currentSort, isReversed) {
    sortSettings = { sortBy: currentSort, isReversed: isReversed };
    GM_setValue('sortSettings', sortSettings);
}

function updateCounter() {
    const avatarWindow = document.getElementById('custom-avatars');
    const counter = document.querySelector('.counter');

    if (avatarWindow && counter) {
        const avatarCount = avatarWindow.children.length;
        counter.textContent = avatarCount;
    }
}

function filterAvatars(query) {
    const avatars = document.querySelectorAll('.AvatarCard');
    avatars.forEach(avatar => {
        const avatarName = avatar.querySelector('.css-1yw163h-custom').textContent.toLowerCase();
        const authorName = avatar.querySelector('.css-so1s8h-custom').textContent.toLowerCase();
        const isMatch = avatarName.includes(query) || authorName.includes(query);
        avatar.style.display = isMatch ? 'block' : 'none';
    });
}

async function getAuthCookieValue() {
    return new Promise((resolve) => {
        GM_cookie.list({
            domain: "vrchat.com",
            name: "auth"
        }, (cookies) => {
            resolve(cookies?.[0]?.value || null);
        });
    });
}

async function getUserID(keyName) {
    const data = JSON.parse(localStorage.getItem(keyName));
    if (!Array.isArray(data) || !data[0]?.user_id) {
        throw new Error('user_id not found in first item of array');
    }
    return data[0].user_id;
}

async function SendRequest(method, url, authCookie) {
    return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
            method,
            url,
            headers: {
                "Cookie": `auth=${authCookie}`
            },
            onload: async function(response) {
                if (response.status === 200) {
                    // Debug
                    const { responseText } = response;
                    const data = JSON.parse(responseText);
                    console.dir(data);
                    showNotification(`Request to ${url} completed with status ${response.status}`, 'success');
                    // -------

                    resolve(response);
                } else if (response.status === 404) {
                    showNotification(`Request failed!\nStatus: ${response.status}\nError: ${response.responseText}`, 'warning', 15000);
                    resolve(null);
                } else {
                    showNotification(`Request failed!\nStatus: ${response.status}\nError: ${response.responseText}`, 'warning', 15000);
                }
            }
        });
    });
}

async function GetPrivateAvatarInfo(method, url, authCookie) {
    return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
            method,
            url,
            headers: {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
                "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
                "Cookie": `auth=${authCookie}`
            },
            onload: function (response) {
                const htmlContent = response.responseText;
                const parser = new DOMParser();
                const doc = parser.parseFromString(htmlContent, "text/html");

                const metaData = {
                    title: doc.title,
                    metaTags: Array.from(doc.querySelectorAll('meta')).map(tag => ({
                        name: tag.getAttribute('name') || tag.getAttribute('property') || tag.getAttribute('itemprop'),
                        content: tag.getAttribute('content')
                    })),
                    scripts: Array.from(doc.querySelectorAll('script')).map(script => script.src),
                    styles: Array.from(doc.querySelectorAll('link[rel="stylesheet"]')).map(link => link.href)
                };

                const ogImageTag = metaData.metaTags.find(tag => tag.name === 'og:image');
                const ogTitleTag = metaData.metaTags.find(tag => tag.name === 'og:title');

                const [name, author] = ogTitleTag.content.trim().split(/[\s]*by[\s]*/).map(x => x.trim());

                const result = {
                    PrivateImage: ogImageTag.content,
                    PrivateName: name,
                    PrivateAuthor: author
                };

                resolve(result);
            }
        });
    })
}

function FixDisplayTime(time) {
    let date = new Date(time);
    let day = String(date.getDate()).padStart(2, '0');
    let month = String(date.getMonth() + 1).padStart(2, '0');
    let year = date.getFullYear();
    let formattedDate = `${day}.${month}.${year}`;
    return formattedDate;
}

async function AddAvatar(metod, avatarID = null) {
    let link = null;

    if (metod == addMetod.current) {
        link = `https://api.vrchat.cloud/api/1/users/${userId}/avatar`;
    } else if (metod == addMetod.ID) {
        link = `https://api.vrchat.cloud/api/1/avatars/${avatarID}`
    }

    let isUnavailable = false;
    let unavailableData = null;
    let data = null;

    const response = await SendRequest('GET', `${link}`, authCookie);
    if (response) {
        const { responseText } = response;
        if (responseText) {
            data = JSON.parse(responseText);
        }
    } else {
        isUnavailable = true;
        let privateAvatarInfo = await GetPrivateAvatarInfo('GET', `https://vrchat.com/home/avatar/${avatarID}`, authCookie);

        unavailableData = {
            PrivateImage: privateAvatarInfo.PrivateImage,
            PrivateName: privateAvatarInfo.PrivateName,
            PrivateAuthor: privateAvatarInfo.PrivateAuthor
        };
    }

    let hasPC = false;
    let hasQuest = false;
    let pcPerformance = '';
    let questPerformance = '';

    if (!isUnavailable && Array.isArray(data.unityPackages) && data.unityPackages.length > 0) {
        data.unityPackages.forEach(pkg => {
            let category = '';
            if (pkg.platform === 'standalonewindows' && pkg.variant === 'security') {
                category = 'pc';
                hasPC = true;
                pcPerformance = pkg.performanceRating;
            } else if (pkg.platform === 'android' && pkg.variant === 'security') {
                category = 'quest';
                hasQuest = true;
                questPerformance = pkg.performanceRating;
            }
        });
    }
    let avatarData = null;

    let currentTime = new Date().toLocaleString();

    if (!isUnavailable) {
        avatarData = {
            authorName: data.authorName,
            authorId: data.authorId,
            created_at: FixDisplayTime(data.created_at),
            description: data.description,
            avatarID: data.id,
            imageUrl: data.imageUrl,
            avatarName: data.name,
            releaseStatus: data.releaseStatus,
            updated_at: FixDisplayTime(data.updated_at),
            version: data.version,
    
            isPlatformPC: hasPC,
            PC_Performance: pcPerformance,
            isPlatformQuest: hasQuest,
            Quest_Performance: questPerformance,

            isUnavalibleAvatar: isUnavailable,
            dateAdded: currentTime
        };
    } else {
        avatarData = {
            authorName: unavailableData.PrivateAuthor,
            authorId: 'Unknown',
            created_at: 'Unknown',
            description: 'Unknown',
            avatarID: avatarID,
            imageUrl: unavailableData.PrivateImage,
            avatarName: unavailableData.PrivateName,
            releaseStatus: 'private',
            updated_at: 'Unknown',
            version: 'Unknown',
    
            isPlatformPC: hasPC,
            PC_Performance: 'Unknown',
            isPlatformQuest: hasQuest,
            Quest_Performance: 'Unknown',

            isUnavalibleAvatar: isUnavailable,
            dateAdded: currentTime
        };
    }

    if (!savedAvatars.some(a => a.avatarID === avatarData.avatarID)) {
        savedAvatars.push(avatarData);
        GM_setValue('savedAvatars', savedAvatars);
        createAvatarCard(avatarData);
        showNotification(`Saved Avatar: ${avatarData.avatarName}!`, 'success');
    } else {
        showNotification('This avatar is already saved!', 'warning', 7000);
    }
}

function createAvatarCard(avatar) {
    const AvatarsSelection = document.getElementById('custom-avatars');

    const AvatarCard = document.createElement('div');
    AvatarCard.classList.add('css-qcqlg7-custom', 'AvatarCard');
    AvatarCard.setAttribute('data-avatar-id', avatar.avatarID);
    AvatarsSelection.appendChild(AvatarCard);

	const ImageAndName = document.createElement('div');
    ImageAndName.classList.add('css-1brgsnm-custom');
    if (avatar.isUnavalibleAvatar) {
        ImageAndName.style.borderColor = 'rgb(238, 84, 84)';
    }
    if (avatar.PC_Performance === 'None') {
        ImageAndName.style.borderColor = 'rgb(235, 114, 33)';
    }
	AvatarCard.appendChild(ImageAndName);

    const linkElement = document.createElement('a');
    linkElement.setAttribute('aria-label', 'Avatar Image');
    linkElement.classList.add('css-1kj6np9-custom');
    linkElement.href = `/home/avatar/${avatar.avatarID}`;
    ImageAndName.appendChild(linkElement);
    
    const syncContainer = document.createElement('div');
    syncContainer.classList.add('sync');
    linkElement.appendChild(syncContainer);

    const syncImage = document.createElement('img');
    syncImage.src = 'https://img.icons8.com/?size=28&id=YyqIYbMdZ1i5&format=png&color=6ae3f9';
    syncImage.classList.add('css-1vrq36y-custom', 'syncButton');
    syncImage.addEventListener('click', async (event) => {
        event.stopPropagation();
        event.preventDefault();
        await syncAvatar(avatar.avatarID);
    });
    syncContainer.appendChild(syncImage);
    
    const iconDiv = document.createElement('div');
    iconDiv.classList.add('platform');
    if (avatar.isUnavalibleAvatar) {
        iconDiv.style.display = 'none';
    }
    linkElement.appendChild(iconDiv);

    if (avatar.isPlatformPC) {
        const Windows = document.createElement('div');
        Windows.setAttribute('role', 'note');
        Windows.setAttribute('title', 'Is a Windows Avatar');
        Windows.classList.add('tw-flex', 'tw-items-center', 'tw-justify-center', 'tw-w-6', 'tw-h-6', 'tw-border', 'tw-border-solid', 'tw-border-current', 'tw-rounded-full');
        Windows.style.color = 'rgb(23, 120, 255)';
        iconDiv.appendChild(Windows);
    
        const PC_Icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        PC_Icon.setAttribute('aria-hidden', 'true');
        PC_Icon.setAttribute('focusable', 'false');
        PC_Icon.setAttribute('data-prefix', 'fab');
        PC_Icon.setAttribute('data-icon', 'windows');
        PC_Icon.classList.add('svg-inline--fa', 'fa-windows', 'css-1efeorg', 'e9fqopp0');
        PC_Icon.setAttribute('role', 'presentation');
        PC_Icon.setAttribute('viewBox', '0 0 448 512');
        Windows.appendChild(PC_Icon);
    
        const PC_IconPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        PC_IconPath.setAttribute('fill', 'currentColor');
        PC_IconPath.setAttribute('d', 'M0 93.7l183.6-25.3v177.4H0V93.7zm0 324.6l183.6 25.3V268.4H0v149.9zm203.8 28L448 480V268.4H203.8v177.9zm0-380.6v180.1H448V32L203.8 65.7z');
        PC_Icon.appendChild(PC_IconPath);
    }

    if (avatar.isPlatformQuest) {
        const Quest = document.createElement('div');
        Quest.setAttribute('role', 'note');
        Quest.setAttribute('title', 'Is an Android Avatar');
        Quest.classList.add('tw-flex', 'tw-items-center', 'tw-justify-center', 'tw-w-6', 'tw-h-6', 'tw-border', 'tw-border-solid', 'tw-border-current', 'tw-rounded-full');
        Quest.style.color = 'rgb(43, 207, 92)';
        iconDiv.appendChild(Quest);
    
        const Quest_Icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        Quest_Icon.setAttribute('aria-hidden', 'true');
        Quest_Icon.setAttribute('focusable', 'false');
        Quest_Icon.setAttribute('data-prefix', 'fab');
        Quest_Icon.setAttribute('data-icon', 'windows');
        Quest_Icon.classList.add('svg-inline--fa', 'fa-windows', 'css-1efeorg', 'e9fqopp0');
        Quest_Icon.setAttribute('role', 'presentation');
        Quest_Icon.setAttribute('viewBox', '0 0 576 512');
        Quest.appendChild(Quest_Icon);
    
        const Quest_IconPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        Quest_IconPath.setAttribute('fill', 'currentColor');
        Quest_IconPath.setAttribute('d', 'M420.55,301.93a24,24,0,1,1,24-24,24,24,0,0,1-24,24m-265.1,0a24,24,0,1,1,24-24,24,24,0,0,1-24,24m273.7-144.48,47.94-83a10,10,0,1,0-17.27-10h0l-48.54,84.07a301.25,301.25,0,0,0-246.56,0L116.18,64.45a10,10,0,1,0-17.27,10h0l47.94,83C64.53,202.22,8.24,285.55,0,384H576c-8.24-98.45-64.54-181.78-146.85-226.55');
        Quest_Icon.appendChild(Quest_IconPath);
    }

    const imgElement = document.createElement('img');
    if (avatar.isUnavalibleAvatar && !avatar.authorName) {
        imgElement.src = 'https://assets.vrchat.com/default/unavailable-avatar.png';
    } else{
        imgElement.src = `${avatar.imageUrl}`;
    }
    imgElement.alt = `${avatar.avatarName}`;
    imgElement.classList.add('avatarCardImage');
    linkElement.appendChild(imgElement);

    const avatarDisplayName = document.createElement('div');
    avatarDisplayName.classList.add('AvatarNameSelection');
    ImageAndName.appendChild(avatarDisplayName);

    const OpenAvatarPage = document.createElement('a');
    OpenAvatarPage.setAttribute('aria-label', 'Open Avatar Page');
    OpenAvatarPage.classList.add('css-1alc1xs-custom');
    OpenAvatarPage.href = `/home/avatar/${avatar.avatarID}`;
    avatarDisplayName.appendChild(OpenAvatarPage);

    const avatarH4 = document.createElement('h4');
    avatarH4.classList.add('css-1yw163h-custom');
    avatarH4.textContent = `${avatar.avatarName}`;
    avatarH4.title = `Description:\n${avatar.description}`;
    if (avatarH4.textContent.length > 23) {
        const fontSize = 1.1 - (avatarH4.textContent.length - 23) * 0.02;
        avatarH4.style.fontSize = `${fontSize}rem`;
    }
    OpenAvatarPage.appendChild(avatarH4);

    const releaseStatus = document.createElement('h2');
    releaseStatus.classList.add('realiseStatus');
    if (avatar.releaseStatus === 'private') {
        if (avatar.isUnavalibleAvatar) {
            avatar.releaseStatus = avatar.authorName ? 'private/deleted' : 'Fully Deleted';
        }
    }
    if (['private', 'private/deleted', 'Fully Deleted'].includes(avatar.releaseStatus)) {
        releaseStatus.classList.add('private');
        if (avatar.authorId === userId) {
            releaseStatus.title = 'Only you can use this avatar';
        }
    }
    releaseStatus.textContent = avatar.releaseStatus;
    avatarDisplayName.appendChild(releaseStatus);

	const mainDiv = document.createElement('div');
    mainDiv.classList.add('css-kfjcvw-custom');
    if (avatar.isUnavalibleAvatar) {
        mainDiv.style.borderColor = 'rgb(238, 84, 84)';
    }
    if (avatar.PC_Performance === 'None') {
        mainDiv.style.borderColor = 'rgb(235, 114, 33)';
    }
	AvatarCard.appendChild(mainDiv);

    const innerDiv = document.createElement('div');
    innerDiv.classList.add('css-1il99ht-custom');
    mainDiv.appendChild(innerDiv);

    const userSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    userSvg.classList.add('svg-icon');
    userSvg.setAttribute('viewBox', '0 0 448 512');
    userSvg.setAttribute('color', '#54b5c5');
    userSvg.setAttribute('width', '20');
    innerDiv.appendChild(userSvg);

    const userPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    userPath.setAttribute('fill', 'currentColor');
    userPath.setAttribute('d', 'M224 256A128 128 0 1 0 224 0a128 128 0 1 0 0 256zm-45.7 48C79.8 304 0 383.8 0 482.3C0 498.7 13.3 512 29.7 512l388.6 0c16.4 0 29.7-13.3 29.7-29.7C448 383.8 368.2 304 269.7 304l-91.4 0z');
    userSvg.appendChild(userPath);

    const authorText = document.createElement('div');
    authorText.classList.add('css-1grfcoa-custom');
    authorText.textContent = 'Author';
    innerDiv.appendChild(authorText);

    const userLink = document.createElement('div');
    userLink.classList.add('css-so1s8h-custom');
    innerDiv.appendChild(userLink);

    const link = document.createElement('a');
    if (!avatar.isUnavalibleAvatar) {
        link.href = `/home/user/${avatar.authorId}`;
    }
    link.textContent = `${avatar.authorName}`;
    userLink.appendChild(link);

    const cloudSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    cloudSvg.classList.add('svg-icon');
    cloudSvg.setAttribute('viewBox', '0 0 640 512');
    cloudSvg.setAttribute('color', '#54b5c5');
    cloudSvg.setAttribute('width', '20');
    innerDiv.appendChild(cloudSvg);

    const cloudPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    cloudPath.setAttribute('fill', 'currentColor');
    cloudPath.setAttribute('d', 'M144 480C64.5 480 0 415.5 0 336c0-62.8 40.2-116.2 96.2-135.9c-.1-2.7-.2-5.4-.2-8.1c0-88.4 71.6-160 160-160c59.3 0 111 32.2 138.7 80.2C409.9 102 428.3 96 448 96c53 0 96 43 96 96c0 12.2-2.3 23.8-6.4 34.6C596 238.4 640 290.1 640 352c0 70.7-57.3 128-128 128l-368 0zm79-217c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l39-39L296 392c0 13.3 10.7 24 24 24s24-10.7 24-24l0-134.1 39 39c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-80-80c-9.4-9.4-24.6-9.4-33.9 0l-80 80z');
    cloudSvg.appendChild(cloudPath);

    const updatedText = document.createElement('div');
    updatedText.classList.add('css-1grfcoa-custom');
    updatedText.textContent = 'Last Updated';
    innerDiv.appendChild(updatedText);

    const dateDiv = document.createElement('div');
    dateDiv.classList.add('text-start', 'css-so1s8h-custom');
    dateDiv.textContent = `${avatar.updated_at}`;
    dateDiv.setAttribute('title', `Created: ${avatar.created_at}\nLast Update: ${avatar.updated_at}\nAvatar Version: ${avatar.version}`);
    innerDiv.appendChild(dateDiv);

    const perfomanceElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    perfomanceElement.classList.add('svg-icon');
    perfomanceElement.setAttribute('viewBox', '0 0 512 512');
    perfomanceElement.setAttribute('color', '#54b5c5');
    perfomanceElement.setAttribute('width', '20');
    innerDiv.appendChild(perfomanceElement);

    const performanceSVG = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    performanceSVG.setAttribute('fill', 'currentColor');
    performanceSVG.setAttribute('d', 'M159.3 5.4c7.8-7.3 19.9-7.2 27.7 .1c27.6 25.9 53.5 53.8 77.7 84c11-14.4 23.5-30.1 37-42.9c7.9-7.4 20.1-7.4 28 .1c34.6 33 63.9 76.6 84.5 118c20.3 40.8 33.8 82.5 33.8 111.9C448 404.2 348.2 512 224 512C98.4 512 0 404.1 0 276.5c0-38.4 17.8-85.3 45.4-131.7C73.3 97.7 112.7 48.6 159.3 5.4zM225.7 416c25.3 0 47.7-7 68.8-21c42.1-29.4 53.4-88.2 28.1-134.4c-4.5-9-16-9.6-22.5-2l-25.2 29.3c-6.6 7.6-18.5 7.4-24.7-.5c-16.5-21-46-58.5-62.8-79.8c-6.3-8-18.3-8.1-24.7-.1c-33.8 42.5-50.8 69.3-50.8 99.4C112 375.4 162.6 416 225.7 416z');
    perfomanceElement.appendChild(performanceSVG);

    const perfomance = document.createElement('div');
    perfomance.classList.add('css-1grfcoa-custom');
    perfomance.textContent = 'Performance';
    innerDiv.appendChild(perfomance);

    const PCText = document.createElement('div');
    PCText.classList.add('text-start', 'css-so1s8h-custom');
    PCText.textContent = `PC`;
    innerDiv.appendChild(PCText);

    const raiting = document.createElement('div');
    if (avatar.PC_Performance === 'Excellent') {
        raiting.classList.add('text-start', 'css-so1s8h-custom', 'Excellent');
    } else if (avatar.PC_Performance === 'Good') {
        raiting.classList.add('text-start', 'css-so1s8h-custom', 'Good');
    } else if (avatar.PC_Performance === 'Medium') {
        raiting.classList.add('text-start', 'css-so1s8h-custom', 'Medium');
    } else if (avatar.PC_Performance === 'Poor') {
        raiting.classList.add('text-start', 'css-so1s8h-custom', 'Poor');
    } else if (avatar.PC_Performance === 'VeryPoor') {
        raiting.classList.add('text-start', 'css-so1s8h-custom', 'VeryPoor');
    }
    if (avatar.PC_Performance !== 'None') {
        raiting.textContent = `${avatar.PC_Performance}`;
    } else {
        raiting.textContent = 'Security Failed';
        raiting.style.color = 'red';
        raiting.style.textDecoration = 'underline';
        raiting.style.textDecorationLine = 'spelling-error';
    }
    raiting.style.marginLeft = '10px';
    PCText.appendChild(raiting);

    if (avatar.PC_Performance !== 'None' && !avatar.isUnavalibleAvatar) {
        const verypoorImage = document.createElement('img');
        if (avatar.PC_Performance === 'Excellent') {
            verypoorImage.src = 'https://dtuitjyhwcl5y.cloudfront.net/b7e99cd3c42a6f1ff2e6f3faaada0e75366945997a7fa5e7e014d26b1d100ef7.svg';
        } else if (avatar.PC_Performance === 'Good') {
            verypoorImage.src = 'https://dtuitjyhwcl5y.cloudfront.net/db3f587335a6602a84d0f0f18d6fbb10904973d0ddb659009f0fc56b3d1f026b.svg';
        } else if (avatar.PC_Performance === 'Medium') {
            verypoorImage.src = 'https://dtuitjyhwcl5y.cloudfront.net/24001ed5aa8ebabaa63a09ffb88ccecccc4c5feb1b4179579e8e6c9f1fed3f16.svg';
        } else if (avatar.PC_Performance === 'Poor') {
            verypoorImage.src = 'https://dtuitjyhwcl5y.cloudfront.net/467c01a863f0a61d30a09465f743678c95a5e6ae6d439b2fecd257464ec111d0.svg';
        } else if (avatar.PC_Performance === 'VeryPoor') {
            verypoorImage.src = 'https://dtuitjyhwcl5y.cloudfront.net/b4bf11dfbd8c3076cb66e8457b3f78659854700e79d5256516e205e37af89247.svg';
        }
        verypoorImage.alt = 'Avatar Icon';
        verypoorImage.style.width = '20px';
        verypoorImage.style.height = '20px';
        verypoorImage.style.marginLeft = '10px';
        verypoorImage.style.marginRight = '10px';
        verypoorImage.classList.add('css-1il99ht-custom');
        raiting.appendChild(verypoorImage);
    }

    const QuestText = document.createElement('div');
    QuestText.classList.add('text-start', 'css-so1s8h-custom');
    QuestText.textContent = `Quest`;
    PCText.appendChild(QuestText);

    const Qraiting = document.createElement('div');
    if (avatar.Quest_Performance === 'Excellent') {
        Qraiting.classList.add('text-start', 'css-so1s8h-custom', 'Excellent');
    } else if (avatar.Quest_Performance === 'Good') {
        Qraiting.classList.add('text-start', 'css-so1s8h-custom', 'Good');
    } else if (avatar.Quest_Performance === 'Medium') {
        Qraiting.classList.add('text-start', 'css-so1s8h-custom', 'Medium');
    } else if (avatar.Quest_Performance === 'Poor') {
        Qraiting.classList.add('text-start', 'css-so1s8h-custom', 'Poor');
    } else if (avatar.Quest_Performance === 'VeryPoor') {
        Qraiting.classList.add('text-start', 'css-so1s8h-custom', 'VeryPoor');
    }
    if (avatar.Quest_Performance) {
        Qraiting.textContent = `${avatar.Quest_Performance}`;
    } else {
        Qraiting.textContent = 'None';
    }
    Qraiting.style.marginLeft = '10px';
    QuestText.appendChild(Qraiting);

    if (avatar.Quest_Performance && avatar.Quest_Performance !== 'None' && !avatar.isUnavalibleAvatar) {
        const QverypoorImage = document.createElement('img');
        if (avatar.Quest_Performance === 'Excellent') {
            QverypoorImage.src = 'https://dtuitjyhwcl5y.cloudfront.net/b7e99cd3c42a6f1ff2e6f3faaada0e75366945997a7fa5e7e014d26b1d100ef7.svg';
        } else if (avatar.Quest_Performance === 'Good') {
            QverypoorImage.src = 'https://dtuitjyhwcl5y.cloudfront.net/db3f587335a6602a84d0f0f18d6fbb10904973d0ddb659009f0fc56b3d1f026b.svg';
        } else if (avatar.Quest_Performance === 'Medium') {
            QverypoorImage.src = 'https://dtuitjyhwcl5y.cloudfront.net/24001ed5aa8ebabaa63a09ffb88ccecccc4c5feb1b4179579e8e6c9f1fed3f16.svg';
        } else if (avatar.Quest_Performance === 'Poor') {
            QverypoorImage.src = 'https://dtuitjyhwcl5y.cloudfront.net/467c01a863f0a61d30a09465f743678c95a5e6ae6d439b2fecd257464ec111d0.svg';
        } else if (avatar.Quest_Performance === 'VeryPoor') {
            QverypoorImage.src = 'https://dtuitjyhwcl5y.cloudfront.net/b4bf11dfbd8c3076cb66e8457b3f78659854700e79d5256516e205e37af89247.svg';
        }
        QverypoorImage.alt = 'Avatar Icon';
        QverypoorImage.style.width = '20px';
        QverypoorImage.style.height = '20px';
        QverypoorImage.style.marginLeft = '10px';
        QverypoorImage.classList.add('css-1il99ht-custom');
        Qraiting.appendChild(QverypoorImage);
    }

    const ButtonsNew = document.createElement('div');
    ButtonsNew.classList.add('avatarCardTogglesSelection');
    if (avatar.isUnavalibleAvatar) {
        ButtonsNew.style.justifyContent = 'flex-end';
    }
    mainDiv.appendChild(ButtonsNew);

    if (!avatar.isUnavalibleAvatar) {
        const selectAvatar = document.createElement('button');
        selectAvatar.textContent = 'Select Avatar';
        selectAvatar.classList.add('px-3', 'avatarCardToggles');
        selectAvatar.addEventListener('click', async (e) => {
            e.preventDefault();
            changeSelectedAvatar(avatar.avatarID);
        });
        ButtonsNew.appendChild(selectAvatar);
    }

	const deleteAvatar = document.createElement('button');
	deleteAvatar.textContent = 'Remove';
	deleteAvatar.style.color = '#ee5454';
	deleteAvatar.style.border = '2px solid #ee5454';
	deleteAvatar.classList.add('px-3', 'avatarCardToggles', 'Remove');
	deleteAvatar.addEventListener('click', async (e) => {
    	e.preventDefault();
        const avatarCard = e.target.closest('.AvatarCard');
        if (avatarCard) {
            const avatarID = avatarCard.getAttribute('data-avatar-id');
            savedAvatars = savedAvatars.filter(a => a.avatarID !== avatarID);
            GM_setValue('savedAvatars', savedAvatars);
            avatarCard.remove();
            showNotification(`Avatar removed: ${avatar.avatarName}!`, 'info');
            updateCounter();
        } else {
            showNotification('Failed to remove avatar.', 'error');
        }
    });
    ButtonsNew.appendChild(deleteAvatar);

    updateCounter();
    sortAvatars(sortSettings.sortBy, sortSettings.isReversed);
}

async function changeSelectedAvatar(avatarID) {
    const authCookie = await getAuthCookieValue();
    await SendRequest('PUT', `https://api.vrchat.cloud/api/1/avatars/${avatarID}/select`, authCookie);
    showNotification(`Avatar selected: ${avatarID}!`, 'success');
}

async function syncAvatar(avatarID) {
    try {
        const { response } = await SendRequest('GET', `https://api.vrchat.cloud/api/1/avatars/${avatarID}`, authCookie);
        
        const newData = JSON.parse(response);
        const savedAvatar = savedAvatars.find(a => a.avatarID === avatarID);

        let changes = [];
        let hasPC = false;
        let hasQuest = false;
        let pcPerformance = '';
        let questPerformance = '';

        if (Array.isArray(newData.unityPackages) && newData.unityPackages.length > 0) {
            newData.unityPackages.forEach(pkg => {
                let category = '';
                if (pkg.platform === 'standalonewindows' && pkg.variant === 'security') {
                    category = 'pc';
                    hasPC = true;
                    pcPerformance = pkg.performanceRating;
                } else if (pkg.platform === 'android' && pkg.variant === 'security') {
                    category = 'quest';
                    hasQuest = true;
                    questPerformance = pkg.performanceRating;
                }
            });
        }

        if (savedAvatar.authorName !== newData.authorName) {
            changes.push({
                field: 'authorName',
                oldVal: savedAvatar.authorName,
                newVal: newData.authorName,
                apply: false
            });
        }

        if (savedAvatar.authorId !== newData.authorId) {
            changes.push({
                field: 'authorId',
                oldVal: savedAvatar.authorId,
                newVal: newData.authorId,
                apply: false
            });
        }

        if(savedAvatar.created_at !== FixDisplayTime(newData.created_at)) {
            changes.push({
                field: 'created_at',
                oldVal: savedAvatar.created_at,
                newVal: FixDisplayTime(newData.created_at),
                apply: false
            });
        }
        
        if (savedAvatar.description !== newData.description) {
            changes.push({
                field: 'description',
                oldVal: savedAvatar.description,
                newVal: newData.description,
                apply: false
            });
        }

        if (savedAvatar.avatarID !== newData.id) {
            changes.push({
                field: 'avatarID',
                oldVal: savedAvatar.avatarID,
                newVal: newData.id,
                apply: false
            });
        }

        if (savedAvatar.imageUrl !== newData.imageUrl) {
            changes.push({
                field: 'imageUrl',
                oldVal: savedAvatar.imageUrl,
                newVal: newData.imageUrl,
                apply: false
            });
        }

        if(savedAvatar.avatarName !== newData.name) {
            changes.push({
                field: 'avatarName',
                oldVal: savedAvatar.avatarName,
                newVal: newData.name,
                apply: false
            });
        }

        if (savedAvatar.releaseStatus !== newData.releaseStatus) {
            changes.push({
                field: 'releaseStatus',
                oldVal: savedAvatar.releaseStatus,
                newVal: newData.releaseStatus,
                apply: false
            });
        }

        if(savedAvatar.updated_at !== FixDisplayTime(newData.updated_at)) {
            changes.push({
                field: 'updated_at',
                oldVal: savedAvatar.updated_at,
                newVal: FixDisplayTime(newData.updated_at),
                apply: false
            });
        }

        if(savedAvatar.version !== newData.version) {
            changes.push({
                field: 'version',
                oldVal: savedAvatar.version,
                newVal: newData.version,
                apply: false
            });
        }

        if(savedAvatar.isPlatformPC !== hasPC) {
            changes.push({
                field: 'isPlatformPC',
                oldVal: savedAvatar.isPlatformPC,
                newVal: hasPC,
                apply: false
            });
        }

        if(savedAvatar.PC_Performance !== pcPerformance) {
            changes.push({
                field: 'PC_Performance',
                oldVal: savedAvatar.PC_Performance,
                newVal: pcPerformance,
                apply: false
            });
        }

        if(savedAvatar.isPlatformQuest !== hasQuest) {
            changes.push({
                field: 'isPlatformQuest',
                oldVal: savedAvatar.isPlatformQuest,
                newVal: hasQuest,
                apply: false
            });
        }

        if (savedAvatar.Quest_Performance !== questPerformance) {
            changes.push({
                field: 'Quest_Performance',
                oldVal: savedAvatar.Quest_Performance,
                newVal: questPerformance,
                apply: false
            });
        }

        if (changes.length === 0) {
            showNotification('No changes detected!', 'success');
            return;
        }

        showSyncConfirmation(changes, avatarID);
    } catch (error) {
        showNotification(`Error during sync: ${error.message}`, 'error');
    }
}

function showSyncConfirmation(changes, avatarID) {
    const modal = document.createElement('div');
    modal.className = 'sync-modal-overlay';
    modal.innerHTML = `
        <div class="modal-sync" style="width: 600px">
            <h3 class="modal-sync-title">Changes detected</h3>
            <div id="sync-changes-list""></div>
            <div class="modal-sync-buttons">
                <button id="sync-apply-changes">Apply selected</button>
                <button id="sync-cancel-changes">Cancel</button>
            </div>
        </div>
    `;
    
    const changesList = modal.querySelector('#sync-changes-list');
    changes.forEach(change => {
        const div = document.createElement('div');
        div.className = 'sync-change-item';
        if (change.field === 'imageUrl') {
            div.innerHTML = `
                <label class="sync-change-label">
                    <div class="sync-change-checkbox-container" style="margin-bottom: 0.5rem;">
                        <input type="checkbox" class="sync-change-checkbox">
                        <span class="sync-field-name">${change.field}</span>
                    </div>
                    <div class="sync-change-checkbox-container" style="justify-content: space-around; align-items: center;">
                        <div class="sync-field-value">
                            ${change.oldVal ? `<img src="${change.oldVal}" alt="Old Image" class="sync-image"">` : 'No image'}
                        </div>
                        <p class="sync-change-arrow">➤</p>
                        <div class="sync-field-value">
                            ${change.newVal ? `<img src="${change.newVal}" alt="New Image" class="sync-image">` : 'No image'}
                        </div>
                    </div>
                </label>
            `;
        } else {
            div.innerHTML = `
                <label class="sync-change-label">
                    <div class="sync-change-checkbox-container" style="margin-bottom: 0.5rem;">
                        <input type="checkbox" class="sync-change-checkbox">
                        <span class="sync-field-name">${change.field}</span>
                    </div>
                    <div class="sync-change-checkbox-container" style="justify-content: space-around;">
                        <div class="sync-field-value">${change.oldVal}</div>
                        <p class="sync-change-arrow">➤</p>
                        <div class="sync-field-value">${change.newVal}</div>
                    </div>
                </label>
            `;
        }
        changesList.appendChild(div);
    });

    document.body.appendChild(modal);

    modal.querySelector('#sync-apply-changes').addEventListener('click', () => {
        const selected = Array.from(modal.querySelectorAll('.sync-change-checkbox:checked'))
            .map(cb => changes[Array.from(changesList.children).indexOf(cb.closest('.sync-change-item'))]);
        
        if (selected.length === 0) {
            showNotification('Select somethig first!', 'warning');
            return;
        }

        const avatarIndex = savedAvatars.findIndex(a => a.avatarID === avatarID);
        selected.forEach(change => {
            savedAvatars[avatarIndex][change.field] = change.newVal;
        });

        GM_setValue('savedAvatars', savedAvatars);
        updateAvatarCard(avatarID);
        handleClose();
    });

    const handleClose = () => {
        modal.classList.add('exit');
        setTimeout(() => {
            modal.remove();
        }, 200);
    };

    document.addEventListener('keydown', (event) => {
        if (event.key === 'Escape') {
            handleClose();
        }
    });

    modal.querySelector('#sync-cancel-changes').addEventListener('click', () => {
        handleClose();
    });
}

function updateAvatarCard(avatarID) {
    const avatarCard = document.querySelector(`[data-avatar-id="${avatarID}"]`);
    if (avatarCard) {
        avatarCard.remove();
        const avatarData = savedAvatars.find(a => a.avatarID === avatarID);
        createAvatarCard(avatarData);
        showNotification('Avatar Updated!', 'success');
    }
}

function showSaveByIDModal() {
    const modal = document.createElement('div');
    modal.id = 'save-by-id-modal';
    modal.classList.add('modal-overlay');
    modal.innerHTML = `
        <div class="modal-content">
            <h3>Save New Avatar</h3>
            <label>Avatar URL or ID:</label>
            <input type="text" id="avatar-url" placeholder="Enter URL or ID" />
            <label>For example: avtr_26187637-0c30-4a09-86e1-bc928c07309e</label>
            <div class="modal-buttons">
                <button id="save-avatar-btn" disabled>Save</button>
                <button id="cancel-btn">Cancel</button>
            </div>
        </div>
    `;

    GM_addStyle(`
        @keyframes modalEnter {
            0% { opacity: 0; transform: scale(1.15); }
            100% { opacity: 1; transform: scale(1); }
        }
        @keyframes modalExit {
            0% { opacity: 1; transform: scale(1); }
            100% { opacity: 0; transform: scale(1.15); }
        }
        .modal-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.7);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 2000;
            backdrop-filter: blur(4px);
            animation: modalEnter 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        }
        .modal-overlay.exit {
            animation: modalExit 0.2s ease-out forwards;
        }
        .modal-content {
            background: rgb(24, 27, 31);
            color: #e0e0e0;
            padding: 1.5rem;
            border-radius: 8px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
            width: 512px;
            border: 2px solid rgb(37, 42, 48);
            position: relative;
        }
        .modal-content h3 {
            margin: 0 0 1.25rem 0;
            font-size: 1.5rem;
            color: rgb(106, 227, 249);
            text-align: center;
            font-weight: 500;
        }
        .modal-content label {
            display: block;
            margin-bottom: 0.5rem;
            font-size: 0.9rem;
            color: rgb(160, 160, 160);
        }
        .modal-content input {
            width: 100%;
            padding: 0.75rem 1rem;
            margin: 0.5rem 0 1.25rem 0;
            border: 2px solid rgb(6, 75, 92);
            border-radius: 4px;
            background: rgb(7, 36, 43);
            color: #e0e0e0;
            font-size: 1rem;
            transition: all 0.2s ease-in-out;
        }
        .modal-content input:focus {
            outline: none;
            border-color: rgb(8, 108, 132);
            box-shadow: 0 0 0 3px rgba(106, 227, 249, 0.1);
        }
        .modal-content input:hover {
            border-color: rgb(8, 108, 132);
        }
        .modal-buttons {
            display: flex;
            gap: 0.75rem;
            margin-top: 1rem;
        }
        #save-avatar-btn, #cancel-btn {
            flex: 1;
            padding: 0.75rem 1.25rem;
            border: 2px solid transparent;
            border-radius: 4px;
            cursor: pointer;
            font-weight: 500;
            transition: all 0.2s ease-in-out;
            background-color: rgb(37, 42, 48);
            color: rgb(106, 227, 249);
        }
        #save-avatar-btn {
            background-color: rgb(6, 75, 92);
            border-color: rgb(8, 108, 132);
        }
        #save-avatar-btn:hover {
            background-color: rgb(8, 108, 132);
            border-color: rgb(255, 255, 255);
            transform: scale(1.05);
        }
        #cancel-btn {
            color: rgb(238, 84, 84);
            background-color: rgb(7, 36, 43);
            border: 3px solid rgb(5, 60, 72);
        }
        #cancel-btn:hover {
            background-color: rgb(5, 25, 29);
            border-color: rgb(5, 25, 29)
            transform: scale(1.05);
        }
        .modal-buttons button {
            --bs-btn-border-radius: 4px;
            --bs-btn-padding-y: 0.75rem;
            --bs-btn-padding-x: 1.25rem;
            --bs-btn-font-size: 1rem;
            border-width: 2px;
        }
        .modal-content input:focus {
            background: linear-gradient(#242424, #242424) padding-box,
                        linear-gradient(135deg, rgba(106, 227, 249, 0.4), rgba(8, 108, 132, 0.4)) border-box;
            border: 2px solid transparent;
        }
        .modal-content input.valid {
            border-color: #4CAF50 !important;
        }
        .modal-content input.invalid {
            border-color: #FF5722 !important;
        }
        .modal-content input:hover {
            border-color: inherit;
        }
        #save-avatar-btn:disabled {
            background-color: rgb(37, 42, 48);
            border-color: transparent;
            cursor: not-allowed;
            transform: none;
        }
        #save-avatar-btn:disabled:hover {
            background-color: rgb(37, 42, 48);
            border-color: transparent;
            box-shadow: none;
        }
    `);

    document.body.appendChild(modal);

    const saveBtn = modal.querySelector('#save-avatar-btn');
    const cancelBtn = modal.querySelector('#cancel-btn');
    const inputField = modal.querySelector('#avatar-url');

    const urlPattern = /^https:\/\/vrchat\.com\/home\/avatar\/avtr_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
    const idPattern = /^avtr_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

    const validateInput = () => {
        const value = inputField.value.trim();
        const isValidURL = urlPattern.test(value);
        const isValidID = idPattern.test(value);
        const isValid = isValidURL || isValidID;

        inputField.classList.toggle('valid', isValid);
        inputField.classList.toggle('invalid', !isValid);
        saveBtn.disabled = !isValid;
    };

    inputField.addEventListener('input', validateInput);

    const handleClose = () => {
        modal.classList.add('exit');
        setTimeout(() => {
            modal.remove();
        }, 200);
    };

    document.addEventListener('keydown', (event) => {
        if (event.key === 'Escape') {
            handleClose();
        }
    });

    saveBtn.addEventListener('click', async () => {
        const value = inputField.value.trim();
        let avatarID;

        if (urlPattern.test(value)) {
        avatarID = value.split('/').pop();
        } else if (idPattern.test(value)) {
            avatarID = value;
        } else {
            showNotification('Invalid URL or ID format!', 'error');
            return;
        }

        try {
            AddAvatar(addMetod.ID, avatarID);
            handleClose();
        } catch (error) {
            showNotification(`Failed to add avatar: ${error.message}`, 'error');
        }

        handleClose();
    });
    inputField.addEventListener('keydown', (event) => {
        if (event.key === 'Enter') {
            saveBtn.click();
        }
    });

    cancelBtn.addEventListener('click', () => {
        handleClose();
    });

    modal.addEventListener('click', (event) => {
        const modalContent = modal.querySelector('.modal-content');
        const modalRect = modalContent.getBoundingClientRect();

        const safeZone = 150;

        const isClickOutsideSafeZone = 
            event.clientX < modalRect.left - safeZone ||
            event.clientX > modalRect.right + safeZone ||
            event.clientY < modalRect.top - safeZone ||
            event.clientY > modalRect.bottom + safeZone;

        if (isClickOutsideSafeZone) {
            handleClose();
        }
    });
}

let allAvatars = [];
let totalPages = 0;
let currentPage = 1;
const avatarsPerPage = 20;

function showAvatarSearchModal(initialQuery = '') {
    const modal = document.createElement('div');
    modal.id = 'avatar-search-modal';
    modal.classList.add('modal-overlay');
    modal.innerHTML = `
        <div class="modal-content" id="avatar-search-modal-content">
            <h3>Search Avatars</h3>
            <div style="display: flex; align-items: center;">
                <input 
                    type="text" 
                    id="search-input" 
                    placeholder="Enter search query"
                    value="${initialQuery}"
                    style="width: 100%; height: 2.5rem; padding: 0.75rem; margin-bottom: 1rem; margin-right: 10px; border: 2px solid rgb(6, 75, 92); border-radius: 4px; background: rgb(7, 36, 43); color: #e0e0e0;"
                >
                <button 
                    id="search-btn" 
                    class="css-1vrq36y-custom"
                    style="margin: 0 auto 1rem; height: 40px; padding: 0rem 1.5rem; border-radius: 4px; background: rgb(6, 75, 92);"
                >
                    Search
                </button>
            </div>
            <div id="pagination-container"></div>
            <div id="search-results" style="max-height: 400px; overflow-y: auto;">The results will appear here</div>
            <button id="close-modal-btn">Close</button>
        </div>
    `;
    document.body.appendChild(modal);

    const searchInput = modal.querySelector('#search-input');
    const searchBtn = modal.querySelector('#search-btn');
    const resultsContainer = modal.querySelector('#search-results');
    const closeModalBtn = modal.querySelector('#close-modal-btn');

    searchInput.addEventListener('keydown', (event) => {
        if (event.key === 'Enter') searchBtn.click();
    });

    const handleClose = () => {
        modal.classList.add('exit');
        setTimeout(() => modal.remove(), 200);
    };

    closeModalBtn.addEventListener('click', handleClose);
    document.addEventListener('keydown', (event) => {
        if (event.key === 'Escape') {
            handleClose();
        }
    });

    modal.addEventListener('click', (event) => {
        const modalContent = modal.querySelector('.modal-content');
        const modalRect = modalContent.getBoundingClientRect();

        const safeZone = 150;

        const isClickOutsideSafeZone = 
            event.clientX < modalRect.left - safeZone ||
            event.clientX > modalRect.right + safeZone ||
            event.clientY < modalRect.top - safeZone ||
            event.clientY > modalRect.bottom + safeZone;

        if (isClickOutsideSafeZone) {
            handleClose();
        }
    });

    searchBtn.addEventListener('click', async () => {
        const query = searchInput.value.trim();
        if (!query) {
            showNotification('Please enter a search query', 'warning');
            return;
        }
        allAvatars = [];
        performAvatarSearch(query, resultsContainer, 1);
    });

    if (initialQuery) {
        performAvatarSearch(initialQuery, resultsContainer, 1);
    }
}

async function performAvatarSearch(query, container, page = 1) {
    if (container) {
        container.scrollTo({
            top: 0,
            behavior: 'smooth'
        });
    }

    if (!query.trim()) {
        showNotification('Please enter a search query', 'warning');
        return;
    }

    if (allAvatars.length === 0) {
        try {
            const { response } = await SendRequest('GET', `https://vrcx.vrcdb.com/avatars/Avatar/VRCX?search=${encodeURIComponent(query)}`);
            const data = JSON.parse(response);

            if (data.length === 0) {
                container.innerHTML = '<p style="margin-top: inherit;">No results found</p>';
                return;
            }

            allAvatars = data;
            totalPages = Math.ceil(allAvatars.length / avatarsPerPage);
        } catch (error) {
            showNotification(`Error while searching: ${error.message}`, 'error');
            return;
        }
    }

    container.innerHTML = '';

    const startIdx = (page - 1) * avatarsPerPage;
    const endIdx = startIdx + avatarsPerPage;
    const currentAvatars = allAvatars.slice(startIdx, endIdx);

    currentAvatars.forEach(avatar => {
        const avatarCard = document.createElement('div');
        avatarCard.classList.add('search-result-card');
        avatarCard.style.display = 'flex';
        avatarCard.style.alignItems = 'center';
        avatarCard.style.marginBottom = '10px';
        avatarCard.style.width = '95%';

        const img = document.createElement('img');
        img.src = avatar.imageUrl || 'https://assets.vrchat.com/default/unavailable-avatar.png';
        avatarCard.appendChild(img);

        const infoDiv = document.createElement('div');
        infoDiv.style.flexGrow = 1;
        infoDiv.style.width = 'min-content';

        const name = document.createElement('h4');
        name.textContent = avatar.avatarName;
        name.title = `Description:\n${avatar.description}`;
        name.style.margin = '0';
        infoDiv.appendChild(name);

        const author = document.createElement('p');
        author.textContent = `Created by: ${avatar.authorName}`;
        author.style.margin = '0';
        author.style.fontSize = '0.9em';
        author.style.color = '#aaa';
        infoDiv.appendChild(author);

        avatarCard.appendChild(infoDiv);

        const addButton = document.createElement('button');
        addButton.textContent = 'Add to Favorites';
        addButton.classList.add('css-1vrq36y-custom');
        addButton.addEventListener('click', () => {
            AddAvatar(addMetod.ID, avatar.id);
            showNotification(`Added ${avatar.avatarName} to favorites`, 'success');
        });

        avatarCard.appendChild(addButton);
        container.appendChild(avatarCard);
    });

    addPaginationButtons(container, totalPages, page);
}

function addPaginationButtons(container, totalPages, currentPage) {
    const paginationContainer = document.getElementById('pagination-container');
    paginationContainer.innerHTML = '';

    const createButton = (text, disabled, onClick) => {
        const btn = document.createElement('button');
        btn.textContent = text;
        btn.classList.add('css-1vrq36y-custom');
        btn.style.margin = '0 5px';
        btn.disabled = disabled;
        btn.addEventListener('click', onClick);
        return btn;
    };

    paginationContainer.appendChild(createButton('Previous', currentPage === 1, () => {
        performAvatarSearch(document.getElementById('search-input').value.trim(), container, currentPage - 1);
    }));

    const pageInfo = document.createElement('span');
    pageInfo.textContent = `Page ${currentPage} of ${totalPages}`;
    pageInfo.style.margin = '0 10px';
    paginationContainer.appendChild(pageInfo);

    paginationContainer.appendChild(createButton('Next', currentPage >= totalPages, () => {
        performAvatarSearch(document.getElementById('search-input').value.trim(), container, currentPage + 1);
    }));
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址