Bumble Filter (Interactive Settings with Radio Buttons & Number Filter)

Highlights profiles on Bumble based on various criteria (Nice, Neutral, No-Go) with an interactive settings panel, radio button selection, and specific handling for height.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Bumble Filter (Interactive Settings with Radio Buttons & Number Filter)
// @namespace    http://tampermonkey.net/
// @version      0.9.1
// @description  Highlights profiles on Bumble based on various criteria (Nice, Neutral, No-Go) with an interactive settings panel, radio button selection, and specific handling for height.
// @author       Your Name
// @match        *://*.bumble.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @run-at       document-idle
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- GLOBAL VARIABLES AND DEFAULT CONFIGURATION ---
    const SETTINGS_KEY = 'bumbleFilterSettings_v0_9_1'; // Updated version key

    const HIGHLIGHT_COLOR_NICE = 'lightgreen';
    const HIGHLIGHT_COLOR_INDIFFERENT = '#f0f0f0'; // Light gray for "neutral"
    const HIGHLIGHT_COLOR_NO_GO = '#ffcccb';

    const defaultSettingsTemplate = {
        education: { imageSubstring: 'education', desired: [], noGo: [], label: "Education", allKnownValues: [], type: "radio" },
        familyplans: { imageSubstring: 'familyplans', desired: [], noGo: [], label: "Family Plans", allKnownValues: [], type: "radio" },
        height: {
            imageSubstring: 'height',
            desiredMinCm: null,
            desiredMaxCm: null,
            noGoSpecificCm: [],
            label: "Height",
            allKnownValues: [],
            type: "numberRange"
        },
        exercise: { imageSubstring: 'exercise', desired: [], noGo: [], label: "Exercise", allKnownValues: [], type: "radio" },
        drinking: { imageSubstring: 'drinking', desired: [], noGo: [], label: "Drinking Habits", allKnownValues: [], type: "radio" },
        smoking: { imageSubstring: 'smoking', desired: [], noGo: [], label: "Smoking Habits", allKnownValues: [], type: "radio" },
        cannabis: { imageSubstring: 'cannabis', desired: [], noGo: [], label: "Cannabis Use", allKnownValues: [], type: "radio" },
        religion: { imageSubstring: 'religion', desired: [], noGo: [], label: "Religion", allKnownValues: [], type: "radio" },
        intentions: { imageSubstring: 'intentions', desired: [], noGo: [], label: "Intentions", allKnownValues: [], type: "radio" },
        politics: { imageSubstring: 'politics', desired: [], noGo: [], label: "Politics", allKnownValues: [], type: "radio" },
        starsign: { imageSubstring: 'starsign', desired: [], noGo: [], label: "Star Sign", allKnownValues: [], type: "radio" }
    };

    let currentSettings = {};
    const profileCardSelectors = ['.encounters-story'];

    function parseCmValue(cmString) {
        if (typeof cmString !== 'string') return null;
        const match = cmString.match(/(\d+)/);
        return match ? parseInt(match[1], 10) : null;
    }

    function loadSettings() {
        console.log("[Bumble Filter] Loading settings...");
        const storedSettings = GM_getValue(SETTINGS_KEY);
        currentSettings = JSON.parse(JSON.stringify(defaultSettingsTemplate));

        if (storedSettings) {
            console.log("[Bumble Filter] Found stored settings:", JSON.parse(JSON.stringify(storedSettings)));
            for (const key in currentSettings) {
                if (storedSettings[key]) {
                    const categoryTemplate = defaultSettingsTemplate[key];
                    const storedCategory = storedSettings[key];

                    if (categoryTemplate.type === "numberRange") {
                        currentSettings[key].desiredMinCm = storedCategory.desiredMinCm !== undefined ? (parseInt(String(storedCategory.desiredMinCm), 10) || null) : categoryTemplate.desiredMinCm;
                        currentSettings[key].desiredMaxCm = storedCategory.desiredMaxCm !== undefined ? (parseInt(String(storedCategory.desiredMaxCm), 10) || null) : categoryTemplate.desiredMaxCm;
                        currentSettings[key].noGoSpecificCm = Array.isArray(storedCategory.noGoSpecificCm) ? storedCategory.noGoSpecificCm.map(s => parseInt(String(s), 10)).filter(n => !isNaN(n)) : categoryTemplate.noGoSpecificCm;
                    } else {
                        currentSettings[key].desired = Array.isArray(storedCategory.desired) ? storedCategory.desired.map(s => String(s).toLowerCase().trim()).filter(s => s) : categoryTemplate.desired;
                        currentSettings[key].noGo = Array.isArray(storedCategory.noGo) ? storedCategory.noGo.map(s => String(s).toLowerCase().trim()).filter(s => s) : categoryTemplate.noGo;
                    }
                    currentSettings[key].allKnownValues = Array.isArray(storedCategory.allKnownValues) ? storedCategory.allKnownValues.map(s => String(s).toLowerCase().trim()).filter(s => s) : [];
                }
            }
        }
        for (const key in currentSettings) {
            const category = currentSettings[key];
            if (category.type === "radio") {
                const knownValuesSet = new Set(category.allKnownValues);
                category.desired.forEach(val => knownValuesSet.add(String(val).toLowerCase().trim()));
                category.noGo.forEach(val => knownValuesSet.add(String(val).toLowerCase().trim()));
                category.allKnownValues = Array.from(knownValuesSet).sort();
            }
        }
        console.log("[Bumble Filter] Settings loaded and merged:", JSON.parse(JSON.stringify(currentSettings)));
    }

    function saveSettings() {
        console.log("[Bumble Filter] Attempting to save settings. Current settings object before reading DOM:", JSON.parse(JSON.stringify(currentSettings)));
        for (const categoryKey in currentSettings) {
            const category = currentSettings[categoryKey];
            const panelContent = document.getElementById('bf-panel-content');
            if (!panelContent) {
                console.error("[Bumble Filter] Panel content not found for saving.");
                continue;
            }

            console.log(`[Bumble Filter] Saving category: ${categoryKey}`);

            if (category.type === "numberRange") {
                const minInput = panelContent.querySelector(`#bf-desiredMinCm-${categoryKey}`);
                const maxInput = panelContent.querySelector(`#bf-desiredMaxCm-${categoryKey}`);
                const noGoInput = panelContent.querySelector(`#bf-noGoSpecificCm-${categoryKey}`);

                category.desiredMinCm = minInput && minInput.value.trim() !== "" ? parseInt(minInput.value, 10) : null;
                category.desiredMaxCm = maxInput && maxInput.value.trim() !== "" ? parseInt(maxInput.value, 10) : null;
                category.noGoSpecificCm = noGoInput ? noGoInput.value.split('\n').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n)) : [];
            } else {
                const newDesired = [];
                const newNoGo = [];

                category.allKnownValues.forEach((value, index) => {
                    const stringValue = String(value);
                    const radioGroupName = `bf-state-${categoryKey}-${index}`;
                    const checkedRadio = panelContent.querySelector(`input[name="${radioGroupName}"]:checked`);

                    if (checkedRadio) {
                        if (checkedRadio.value === 'nice') {
                            newDesired.push(stringValue);
                        } else if (checkedRadio.value === 'nogo') {
                            newNoGo.push(stringValue);
                        }
                    }
                });
                category.desired = newDesired;
                category.noGo = newNoGo;
                console.log(`[Bumble Filter] Category ${categoryKey} after DOM read - Desired:`, [...category.desired], "NoGo:", [...category.noGo]);
            }
        }
        GM_setValue(SETTINGS_KEY, currentSettings);
        console.log("[Bumble Filter] Settings saved to GM_setValue:", JSON.parse(JSON.stringify(currentSettings)));
        document.querySelectorAll('[data-custom-filter-processed-v9-1]').forEach(card => {
            card.removeAttribute('data-custom-filter-processed-v9-1');
        });
        processVisibleProfileCards();
    }

    function resetSettings() {
        console.log("[Bumble Filter] Resetting settings to default.");
        if (confirm("Do you really want to reset all filter settings to their default values?")) {
            GM_setValue(SETTINGS_KEY, undefined);
            loadSettings();
            const panel = document.getElementById('bumble-filter-settings-panel');
            if (panel && panel.style.display === 'block') {
                 buildSettingsPanelContent(panel.querySelector('#bf-panel-content'));
            }
            document.querySelectorAll('[data-custom-filter-processed-v9-1]').forEach(card => {
                card.removeAttribute('data-custom-filter-processed-v9-1');
            });
            processVisibleProfileCards();
            alert("Settings have been reset.");
        }
    }

    function buildSettingsPanelContent(panelContentDiv) {
        if (!panelContentDiv) {
            console.error("[Bumble Filter] buildSettingsPanelContent: panelContentDiv is null");
            return;
        }
        for (const key in currentSettings) {
            const category = currentSettings[key];
            if (category.type === "radio") {
                const knownValuesSet = new Set(category.allKnownValues);
                currentSettings[key].desired.forEach(val => knownValuesSet.add(String(val).toLowerCase().trim()));
                currentSettings[key].noGo.forEach(val => knownValuesSet.add(String(val).toLowerCase().trim()));
                category.allKnownValues = Array.from(knownValuesSet).sort();
            }
        }
        console.log("[Bumble Filter] Building panel content with current settings for UI:", JSON.parse(JSON.stringify(currentSettings)));

        let contentHtml = '';
        for (const categoryKey in currentSettings) {
            const category = currentSettings[categoryKey];
            contentHtml += `<div class="bf-category-section"><h3>${category.label} (ID: ${category.imageSubstring})</h3>`;

            if (category.type === "numberRange") {
                contentHtml += `
                    <div class="bf-input-group">
                        <label for="bf-desiredMinCm-${categoryKey}">Min. desired height (cm):</label>
                        <input type="number" id="bf-desiredMinCm-${categoryKey}" value="${category.desiredMinCm === null ? '' : category.desiredMinCm}" placeholder="e.g. 170">
                    </div>
                    <div class="bf-input-group">
                        <label for="bf-desiredMaxCm-${categoryKey}">Max. desired height (cm):</label>
                        <input type="number" id="bf-desiredMaxCm-${categoryKey}" value="${category.desiredMaxCm === null ? '' : category.desiredMaxCm}" placeholder="e.g. 185">
                    </div>
                    <div class="bf-input-group">
                        <label for="bf-noGoSpecificCm-${categoryKey}">No-Go heights (cm, per line):</label>
                        <textarea id="bf-noGoSpecificCm-${categoryKey}" placeholder="e.g. 160\n195">${category.noGoSpecificCm.join('\n')}</textarea>
                    </div>`;
                if (category.allKnownValues.length > 0) {
                     contentHtml += `<p class="bf-info-text">Discovered heights (examples): ${category.allKnownValues.map(v => parseCmValue(v) + 'cm').filter(v => v !== 'nullcm').slice(0,10).join(', ')}${category.allKnownValues.length > 10 ? '...' : ''}</p>`;
                }
            } else {
                if (category.allKnownValues.length === 0) {
                    contentHtml += `<p class="bf-no-values">No values discovered for this category yet. They will be collected as you swipe.</p>`;
                }
                const sortedKnownValues = [...category.allKnownValues].sort();

                sortedKnownValues.forEach((value, index) => {
                    const stringValue = String(value);
                    const isDesired = category.desired.includes(stringValue);
                    const isNoGo = category.noGo.includes(stringValue);
                    const isNeutral = !isDesired && !isNoGo;
                    const radioGroupName = `bf-state-${categoryKey}-${index}`;

                    contentHtml += `
                        <div class="bf-value-row">
                            <span class="bf-value-label" title="${stringValue}">${stringValue.length > 25 ? stringValue.substring(0, 22) + '...' : stringValue}</span>
                            <div class="bf-radio-group">
                                <label class="bf-radio-label"><input type="radio" name="${radioGroupName}" value="nice" ${isDesired ? 'checked' : ''}> Nice</label>
                                <label class="bf-radio-label"><input type="radio" name="${radioGroupName}" value="neutral" ${isNeutral ? 'checked' : ''}> Neutral</label>
                                <label class="bf-radio-label"><input type="radio" name="${radioGroupName}" value="nogo" ${isNoGo ? 'checked' : ''}> No-Go</label>
                            </div>
                        </div>`;
                });
            }
            contentHtml += `</div>`;
        }
        panelContentDiv.innerHTML = contentHtml;
    }

    function createSettingsPanelShell() {
        let panel = document.getElementById('bumble-filter-settings-panel');
        if (panel) return panel;

        console.log("[Bumble Filter] Creating settings panel shell for the first time.");
        let panelHtml = `
            <div id="bf-panel-header">
                <h2>Bumble Filter Settings</h2>
                <button id="bf-close-panel" title="Close">X</button>
            </div>
            <div id="bf-panel-content"></div>
            <div id="bf-panel-footer">
                <button id="bf-reset-settings" title="Reset all settings to default">Reset</button>
                <button id="bf-save-settings">Save & Apply</button>
            </div>`;
        panel = document.createElement('div');
        panel.id = 'bumble-filter-settings-panel';
        panel.innerHTML = panelHtml;
        document.body.appendChild(panel);

        document.getElementById('bf-save-settings').addEventListener('click', () => saveSettings());
        document.getElementById('bf-close-panel').addEventListener('click', () => {
            const panelToClose = document.getElementById('bumble-filter-settings-panel');
            if(panelToClose) panelToClose.style.display = 'none';
        });
        document.getElementById('bf-reset-settings').addEventListener('click', () => resetSettings());
        return panel;
    }

    function addStyles() {
        GM_addStyle(`
            #bumble-filter-settings-panel {
                position: fixed; top: 50px; right: 20px; width: 480px; max-height: 85vh;
                background-color: white; border: 1px solid #ccc; box-shadow: 0 0 15px rgba(0,0,0,0.2);
                z-index: 100000 !important; display: none; font-family: Arial, sans-serif; font-size: 14px; border-radius: 8px;
                pointer-events: auto !important;
            }
            #bf-panel-header {
                background-color: #f0f0f0; padding: 10px 15px; border-bottom: 1px solid #ccc;
                display: flex; justify-content: space-between; align-items: center;
                border-top-left-radius: 8px; border-top-right-radius: 8px;
            }
            #bf-panel-header h2 { margin: 0; font-size: 16px; }
            #bf-close-panel, #bf-save-settings, #bf-open-settings-button, #bf-reset-settings {
                pointer-events: auto !important; cursor: pointer;
            }
            #bf-close-panel { background: none; border: none; font-size: 20px; padding: 0 5px;}
            #bf-panel-content { padding: 15px; overflow-y: auto; max-height: calc(85vh - 100px); }
            .bf-category-section { margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #eee; }
            .bf-category-section:last-child { border-bottom: none; margin-bottom: 0; }
            .bf-category-section h3 { font-size: 14px; margin-top: 0; margin-bottom: 10px; color: #333; }
            .bf-no-values, .bf-info-text { font-style: italic; color: #777; margin-left: 10px; font-size: 12px; }
            .bf-value-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; padding: 4px; border-radius: 3px; }
            .bf-value-row:hover { background-color: #f9f9f9; }
            .bf-value-label { flex-basis: 35%; font-size: 13px; margin-right: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
            .bf-radio-group { display: flex; gap: 8px; pointer-events: auto !important; flex-basis: 65%; justify-content: flex-end; }
            .bf-radio-label { font-size: 12px; cursor: pointer; display:flex; align-items:center; pointer-events: auto !important; padding: 2px 5px; border-radius:3px; }
            .bf-radio-label:hover { background-color: #e9e9e9; }
            .bf-radio-label input[type="radio"] {
                margin-right: 3px; cursor: pointer; pointer-events: auto !important;
                z-index: 100001 !important; position: relative; appearance: auto !important;
                -webkit-appearance: auto !important; -moz-appearance: auto !important;
                opacity: 1 !important; visibility: visible !important;
                width: auto !important; height: auto !important;
            }
            .bf-input-group { margin-bottom: 10px; }
            .bf-input-group label { display: block; margin-bottom: 4px; font-weight: bold; font-size: 13px; }
            .bf-input-group input[type="number"], .bf-input-group textarea {
                width: 95%; padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px;
            }
            .bf-input-group textarea { min-height: 50px; resize: vertical; }
            #bf-panel-footer {
                padding: 10px 15px; display: flex; justify-content: space-between; align-items: center;
                border-top: 1px solid #ccc; background-color: #f9f9f9;
                border-bottom-left-radius: 8px; border-bottom-right-radius: 8px;
            }
            #bf-save-settings, #bf-reset-settings {
                padding: 8px 15px; color: white; border: none;
                border-radius: 4px; font-size: 14px;
            }
            #bf-save-settings { background-color: #5cb85c; }
            #bf-save-settings:hover { background-color: #4cae4c; }
            #bf-reset-settings { background-color: #d9534f; }
            #bf-reset-settings:hover { background-color: #c9302c; }
            #bf-open-settings-button {
                position: fixed; bottom: 20px; right: 20px; width: 40px; height: 40px;
                background-color: #7B1FA2; color: white; border: none; border-radius: 50%;
                font-size: 20px; font-weight: bold; text-align: center;
                box-shadow: 0 2px 5px rgba(0,0,0,0.2); z-index: 99999 !important;
            }
            #bf-open-settings-button:hover { background-color: #6A1B9A; }
        `);
    }

    function logPills(profileCard) {
        const allTextPillsOnCard = profileCard.querySelectorAll('.pill__title > div.p-3.text-ellipsis.font-weight-medium, .pill__title > div.text-ellipsis.font-weight-medium.p-3');
        let cardMatchesAnyNiceCriteria = false;
        let cardHasAnyNoGo = false;
        let settingsModifiedByNewValues = false;

        allTextPillsOnCard.forEach(pillTextElement => {
            const pillTextOriginal = pillTextElement.textContent.trim();
            const pillTextLower = pillTextOriginal.toLowerCase().replace(/\u00a0/g, " ").trim();

            if (!pillTextLower) return;

            let pillTypeKey = null;
            let imageSrcFound = null;

            const pillTitleDiv = pillTextElement.parentElement;
            if (pillTitleDiv && pillTitleDiv.classList.contains('pill__title')) {
                const commonPillWrapper = pillTextElement.closest('li, .pill, .profile-badge-container, .user-interest, div.p-2, div.profile-v2-interest__badge') || pillTitleDiv.parentElement;
                if (commonPillWrapper) {
                    const imageElement = commonPillWrapper.querySelector('img.pill__image');
                    if (imageElement && imageElement.src) {
                        imageSrcFound = imageElement.src.toLowerCase();
                        for (const key in currentSettings) {
                            if (currentSettings[key] && currentSettings[key].imageSubstring && imageSrcFound.includes(currentSettings[key].imageSubstring.toLowerCase())) {
                                pillTypeKey = key;
                                break;
                            }
                        }
                    }
                }
            }

            if (pillTypeKey) {
                const category = currentSettings[pillTypeKey];
                const stringPillTextLower = String(pillTextLower);

                if (!category.allKnownValues.map(String).includes(stringPillTextLower)) {
                    category.allKnownValues.push(stringPillTextLower);
                    category.allKnownValues.sort();
                    settingsModifiedByNewValues = true;
                    console.log(`[Bumble Filter] New value discovered for '${category.label}': "${pillTextOriginal}"`);
                }

                let currentPillHighlightColor = HIGHLIGHT_COLOR_INDIFFERENT;
                let isNice = false;
                let isNoGo = false;

                if (category.type === "numberRange") {
                    const heightValue = parseCmValue(stringPillTextLower);
                    if (heightValue !== null) {
                        if (category.noGoSpecificCm && category.noGoSpecificCm.includes(heightValue)) {
                            isNoGo = true;
                        } else {
                            const minOk = category.desiredMinCm === null || heightValue >= category.desiredMinCm;
                            const maxOk = category.desiredMaxCm === null || heightValue <= category.desiredMaxCm;
                            if (minOk && maxOk && (category.desiredMinCm !== null || category.desiredMaxCm !== null)) {
                                isNice = true;
                            }
                        }
                    }
                } else {
                    isNice = category.desired.map(String).includes(stringPillTextLower);
                    isNoGo = category.noGo.map(String).includes(stringPillTextLower);
                }

                if (isNice) {
                    currentPillHighlightColor = HIGHLIGHT_COLOR_NICE;
                    cardMatchesAnyNiceCriteria = true;
                } else if (isNoGo) {
                    currentPillHighlightColor = HIGHLIGHT_COLOR_NO_GO;
                    cardHasAnyNoGo = true;
                }

                pillTextElement.style.backgroundColor = currentPillHighlightColor;
                pillTextElement.style.borderRadius = '5px';
                pillTextElement.style.padding = '2px 4px';
                pillTextElement.style.display = 'inline-block';
                pillTextElement.style.margin = '1px';
            }
        });

        if (settingsModifiedByNewValues) {
            const panel = document.getElementById('bumble-filter-settings-panel');
            if (panel && panel.style.display === 'block') {
                console.log("[Bumble Filter] New values discovered, refreshing panel content.");
                buildSettingsPanelContent(panel.querySelector('#bf-panel-content'));
            }
        }

        if (cardHasAnyNoGo) {
            // profileCard.style.border = `3px solid ${HIGHLIGHT_COLOR_NO_GO}`;
        } else if (cardMatchesAnyNiceCriteria) {
            // profileCard.style.border = `3px solid ${HIGHLIGHT_COLOR_NICE}`;
        }
    }

    function processVisibleProfileCards() {
        let processedSomethingOnThisRun = false;
        for (const selector of profileCardSelectors) {
            const cards = document.querySelectorAll(selector);
            if (cards.length > 0) {
                cards.forEach(card => {
                    if (!card.dataset.customFilterProcessedV9_0) { // Updated dataset version
                        logPills(card);
                        card.dataset.customFilterProcessedV9_0 = 'true';
                        processedSomethingOnThisRun = true;
                    }
                });
                if (processedSomethingOnThisRun) break;
            }
        }
    }

    const observer = new MutationObserver((mutationsList) => {
        let newNodesAddedInThisBatch = false;
        for (const mutation of mutationsList) {
            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                newNodesAddedInThisBatch = true;
                break;
            }
        }
        if (newNodesAddedInThisBatch) {
            setTimeout(processVisibleProfileCards, 150);
        }
    });

    function init() {
        console.log("[Bumble Filter] Script started (v0.9.0 - Radio Buttons & Number Filter).");
        loadSettings();
        addStyles();

        if (!document.getElementById('bf-open-settings-button')) {
            const openButton = document.createElement('button');
            openButton.id = 'bf-open-settings-button';
            openButton.textContent = 'B';
            openButton.title = 'Bumble Filter Settings';
            document.body.appendChild(openButton);

            openButton.addEventListener('click', () => {
                let panel = document.getElementById('bumble-filter-settings-panel');
                if (!panel) {
                    panel = createSettingsPanelShell();
                }

                if (panel.style.display === 'block') {
                    panel.style.display = 'none';
                } else {
                    buildSettingsPanelContent(panel.querySelector('#bf-panel-content'));
                    panel.style.display = 'block';
                }
            });
        }

        let targetNode = document.querySelector('main.page__content') || document.querySelector('#main main') || document.body;
        if (targetNode) {
            processVisibleProfileCards();
            observer.observe(targetNode, { childList: true, subtree: true });
        } else {
            console.warn("[Bumble Filter] Main target node for observer not found.");
            const lateObserver = new MutationObserver(() => {
                targetNode = document.querySelector('main.page__content') || document.querySelector('#main main') || document.body;
                if (targetNode) {
                    lateObserver.disconnect();
                    processVisibleProfileCards();
                    observer.observe(targetNode, { childList: true, subtree: true });
                }
            });
            lateObserver.observe(document.body, { childList: true, subtree: true });
        }
    }

    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        init();
    } else {
        window.addEventListener('DOMContentLoaded', init);
    }

})();