Script Notifier

Sistema de notificações para UserScripts.

Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greasyfork.org/scripts/549920/1686683/Script%20Notifier.js

// ==UserScript==
// @name               Script Notifier
// @namespace          http://github.com/0H4S
// @version            1.5
// @author             OHAS
// @description        Sistema de notificação para UserScripts.
// @license            CC-BY-NC-ND-4.0
// @copyright          2025 OHAS. All Rights Reserved. (https://gist.github.com/0H4S/ae2fa82957a089576367e364cbf02438)
// ==/UserScript==

/*
    Copyright Notice & Terms of Use
    Copyright © 2025 OHAS. All Rights Reserved.

    This software is the exclusive property of OHAS and is licensed for personal, non-commercial use only.

    You may:
    - Install, use, and inspect the code for learning or personal purposes.

    You may NOT (without prior written permission from the author):
    - Copy, redistribute, or republish this software.
    - Modify, sell, or use it commercially.
    - Create derivative works.

    For questions, permission requests, or alternative licensing, please contact via
    - GitHub:       https://github.com/0H4S
    - Greasy Fork:  https://greasyfork.org/users/1464180

    This software is provided "as is", without warranty of any kind. The author is not liable for any damages arising from its use.
*/

class ScriptNotifier {
    constructor({ notificationsUrl, scriptVersion, currentLang }) {
        this.NOTIFICATIONS_URL = notificationsUrl;
        this.SCRIPT_VERSION = scriptVersion;
        this.forcedLang = currentLang;
        this.currentLang = 'en';
        this.STAGGER_DELAY = 70;
        this.DISMISSED_NOTIFICATIONS_KEY = 'DismissedNotifications';
        this.NOTIFICATIONS_ENABLED_KEY = 'NotificationsEnabled';
        this.LANG_STORAGE_KEY = 'UserScriptLang';
        this.hostElement = null;
        this.shadowRoot = null;
        this.activeNotifications = [];
        this.uiStrings = {};
        this.icons = this._getIcons();
        this.scriptPolicy = this._createPolicy();
    }
    async run() {
        await this._initializeLanguage();
        await this._registerUserCommands();
        setTimeout(() => this.checkForNotifications(), 1500);
    }
    forceShowAllNotifications() {
        this.checkForNotifications(true);
    }
    checkForNotifications(forceShow = false) {
        if (!this.NOTIFICATIONS_URL || this.NOTIFICATIONS_URL.includes("SEU_USUARIO")) return;
        GM_xmlhttpRequest({
            method: 'GET',
            url: `${this.NOTIFICATIONS_URL}?t=${new Date().getTime()}`,
            onload: async (response) => {
                if (response.status < 200 || response.status >= 300) {
                    console.error(`Script Notifier: Falha ao buscar notificações. Status: ${response.status}`);
                    return;
                }
                let jsonString;
                const isGistPage = this.NOTIFICATIONS_URL.includes('gist.github.com/') && !this.NOTIFICATIONS_URL.includes('usercontent');
                if (isGistPage) {
                    try {
                        const parser = new DOMParser();
                        const trustedHtml = this.scriptPolicy
                            ? this.scriptPolicy.createHTML(response.responseText)
                            : response.responseText;
                        const doc = parser.parseFromString(trustedHtml, 'text/html');
                        const codeLines = doc.querySelectorAll('table.highlight .blob-code-inner');
                        if (codeLines.length === 0) {
                            console.error('Script Notifier: A página Gist foi encontrada, mas nenhum conteúdo pôde ser extraído.');
                            return;
                        }
                        jsonString = Array.from(codeLines).map(line => line.innerText).join('\n');
                    } catch (e) {
                        console.error('Script Notifier: Falha ao analisar a página Gist.', e);
                        return;
                    }
                } else {
                    jsonString = response.responseText;
                }
                try {
                    const data = JSON.parse(jsonString);
                    const notifications = data.notifications || [];
                    if (forceShow) {
                        this.activeNotifications.forEach(n => n.element.remove());
                        this.activeNotifications = [];
                    }
                    await this._cleanupDismissedNotifications(notifications);
                    const dismissed = await GM_getValue(this.DISMISSED_NOTIFICATIONS_KEY, []);
                    const notificationsToDisplay = notifications.filter(notification => {
                        if (this.activeNotifications.some(n => n.id === notification.id)) return false;
                        if (!forceShow && dismissed.includes(notification.id)) return false;
                        if (notification.expires && new Date(notification.expires) < new Date()) return false;
                        if (notification.targetVersion !== 'all' && notification.targetVersion !== this.SCRIPT_VERSION) return false;
                        if (notification.targetHostname && window.location.hostname !== notification.targetHostname) return false;
                        return true;
                    });

                    notificationsToDisplay.forEach((notification, index) => {
                        setTimeout(() => this.displayNotification(notification), index * 200);
                    });
                } catch (e) {
                    console.error('Script Notifier: Falha ao analisar o JSON das notificações.', e);
                }
            },
            onerror: (error) => {
                console.error('Script Notifier: Erro de rede ao buscar as notificações.', error);
            }
        });
    }
    async displayNotification(notification) {
        this._ensureHostElement();
        const notificationsEnabled = await GM_getValue(this.NOTIFICATIONS_ENABLED_KEY, true);
        if (notification.priority !== 'high' && !notificationsEnabled) return;
        const notificationId = `notification-${notification.id}`;
        if (this.shadowRoot.getElementById(notificationId)) return;
        const title = this._getTranslatedText(notification.title);
        const message = this._getTranslatedText(notification.message);
        if (!title && !message) return;
        const container = document.createElement('div');
        container.id = notificationId;
        container.className = 'notification-container';
        const notificationType = notification.type || 'info';
        container.dataset.type = notificationType;
        if (notification.customColor) {
            container.style.borderLeftColor = notification.customColor;
            container.style.setProperty('--type-color', notification.customColor);
        }
        let iconHTML = this.icons[notificationType] || this.icons['info'];
        if (notification.customIconSvg) {
            iconHTML = this._sanitizeAndStyleSvg(notification.customIconSvg);
        }
        const imageOrIconHTML = notification.imageUrl ?
            `<img src="${notification.imageUrl}" class="notification-image" alt="Notification Image">` :
            `<div class="notification-icon">${iconHTML}</div>`;
        const closeIconSVG = `<svg viewBox="0 0 24 24" fill="currentColor" width="20" height="20"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path></svg>`;
        const notificationHTML = `
          ${imageOrIconHTML}
          <div class="notification-content">
              <h3 class="notification-title">${this._prepareMessageHTML(title)}</h3>
              <div class="notification-message">${this._prepareMessageHTML(message)}</div>
          </div>
          <button class="dismiss-button" title="${this._getUIText('closeButtonTitle')}">${closeIconSVG}</button>
        `;
        this._setSafeInnerHTML(container, notificationHTML);
        if (notification.buttons && notification.buttons.length > 0) {
            const buttonsContainer = this._createButtons(notification.buttons, notification.id);
            container.querySelector('.notification-content').appendChild(buttonsContainer);
        }
        this.shadowRoot.appendChild(container);
        this.activeNotifications.push({ id: notification.id, element: container, isNew: true });
        this._updateNotificationPositions();
        container.querySelector('.dismiss-button').onclick = (e) => {
            e.stopPropagation();
            this._dismissNotification(notification.id);
        };
    }
    _createButtons(buttonDataArray, notificationId) {
        const buttonsContainer = document.createElement('div');
        buttonsContainer.className = 'notification-buttons';
        buttonDataArray.forEach((buttonData, index) => {
            const button = document.createElement('button');
            const buttonText = this._getTranslatedText(buttonData.text);
            this._setSafeInnerHTML(button, this._prepareMessageHTML(buttonText));
            button.className = 'notification-button';
            if (buttonData.backgroundColor) {
                button.style.backgroundColor = buttonData.backgroundColor;
                button.classList.add('custom-bg');
            } else if (index === 0) {
                button.classList.add('primary');
            }
            if (buttonData.textColor) {
                button.style.color = buttonData.textColor;
            }
            button.onclick = (e) => {
                e.stopPropagation();
                if (buttonData.action) {
                    switch (buttonData.action) {
                        case 'open_url': window.location.href = buttonData.value; break;
                        case 'open_url_new_tab': window.open(buttonData.value, '_blank'); break;
                    }
                }
                this._dismissNotification(notificationId);
            };
            buttonsContainer.appendChild(button);
        });
        return buttonsContainer;
    }
    async _dismissNotification(notificationId) {
        this._ensureHostElement();
        const notification = this.activeNotifications.find(n => n.id === notificationId);
        if (!notification) return;
        const dismissed = await GM_getValue(this.DISMISSED_NOTIFICATIONS_KEY, []);
        if (!dismissed.includes(notificationId)) {
            dismissed.push(notificationId);
            await GM_setValue(this.DISMISSED_NOTIFICATIONS_KEY, dismissed);
        }
        notification.element.classList.remove('animate-in');
        notification.element.classList.add('animate-out');
        setTimeout(() => {
            this.activeNotifications = this.activeNotifications.filter(n => n.id !== notificationId);
            notification.element.remove();
            this._updateNotificationPositions();
        }, 600);
    }
    _updateNotificationPositions() {
        this._ensureHostElement();
        const spacingValue = parseInt(getComputedStyle(this.shadowRoot.host).getPropertyValue('--sn-spacing')) || 20;
        let currentTop = spacingValue;
        this.activeNotifications.forEach((notif, index) => {
            const { element } = notif;
            element.style.top = `${currentTop}px`;
            element.style.transitionDelay = `${index * this.STAGGER_DELAY}ms`;
            if (notif.isNew) {
                requestAnimationFrame(() => {
                    element.classList.add('animate-in');
                });
                delete notif.isNew;
            }
            currentTop += element.offsetHeight + (spacingValue / 2);
        });
    }
    _ensureHostElement() {
        const hostId = 'script-notifier-host';
        this.hostElement = document.getElementById(hostId);
        if (!this.hostElement) {
            this.hostElement = document.createElement('div');
            this.hostElement.id = hostId;
            document.body.appendChild(this.hostElement);
        }
        if (!this.hostElement.shadowRoot) {
            this.shadowRoot = this.hostElement.attachShadow({ mode: 'open' });
            const style = document.createElement('style');
            style.textContent = this._getNotifierStyles();
            this.shadowRoot.appendChild(style);
        } else {
            this.shadowRoot = this.hostElement.shadowRoot;
        }
    }
    async _initializeLanguage() {
        const supportedLanguages = ['pt-BR', 'zh-CN', 'en', 'es', 'ja', 'ko' ];
        let lang = this.forcedLang || await GM_getValue(this.LANG_STORAGE_KEY) || navigator.language || 'en';
        if          (lang.startsWith('pt')) lang = 'pt-BR';
        else if     (lang.startsWith('zh')) lang = 'zh-CN';
        else if     (lang.startsWith('en')) lang = 'en';
        else if     (lang.startsWith('es')) lang = 'es';
        else if     (lang.startsWith('ja')) lang = 'ja';
        else if     (lang.startsWith('ko')) lang = 'ko';
        this.currentLang = supportedLanguages.includes(lang) ? lang : 'en';
        this.uiStrings = this._getUIStrings();
    }
    async _cleanupDismissedNotifications(serverNotifications) {
        const dismissed = await GM_getValue(this.DISMISSED_NOTIFICATIONS_KEY, []);
        if (dismissed.length === 0) return;
        const validServerIds = new Set(serverNotifications.filter(n => !n.expires || new Date(n.expires) >= new Date()).map(n => n.id));
        const cleanedDismissed = dismissed.filter(id => validServerIds.has(id));
        if (cleanedDismissed.length < dismissed.length) {
            await GM_setValue(this.DISMISSED_NOTIFICATIONS_KEY, cleanedDismissed);
        }
    }
    async _registerUserCommands() {
        const notificationsEnabled = await GM_getValue(this.NOTIFICATIONS_ENABLED_KEY, true);
        const toggleCommandText = notificationsEnabled ? this._getUIText('disableNotificationsCmd') : this._getUIText('enableNotificationsCmd');

        GM_registerMenuCommand(this._getUIText('showAllNotificationsCmd'), () => this.forceShowAllNotifications());
        GM_registerMenuCommand(toggleCommandText, async () => {
            const currentState = await GM_getValue(this.NOTIFICATIONS_ENABLED_KEY, true);
            await GM_setValue(this.NOTIFICATIONS_ENABLED_KEY, !currentState);
            window.location.reload();
        });
    }
    _createPolicy() {
        return window.trustedTypes ? window.trustedTypes.createPolicy('script-notifier-policy-unico', {
            createHTML: (input) => input
        }) : null;
    }
    _getUIText(key) { return this.uiStrings[key]?.[this.currentLang] || this.uiStrings[key]?.['en'] || ''; }
    _setSafeInnerHTML(element, html) {
        if (!element) return;
        element.innerHTML = this.scriptPolicy ? this.scriptPolicy.createHTML(html) : html;
    }
    _getTranslatedText(translationObject) {
        if (!translationObject) return '';
        if (typeof translationObject === 'string') return translationObject;
        return translationObject[this.currentLang] || translationObject[this.currentLang.split('-')[0]] || translationObject['en'] || '';
    }
    _prepareMessageHTML(text) { return text || ''; }
    _sanitizeAndStyleSvg(svgString) {
        try {
            const tempDiv = document.createElement('div');
            this._setSafeInnerHTML(tempDiv, svgString);
            const svgElement = tempDiv.querySelector('svg');
            if (!svgElement) return '';
            svgElement.setAttribute     ('fill', 'currentColor');
            svgElement.removeAttribute  ('width');
            svgElement.removeAttribute  ('height');
            svgElement.removeAttribute  ('style');
            svgElement.removeAttribute  ('class');
            return svgElement.outerHTML;
        } catch (e) {
            return '';
        }
    }
    _getUIStrings() {
        return {
            showAllNotificationsCmd: {
              'pt-BR':  '🔔 Notificações',
              'zh-CN':  '🔔 通知',
              'en':     '🔔 Notifications',
              'es':     '🔔 Notificaciones',
              'ja':     '🔔 通知',
              'ko':     '🔔 알림' },
            disableNotificationsCmd: {
              'pt-BR':  '❌ Desativar Notificações',
              'zh-CN':  '❌ 禁用通知',
              'en':     '❌ Disable Notifications',
              'es':     '❌ Desactivar Notificaciones',
              'ja':     '❌ 通知を無効にする',
              'ko':     '❌ 알림 비활성화' },
            enableNotificationsCmd: {
              'pt-BR':  '✅ Ativar Notificações',
              'zh-CN':  '✅ 启用通知',
              'en':     '✅ Enable Notifications',
              'es':     '✅ Activar Notificaciones',
              'ja':     '✅ 通知を有効にする',
              'ko':     '✅ 알림 활성화' },
            closeButtonTitle: {
              'pt-BR':  'Fechar',
              'zh-CN':  '关闭',
              'en':     'Close',
              'es':     'Cerrar',
              'ja':     '閉じる',
              'ko':     '닫기' }
        };
    }
    _getIcons() {
        return {
            success:    `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"></path></svg>`,
            warning:    `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"></path></svg>`,
            info:       `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"></path></svg>`
        };
    }
    _getNotifierStyles() {
        return `
            :host {
                --sn-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
                --sn-color-background: #fff;
                --sn-color-text-primary: #000;
                --sn-color-text-secondary: #333;
                --sn-color-border: #ddd;
                --sn-color-link: currentColor;
                --sn-color-link-underline: currentColor;
                --sn-color-dismiss: #999;
                --sn-color-dismiss-hover: #ff4d4d;
                --sn-shadow-default: 0 8px 20px rgba(0, 0, 0, 0.15);
                --sn-shadow-button-hover: 0 4px 10px rgba(0,0,0,0.2);
                --sn-card-background: rgba(0, 0, 0, 0.05);
                --sn-card-border: #ccc;
                --sn-scrollbar-track: #f1f1f1;
                --sn-scrollbar-thumb: #ccc;
                --sn-scrollbar-thumb-hover: #aaa;
                --sn-button-hover-bg: #555;
                --sn-button-hover-text: #fff;
                --sn-border-radius: 12px;
                --sn-border-radius-small: 6px;
                --sn-padding: 16px;
                --sn-notification-width: 380px;
                --sn-spacing: 20px;
                --sn-icon-size: 24px;
                --sn-image-size: 48px;
                --sn-font-size-title: 16px;
                --sn-font-size-body: 14px;
                --sn-font-weight-title: 600;
                --sn-message-max-height: 110px;
                --sn-animation-duration-fast: 0.2s;
                --sn-animation-duration-medium: 0.4s;
                --sn-animation-duration-slow: 0.8s;
            }
            @media (prefers-color-scheme: dark) {
                :host {
                    --sn-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
                    --sn-color-background: #333;
                    --sn-color-text-primary: #fff;
                    --sn-color-text-secondary: #ddd;
                    --sn-color-border: #444;
                    --sn-color-link: currentColor;
                    --sn-color-link-underline: currentColor;
                    --sn-color-dismiss: #aaa;
                    --sn-color-dismiss-hover: #ff4d4d;
                    --sn-shadow-default: 0 8px 20px rgba(0, 0, 0, 0.5);
                    --sn-shadow-button-hover: 0 4px 12px rgba(0,0,0,0.4);
                    --sn-card-background: rgba(0, 0, 0, 0.1);
                    --sn-card-border: #555;
                    --sn-scrollbar-track: #444;
                    --sn-scrollbar-thumb: #666;
                    --sn-scrollbar-thumb-hover: #888;
                    --sn-button-hover-bg: #777;
                    --sn-button-hover-text: #fff;
                    --sn-border-radius: 12px;
                    --sn-border-radius-small: 6px;
                    --sn-padding: 16px;
                    --sn-notification-width: 380px;
                    --sn-spacing: 20px;
                    --sn-icon-size: 24px;
                    --sn-image-size: 48px;
                    --sn-font-size-title: 16px;
                    --sn-font-size-body: 14px;
                    --sn-font-weight-title: 600;
                    --sn-message-max-height: 110px;
                    --sn-animation-duration-fast: 0.2s;
                    --sn-animation-duration-medium: 0.4s;
                    --sn-animation-duration-slow: 0.8s;
                }
            }
            .notification-container {
                position: fixed;
                top: 0;
                right: var(--sn-spacing);
                z-index: 2147483647;
                width: var(--sn-notification-width);
                font-family: var(--sn-font-family);
                background-color: var(--sn-color-background);
                color: var(--sn-color-text-secondary);
                border-radius: var(--sn-border-radius);
                box-shadow: var(--sn-shadow-default);
                border: 1px solid var(--sn-color-border);
                display: flex;
                padding: var(--sn-padding);
                box-sizing: border-box;
                border-left: 5px solid transparent;
                opacity: 0;
                transform: translateX(120%);
                will-change: transform, opacity, top;
            }
            .notification-container.animate-in {
                opacity: 1;
                transform: translateX(0);
                transition: transform var(--sn-animation-duration-slow) cubic-bezier(0.22, 1.6, 0.5, 1), opacity var(--sn-animation-duration-medium) ease-out, top var(--sn-animation-duration-slow) cubic-bezier(0.22, 1.6, 0.5, 1);
            }
            .notification-container.animate-out {
                opacity: 0;
                transform: translateX(120%);
                transition: transform var(--sn-animation-duration-medium) cubic-bezier(0.6, -0.28, 0.735, 0.045), opacity var(--sn-animation-duration-medium) ease-out, top var(--sn-animation-duration-medium) ease-out;
            }
            .notification-container[data-type="success"] {
                --type-color: #22c55e;
            }
            .notification-container[data-type="warning"] {
                --type-color: #f97316;
            }
            .notification-container[data-type="info"] {
                --type-color: #3b82f6;
            }
            .notification-container[data-type] {
                border-left-color: var(--type-color);
            }
            .notification-icon {
                width: var(--sn-icon-size);
                height: var(--sn-icon-size);
                margin-right: 12px;
                flex-shrink: 0;
                color: var(--type-color);
            }
            .notification-image {
                width: var(--sn-image-size);
                height: var(--sn-image-size);
                border-radius: var(--sn-border-radius-small);
                object-fit: cover;
                flex-shrink: 0;
                margin-right: 15px;
            }
            .notification-content {
                flex-grow: 1;
                word-break: break-word;
            }
            .notification-title {
                margin: 0 0 8px;
                font-size: var(--sn-font-size-title);
                font-weight: var(--sn-font-weight-title);
                color: var(--sn-color-text-primary);
            }
            .notification-message {
                font-size: var(--sn-font-size-body);
                line-height: 1.5;
                max-height: var(--sn-message-max-height);
                overflow-y: auto;
                padding-right: 8px;
            }
            .notification-message ul,
            .notification-message ol {
                padding-left: 1.5rem;
                margin: 0.5rem 0;
            }
            .notification-message blockquote {
                margin: 0.5em 0; padding: 0.5em 1em;
                border-radius: 0;
                background-color: var(--sn-card-background);
                border-left: 4px solid var(--sn-card-border);
            }
            .notification-message a,
            .notification-title a {
                color: var(--sn-color-link);
                text-decoration: none;
            }
            .notification-message a:hover,
            .notification-title a:hover {
                text-decoration: underline;
                text-decoration-color: var(--sn-color-link-underline);
            }
            .dismiss-button {
                background: none;
                border: none;
                color: var(--sn-color-dismiss);
                cursor: pointer;
                padding: 0;
                margin-left: 10px;
                align-self: flex-start;
                transition: color var(--sn-animation-duration-fast) ease, transform var(--sn-animation-duration-medium) cubic-bezier(0.25, 0.1, 0.25, 1.5);
                width: var(--sn-icon-size);
                height: var(--sn-icon-size);
                display: inline-flex;
                align-items: center;
                justify-content: center;
            }
            .dismiss-button:hover {
                color: var(--sn-color-dismiss-hover);
                transform: rotate(90deg);
            }
            .dismiss-button:active {
                transform: rotate(90deg) scale(0.9);
            }
            .notification-buttons {
                margin-top: 12px;
                display: flex;
                gap: 8px;
                flex-wrap: wrap;
            }
            .notification-button {
                background-color: var(--sn-color-border);
                color: var(--sn-color-text-secondary);
                border: none;
                border-radius: var(--sn-border-radius-small);
                padding: 6px 12px;
                font-size: var(--sn-font-size-body);
                font-weight: 500;
                cursor: pointer;
                transition: transform var(--sn-animation-duration-fast) ease-in-out, box-shadow var(--sn-animation-duration-fast) ease-in-out, background-color var(--sn-animation-duration-fast) ease-in-out,filter var(--sn-animation-duration-fast) ease-in-out, color var(--sn-animation-duration-fast) ease-in-out;
            }
            .notification-button:hover {
                transform: translateY(-2px);
                box-shadow: var(--sn-shadow-button-hover);
                filter: brightness(1.15);
            }
            .notification-button:not(.custom-bg):hover {
                background-color: var(--sn-button-hover-bg);
                color: var(--sn-button-hover-text);
                filter: brightness(1);
            }
            .notification-button:active {
                transform: translateY(0);
                box-shadow: none;
                transition-duration: 0.1s;
            }
            .notification-button.primary {
                background-color: var(--sn-color-link);
                color: #fff;
            }
            .notification-button.primary:hover {
                background-color: var(--sn-color-link);
                color: #fff;
                filter: brightness(1.1);
            }
            .notification-button.custom-bg:hover {
                filter: brightness(1.15);
            }
            .notification-message::-webkit-scrollbar {
                width: 6px;
            }
            .notification-message::-webkit-scrollbar-track {
                background: var(--sn-scrollbar-track);
                border-radius: 3px;
            }
            .notification-message::-webkit-scrollbar-thumb {
                background: var(--sn-scrollbar-thumb);
                border-radius: 3px;
            }
            .notification-message::-webkit-scrollbar-thumb:hover {
                background: var(--sn-scrollbar-thumb-hover);
            }
        `;
    }
}