Declutter Pinterest

Removes intrusive Pinterest shopping promotions, ads, and clutter, and makes the website more user-friendly

Verze ze dne 09. 05. 2025. Zobrazit nejnovější verzi.

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

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.

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

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

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

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

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         Declutter Pinterest
// @namespace    August4067
// @version      0.9.0
// @description  Removes intrusive Pinterest shopping promotions, ads, and clutter, and makes the website more user-friendly
// @license      MIT
// @match        https://www.pinterest.com/*
// @match        https://*.pinterest.com/*
// @match        https://*.pinterest.co.uk/*
// @match        https://*.pinterest.fr/*
// @match        https://*.pinterest.de/*
// @match        https://*.pinterest.ca/*
// @match        https://*.pinterest.jp/*
// @match        https://*.pinterest.it/*
// @match        https://*.pinterest.au/*
// @icon         https://www.pinterest.com/favicon.ico
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @sandbox      Javascript
// ==/UserScript==

/*--- waitForKeyElements():  A utility function, for Greasemonkey scripts,
    that detects and handles AJAXed content.

    Usage example:

        waitForKeyElements (
            "div.comments"
            , commentCallbackFunction
        );

        //--- Page-specific function to do what we want when the node is found.
        function commentCallbackFunction (jNode) {
            jNode.text ("This comment changed by waitForKeyElements().");
        }

    IMPORTANT: This function requires your script to have loaded jQuery.
*/

// Pulled from: https://gist.github.com/raw/2625891/waitForKeyElements.js
function waitForKeyElements(
  selectorTxt /* Required: The jQuery selector string that
                        specifies the desired element(s).
                    */,
  actionFunction /* Required: The code to run when elements are
                        found. It is passed a jNode to the matched
                        element.
                    */,
  bWaitOnce /* Optional: If false, will continue to scan for
                        new elements even after the first match is
                        found.
                    */,
  iframeSelector /* Optional: If set, identifies the iframe to
                        search.
                    */,
) {
  var targetNodes, btargetsFound;

  if (typeof iframeSelector == "undefined") targetNodes = $(selectorTxt);
  else targetNodes = $(iframeSelector).contents().find(selectorTxt);

  if (targetNodes && targetNodes.length > 0) {
    btargetsFound = true;
    /*--- Found target node(s).  Go through each and act if they
            are new.
        */
    targetNodes.each(function () {
      var jThis = $(this);
      var alreadyFound = jThis.data("alreadyFound") || false;

      if (!alreadyFound) {
        //--- Call the payload function.
        var cancelFound = actionFunction(jThis);
        if (cancelFound) btargetsFound = false;
        else jThis.data("alreadyFound", true);
      }
    });
  } else {
    btargetsFound = false;
  }

  //--- Get the timer-control variable for this selector.
  var controlObj = waitForKeyElements.controlObj || {};
  var controlKey = selectorTxt.replace(/[^\w]/g, "_");
  var timeControl = controlObj[controlKey];

  //--- Now set or clear the timer as appropriate.
  if (btargetsFound && bWaitOnce && timeControl) {
    //--- The only condition where we need to clear the timer.
    clearInterval(timeControl);
    delete controlObj[controlKey];
  } else {
    //--- Set a timer, if needed.
    if (!timeControl) {
      timeControl = setInterval(function () {
        waitForKeyElements(
          selectorTxt,
          actionFunction,
          bWaitOnce,
          iframeSelector,
        );
      }, 300);
      controlObj[controlKey] = timeControl;
    }
  }
  waitForKeyElements.controlObj = controlObj;
}

// We will set the Pinterest page title to this, to remove
// the flashing title notifications like Pinterest (2)
const ORIGINAL_TITLE = 'Pinterest';

const SETTINGS_CONFIG = {
  removeShoppablePins: {
    displayName: "Remove shoppable pins",
    default: true
  },
  removePinFooters: {
    displayName: "Remove pin footers",
    default: false
  },
  removeComments: {
    displayName: "Remove pin comments",
    default: false
  },
  removeNavbarMessagesIcon: {
    displayName: "Remove navbar messages icon",
    default: false
  },
  removeNavbarNotificationIcon: {
    displayName: "Remove navbar notifications icon", 
    default: false
  }
};

class Setting {
  constructor(name, config) {
    this.name = name;
    this.displayName = config.displayName;
    this.default = config.default;
  }

  currentValue() {
    return GM_getValue(this.name, this.default);
  }

  toggleSetting() {
    GM_setValue(this.name, !this.currentValue());
  }
}

// Create settings object by mapping config to Setting instances
const SETTINGS = Object.fromEntries(
  Object.entries(SETTINGS_CONFIG).map(([name, config]) => [
    name,
    new Setting(name, config)
  ])
);

// MENU SETTINGS
function toggleMenuSetting(settingName) {
  var setting = SETTINGS[settingName];
  setting.toggleSetting();
  updateSettingsMenu();
  console.debug(`Setting ${settingName} set to: ${setting.currentValue()}}`);
  location.reload();
}

function updateSettingsMenu() {
  for (const [setting_name, setting] of Object.entries(SETTINGS)) {
    GM_registerMenuCommand(
      `${setting.displayName}: ${setting.currentValue() ? "Enabled" : "Disabled"}`,
      () => {
        toggleMenuSetting(setting_name);
      },
    );
  }
}

// HELPER FUNCTIONS
function waitAndRemove(selector, removeFunction) {
  if (removeFunction == undefined) {
    removeFunction = (elem) => elem.remove();
  }

  waitForKeyElements(selector, function (node) {
    if (node && node.length > 0) {
      removeFunction(node[0]);
    }
  });
}

/**
 * Hide an element by setting its display style to "none" if it exists
 * @param {HTMLElement} element - The DOM element to hide
 */

function hideElement(element) {
  if (element) {
    element.style.display = "none";
  }
}

/**
 * Return an array of pins, filtering out those that already have
 * style.display == "none" (pins that we have removed)
 * @param {*} pins
 * @returns array of pins
 */
function filterRemovedPins(pins) {
  return pins ? pins.filter((pin) => pin.style.display !== "none") : [];
}

// DECLUTTER BOARDS
/**
 * Clean the Shop button from the top of boards (from the 3 button group with "Shop", "Organize", and "More ideas")
 */
function cleanShopButtonsFromBoard() {
  console.debug("Cleaning Shop buttons from board");
  waitForKeyElements('div[data-test-id="board-tools"]', function (node) {
    var shopButton = node[0].querySelector('div[data-test-id="Shop"]');
    hideElement(shopButton);
    console.debug("Removed Shop button from top of board");
  });
}

/**
 * Remove the banner of shopping pins wherever we find it (with title "Shop products inspired by this board").
 * They sometimes show up at the top of boards, at the bottom of boards, and at the top of searches.
 */
function cleanShopByBanners() {
  waitForKeyElements('div[data-test-id="sf-header-heading"]', function (node) {
    var shopByBannerAtTopOfBoard = node[0].closest(
      'div[class="PKX zI7 iyn Hsu"]',
    );
    hideElement(shopByBannerAtTopOfBoard);
    console.debug("Removed shop by banner from top of board");

    if (node[0].closest('div[data-test-id="base-board-pin-grid"]')) {
      var shopByBannerAtBottomOfBoard = node[0].closest(
        'div[class="gcw zI7 iyn Hsu"]',
      );
      hideElement(shopByBannerAtBottomOfBoard);
      console.debug("Removed shop by banner from bottom of board");
    }

    var shopByBannerAtTopOfSearch = node[0].closest('div[role="listitem"]');
    hideElement(shopByBannerAtTopOfSearch);
    console.debug("Removed shop by banner from top of search results");
  });
}

// DECLUTTER NAVBAR
/**
 * The search bar now has dynamic placeholder text with suggested searches. These are distracting, and we will remove them.
 */
function cleanSearchBarDynamicText(searchBox) {
  searchBox
    .querySelector('div[data-test-id="dynamic-search-placeholder"]')
    ?.remove();
}

function cleanSearchBarSuggestions(searchBox) {
  var suggestionsMenu = searchBox.querySelector('div[id="SuggestionsMenu"]');
  if (!suggestionsMenu) {
    return;
  }

  var popularOnPinterestSuggestions = suggestionsMenu.querySelector(
    'div[title="Popular on Pinterest"]',
  );
  if (popularOnPinterestSuggestions) {
    var popularOnPinterestBanner = popularOnPinterestSuggestions.closest(
      'div[class="jzS un8 L4V jDD"]',
    );
    hideElement(popularOnPinterestBanner);
    console.debug("Removed Popular on Pinterest search suggestions");
  }
}

/**
 * A series of tabs is at the top of search results to refine results by your interests ("all", "holiday finds", "my board 1", "my board 2", etc).
 * We will remove the shopping / promoted tabs
 */
function cleanNavTabCarousel() {
  waitForKeyElements('div[class="localNavTabCarousel"]', function (nodes) {
    var navTabCarousel = nodes[0];
    if (!navTabCarousel) {
      return;
    }

    const promotedTabs = new Set(["holiday finds"]);
    navTabCarousel.querySelectorAll('div[class="xuA"]').forEach((item) => {
      if (!item.innerText) {
        return;
      }

      var navTabText = item.innerText.trim().toLowerCase();
      if (promotedTabs.has(navTabText)) {
        hideElement(item);
        console.debug(`Hid promoted nav tab: ${navTabText}`);
      }
    });
  });
}

function removeBellIcon() {
  if (SETTINGS.removeNavbarNotificationIcon.currentValue()) {
    const bellIconDiv = document.querySelector('div[data-test-id="bell-icon"]');
    hideElement(bellIconDiv);
    console.debug("Removed bell icon from navbar.");
  }
}

function removeMessagesIcon() {
  if (SETTINGS.removeNavbarMessagesIcon.currentValue()) {
    const messagesIconDiv = document.querySelector(
      'div[aria-label="Messages"]',
    );
    if (messagesIconDiv) {
      var messagesParent = messagesIconDiv.closest(
        'div[class="XiG zI7 iyn Hsu"]',
      );
      hideElement(messagesParent);
      console.debug("Removed messages button");
    }
  }
}

function removeExploreTabNotificationsIcon() {
  console.debug("Removing notifications icon from Explore tab (top nav and sidebar)");

  // --- Remove notification icon from Explore tab in the top nav (old behavior)
  var exploreTab = document.querySelector('div[data-test-id="today-tab"]');
  if (exploreTab) {
    var notificationsIcon = exploreTab.querySelector(
      'div[aria-label="Notifications"]',
    );
    hideElement(notificationsIcon);
    if (notificationsIcon) {
      console.debug("Removed notifications icon from Explore tab (top nav)");
    }
  }

  // --- Remove notification badge from Explore tab in the sidebar (new behavior)
  // Find the Explore tab link in the sidebar
  var exploreTabLink = document.querySelector('a[data-test-id="today-tab"]');
  if (exploreTabLink) {
    // The parent of the link is the icon container, its parent is the sidebar item
    var iconContainer = exploreTabLink.closest('div[class*="XiG"]');
    var sidebarItem = iconContainer?.parentElement?.parentElement;
    if (sidebarItem) {
      // The notification badge is a sibling div with class "MIw" and pointer-events: none
      var notificationBadge = sidebarItem.parentElement?.querySelector('.MIw[style*="pointer-events: none"]');
      if (notificationBadge) {
        hideElement(notificationBadge);
        console.debug("Removed notifications badge from Explore tab (sidebar)");
      }
    }
  }
}

function cleanNavBarCallback(mutationsList) {
  console.debug("Cleaning navbar");
  removeBellIcon();
  removeMessagesIcon();
  var callbackCount = 0;
  waitForKeyElements('div[data-test-id="today-tab"]', function (nodes) {
    new MutationObserver((mutations, observer) => {
      removeExploreTabNotificationsIcon();
    }).observe(nodes[0], {
      childList: true,
      subtree: true,
    });
  });

  removeExploreTabNotificationsIcon();

  waitForKeyElements('div[id="searchBoxContainer"]', function (nodes) {
    new MutationObserver((mutations, observer) => {
      cleanSearchBarSuggestions(nodes[0]);
      cleanSearchBarDynamicText(nodes[0]);
    }).observe(nodes[0], {
      childList: true,
      subtree: true,
    });
  });
}

function cleanVerticalNavBar(verticalNavContent) {
  // TODO: Remove notification badges from explore icon

  waitForKeyElements('div[aria-label="Messages"]', function (nodes) {
    if (SETTINGS.removeNavbarMessagesIcon.currentValue()) {
      if (nodes && nodes.length > 0) {
        var closest = nodes[0].closest('div[class="xuA"]');
        hideElement(closest);
      }
    }
  });

  waitForKeyElements('div[aria-label="Notifications"]', function (nodes) {
    if (SETTINGS.removeNavbarNotificationIcon.currentValue()) {
      if (nodes && nodes.length > 0) {
        var closest = nodes[0].closest('div[class="xuA"]');
        hideElement(closest);
      }
    }
  });
}

function cleanNavBar() {
  // This is the nav bar at the top of the page
  waitForKeyElements('div[id="HeaderContent"]', function (nodes) {
    const headerContent = document.getElementById("HeaderContent");
    if (headerContent) {
      var observerCallCount = 0;
      const observer = new MutationObserver((mutations, observer) => {
        cleanNavBarCallback(mutations);
        if (++observerCallCount >= 5) {
          observer.disconnect();
          console.debug("Disconnected cleanNavBar() mutation observer");
        }
      });

      observer.observe(headerContent, {
        childList: true,
        subtree: true,
      });
      cleanNavBarCallback([]);
    }
  });

  // This is the vertical nav bar at the left of the page
  waitForKeyElements('nav[id="VerticalNavContent"]', function (nodes) {
    console.debug("Cleaning vertical nav bar");
    if (nodes && nodes.length > 0) {
      console.debug("Cleaning vertical nav bar:", nodes[0]);
      cleanVerticalNavBar(nodes[0]);
    }
  });
}

// DECLUTTER PINS
function cleanShoppingAds(pins) {
  const shoppingAdDivs = document.querySelectorAll(
    "div.Ch2.zDA.IZT.CKL.tBJ.dyH.iFc.GTB.H2s",
  );

  shoppingAdDivs.forEach((adDiv) => {
    let parent = adDiv.closest('div[role="listitem"]');
    hideElement(parent);
    console.debug("Removed shopping container");
  });
}

function cleanIdeasYouMightLove(pins) {
  pins.forEach((pin) => {
    if (
      pin.textContent.toLowerCase().includes("ideas you might love") ||
      pin.textContent.toLowerCase().includes("shop similar") ||
      pin.textContent.toLowerCase().includes("shop featured boards")
    ) {
      hideElement(pin);
      console.debug('Removed "Ideas you might love" item:', pin);
    }
  });
}

function removePinFooters(pins) {
  if (!SETTINGS.removePinFooters.currentValue()) {
    return;
  }

  pins.forEach((pin) => {
    const footer = pin.querySelector('div[data-test-id="pinrep-footer"]');
    if (!footer) {
      return;
    }

    hideElement(footer);
    console.debug("Removed pin footer:", footer);
  });
}

/**
 * Remove Shoppable Pins by looking for a little tag ("Shoppable Pin indicator") in the pin somewhere.
 * Shoppable pin indicators could be in the footer, or as an svg on top of the image
 */
function removeShoppablePins(pins) {
  if (!SETTINGS.removeShoppablePins.currentValue()) {
    return;
  }

  pins.forEach((pin) => {
    const shoppableIndicator = pin.querySelector(
      '[aria-label="Shoppable Pin indicator"]',
    );

    if (shoppableIndicator) {
      hideElement(pin);
      console.debug("Removed shoppable pin:", pin);
    }
  });
}

function removeSponsoredPins(pins, mutations) {
  var promotedPinSelector =
    'a[aria-label="Promoted by"], a[aria-label="Promoted by; Opens a new tab"], div[title="Sponsored"]';

  pins.forEach((pin) => {
    if (pin && pin.querySelector(promotedPinSelector)) {
      hideElement(pin);
    }
  });
}

/**
 * Pinterest now has a "Shop now" module as the first result of some searches.
 * We will remove this.
 */
function removeShoppingModule(pins) {
  console.debug("Cleaning shopping modules");
  if (pins) {
    pins.forEach((pin) => {
      var module = pin.querySelector("div.Module");
      if (!module || !module.innerText) {
        return;
      }

      const shoppingModuleTexts = new Set(["shop now", "continue shopping"]);
      const innerTextLines = module.innerText
        .trim()
        .split("\n")
        .map((x) => x.trim().toLowerCase());
      for (var i = 0; i < innerTextLines.length; i++) {
        if (shoppingModuleTexts.has(innerTextLines[i])) {
          hideElement(pin);
          console.debug("Removed shopping module");
          break;
        }
      }
    });
  }
}

/**
 * Pinterest now is promoting their own shopping boards (from the "Pinterest Shop" user). We
 * will remove those.
 */
function removeFeaturedBoards(pins) {
  if (pins) {
    pins.forEach((pin) => {
      if (pin.style.display === "none") {
        return;
      }

      if (pin.querySelector('[data-test-id="pinRepPresentation"]')) {
        return;
      }
      var innerText = pin.innerText ? pin.innerText.trim().toLowerCase() : "";

      var splitText = innerText.split("\n").map((x) => x.trim().toLowerCase());
      if (
        splitText.includes("explore featured boards") ||
        splitText.includes("pinterest shop")
      ) {
        hideElement(pin);
        console.debug("Removed featured boards module");
      }
    });
  }
}

function cleanRelatedPinsSection(mutations) {
  console.debug("Cleaning related pins");
  var pins = document.querySelectorAll('div[role="listitem"]');

  cleanIdeasYouMightLove(pins);
  cleanShoppingAds(pins);
  removePinFooters(pins);
  removeShoppablePins(pins);
  removeSponsoredPins(pins, mutations);
  removeShoppingModule(pins);
  removeFeaturedBoards(pins);
}

function cleanProductListingPinPage() {
  waitAndRemove('div[data-test-id="product-price"]');
  waitAndRemove('div[data-test-id="pdp-product-metadata-domain-owner"]');
  waitAndRemove(
    'div[data-test-id="product-shop-button"]',
    (elem) => (elem.parentElement.style.display = "none"),
  );
  waitAndRemove(
    'div[data-test-id="product-description"]',
    (elem) => (elem.parentElement.style.display = "none"),
  );
}

function cleanPinVisualContainer() {
  var pinVisualContainer = document.querySelector(
    'div[data-test-id="closeup-visual-container"]',
  );
  if (pinVisualContainer) {
    var shopButton = pinVisualContainer.querySelector(
      'div[data-test-id="experimental-closeup-image-overlay-layer-shop-button"]',
    );
    hideElement(shopButton);

    var domainLinkButton = pinVisualContainer.querySelector(
      'div[data-test-id="experimental-closeup-image-overlay-layer-domain-link-button"]'
    );
    hideElement(domainLinkButton);
    
    var closeupImageOverlay = pinVisualContainer.querySelector(
      'div[data-test-id="closeup-image-overlay-layer-domain-link-button"]'
    );
    hideElement(closeupImageOverlay);
  }
}

/**
 * The collapsible layout seems to be only used for the "Shop the look" section.
 * So we will remove it.
 */
function removeShopTheLookSection() {
  // 1. CSS rule as a backup (will not match text, but can help if Pinterest adds a class or data attribute in the future)
  const style = document.createElement('style');
  style.textContent = `
    /* This will hide the whole section if you can add a class or data attribute in the future */
    /* For now, we rely on JS for text matching */
  `;
  document.head.appendChild(style);

  // 2. Function to hide all "Shop the look" sections
  function hideShopTheLookSection() {
    document.querySelectorAll('div[data-test-id="collapsible-layout"]').forEach(function(layout) {
      // Look for any h2 or h3 with text "Shop the look" (case-insensitive)
      const headings = layout.querySelectorAll('h2, h3');
      for (const heading of headings) {
        if (heading.textContent && heading.textContent.trim().toLowerCase() === 'shop the look') {
          hideElement(layout);
          console.debug('Removed Shop the look section (robust observer)');
          break;
        }
      }
    });
  }

  // Initial hide
  hideShopTheLookSection();

  // 3. Observe the whole body for new nodes
  const observer = new MutationObserver(hideShopTheLookSection);
  observer.observe(document.querySelector('div[role="list"]'), { childList: true, subtree: true });
}

function cleanComments() {
  if (SETTINGS.removeComments.currentValue()) {
    waitForKeyElements("#canonical-card", function (canonicalCards) {
      if (canonicalCards && canonicalCards.length == 1) {
        var canonicalCard = canonicalCards[0];
        if (!canonicalCard) {
          return;
        }
        var hasCommentsHeading =
          canonicalCard.querySelector("#comments-heading") != null;
        if (hasCommentsHeading) {
          hideElement(canonicalCard);
          console.debug("Removed comments section from pin");
        }
      }
    });

    waitForKeyElements(
      'div[data-test-id="inline-comment-composer-container"]',
      function (nodes) {
        var commentBox = nodes[0];
        if (commentBox) {
          hideElement(commentBox);
          console.debug("Removed comment box from pin");
        }
      },
    );
  }
}

function cleanPinDescriptionContainer() {
  var pinDescriptionContainer = document.querySelector(
    'div[data-test-id="description-content-container"]',
  );

  if (pinDescriptionContainer) {
    var shopButton = pinDescriptionContainer.querySelector(
      'div[data-test-id="product-shop-button"]',
    );
    hideElement(shopButton);

    // Remove the new Shop the look section
    var collapsibleLayouts = document.querySelectorAll('div[data-test-id="collapsible-layout"]');
    collapsibleLayouts.forEach(function(layout) {
      var heading = layout.querySelector('h2#comments-heading');
      if (heading && heading.textContent.trim().toLowerCase() === 'shop the look') {
        hideElement(layout);
        console.debug('Removed Shop the look section');
      }
    });
  }
}

/**
 * Pinterest adds the number of notifications to the title
 * in a distracting, flashing manner like Pinerest (2).
 * So this will keep the page title at: Pinterest
 */
function enforceTitle() {
  if (document.title !== ORIGINAL_TITLE) {
    console.debug(
      `Changing title from: ${document.title} to ${ORIGINAL_TITLE}`,
    );
    document.title = ORIGINAL_TITLE;
  }
}

/**
 * Pinterest now has a popup modal asking you to disable adblock, if it is present.
 *
 * We will remove this modal.
 */
function removeAntiAdblockModalIfExists() {
  console.debug("Waiting for anti-Adblock modal");
  waitForKeyElements('div[aria-label="Ad blocker modal"]', function (nodes) {
    nodes[0]?.remove();
    document.querySelector('div[name="trap-focus"]')?.remove();
    document.body.style.overflow = "auto";
    console.debug("Removed anti-Adblock modal");
  });
}

// PAGE IDENTIFIERS
function isProductPin() {
  return document.getElementById("product-sticky-container ") != null;
}

function hasDescriptionContentContainer() {
  return (
    document.querySelector(
      'div[data-test-id="description-content-container"]',
    ) != null
  );
}

function isBoard() {
  return document.querySelector('div[data-test-id="board-header"]') != null;
}

function observeSidebarForExploreBadge() {
  // Find the sidebar navigation container (adjust selector if needed)
  const sidebarNav = document.querySelector('nav[id="VerticalNavContent"]') || document.querySelector('div[role="navigation"]');
  if (!sidebarNav) {
    // Try again later if sidebar not yet loaded
    setTimeout(observeSidebarForExploreBadge, 500);
    return;
  }
  // Remove any existing badge immediately
  removeExploreTabNotificationsIcon();

  // Set up observer
  const observer = new MutationObserver(() => {
    removeExploreTabNotificationsIcon();
  });
  observer.observe(sidebarNav, { childList: true, subtree: true });
}

function main() {
  "use strict";
  console.debug("Running main()");

  updateSettingsMenu();
  cleanNavBar();
  cleanComments();
  cleanNavTabCarousel();
  removeAntiAdblockModalIfExists();

  if (isProductPin()) {
    cleanProductListingPinPage();
  }

  cleanShopButtonsFromBoard();
  cleanShopByBanners();

  // The nav tab tab carousel pops up after page load
  waitForKeyElements('div[class="localNavTabCarousel"]', function (nodes) {
    cleanNavTabCarousel();
  });

  cleanPinDescriptionContainer();
  removeShopTheLookSection();
  cleanPinVisualContainer();
  // The pin visual container changes to add those little "shop" buttons
  // over the top of the image, so we need to watch for those and remove them
  waitForKeyElements(
    'div[data-test-id="closeup-visual-container"]',
    function (nodes) {
      new MutationObserver((mutations, observer) => {
        cleanPinVisualContainer();
      }).observe(nodes[0], {
        childList: true,
        subtree: true,
      });
    },
  );

  const relatedPinsSectionCleanerObserver = new MutationObserver(
    (mutations) => {
      cleanRelatedPinsSection(mutations);
    },
  );

  waitForKeyElements('div[role="list"]', function (node) {
    // Related pins section must be watched for changes,
    // as new pins pop up as the user scrolls
    cleanRelatedPinsSection([]);
    relatedPinsSectionCleanerObserver.observe(node[0], {
      childList: true,
    });
  });

  enforceTitle();
  const titleElement = document.querySelector("title");
  new MutationObserver(enforceTitle).observe(titleElement, { childList: true });

  observeSidebarForExploreBadge();
}

main();

let lastUrl = window.location.href;
setInterval(() => {
  const currentUrl = window.location.href;
  if (currentUrl !== lastUrl) {
    console.debug(
      `Detected new page, currentURL=${currentUrl}, previousURL=${lastUrl}`,
    );
    lastUrl = currentUrl;
    main();
  }
}, 750);