Comick User Blocker

Block users on comick.dev and hide their reviews, comments, list activity, and custom lists

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

// ==UserScript==
// @name         Comick User Blocker
// @namespace    https://github.com/GooglyBlox
// @version      1.0
// @description  Block users on comick.dev and hide their reviews, comments, list activity, and custom lists
// @author       GooglyBlox
// @license      MIT
// @match        https://comick.dev/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @connect      auth.comick.dev
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    const STORAGE_KEY = 'comick_blocked_users';
    const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
    const USER_PATH_RE = /^\/user\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:\/|$)/i;

    let currentUserId = null;
    let blockedCache = null;
    let scheduled = false;
    let suppressObserver = false;

    function getBlocked() {
        if (blockedCache) return blockedCache;
        try {
            const raw = GM_getValue(STORAGE_KEY, '{}');
            blockedCache = JSON.parse(raw);
        } catch {
            blockedCache = {};
        }
        return blockedCache;
    }

    function saveBlocked(map) {
        blockedCache = map;
        GM_setValue(STORAGE_KEY, JSON.stringify(map));
    }

    function blockUser(uuid, username) {
        if (!UUID_RE.test(uuid)) return;
        const map = getBlocked();
        map[uuid] = {
            username: username || 'Unknown',
            blockedAt: Date.now()
        };
        saveBlocked(map);
        scheduleUpdate(true);
    }

    function unblockUser(uuid) {
        const map = getBlocked();
        delete map[uuid];
        saveBlocked(map);
        scheduleUpdate(true);
    }

    function isBlocked(uuid) {
        if (!uuid) return false;
        return Object.prototype.hasOwnProperty.call(getBlocked(), uuid);
    }

    function fetchCurrentUser() {
        return new Promise((resolve) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: 'https://auth.comick.dev/sessions/whoami',
                headers: {
                    'accept': 'application/json, text/plain, */*'
                },
                withCredentials: true,
                onload(res) {
                    try {
                        const data = JSON.parse(res.responseText);
                        const id = data && data.identity && data.identity.id;
                        if (id && UUID_RE.test(id)) {
                            currentUserId = id;
                        }
                    } catch {
                        /* not logged in */
                    }
                    resolve(currentUserId);
                },
                onerror() {
                    resolve(null);
                },
                ontimeout() {
                    resolve(null);
                }
            });
        });
    }

    function extractUuidFromHref(href) {
        if (!href) return null;
        try {
            const url = new URL(href, location.origin);
            const m = url.pathname.match(USER_PATH_RE);
            return m ? m[1].toLowerCase() : null;
        } catch {
            return null;
        }
    }

    function findUuidInSubtree(root) {
        if (!root || root.nodeType !== 1) return null;
        const self = root.getAttribute && root.getAttribute('href');
        const selfUuid = extractUuidFromHref(self);
        if (selfUuid) return selfUuid;
        const anchors = root.querySelectorAll ? root.querySelectorAll('a[href*="/user/"]') : [];
        for (const a of anchors) {
            const uuid = extractUuidFromHref(a.getAttribute('href'));
            if (uuid) return uuid;
        }
        return null;
    }

    function escapeHtml(str) {
        return String(str)
            .replace(/&/g, '&')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#39;');
    }

    function findRepliesContainer(commentEl) {
        const wrapper = commentEl.closest('.mb-2.relative');
        if (!wrapper) return null;
        let sibling = wrapper.nextElementSibling;
        while (sibling) {
            if (sibling.classList && sibling.classList.contains('ml-11')) {
                if (sibling.querySelector('div[id^="comment-"]')) {
                    return sibling;
                }
                return null;
            }
            sibling = sibling.nextElementSibling;
        }
        return null;
    }

    function replaceCommentWithPlaceholder(commentEl, uuid, username) {
        if (commentEl.getAttribute('data-cub-placeholder') === '1') return;
        suppressObserver = true;
        try {
            const avatar = commentEl.querySelector(':scope > div > img');
            const bubble = commentEl.querySelector(':scope > div > div.ml-2');
            if (!bubble) return;

            const avatarSize = avatar && avatar.classList.contains('h-7') ? 'small' : 'large';

            bubble.classList.add('cub-blocked-bubble');
            bubble.innerHTML = '';

            const row = document.createElement('div');
            row.className = 'cub-blocked-row';

            const icon = document.createElement('span');
            icon.className = 'cub-blocked-icon';
            icon.innerHTML = ICON_BLOCK;
            row.appendChild(icon);

            const text = document.createElement('div');
            text.className = 'cub-blocked-text';
            const profileHref = `/user/${uuid}`;
            text.innerHTML = `Comment from <a href="${profileHref}" class="cub-blocked-username">${escapeHtml(username)}</a> is hidden (blocked)`;
            row.appendChild(text);

            const showBtn = document.createElement('button');
            showBtn.type = 'button';
            showBtn.className = 'cub-blocked-show';
            showBtn.textContent = 'Unblock';
            showBtn.addEventListener('click', (e) => {
                e.preventDefault();
                unblockUser(uuid);
            });
            row.appendChild(showBtn);

            bubble.appendChild(row);

            if (avatar) {
                avatar.classList.add('cub-blocked-avatar');
                if (avatarSize === 'small') {
                    avatar.classList.add('cub-blocked-avatar-sm');
                }
            }

            commentEl.setAttribute('data-cub-placeholder', '1');
            commentEl.setAttribute('data-cub-hidden', '1');

            const actionsRow = commentEl.parentElement && commentEl.parentElement.querySelector(':scope > .flex.items-center.mt-1');
            if (actionsRow) {
                actionsRow.style.display = 'none';
                actionsRow.setAttribute('data-cub-action-hidden', '1');
            }
        } finally {
            suppressObserver = false;
        }
    }

    function hideReviewsAndComments() {
        const articles = document.querySelectorAll('article[id^="review-"]:not([data-cub-processed])');
        articles.forEach((art) => {
            art.setAttribute('data-cub-processed', '1');
            const uuid = findUuidInSubtree(art);
            if (uuid && isBlocked(uuid)) {
                art.style.display = 'none';
                art.setAttribute('data-cub-hidden', '1');
            }
        });

        const comments = document.querySelectorAll('div[id^="comment-"]:not([data-cub-processed])');
        comments.forEach((cmt) => {
            cmt.setAttribute('data-cub-processed', '1');
            const authorLink = cmt.querySelector('a[href^="/user/"]');
            const uuid = extractUuidFromHref(authorLink && authorLink.getAttribute('href'));
            if (!uuid || !isBlocked(uuid)) return;

            const info = getBlocked()[uuid] || {};
            const username = info.username || 'Unknown';
            const hasReplies = findRepliesContainer(cmt) !== null;

            if (hasReplies) {
                replaceCommentWithPlaceholder(cmt, uuid, username);
            } else {
                const wrapper = cmt.closest('.mb-2.relative') || cmt.parentElement;
                if (wrapper) {
                    wrapper.style.display = 'none';
                    wrapper.setAttribute('data-cub-hidden', '1');
                } else {
                    cmt.style.display = 'none';
                    cmt.setAttribute('data-cub-hidden', '1');
                }
            }
        });
    }

    function hideFollowsFeed() {
        const rows = document.querySelectorAll('.flex.items-center.border-t:not([data-cub-processed])');
        rows.forEach((row) => {
            const links = row.querySelectorAll('a[href*="/user/"]');
            if (!links.length) return;
            row.setAttribute('data-cub-processed', '1');
            for (const a of links) {
                const uuid = extractUuidFromHref(a.getAttribute('href'));
                if (uuid && isBlocked(uuid)) {
                    row.style.display = 'none';
                    row.setAttribute('data-cub-hidden', '1');
                    break;
                }
            }
        });
    }

    function hideCustomListCards() {
        const cards = document.querySelectorAll('a[href*="/user/"][href*="/list/"]:not([data-cub-processed])');
        cards.forEach((a) => {
            a.setAttribute('data-cub-processed', '1');
            const uuid = extractUuidFromHref(a.getAttribute('href'));
            if (uuid && isBlocked(uuid)) {
                a.style.display = 'none';
                a.setAttribute('data-cub-hidden', '1');
            }
        });
    }

    function hideRecentReviewCards() {
        const anchors = document.querySelectorAll('a[href*="?review-id="]:not([data-cub-processed-rev])');
        anchors.forEach((a) => {
            a.setAttribute('data-cub-processed-rev', '1');
            const article = a.closest('article');
            if (!article) return;
            const outer = article.closest('.overflow-hidden') || article.parentElement;
            if (!outer || outer.getAttribute('data-cub-hidden') === '1') return;

            const reviewId = (a.getAttribute('href').match(/review-id=(\d+)/) || [])[1];
            if (!reviewId) return;
            const exists = document.getElementById(`review-${reviewId}`);
            if (exists) {
                const uuid = findUuidInSubtree(exists);
                if (uuid && isBlocked(uuid)) {
                    outer.style.display = 'none';
                    outer.setAttribute('data-cub-hidden', '1');
                }
            }
        });
    }

    function applyBlocking() {
        hideReviewsAndComments();
        hideFollowsFeed();
        hideCustomListCards();
        hideRecentReviewCards();
    }

    function resetProcessedFlags() {
        document.querySelectorAll('[data-cub-processed]').forEach((el) => el.removeAttribute('data-cub-processed'));
        document.querySelectorAll('[data-cub-processed-rev]').forEach((el) => el.removeAttribute('data-cub-processed-rev'));
        document.querySelectorAll('[data-cub-hidden="1"]').forEach((el) => {
            el.style.display = '';
            el.removeAttribute('data-cub-hidden');
        });
        document.querySelectorAll('[data-cub-action-hidden="1"]').forEach((el) => {
            el.style.display = '';
            el.removeAttribute('data-cub-action-hidden');
        });
    }

    function iconSvg(pathD, extraAttrs = '') {
        return `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ${extraAttrs}>${pathD}</svg>`;
    }

    const ICON_BLOCK = iconSvg('<circle cx="12" cy="12" r="10"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/>');
    const ICON_UNBLOCK = iconSvg('<path d="M20 6 9 17l-5-5"/>');
    const ICON_SHIELD = iconSvg('<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>');

    function getProfileUuidFromPath() {
        const m = location.pathname.match(USER_PATH_RE);
        return m ? m[1].toLowerCase() : null;
    }

    function getProfileUsername() {
        const h1 = document.querySelector('h1');
        if (h1 && h1.textContent) return h1.textContent.trim();
        return 'Unknown';
    }

    function ensureProfileBlockButton() {
        const uuid = getProfileUuidFromPath();
        if (!uuid) return;
        if (currentUserId && uuid === currentUserId) {
            const existing = document.querySelector('[data-cub-profile-btn]');
            if (existing) {
                suppressObserver = true;
                existing.remove();
                suppressObserver = false;
            }
            return;
        }

        const h1 = document.querySelector('h1');
        if (!h1) return;
        const nameContainer = h1.parentElement;
        if (!nameContainer) return;

        const blocked = isBlocked(uuid);
        const desiredState = blocked ? 'blocked' : 'unblocked';
        const desiredUuid = uuid;

        let wrap = nameContainer.querySelector(':scope > [data-cub-profile-btn]');
        if (wrap && wrap.getAttribute('data-cub-uuid') === desiredUuid && wrap.getAttribute('data-cub-state') === desiredState) {
            return;
        }

        suppressObserver = true;
        try {
            if (wrap) wrap.remove();
            wrap = document.createElement('div');
            wrap.setAttribute('data-cub-profile-btn', '1');
            wrap.setAttribute('data-cub-uuid', desiredUuid);
            wrap.setAttribute('data-cub-state', desiredState);
            wrap.style.marginTop = '8px';

            const btn = document.createElement('button');
            btn.type = 'button';
            btn.className = 'cub-btn';
            btn.innerHTML = (blocked ? ICON_UNBLOCK : ICON_BLOCK) +
                `<span>${blocked ? 'Unblock user' : 'Block user'}</span>`;
            if (blocked) btn.classList.add('cub-btn-danger');
            btn.addEventListener('click', () => {
                if (isBlocked(uuid)) {
                    unblockUser(uuid);
                } else {
                    const username = getProfileUsername();
                    blockUser(uuid, username);
                }
            });
            wrap.appendChild(btn);
            nameContainer.appendChild(wrap);
        } finally {
            suppressObserver = false;
        }
    }

    function ensureBlockedTab() {
        const uuid = getProfileUuidFromPath();
        if (!uuid || !currentUserId || uuid !== currentUserId) {
            removeBlockedTab();
            return;
        }

        const nav = document.querySelector('nav[aria-label="Tabs"]');
        if (!nav) return;

        const scrollContainer = nav.parentElement;
        const widthContainer = nav.closest('.xl\\:max-w-3xl');

        if (scrollContainer && !scrollContainer.hasAttribute('data-cub-scroll-neutralized')) {
            suppressObserver = true;
            scrollContainer.setAttribute('data-cub-scroll-neutralized', '1');
            scrollContainer.classList.add('cub-scroll-neutralized');
            suppressObserver = false;
        }

        if (widthContainer && !widthContainer.hasAttribute('data-cub-nav-widened')) {
            suppressObserver = true;
            widthContainer.setAttribute('data-cub-nav-widened', '1');
            widthContainer.classList.add('cub-nav-widened');
            suppressObserver = false;
        }

        if (nav.querySelector('[data-cub-tab]')) {
            updateTabCount();
            syncTabActiveState();
            return;
        }

        suppressObserver = true;
        try {
            const tab = document.createElement('button');
            tab.type = 'button';
            tab.setAttribute('data-cub-tab', '1');
            tab.className = 'border-transparent text-gray-600 dark:text-gray-300 hover:text-blue-700 dark:hover:text-gray-200 hover:border-blue-500 dark:hover:border-blue-500 whitespace-nowrap flex py-4 px-1 border-b-2 font-medium text-sm flex-1';
            tab.style.background = 'transparent';
            tab.style.cursor = 'pointer';
            tab.innerHTML = `<span class="cub-tab-icon">${ICON_SHIELD}</span><span>Blocked</span><span data-cub-count class="cub-tab-count"></span>`;
            tab.addEventListener('click', (e) => {
                e.preventDefault();
                openBlockedView();
            });
            nav.appendChild(tab);
        } finally {
            suppressObserver = false;
        }
        updateTabCount();
    }

    function removeBlockedTab() {
        suppressObserver = true;
        try {
            const existing = document.querySelector('[data-cub-tab]');
            if (existing) existing.remove();
            document.querySelectorAll('[data-cub-nav-widened]').forEach((el) => {
                el.classList.remove('cub-nav-widened');
                el.removeAttribute('data-cub-nav-widened');
            });
            document.querySelectorAll('[data-cub-scroll-neutralized]').forEach((el) => {
                el.classList.remove('cub-scroll-neutralized');
                el.removeAttribute('data-cub-scroll-neutralized');
            });
        } finally {
            suppressObserver = false;
        }
    }

    function updateTabCount() {
        const el = document.querySelector('[data-cub-tab] [data-cub-count]');
        if (!el) return;
        const n = Object.keys(getBlocked()).length;
        const text = n > 0 ? String(n) : '';
        if (el.textContent !== text) {
            suppressObserver = true;
            el.textContent = text;
            suppressObserver = false;
        }
    }

    function syncTabActiveState() {
        const tab = document.querySelector('[data-cub-tab]');
        if (!tab) return;
        const isOpen = !!document.querySelector('[data-cub-blocked-view]');
        suppressObserver = true;
        if (isOpen) {
            tab.classList.remove('border-transparent', 'text-gray-600', 'dark:text-gray-300');
            tab.classList.add('border-blue-500', 'text-blue-600', 'dark:border-blue-200', 'dark:text-blue-300');
        } else {
            tab.classList.add('border-transparent', 'text-gray-600', 'dark:text-gray-300');
            tab.classList.remove('border-blue-500', 'text-blue-600', 'dark:border-blue-200', 'dark:text-blue-300');
        }
        suppressObserver = false;
    }

    function closeBlockedView() {
        suppressObserver = true;
        try {
            const existing = document.querySelector('[data-cub-blocked-view]');
            if (existing) existing.remove();
            document.querySelectorAll('[data-cub-hidden-by-view="1"]').forEach((el) => {
                el.style.display = '';
                el.removeAttribute('data-cub-hidden-by-view');
            });
        } finally {
            suppressObserver = false;
        }
        syncTabActiveState();
    }

    function openBlockedView() {
        const tab = document.querySelector('[data-cub-tab]');
        if (!tab) return;
        closeBlockedView();

        const anchor = tab.closest('.xl\\:max-w-3xl') || tab.closest('div');
        if (!anchor || !anchor.parentElement) return;

        suppressObserver = true;
        try {
            let sibling = anchor.nextElementSibling;
            while (sibling) {
                const next = sibling.nextElementSibling;
                sibling.style.display = 'none';
                sibling.setAttribute('data-cub-hidden-by-view', '1');
                sibling = next;
            }
            const summarySection = document.querySelector('section.relative.mt-6.overflow-hidden');
            if (summarySection) {
                summarySection.style.display = 'none';
                summarySection.setAttribute('data-cub-hidden-by-view', '1');
            }

            const view = document.createElement('div');
            view.setAttribute('data-cub-blocked-view', '1');
            view.className = 'cub-blocked-view';
            anchor.parentElement.insertBefore(view, anchor.nextSibling);
            renderBlockedView(view);
        } finally {
            suppressObserver = false;
        }
        syncTabActiveState();
    }

    function renderBlockedView(container) {
        suppressObserver = true;
        try {
            container.innerHTML = '';
            const map = getBlocked();
            const entries = Object.entries(map).sort((a, b) => (b[1].blockedAt || 0) - (a[1].blockedAt || 0));

            const header = document.createElement('h2');
            header.className = 'cub-heading';
            header.textContent = `Blocked users (${entries.length})`;
            container.appendChild(header);

            if (entries.length === 0) {
                const empty = document.createElement('p');
                empty.className = 'cub-empty';
                empty.textContent = 'You have not blocked anyone yet.';
                container.appendChild(empty);
                return;
            }

            const list = document.createElement('ul');
            list.className = 'cub-list';

            entries.forEach(([uuid, info]) => {
                const li = document.createElement('li');
                li.className = 'cub-list-item';

                const left = document.createElement('div');
                left.className = 'cub-list-main';

                const nameLink = document.createElement('a');
                nameLink.href = `/user/${uuid}`;
                nameLink.className = 'cub-list-name';
                nameLink.textContent = info.username || 'Unknown';
                left.appendChild(nameLink);

                const meta = document.createElement('div');
                meta.className = 'cub-list-meta';
                const date = info.blockedAt ? new Date(info.blockedAt).toLocaleString() : '';
                meta.textContent = date ? `Blocked ${date}` : 'Blocked';
                left.appendChild(meta);

                const uuidRow = document.createElement('div');
                uuidRow.className = 'cub-list-uuid';
                uuidRow.textContent = uuid;
                left.appendChild(uuidRow);

                const actions = document.createElement('div');
                actions.className = 'cub-list-actions';

                const unblockBtn = document.createElement('button');
                unblockBtn.type = 'button';
                unblockBtn.className = 'cub-btn';
                unblockBtn.innerHTML = ICON_UNBLOCK + '<span>Unblock</span>';
                unblockBtn.addEventListener('click', () => {
                    unblockUser(uuid);
                    renderBlockedView(container);
                    updateTabCount();
                });
                actions.appendChild(unblockBtn);

                li.appendChild(left);
                li.appendChild(actions);
                list.appendChild(li);
            });

            container.appendChild(list);
        } finally {
            suppressObserver = false;
        }
    }

    function injectStyles() {
        GM_addStyle(`
            .cub-btn {
                display: inline-flex;
                align-items: center;
                gap: 6px;
                padding: 6px 12px;
                font-size: 13px;
                font-weight: 500;
                border-radius: 6px;
                border: 1px solid rgb(209 213 219);
                background: rgb(249 250 251);
                color: rgb(17 24 39);
                cursor: pointer;
                transition: background-color .15s ease, border-color .15s ease, color .15s ease;
            }
            .cub-btn:hover {
                background: rgb(243 244 246);
                border-color: rgb(156 163 175);
            }
            .dark .cub-btn {
                background: rgb(55 65 81);
                border-color: rgb(75 85 99);
                color: rgb(243 244 246);
            }
            .dark .cub-btn:hover {
                background: rgb(75 85 99);
                border-color: rgb(107 114 128);
            }
            .cub-btn-danger {
                border-color: rgb(239 68 68);
                color: rgb(220 38 38);
            }
            .cub-btn-danger:hover {
                background: rgb(254 226 226);
            }
            .dark .cub-btn-danger {
                color: rgb(252 165 165);
                border-color: rgb(153 27 27);
                background: rgba(127, 29, 29, 0.25);
            }
            .dark .cub-btn-danger:hover {
                background: rgba(127, 29, 29, 0.5);
            }
            .cub-tab-icon {
                margin-right: 8px;
                color: rgb(156 163 175);
                display: inline-flex;
                align-items: center;
            }
            .cub-tab-count {
                margin-left: 8px;
                font-size: 12px;
                color: rgb(156 163 175);
            }
            .cub-blocked-view {
                margin-top: 24px;
                padding: 0 4px;
            }
            .cub-heading {
                font-size: 1.5rem;
                font-weight: 600;
                color: rgb(31 41 55);
                margin-bottom: 16px;
            }
            .dark .cub-heading {
                color: rgb(229 231 235);
            }
            .cub-empty {
                color: rgb(107 114 128);
                font-size: 14px;
                padding: 24px 0;
            }
            .dark .cub-empty {
                color: rgb(156 163 175);
            }
            .cub-list {
                list-style: none;
                margin: 0;
                padding: 0;
                display: flex;
                flex-direction: column;
                gap: 8px;
            }
            .cub-list-item {
                display: flex;
                align-items: center;
                justify-content: space-between;
                gap: 12px;
                padding: 12px 16px;
                border: 1px solid rgb(229 231 235);
                border-radius: 8px;
                background: rgb(255 255 255);
            }
            .dark .cub-list-item {
                border-color: rgb(55 65 81);
                background: rgba(31, 41, 55, 0.5);
            }
            .cub-list-main {
                min-width: 0;
                flex: 1;
            }
            .cub-list-name {
                font-weight: 600;
                color: rgb(17 24 39);
                text-decoration: none;
            }
            .cub-list-name:hover {
                text-decoration: underline;
            }
            .dark .cub-list-name {
                color: rgb(243 244 246);
            }
            .cub-list-meta {
                font-size: 12px;
                color: rgb(107 114 128);
                margin-top: 2px;
            }
            .dark .cub-list-meta {
                color: rgb(156 163 175);
            }
            .cub-list-uuid {
                font-size: 11px;
                color: rgb(156 163 175);
                font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
                margin-top: 2px;
                overflow: hidden;
                text-overflow: ellipsis;
            }
            .cub-list-actions {
                flex-shrink: 0;
            }
            [data-cub-profile-btn] {
                display: inline-flex;
            }
            .cub-blocked-bubble {
                padding: 8px 12px !important;
                background: rgb(243 244 246) !important;
                border-radius: 12px !important;
                flex: 1;
            }
            .dark .cub-blocked-bubble {
                background: rgba(55, 65, 81, 0.4) !important;
            }
            .cub-blocked-row {
                display: flex;
                align-items: center;
                gap: 8px;
                font-size: 13px;
                color: rgb(107 114 128);
            }
            .dark .cub-blocked-row {
                color: rgb(156 163 175);
            }
            .cub-blocked-icon {
                display: inline-flex;
                color: rgb(156 163 175);
                flex-shrink: 0;
            }
            .cub-blocked-text {
                flex: 1;
                min-width: 0;
            }
            .cub-blocked-username {
                font-weight: 500;
                color: rgb(75 85 99);
                text-decoration: none;
            }
            .cub-blocked-username:hover {
                text-decoration: underline;
            }
            .dark .cub-blocked-username {
                color: rgb(209 213 219);
            }
            .cub-blocked-show {
                background: transparent;
                border: 1px solid rgb(209 213 219);
                color: rgb(75 85 99);
                padding: 2px 8px;
                border-radius: 4px;
                font-size: 12px;
                cursor: pointer;
                flex-shrink: 0;
            }
            .cub-blocked-show:hover {
                background: rgb(229 231 235);
            }
            .dark .cub-blocked-show {
                border-color: rgb(75 85 99);
                color: rgb(209 213 219);
            }
            .dark .cub-blocked-show:hover {
                background: rgb(55 65 81);
            }
            .cub-blocked-avatar {
                opacity: 0.5;
                filter: grayscale(1);
            }
            .cub-nav-widened {
                max-width: none !important;
            }
            @media (min-width: 1280px) {
                .cub-nav-widened {
                    max-width: min(56rem, 100%) !important;
                }
            }
        `);
    }

    function scheduleUpdate(resetFlags = false) {
        if (resetFlags) resetProcessedFlags();
        if (scheduled) return;
        scheduled = true;
        requestAnimationFrame(() => {
            scheduled = false;
            applyBlocking();
            ensureProfileBlockButton();
            ensureBlockedTab();
            const view = document.querySelector('[data-cub-blocked-view]');
            if (view) renderBlockedView(view);
        });
    }

    function onUrlChange() {
        closeBlockedView();
        suppressObserver = true;
        document.querySelectorAll('[data-cub-profile-btn]').forEach((el) => el.remove());
        suppressObserver = false;
        removeBlockedTab();
        scheduleUpdate(true);
    }

    function patchHistory() {
        const push = history.pushState;
        const replace = history.replaceState;
        history.pushState = function() {
            const ret = push.apply(this, arguments);
            window.dispatchEvent(new Event('cub-locationchange'));
            return ret;
        };
        history.replaceState = function() {
            const ret = replace.apply(this, arguments);
            window.dispatchEvent(new Event('cub-locationchange'));
            return ret;
        };
        window.addEventListener('popstate', () => window.dispatchEvent(new Event('cub-locationchange')));
        window.addEventListener('cub-locationchange', onUrlChange);
    }

    function isOurMutation(mutation) {
        const check = (node) => {
            if (!node || node.nodeType !== 1) return false;
            if (node.hasAttribute && (
                    node.hasAttribute('data-cub-profile-btn') ||
                    node.hasAttribute('data-cub-tab') ||
                    node.hasAttribute('data-cub-blocked-view') ||
                    node.hasAttribute('data-cub-processed') ||
                    node.hasAttribute('data-cub-processed-rev') ||
                    node.hasAttribute('data-cub-hidden') ||
                    node.hasAttribute('data-cub-hidden-by-view') ||
                    node.hasAttribute('data-cub-count') ||
                    node.hasAttribute('data-cub-uuid') ||
                    node.hasAttribute('data-cub-state') ||
                    node.hasAttribute('data-cub-placeholder') ||
                    node.hasAttribute('data-cub-action-hidden')
                )) return true;
            if (node.closest && (
                    node.closest('[data-cub-profile-btn]') ||
                    node.closest('[data-cub-tab]') ||
                    node.closest('[data-cub-blocked-view]') ||
                    node.closest('[data-cub-placeholder]')
                )) return true;
            return false;
        };
        if (check(mutation.target)) return true;
        for (const n of mutation.addedNodes) {
            if (check(n)) return true;
        }
        return false;
    }

    function startObserver() {
        const observer = new MutationObserver((mutations) => {
            if (suppressObserver) return;
            let relevant = false;
            for (const m of mutations) {
                if (m.type === 'attributes') continue;
                if (isOurMutation(m)) continue;
                if (m.addedNodes && m.addedNodes.length > 0) {
                    relevant = true;
                    break;
                }
            }
            if (relevant) scheduleUpdate(false);
        });
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    async function init() {
        injectStyles();
        patchHistory();
        await fetchCurrentUser();

        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', afterReady, {
                once: true
            });
        } else {
            afterReady();
        }
    }

    function afterReady() {
        scheduleUpdate(false);
        startObserver();
    }

    init();
})();