Pinterest Dark Mode

Force dark mode on pinterest.com, keeps images/svg normal, supports toggle and persists choice.

// ==UserScript==
// @name         Pinterest Dark Mode
// @namespace    https://github.com/trojaninfect
// @version      1.5
// @description  Force dark mode on pinterest.com, keeps images/svg normal, supports toggle and persists choice.
// @author       NoSleep
// @match        https://*.pinterest.com/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @run-at       document-start
// ==/UserScript==

(function () {
  'use strict';

  // ID for the injected <style>
  const STYLE_ID = 'tm-pinterest-dark-style-v1';

  // Default: enabled
  let enabled = GM_getValue('tm_pinterest_dark_enabled', true);

  // The main dark CSS. It uses broad selectors and !important to override Pinterest's styles.
  const darkCSS = `
    /* container/backgrounds */
    html.tm-pinterest-dark, html.tm-pinterest-dark body {
      background: #0b0b0b !important;
      color: #e6e6e6 !important;
    }

    /* generic elements */
    html.tm-pinterest-dark body,
    html.tm-pinterest-dark header,
    html.tm-pinterest-dark main,
    html.tm-pinterest-dark nav,
    html.tm-pinterest-dark section,
    html.tm-pinterest-dark aside,
    html.tm-pinterest-dark footer,
    html.tm-pinterest-dark article,
    html.tm-pinterest-dark div,
    html.tm-pinterest-dark li,
    html.tm-pinterest-dark a,
    html.tm-pinterest-dark p,
    html.tm-pinterest-dark span,
    html.tm-pinterest-dark button,
    html.tm-pinterest-dark input,
    html.tm-pinterest-dark textarea {
      background-color: transparent !important;
      color: #e6e6e6 !important;
      border-color: rgba(255,255,255,0.06) !important;
    }

    /* pin cards / tiles / modal backgrounds */
    html.tm-pinterest-dark .BoardPage, /* board pages */
    html.tm-pinterest-dark .Grid,      /* generic grid */
    html.tm-pinterest-dark .GrowthUnauthPage,
    html.tm-pinterest-dark .Collection,
    html.tm-pinterest-dark .pin,
    html.tm-pinterest-dark .Modal,
    html.tm-pinterest-dark .modal,
    html.tm-pinterest-dark [data-test-id="pin"] {
      background: #0f0f10 !important;
      box-shadow: 0 1px 0 rgba(255,255,255,0.03) inset, 0 8px 20px rgba(0,0,0,0.6) !important;
    }

    /* headers, top nav */
    html.tm-pinterest-dark header[role="banner"],
    html.tm-pinterest-dark nav[role="navigation"],
    html.tm-pinterest-dark .Header,
    html.tm-pinterest-dark .topNav {
      background: linear-gradient(180deg,#0d0d0d,#0a0a0a) !important;
      border-bottom: 1px solid rgba(255,255,255,0.04) !important;
    }

    /* inputs, search box */
    html.tm-pinterest-dark input,
    html.tm-pinterest-dark textarea,
    html.tm-pinterest-dark select {
      background: rgba(255,255,255,0.03) !important;
      color: #e6e6e6 !important;
      border: 1px solid rgba(255,255,255,0.04) !important;
    }

    /* links and accents */
    html.tm-pinterest-dark a,
    html.tm-pinterest-dark a:visited {
      color: #ff7a7a !important; /* gentle accent - tweak if you want */
    }

    /* keep images, svgs, videos, icons normal (no site-wide invert) */
    html.tm-pinterest-dark img,
    html.tm-pinterest-dark svg,
    html.tm-pinterest-dark video,
    html.tm-pinterest-dark picture {
      filter: none !important;
      background: transparent !important;
    }

    /* make sure pinned images still have rounded corners etc */
    html.tm-pinterest-dark img[style],
    html.tm-pinterest-dark img {
      image-rendering: auto !important;
    }

    /* text-muted & small text */
    html.tm-pinterest-dark .tappable,
    html.tm-pinterest-dark .small,
    html.tm-pinterest-dark .meta,
    html.tm-pinterest-dark .description {
      color: rgba(230,230,230,0.75) !important;
    }

    /* overlays and tooltips */
    html.tm-pinterest-dark .Overlay,
    html.tm-pinterest-dark .Tooltip,
    html.tm-pinterest-dark .Popover {
      background: rgba(12,12,12,0.95) !important;
      color: #e6e6e6 !important;
    }

    /* remove extremely bright borders */
    html.tm-pinterest-dark * {
      box-shadow: none !important;
    }

    /* scrollbars (if supported) */
    html.tm-pinterest-dark ::-webkit-scrollbar { width: 10px; height: 10px; }
    html.tm-pinterest-dark ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.06); border-radius: 10px; }
    html.tm-pinterest-dark ::-webkit-scrollbar-track { background: rgba(0,0,0,0.12); }

    /* keep images, svgs, videos, icons normal (no site-wide invert) */
html.tm-pinterest-dark img,
html.tm-pinterest-dark svg,
html.tm-pinterest-dark video,
html.tm-pinterest-dark picture {
    filter: none !important;
    background: transparent !important;
}

/* force all non-root SVGs to white */
html.tm-pinterest-dark svg:not(:root) {
    fill: #ffffff !important;
    stroke: #ffffff !important;
    background: transparent !important;
}

/* Fix all sticky/fixed profile header elements */
html.tm-pinterest-dark .ProfilePageHeader,
html.tm-pinterest-dark .ProfilePageHeader-content,
html.tm-pinterest-dark [data-test-id="profile-header"],
html.tm-pinterest-dark .ProfilePageHeader-content > *,
html.tm-pinterest-dark [data-test-id="profile-header"] > * {
    position: relative !important;  /* use relative to scroll normally */
    top: auto !important;
    z-index: auto !important;
    background: #0b0b0b !important;
}

/* Fix profile header from following scroll */
html.tm-pinterest-dark .qiB,
html.tm-pinterest-dark [data-test-id="self-profile-header"] {
    position: relative !important; /* force it into normal flow */
    top: auto !important;          /* reset top offset */
    z-index: auto !important;      /* reset stacking */
    background: #0b0b0b !important; /* match dark mode */
}


  `;

  // Injects or removes the style element and toggles the html class
  function applyStyle(on) {
    // add/remove class on <html>
    const html = document.documentElement;
    if (on) html.classList.add('tm-pinterest-dark'); else html.classList.remove('tm-pinterest-dark');

    let el = document.getElementById(STYLE_ID);
    if (on) {
      if (!el) {
        try {
          // Use GM_addStyle if available for best compatibility
          if (typeof GM_addStyle === 'function') {
            GM_addStyle(darkCSS);
            // GM_addStyle does not set id, so add a marker <style> so observer can find it
            el = document.querySelector('style');
            if (el) el.id = STYLE_ID;
          } else {
            el = document.createElement('style');
            el.id = STYLE_ID;
            el.type = 'text/css';
            el.textContent = darkCSS;
            document.head.appendChild(el);
          }
        } catch (e) {
          // fallback
          el = document.createElement('style');
          el.id = STYLE_ID;
          el.type = 'text/css';
          el.textContent = darkCSS;
          document.head.appendChild(el);
        }
      } else {
        el.textContent = darkCSS;
      }
    } else {
      if (el && el.parentNode) el.parentNode.removeChild(el);
    }
  }

  // Persisted toggle
  function setEnabled(value) {
    enabled = !!value;
    GM_setValue('tm_pinterest_dark_enabled', enabled);
    applyStyle(enabled);
  }

  // Toggle function (used by keyboard shortcut)
  function toggle() {
    setEnabled(!enabled);
  }

  // Keyboard shortcut Ctrl+Shift+D to toggle
  window.addEventListener('keydown', function (e) {
    if (e.key === 'D' && e.ctrlKey && e.shiftKey && !e.altKey && !e.metaKey) {
      toggle();
      e.preventDefault();
    }
  }, true);

  // Add Tampermonkey menu command
  try {
    if (typeof GM_registerMenuCommand === 'function') {
      GM_registerMenuCommand(enabled ? 'Disable Pinterest Dark Mode' : 'Enable Pinterest Dark Mode', toggle);
    }
  } catch (e) { /* ignore */ }

  // Observer: re-add style if Pinterest replaces head or removes style
  const observer = new MutationObserver(muts => {
    // if style removed but enabled, re-add
    if (enabled && !document.getElementById(STYLE_ID)) {
      applyStyle(true);
    }
    // ensure html class survives
    if (enabled && !document.documentElement.classList.contains('tm-pinterest-dark')) {
      document.documentElement.classList.add('tm-pinterest-dark');
    }
  });

  // Start observer on documentElement
  function startObserver() {
    observer.observe(document.documentElement || document, {
      childList: true,
      subtree: true,
      attributes: false
    });
  }

  // Wait for DOM to exist
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => {
      applyStyle(enabled);
      startObserver();
    }, { once: true });
  } else {
    applyStyle(enabled);
    startObserver();
  }

  // In case the user wants immediate feedback before DOMContentLoaded, attempt early apply
  try { applyStyle(enabled); } catch (e) { /* noop */ }

})();