OkCupid better filtering

Adds filters to the discover feature

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         OkCupid better filtering
// @namespace    http://tampermonkey.net/
// @version      20241108.3
// @description  Adds filters to the discover feature
// @author       You
// @match        https://www.okcupid.com/home
// @match        https://www.okcupid.com/discover
// @icon         https://www.google.com/s2/favicons?sz=64&domain=okcupid.com
// @grant        none
// @license      MIT
// ==/UserScript==

const dynamicStylePrefix = `css-${window.crypto.randomUUID()}`;

const locationIcon = `<svg width="24" height="24" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39" /></svg>`;
const basicsIcon = `<svg width="24" height="24" viewBox="0 0 24 24"><path d="M17.75 12.843a5.454 5.454 0 1 1-.001 10.907 5.454 5.454 0 0 1 0-10.907zm0 1.5a3.953 3.953 0 1 0-.001 7.907 3.953 3.953 0 0 0 0-7.907zM10.431 8.55a.75.75 0 0 1 .75.75v8.885a.75.75 0 0 1-.75.75H1.547a.75.75 0 0 1-.75-.75V9.3a.75.75 0 0 1 .75-.75h8.885zm-.75 1.5H2.297v7.385h7.385V10.05zM11.42.275l9.91 2.656a.75.75 0 0 1 .336 1.255l-7.254 7.254a.75.75 0 0 1-1.255-.337l-2.655-9.91a.75.75 0 0 1 .918-.918zm.867 1.785l1.983 7.4 5.417-5.417-7.4-1.983z" fill="#1A1A1A" fill-rule="nonzero"></path></svg>`;
const looksIcon = `<svg width="24" height="24" viewBox="0 0 24 24"><path d="M12.03 7.104a3.552 3.552 0 1 1 3.552-3.543 3.543 3.543 0 0 1-3.552 3.543zm0-5.704a2.16 2.16 0 1 0 2.16 2.161 2.15 2.15 0 0 0-2.16-2.16z" fill="#191919"></path><path d="M14.376 24.01a2.059 2.059 0 0 1-1.984-1.743l-.334-2.57-.38 2.57a2.077 2.077 0 0 1-2.069 1.743 2.225 2.225 0 0 1-1.641-.705 2.207 2.207 0 0 1-.621-1.669l.62-6.946-.416.686a1.957 1.957 0 0 1-2.152.928 1.854 1.854 0 0 1-1.215-1.02 1.8 1.8 0 0 1 0-1.586l2.745-5.25a3.617 3.617 0 0 1 2.68-1.947l1.725-.25a4.48 4.48 0 0 1 1.317 0l1.734.25a3.617 3.617 0 0 1 2.7 1.948l2.726 5.212a1.855 1.855 0 0 1-1.178 2.606 1.957 1.957 0 0 1-2.152-.928l-.417-.686.621 6.918a2.27 2.27 0 0 1-2.262 2.402l-.047.037zm-2.365-9.162a.871.871 0 0 1 .872.77l.835 6.426a.668.668 0 0 0 .658.557.873.873 0 0 0 .64-.27.844.844 0 0 0 .232-.648l-.844-9.47a.788.788 0 0 1 1.465-.481l1.762 2.912a.566.566 0 0 0 .622.25.428.428 0 0 0 .278-.612L15.84 9.07a2.215 2.215 0 0 0-1.65-1.187l-1.725-.232a3.051 3.051 0 0 0-.928 0l-1.725.25A2.216 2.216 0 0 0 8.172 9.09L5.482 14.3a.427.427 0 0 0 .279.613.556.556 0 0 0 .621-.26l1.762-2.903a.788.788 0 0 1 1.465.482l-.853 9.497a.862.862 0 0 0 .533.822c.11.046.229.07.348.068a.668.668 0 0 0 .659-.566l.834-6.417a.927.927 0 0 1 .881-.788z" fill="#191919"></path></svg>`;
const backgroundIcon = `<svg width="24" height="24" viewBox="0 0 24 24"><g fill="none" fill-rule="evenodd"><path d="M12.155.016L12 .015C18.627.015 24 5.38 24 12c0 6.566-5.287 11.899-11.841 11.984a.769.769 0 0 1-.317-.001l.158.002C5.373 23.985 0 18.619 0 12 0 5.433 5.289.1 11.844.016a.764.764 0 0 1 .311 0zM6.773 12.748l-5.247.001c.344 4.855 3.996 8.797 8.718 9.592a17.025 17.025 0 0 1-3.47-9.593zm15.7.001h-5.246a17.025 17.025 0 0 1-3.47 9.591c4.721-.794 8.373-4.736 8.717-9.591zm-6.748 0h-7.45A15.543 15.543 0 0 0 12 22.13a15.54 15.54 0 0 0 3.725-9.383zm-5.481-11.09l-.191.034c-4.63.867-8.188 4.768-8.527 9.557h5.247a17.027 17.027 0 0 1 3.47-9.591zm1.756.21l-.253.303a15.537 15.537 0 0 0-3.472 9.078h7.45a15.535 15.535 0 0 0-3.472-9.078L12 1.869zm1.755-.21l.137.18a17.022 17.022 0 0 1 3.335 9.411h5.247c-.344-4.855-3.996-8.798-8.719-9.591z" id="Icon/Background/24" fill="#1A1A1A" fill-rule="nonzero"></path></g></svg>`;
const lifestyleIcon = `<svg width="17" height="24" viewBox="0 0 17 24"><g fill="none" fill-rule="evenodd"><path d="M18.617.153c.305.232.383.65.196.973l-.053.078-.568.743h2.054a.75.75 0 0 1 .655 1.12l-.06.09-7.589 9.762V22.5h2.326c.416 0 .753.336.753.75 0 .385-.29.702-.665.745l-.088.005H9.422a.752.752 0 0 1-.753-.75c0-.385.29-.702.665-.745l.088-.005h2.324v-9.582L4.159 3.157a.75.75 0 0 1 .488-1.202l.107-.007L16.3 1.947 17.562.296a.755.755 0 0 1 1.055-.143zm.093 3.294h-1.664l-3.947 5.167a.755.755 0 0 1-1.055.143.748.748 0 0 1-.196-.973l.052-.078 3.253-4.259H6.289l6.21 7.99 6.211-7.99z" transform="translate(-4)" id="Icon/Lifestyle/24" fill="#1A1A1A" fill-rule="nonzero"></path></g></svg>`;
const familyIcon = `<svg width="24" height="20" viewBox="0 0 24 20"><g fill="none" fill-rule="evenodd"><path d="M17.976 2c.229 0 .445.107.587.29l5.274 6.802a.77.77 0 0 1 .164.455L24 9.57v11.66c0 .389-.282.71-.648.761L23.25 22H.75a.757.757 0 0 1-.743-.664L0 21.23V9.571l.001-.041a.79.79 0 0 1 .004-.044L0 9.571c0-.18.06-.344.16-.475l.003-.003 5.268-6.795a.737.737 0 0 1 .35-.258c.003 0 .007 0 .01-.002A.699.699 0 0 1 6.024 2l-.062.003A.736.736 0 0 1 6.014 2h.01zM6.024 4.005L1.5 9.84v10.62h2.063v-6.921c0-.39.282-.712.648-.763l.102-.007h3.375a.76.76 0 0 1 .75.77v6.921h2.111V9.838L6.024 4.005zM22.5 10.34H12.049v10.12H22.5V10.34zM6.938 14.308H5.063v6.152h1.875v-6.152zM20.3 12.769a.76.76 0 0 1 .75.77V17a.76.76 0 0 1-.75.77h-6a.76.76 0 0 1-.75-.77V13.54a.76.76 0 0 1 .75-.77zm-3.776 1.538H15.05v1.924l1.474-.001v-1.923zm3.026 0h-1.526v1.923h1.526v-1.922zm-1.937-10.77H7.578l4.083 5.264h10.033l-4.081-5.263z" transform="translate(0 -2)" id="Icon/Family/24" fill="#1A1A1A" fill-rule="nonzero"></path></g></svg>`;
const lookingIcon = `<svg width="24" height="22" viewBox="0 0 24 22"><g fill="none" fill-rule="evenodd"><path d="M11.994 6.415l.412.007c1.898.059 3.727.552 5.499 1.51.977.524 1.95 1.221 2.932 2.098.5.467.89.858 1.255 1.27l.428.496c.41.492.814 1.052 1.213 1.68a.75.75 0 0 1 0 .804 16.127 16.127 0 0 1-1.207 1.673l-.427.495c-.247.279-.502.545-.798.834l-.476.455c-.97.866-1.943 1.562-2.919 2.086-1.9 1.027-3.864 1.52-5.912 1.517-2.053-.015-4.012-.51-5.899-1.535-.976-.525-1.948-1.22-2.93-2.095a17.247 17.247 0 0 1-1.438-1.473 15.582 15.582 0 0 1-1.459-1.955.75.75 0 0 1 0-.808 15.51 15.51 0 0 1 1.45-1.945c.432-.505.873-.952 1.458-1.494.971-.864 1.943-1.56 2.917-2.083C7.982 6.927 9.94 6.43 11.994 6.415zm.01 1.5l-.387.01c-1.668.069-3.258.504-4.811 1.347-.868.466-1.746 1.094-2.621 1.874-.446.413-.8.765-1.137 1.14l-.41.473c-.28.335-.561.708-.84 1.119.35.514.701.97 1.06 1.373.388.454.788.86 1.315 1.35.887.788 1.765 1.416 2.635 1.884 1.671.907 3.388 1.342 5.193 1.355 1.794.002 3.513-.429 5.194-1.337.868-.467 1.745-1.095 2.618-1.873.47-.44.827-.797 1.168-1.182l.381-.441c.281-.338.562-.714.842-1.13-.28-.415-.562-.793-.848-1.135l-.41-.473a17.134 17.134 0 0 0-1.12-1.131c-.885-.79-1.763-1.419-2.633-1.886-1.559-.843-3.151-1.275-4.807-1.33l-.383-.007zM12 9.162a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9zm0 1.5a3 3 0 1 0 0 6 3 3 0 0 0 0-6zm10.088-7.307a.75.75 0 0 1 1.06 1.06L20.975 6.59a.75.75 0 0 1-1.06-1.06zm-21.236 0a.75.75 0 0 1 1.06 0l2.174 2.173a.75.75 0 0 1-1.061 1.06L.852 4.416a.75.75 0 0 1 0-1.06zM12 0a.75.75 0 0 1 .75.75v3.074a.75.75 0 1 1-1.5 0V.75A.75.75 0 0 1 12 0z" id="lookingfor-eye-Icon/Looking-For/24" fill="#1A1A1A" fill-rule="nonzero"></path></g></svg>`;
const profileIDTest = /profile\/(.+)\/questions/;
const skippedIds = [];

const currentProfile = {
    id: null,
    username: null,
    exclusions: [],
};

let debugMode = false;

let filters = {
    filterLocation: [],
    filterBasics: [],
    filterLooks: [],
    filterBackground: [],
    filterLifetsyle: [],
    filterFamily: [],
    filterLooking: [],
};

if (localStorage.getItem('filterList') !== null && localStorage.getItem('filterList') !== '') {
    filters = JSON.parse(localStorage.getItem('filterList'));
}

function updateStorage() {
    // Remove empty strings.
    Object.keys(filters).forEach((storageKey) => {
        filters[storageKey] = filters[storageKey].filter((term) => term !== '');
    });

    localStorage.setItem('filterList', JSON.stringify(filters));
}

const filtersSkeleton = [
    {
        icon: locationIcon,
        storageKey: 'filterLocation',
        title: 'Location',
        selector: 'div.card-content-header__location',
    },
    {
        icon: basicsIcon,
        storageKey: 'filterBasics',
        title: 'Basics',
        selector: 'div.matchprofile-details-section--basics div.matchprofile-details-text',
    },
    {
        icon: looksIcon,
        storageKey: 'filterLooks',
        title: 'Looks',
        selector: 'div.matchprofile-details-section--looks div.matchprofile-details-text',
    },
    {
        icon: backgroundIcon,
        storageKey: 'filterBackground',
        title: 'Background',
        selector: 'div.matchprofile-details-section--background div.matchprofile-details-text',
    },
        {
        icon: lifestyleIcon,
        storageKey: 'filterLifetsyle',
        title: 'Lifestyle',
        selector: 'div.matchprofile-details-section--lifestyle div.matchprofile-details-text',
    },
    {
        icon: familyIcon,
        storageKey: 'filterFamily',
        title: 'Family',
        selector: 'div.matchprofile-details-section--family div.matchprofile-details-text',
    },
    {
        icon: lookingIcon,
        storageKey: 'filterLooking',
        title: 'Loooking for',
        selector: 'div.matchprofile-details-section--wiw div.matchprofile-details-text',
    },
];

function buildFilterHTML() {
    const htmlArray = filtersSkeleton.map((filter) => {
        const listElements = filters[filter.storageKey].map((el) => {
            return `<li class="${dynamicStylePrefix}exclusion" data-key="${filter.storageKey}" data-value="${el}">${el}</li>`;
        }).join(`\n`);

        return `
          <div class="${dynamicStylePrefix}filter-item">
            <div class="${dynamicStylePrefix}filter-addition">
              <div title="${filter.title}">${filter.icon}</div>
              <div><input type="text" id="${dynamicStylePrefix}${filter.storageKey}" data-key="${filter.storageKey}" /></div>
            </div>
            <ul class="${dynamicStylePrefix}exclusion-list">
            ${listElements}
            </ul>
          </div>\n`;
    });

    return htmlArray.join(`\n`);
}

function toggleFeature() {
    const toggleElement = document.getElementById(`${dynamicStylePrefix}button-base-root`);
    const toggleTrack = document.getElementById(`${dynamicStylePrefix}track`);

    // Local storage saves only strings.
    const filtersEnabled = localStorage.getItem('filtersEnabled') === 'true';

    if (filtersEnabled) {
        // Turn off the filter.
        toggleElement.classList.remove('checked');
        toggleTrack.classList.remove('checked');
    } else {
        // Turn on the filter.
        toggleElement.classList.add('checked');
        toggleTrack.classList.add('checked');
    }

    console.debug(`[user script] Setting the filtersEnabled value to ${!filtersEnabled}.`);

    localStorage.setItem('filtersEnabled', !filtersEnabled);
}

function bindFilterListItem(el) {
    el.addEventListener('click', () => {
        const value = el.innerText;
        const storageKey = el.getAttribute('data-key');

        console.debug(`[user script] The user wants to remove this value. ${value}`);

        filters[storageKey] = filters[storageKey].filter((item) => item !== value);

        updateStorage();

        el.remove();
    });
}

function bindFilterItem(el) {
    const inputField = el.querySelector('input');
    const listElement = el.querySelector('ul');
    const storageKey = inputField.getAttribute('data-key');

    inputField.addEventListener('keyup', ({ target, key, keyCode }) => {
        const searchTerm = target.value.trim().toLowerCase();

        if (searchTerm !== '' && (key === 'Enter' || keyCode === 13) && !filters[storageKey].includes(searchTerm)) {
            const newListItem = document.createElement('li');

            newListItem.innerHTML = searchTerm;
            newListItem.setAttribute('data-key', storageKey);
            newListItem.setAttribute('data-value', searchTerm);

            bindFilterListItem(newListItem);

            listElement.insertAdjacentElement('beforeend', newListItem);

            inputField.value = null;

            filters[storageKey].push(searchTerm);

            updateStorage();
        }
    });
}

let profileLoop = null;

function getProfileID() {
    let matchButton = document.querySelector('[data-cy="discover.userCardMatchPercentage"]');

    if (matchButton) {
        const { href } = matchButton;
        const matches = href.match(profileIDTest);

        if (matches !== null) {
            return matches[1];
        }
    }

    return 'unknown';
};

function getProfileUsername() {
    const headerElement = document.querySelector('div.card-content-header__name-container h2');

    if (headerElement) {
        return headerElement.innerHTML;
    }

    return 'unknown';
}

function compileExclusions() {
    // Reset the found exclusions list.
    currentProfile.exclusions = [];

    filtersSkeleton.forEach((filter) => {
        // Are there any exlusions set for this category?
        if (filters[filter.storageKey].length === 0) {
            return false;
        }

        const detailElement = document.querySelector(filter.selector);

        if (!detailElement) {
            return false;
        }

        detailElement.innerText.split('|').forEach((detail) => {
            const profileDetail = detail.trim().toLowerCase();

            filters[filter.storageKey].forEach((filterTerm) => {
                const exclusionListItem = document.querySelector(`li[data-value="${filterTerm}"]`);

                exclusionListItem.classList.remove('found');

                if (filterTerm !== '' && (profileDetail.includes(filterTerm) || profileDetail === filterTerm)) {
                    exclusionListItem.classList.add('found');

                    currentProfile.exclusions.push(profileDetail);
                }
            });
        });
    });
}

let passing = false;

function pass() {
    const clickEvent = new Event('click');
    const blockButton = document.getElementById('user-script-block');
    const passButton = document.querySelector('button.dt-action-buttons-button.pass');
    const notificationElement = document.getElementById(`${dynamicStylePrefix}notification`);

    // The page has not yet loaded enough to have rendered the elements we need.
    if (!passButton || !notificationElement) {
        console.debug(`[user script] [${currentProfile.id}] Waiting for elements.`);

        setTimeout(() => pass, 100);
    }

    const { id } = currentProfile;

    if (blockButton) {
        console.debug(`[user script] [${currentProfile.id}] Blocking profile.`);

        blockButton.dispatchEvent(clickEvent);
    } else {
        console.debug(`[user script] [${currentProfile.id}] Passing on profile.`);

        passButton.dispatchEvent(clickEvent);
    }

    skippedIds.push(id);

    setTimeout(() => {
        // The profile still hasn't changed.
        if (id === currentProfile.id) {
            pass();
        }
    }, 2000);
}

function checkProfileLoop() {
    clearInterval(profileLoop);

    const notificationElement = document.getElementById(`${dynamicStylePrefix}notification`);
    const debugSpanID = document.getElementById(`${dynamicStylePrefix}debug-id`);
    const debugSpanUsername = document.getElementById(`${dynamicStylePrefix}debug-username`);
    const debugSpanExclusions = document.getElementById(`${dynamicStylePrefix}debug-exclusions`);

    profileLoop = setInterval(() => {
        const profileId = getProfileID();
        const username = getProfileUsername();

        compileExclusions();

        const hasExclusion = currentProfile.exclusions.length > 0;

        // The profile has changed.
        if (profileId !== currentProfile.id) {
            console.debug(`[user script] [${profileId}] Found a new profile.`);

            passing = false;

            if (hasExclusion) {
                console.debug(`[user script] [${profileId}] This profile contains ${currentProfile.exclusions.length} exclusions.`);
            }
        }

        // Update the current profile.
        currentProfile.id = profileId;
        currentProfile.username = username;

        debugSpanUsername.innerHTML = username;
        debugSpanID.innerHTML = profileId;
        debugSpanExclusions.innerHTML = !hasExclusion ? '' : currentProfile.exclusions.join('<br />');

        notificationElement.style.display = hasExclusion ? 'flex' : 'none';

        if (!skippedIds.includes(profileId) && hasExclusion) {
            if (localStorage.getItem('filtersEnabled') === 'true') {
                console.debug(`[user script] [${currentProfile.id}] Setting this profile to be passed.`);

                passing = true;
                pass();
            }
        }
    }, 500);
}

function initialize(detailsTopElement) {
    console.debug('[user script] Initializing the detail filter.');

    const debugContainer = document.createElement('div');

    debugContainer.classList.add(`${dynamicStylePrefix}hidden`);
    debugContainer.classList.add(`${dynamicStylePrefix}debug-container`);
    debugContainer.innerHTML = `<h3 class="dt-section-title">Debug</h3>
      <div class="dt-section-content">
        <div>
          <span>id</span>
          <span id="${dynamicStylePrefix}debug-id"></span>
        </div>
        <div>
          <span>username</span>
          <span id="${dynamicStylePrefix}debug-username"></span>
        </div>
        <div style="margin-top: 1em;">
          <span>exclusions</span>
          <br>
          <span id="${dynamicStylePrefix}debug-exclusions"></span>
        <div>
      </div>`;

    document.querySelector('body').insertAdjacentElement('beforeend', debugContainer);

    const enabled = localStorage.getItem('filtersEnabled') === 'true';
    const filterContainer = document.createElement('div');

    filterContainer.classList.add('dt-section');
    filterContainer.innerHTML = `
      <h3 class="dt-section-title">Filters</h3>
      <div class="dt-section-content">
        <div class="${dynamicStylePrefix}control-container">
          <div style="flex-grow: 1;">
            <span class="${dynamicStylePrefix}switch-root" id="${dynamicStylePrefix}switch-root">
              <span class="${dynamicStylePrefix}button-base-root ${enabled ? 'checked' : ''}" id="${dynamicStylePrefix}button-base-root">
                <input class="${dynamicStylePrefix}input-toggle" />
                <span class="${dynamicStylePrefix}thumb"></span>
                <span class="${dynamicStylePrefix}ripple"></span>
              </span>
              <span class="${dynamicStylePrefix}track ${enabled ? 'checked' : ''}" id="${dynamicStylePrefix}track"></span>
            </span>
            <span id="${dynamicStylePrefix}toggle-label">Automatically pass</span>
          </div>
          <div id="${dynamicStylePrefix}notification">This profile contains an exclusion.</div>
          <div class="${dynamicStylePrefix}icon-button" id="${dynamicStylePrefix}icon-button">
            <svg><path d="M10 18h4v-2h-4zM3 6v2h18V6zm3 7h12v-2H6z" /></svg>
          </div>
          <div class="${dynamicStylePrefix}icon-button" id="${dynamicStylePrefix}icon-button-debug">
            <svg viewBox="0 0 24 24"><path d="M20 8h-2.81c-.45-.78-1.07-1.45-1.82-1.96L17 4.41 15.59 3l-2.17 2.17C12.96 5.06 12.49 5 12 5s-.96.06-1.41.17L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20zm-6 8h-4v-2h4zm0-4h-4v-2h4z"></path></svg>
          </div>
        </div>
        <div class="${dynamicStylePrefix}filter-container" id="${dynamicStylePrefix}filter-container">
          <p>
            Profiles containing any of these exclusions will be passed. All exclusions are case-insensitive.
            If you are also using the <a href="https://greasyfork.org/en/scripts/504158-okcupid-more-effective-pass">more effective pass</a>
            script then the profiles will instead be blocked.
          </p>
          <div class="${dynamicStylePrefix}filter-list">
            ${buildFilterHTML()}
          </div>
        </div>
      </div>
    `;

    detailsTopElement.insertAdjacentElement('afterend', filterContainer);

    const toggleElement = document.getElementById(`${dynamicStylePrefix}switch-root`);

    toggleElement.addEventListener('click', toggleFeature);

    const showFiltersButton = document.getElementById(`${dynamicStylePrefix}icon-button`);
    const showDebugButton = document.getElementById(`${dynamicStylePrefix}icon-button-debug`);
    const filterContainerElement = document.getElementById(`${dynamicStylePrefix}filter-container`);

    showFiltersButton.addEventListener('click', () => {
        const display = filterContainerElement.style.display === 'none' || filterContainerElement.style.display === '' ? 'flex' : 'none';

        filterContainerElement.style.display = display;
    });

    showDebugButton.addEventListener('click', () => {
        debugContainer.classList.toggle(`${dynamicStylePrefix}hidden`);
    });

    [...document.querySelectorAll(`div.${dynamicStylePrefix}filter-item`)].forEach(bindFilterItem);

    // Bind the existing list items that were loaded from local storage.
    [...document.querySelectorAll(`ul.${dynamicStylePrefix}exclusion-list li`)].forEach(bindFilterListItem);

    checkProfileLoop();
}

const styleString = `
  div.${dynamicStylePrefix}control-container {
    display: flex;
    alignItems: center;
  }

  span.${dynamicStylePrefix}switch-root {
    display: inline-flex;
    width: 58px;
    height: 38px;
    overflow: hidden;
    padding: 12px;
    box-sizing: border-box;
    position: relative;
    flex-shrink: 0;
    z-index: 0;
    vertical-align: middle;
  }

  span.${dynamicStylePrefix}button-base-root {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    background-color: transparent;
    outline: 0px;
    border: 0px;
    margin: 0px;
    cursor: pointer;
    user-select: none;
    vertical-align: middle;
    appearance: none;
    text-decoration: none;
    padding: 9px;
    border-radius: 50%;
    position: absolute;
    top: 0px;
    left: 0px;
    z-index: 1;
    color: rgb(255, 255, 255);
    transition: left 150ms cubic-bezier(0.4, 0, 0.2, 1), transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
  }

  span.${dynamicStylePrefix}button-base-root.checked {
    color: rgb(25, 118, 210);
    transform: translateX(20px);
  }

  span.${dynamicStylePrefix}button-base-root:hover {
    background-color: rgba(0, 0, 0, 0.04);
  }

  input.${dynamicStylePrefix}input-toggle {
    left: -100%;
    width: 300%;
    cursor: inherit;
    position: absolute;
    opacity: 0;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    margin: 0;
    padding: 0;
    z-index: 1;
  }

  span.${dynamicStylePrefix}thumb {
    box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 1px -1px, rgba(0, 0, 0, 0.14) 0px 1px 1px 0px, rgba(0, 0, 0, 0.12) 0px 1px 3px 0px;
    background-color: currentcolor;
    width: 20px;
    height: 20px;
    border-radius: 50%;
  }

  span.${dynamicStylePrefix}ripple {
    overflow: hidden;
    pointer-events: none;
    position: absolute;
    z-index: 0;
    inset: 0px;
    border-radius: inherit;
  }

  span.${dynamicStylePrefix}track {
    height: 100%;
    width: 100%;
    border-radius: 7px;
    z-index: -1;
    transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1), background-color 150ms cubic-bezier(0.4, 0, 0.2, 1);
    background-color: rgb(0, 0, 0);
    opacity: 0.38;
  }

  span.${dynamicStylePrefix}track.checked {
    background-color: rgb(25, 118, 210);
  }

  div.${dynamicStylePrefix}icon-button {
    width: 32px;
    height: 32px;
    border: 1px solid silver;
    cursor: pointer;
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  div.${dynamicStylePrefix}icon-button:hover {
    background-color: rgb(246 246 246);
  }

  div.${dynamicStylePrefix}icon-button:hover svg {
    fill: rgb(25, 118, 210);
  }

  div.${dynamicStylePrefix}icon-button svg {
    width: 24px;
    height: 24px;
  }

  div.${dynamicStylePrefix}filter-container {
    display: none;
    flex-direction: column;
    gap: 1em;
    border-top: 1px solid black;
    padding: 1em;
    margin-top: 1em;
  }

  div.${dynamicStylePrefix}filter-list {
    display: flex;
    flex-basis: auto;
    justify-content: center;
    gap: 1em;
    flex-wrap: wrap;
  }

  div.${dynamicStylePrefix}filter-item {
    display: flex;
    flex-direction: column;
    gap: 0.5em;
    border: 1px solid grey;
    border-radius: 4px;
    width: 260px;
    height: 200px;
    padding: 3px;
  }

  div.${dynamicStylePrefix}filter-addition {
    display: flex;
    gap: 2px;
    align-items: center;
    border-bottom: 1px dashed black;
  }

  div.${dynamicStylePrefix}filter-addition svg {
    width: 32px;
    height: 32px;
  }

  div.${dynamicStylePrefix}filter-addition input {
    width: 100%;
  }

  ul.${dynamicStylePrefix}exclusion-list {
    list-style: none;
    overflow: scroll;
  }

  ul.${dynamicStylePrefix}exclusion-list li {
    cursor: pointer;
  }

  ul.${dynamicStylePrefix}exclusion-list li:hover {
    text-decoration: line-through;
    background-color: rgb(246 246 246);
  }

  div#${dynamicStylePrefix}notification {
    display: none;
    flex-grow: 1;
    font-style: italic;
    color: red;
  }

  li.${dynamicStylePrefix}exclusion.found {
    color: red;
  }

  .${dynamicStylePrefix}hidden {
    display: none !important;
  }

  div.${dynamicStylePrefix}debug-container {
    display: flex;
    flex-direction: column;
    position: fixed;
    bottom: 1em;
    left: 1em;
    width: 300px;
  }

  div.${dynamicStylePrefix}debug-container .dt-section-content {
    background-color: rgb(255 255 255 / 70%);
    display: flex;
    flex-direction: column;
  }

  div#${dynamicStylePrefix}icon-button-debug {
    margin-left: 1em;
  }
`;

(function() {
    'use strict';

    const sheet = new CSSStyleSheet();

    sheet.replaceSync(styleString);

    console.debug(`[user scripts] Adding ${sheet.rules.length} new CSS rules.`);

    document.adoptedStyleSheets.push(sheet);

    let detailsTopElement = null;

    setInterval(() => {
        if (detailsTopElement === null || !document.body.contains(detailsTopElement)) {
            detailsTopElement = document.querySelector('div.desktop-dt-top');

            // The details container now exists. Create our custom elements.
            if (detailsTopElement !== null) {
                initialize(detailsTopElement);
            }
        }
    }, 100);
})();