GitHub Animated Avatar Border

Добавляет анимированную рамку для аватарки на GitHub.

// ==UserScript==
// @name         GitHub Animated Avatar Border
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Добавляет анимированную рамку для аватарки на GitHub.
// @homepageURL  https://github.com/smi-falcon/GitHub-Animated-Avatar-Border
// @supportURL   https://github.com/smi-falcon/GitHub-Animated-Avatar-Border/issues
// @author       Falcon (https://github.com/smi-falcon)
// @match        https://github.com/*
// @match        https://gist.github.com/*
// @icon         https://github.githubassets.com/favicons/favicon.svg
// @icon64       https://github.githubassets.com/favicons/favicon.png
// @license      MIT
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const style = document.createElement('style');
    style.textContent = `
        @keyframes avatar-glow {
            0% {
                box-shadow: 0 0 5px #ff6b6b, 0 0 10px #ff6b6b, 0 0 15px #ff6b6b;
            }
            25% {
                box-shadow: 0 0 10px #4ecdc4, 0 0 20px #4ecdc4, 0 0 30px #4ecdc4;
            }
            50% {
                box-shadow: 0 0 15px #45b7d1, 0 0 30px #45b7d1, 0 0 45px #45b7d1;
            }
            75% {
                box-shadow: 0 0 10px #96ceb4, 0 0 20px #96ceb4, 0 0 30px #96ceb4;
            }
            100% {
                box-shadow: 0 0 5px #ff6b6b, 0 0 10px #ff6b6b, 0 0 15px #ff6b6b;
            }
        }

        .animated-avatar-border {
            animation: avatar-glow 3s ease-in-out infinite;
            border-radius: 50% !important;
            padding: 3px !important;
            background: linear-gradient(45deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4) !important;
            background-size: 400% 400% !important;
            animation: avatar-glow 3s ease infinite, gradient-shift 4s ease infinite !important;
        }

        @keyframes gradient-shift {
            0% { background-position: 0% 50%; }
            50% { background-position: 100% 50%; }
            100% { background-position: 0% 50%; }
        }
    `;
    document.head.appendChild(style);

    let myUsername = null;

    function getMyUsername() {
        if (myUsername) return myUsername;

        const metaUser = document.querySelector('meta[name="user-login"]');
        if (metaUser) {
            myUsername = metaUser.getAttribute('content');
            return myUsername;
        }

        const userNav = document.querySelector('[data-feature-category="user_nav"]');
        if (userNav) {
            const links = userNav.querySelectorAll('a');
            for (let link of links) {
                const href = link.getAttribute('href');
                if (href && href.startsWith('/') && href !== '/') {
                    const match = href.match(/^\/([^\/?]+)/);
                    if (match && match[1]) {
                        myUsername = match[1];
                        return myUsername;
                    }
                }
            }
        }

        return null;
    }

    function isMyAvatar(avatar) {
        const username = getMyUsername();
        if (!username) return false;

        const currentUrl = window.location.pathname;
        const isMyProfile = currentUrl === `/${username}` || currentUrl.startsWith(`/${username}/`);

        if (isMyProfile) {
            const profileAvatars = document.querySelectorAll('.js-profile-editable-area img, .Layout-sidebar img.avatar, [itemprop="image"], .avatar.avatar-small, header img.avatar');
            for (let profileAvatar of profileAvatars) {
                if (profileAvatar === avatar) {
                    return true;
                }
            }

            if (avatar.closest('header') || avatar.closest('.Layout-header')) {
                return true;
            }

            if (avatar.closest('[data-test-selector="profile-avatar"]')) {
                return true;
            }

            return false;
        }

        const parentLink = avatar.closest('a');
        if (parentLink) {
            const href = parentLink.getAttribute('href');
            if (href && href.startsWith(`/${username}`)) {
                return true;
            }
        }

        const altText = avatar.getAttribute('alt') || '';
        if (altText.toLowerCase().includes(username.toLowerCase())) {
            return true;
        }

        const hovercardParent = avatar.closest('[data-hovercard-type="user"]');
        if (hovercardParent) {
            const hovercardUrl = hovercardParent.getAttribute('data-hovercard-url');
            if (hovercardUrl && hovercardUrl.includes(`/${username}`)) {
                return true;
            }
        }

        const repositoryHeader = avatar.closest('.Layout-header, .repohead, .gh-header');
        if (repositoryHeader && isMyProfile) {
            return true;
        }

        return false;
    }

    function applyAnimatedBorder() {
        const avatarSelectors = [
            'img.avatar',
            'img.avatar-user',
            '[data-hovercard-type="user"] img',
            'header img.avatar',
            '.Layout-header img.avatar',
            '.avatar.avatar-small',
            '[data-test-selector="profile-avatar"] img'
        ];

        avatarSelectors.forEach(selector => {
            const avatars = document.querySelectorAll(selector);
            avatars.forEach(avatar => {
                if (avatar.tagName === 'IMG' &&
                    !avatar.classList.contains('animated-border-applied') &&
                    isMyAvatar(avatar)) {

                    avatar.classList.add('animated-avatar-border');
                    avatar.classList.add('animated-border-applied');

                    const observer = new MutationObserver(function(mutations) {
                        mutations.forEach(function(mutation) {
                            if (mutation.type === 'attributes' && mutation.attributeName === 'src') {
                                setTimeout(() => {
                                    if (!avatar.classList.contains('animated-avatar-border')) {
                                        avatar.classList.add('animated-avatar-border');
                                    }
                                }, 100);
                            }
                        });
                    });

                    observer.observe(avatar, { attributes: true, attributeFilter: ['src'] });
                }
            });
        });

        const currentUrl = window.location.pathname;
        if (currentUrl === '/settings/profile' || currentUrl === '/dashboard') {
            const headerAvatars = document.querySelectorAll('header img.avatar, .AppHeader img.avatar, [data-test-selector="user-avatar"]');
            headerAvatars.forEach(avatar => {
                if (avatar.tagName === 'IMG' && !avatar.classList.contains('animated-border-applied')) {
                    avatar.classList.add('animated-avatar-border');
                    avatar.classList.add('animated-border-applied');
                }
            });
        }
    }

    window.addEventListener('load', applyAnimatedBorder);

    const observer = new MutationObserver(function() {
        applyAnimatedBorder();
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    applyAnimatedBorder();
    setTimeout(applyAnimatedBorder, 1000);
    setTimeout(applyAnimatedBorder, 3000);
})();