WSJ Dark Mode

Elegant dark mode for Wall Street Journal when system dark mode is enabled

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         WSJ Dark Mode
// @namespace    https://www.wsj.com
// @version      0.0.1
// @description  Elegant dark mode for Wall Street Journal when system dark mode is enabled
// @author       hxueh
// @match        *://*.wsj.com/*
// @grant        GM_addStyle
// @run-at       document-start
// @icon         https://s.wsj.net/img/meta/wsj_favicon.svg
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  // Color palette - warm, readable dark theme
  const colors = {
    bg: '#121417',
    bgElevated: '#1a1d21',
    bgCard: '#22262b',
    bgHover: '#2a2f36',
    text: '#e8e6e3',
    textMuted: '#a8a5a0',
    textSubtle: '#7a7875',
    accent: '#d4a853',
    accentHover: '#e6bc6a',
    link: '#7eb8da',
    linkHover: '#a3d0eb',
    border: '#2e3238',
    borderSubtle: '#252930',
  };

  const darkModeCSS = `
    /*========================================
      WSJ Dark Mode - Base Styles
    ========================================*/

    :root {
      color-scheme: dark;
    }

    /* Global resets */
    html, body {
      background-color: ${colors.bg} !important;
      color: ${colors.text} !important;
    }

    /* Main content areas */
    body,
    main,
    article,
    section,
    div,
    header,
    footer,
    nav,
    aside {
      background-color: transparent !important;
    }

    /* Override specific WSJ containers */
    .WSJTheme--page-container--,
    .WSJTheme--pageWrapper--,
    [class*="PageWrapper"],
    [class*="page-container"],
    [class*="MainContent"],
    [class*="ArticleBody"],
    [class*="wrapper"],
    #root,
    #__next,
    .container {
      background-color: ${colors.bg} !important;
    }

    /*========================================
      Typography
    ========================================*/

    /* Headings */
    h1, h2, h3, h4, h5, h6,
    [class*="Headline"],
    [class*="headline"],
    [class*="Title"],
    [class*="title"] {
      color: ${colors.text} !important;
    }

    /* Body text */
    p, span, li, td, th, label,
    [class*="Paragraph"],
    [class*="paragraph"],
    [class*="Body"],
    [class*="Summary"],
    [class*="summary"],
    [class*="Description"],
    [class*="description"] {
      color: ${colors.text} !important;
    }

    /* Muted text */
    [class*="Byline"],
    [class*="byline"],
    [class*="Timestamp"],
    [class*="timestamp"],
    [class*="Meta"],
    [class*="meta"],
    time,
    figcaption,
    .caption,
    [class*="Caption"] {
      color: ${colors.textMuted} !important;
    }

    /* Subtle text */
    [class*="Label"],
    [class*="Tag"],
    small {
      color: ${colors.textSubtle} !important;
    }

    /*========================================
      Links
    ========================================*/

    a {
      color: ${colors.link} !important;
      transition: color 0.15s ease !important;
    }

    a:hover {
      color: ${colors.linkHover} !important;
    }

    /* Article headlines as links */
    a h1, a h2, a h3, a h4,
    h1 a, h2 a, h3 a, h4 a,
    [class*="Headline"] a,
    a[class*="Headline"] {
      color: ${colors.text} !important;
    }

    a:hover h1, a:hover h2, a:hover h3, a:hover h4,
    h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover {
      color: ${colors.accent} !important;
    }

    /*========================================
      Cards & Containers
    ========================================*/

    [class*="Card"],
    [class*="card"],
    [class*="Module"],
    [class*="module"],
    [class*="Story"],
    [class*="story"],
    [class*="Item"],
    [class*="Promo"],
    [class*="promo"] {
      background-color: ${colors.bgCard} !important;
      border-color: ${colors.border} !important;
      border-radius: 8px !important;
    }

    /* Elevated surfaces */
    [class*="Modal"],
    [class*="modal"],
    [class*="Popup"],
    [class*="popup"],
    [class*="Dropdown"],
    [class*="dropdown"],
    [class*="Menu"],
    [class*="menu"],
    [class*="Tooltip"],
    [class*="tooltip"] {
      background-color: ${colors.bgElevated} !important;
      border-color: ${colors.border} !important;
      box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5) !important;
    }

    /*========================================
      Navigation & Header
    ========================================*/

    header,
    nav,
    [class*="Header"],
    [class*="header"],
    [class*="Nav"],
    [class*="nav"],
    [class*="Masthead"],
    [class*="masthead"],
    [class*="TopBar"],
    [class*="topbar"] {
      background-color: ${colors.bgElevated} !important;
      border-bottom-color: ${colors.border} !important;
    }

    /* WSJ Logo - invert for dark mode */
    [class*="Logo"] img,
    [class*="logo"] img,
    .wsj-logo img {
      filter: brightness(0) invert(1) !important;
    }

    /*========================================
      Footer
    ========================================*/

    footer,
    [class*="Footer"],
    [class*="footer"] {
      background-color: ${colors.bgElevated} !important;
      border-top-color: ${colors.border} !important;
    }

    /*========================================
      Sidebar & Secondary Content
    ========================================*/

    aside,
    [class*="Sidebar"],
    [class*="sidebar"],
    [class*="Rail"],
    [class*="rail"],
    [class*="Secondary"],
    [class*="secondary"] {
      background-color: ${colors.bg} !important;
    }

    /*========================================
      Forms & Inputs
    ========================================*/

    input,
    textarea,
    select {
      background-color: ${colors.bgCard} !important;
      color: ${colors.text} !important;
      border-color: ${colors.border} !important;
      border-radius: 6px !important;
    }

    input:focus,
    textarea:focus,
    select:focus {
      border-color: ${colors.accent} !important;
      outline: none !important;
      box-shadow: 0 0 0 2px rgba(212, 168, 83, 0.2) !important;
    }

    input::placeholder,
    textarea::placeholder {
      color: ${colors.textSubtle} !important;
    }

    /*========================================
      Buttons
    ========================================*/

    button,
    [class*="Button"],
    [class*="button"],
    [class*="Btn"],
    [class*="btn"],
    [role="button"] {
      background-color: ${colors.bgCard} !important;
      color: ${colors.text} !important;
      border-color: ${colors.border} !important;
      transition: all 0.15s ease !important;
    }

    button:hover,
    [class*="Button"]:hover,
    [class*="button"]:hover,
    [class*="Btn"]:hover,
    [class*="btn"]:hover,
    [role="button"]:hover {
      background-color: ${colors.bgHover} !important;
      border-color: ${colors.accent} !important;
    }

    /* Primary buttons */
    [class*="Primary"],
    [class*="primary"],
    button[class*="subscribe" i],
    button[class*="Subscribe" i] {
      background-color: ${colors.accent} !important;
      color: ${colors.bg} !important;
      border-color: ${colors.accent} !important;
    }

    [class*="Primary"]:hover,
    [class*="primary"]:hover {
      background-color: ${colors.accentHover} !important;
      border-color: ${colors.accentHover} !important;
    }

    /*========================================
      Tables
    ========================================*/

    table {
      background-color: ${colors.bg} !important;
      border-color: ${colors.border} !important;
    }

    th {
      background-color: ${colors.bgElevated} !important;
      color: ${colors.text} !important;
      border-color: ${colors.border} !important;
    }

    td {
      background-color: ${colors.bg} !important;
      color: ${colors.text} !important;
      border-color: ${colors.borderSubtle} !important;
    }

    tr:hover td {
      background-color: ${colors.bgCard} !important;
    }

    /*========================================
      Borders & Dividers
    ========================================*/

    hr,
    [class*="Divider"],
    [class*="divider"],
    [class*="Border"],
    [class*="border"],
    [class*="Separator"],
    [class*="separator"] {
      border-color: ${colors.border} !important;
      background-color: ${colors.border} !important;
    }

    /*========================================
      Images & Media
    ========================================*/

    img {
      opacity: 0.92;
      transition: opacity 0.2s ease;
    }

    img:hover {
      opacity: 1;
    }

    figure {
      background-color: transparent !important;
    }

    /* Video containers */
    [class*="Video"],
    [class*="video"],
    [class*="Player"],
    [class*="player"] {
      background-color: ${colors.bgCard} !important;
    }

    /*========================================
      Article Specific
    ========================================*/

    /* Article body */
    article,
    [class*="Article"],
    [class*="article"] {
      background-color: ${colors.bg} !important;
    }

    /* Blockquotes */
    blockquote,
    [class*="Quote"],
    [class*="quote"],
    [class*="Pullquote"],
    [class*="pullquote"] {
      background-color: ${colors.bgCard} !important;
      border-left-color: ${colors.accent} !important;
      color: ${colors.text} !important;
      border-radius: 0 8px 8px 0 !important;
      padding: 1rem 1.5rem !important;
    }

    /* Code blocks */
    pre, code {
      background-color: ${colors.bgCard} !important;
      color: ${colors.text} !important;
      border-color: ${colors.border} !important;
    }

    /*========================================
      Ads & Promotions (dim them)
    ========================================*/

    [class*="Ad"],
    [class*="ad-"],
    [id*="ad-"],
    [class*="Advertisement"],
    [class*="advertisement"],
    [class*="Promo"],
    [data-ad],
    iframe[src*="ad"] {
      opacity: 0.7 !important;
      filter: brightness(0.85) !important;
    }

    /*========================================
      Scrollbar
    ========================================*/

    ::-webkit-scrollbar {
      width: 10px;
      height: 10px;
    }

    ::-webkit-scrollbar-track {
      background: ${colors.bg};
    }

    ::-webkit-scrollbar-thumb {
      background: ${colors.bgHover};
      border-radius: 5px;
    }

    ::-webkit-scrollbar-thumb:hover {
      background: ${colors.textSubtle};
    }

    /* Firefox scrollbar */
    * {
      scrollbar-width: thin;
      scrollbar-color: ${colors.bgHover} ${colors.bg};
    }

    /*========================================
      Selection
    ========================================*/

    ::selection {
      background-color: ${colors.accent} !important;
      color: ${colors.bg} !important;
    }

    ::-moz-selection {
      background-color: ${colors.accent} !important;
      color: ${colors.bg} !important;
    }

    /*========================================
      Override inline styles (nuclear option)
    ========================================*/

    [style*="background-color: rgb(255"],
    [style*="background-color: #fff"],
    [style*="background-color: white"],
    [style*="background: rgb(255"],
    [style*="background: #fff"],
    [style*="background: white"] {
      background-color: ${colors.bg} !important;
    }

    [style*="color: rgb(0"],
    [style*="color: #000"],
    [style*="color: black"] {
      color: ${colors.text} !important;
    }

    /*========================================
      WSJ-Specific Overrides
    ========================================*/

    /* Market data colors - keep green/red for stocks */
    [class*="positive"],
    [class*="Positive"],
    [class*="gain"],
    [class*="Gain"],
    [class*="up"] {
      color: #4ade80 !important;
    }

    [class*="negative"],
    [class*="Negative"],
    [class*="loss"],
    [class*="Loss"],
    [class*="down"] {
      color: #f87171 !important;
    }

    /* Paywall/subscription modals */
    [class*="Paywall"],
    [class*="paywall"],
    [class*="Subscribe"],
    [class*="subscribe"] {
      background-color: ${colors.bgElevated} !important;
    }

    /* Search results */
    [class*="Search"],
    [class*="search"] {
      background-color: ${colors.bg} !important;
    }

    /* Comments section */
    [class*="Comment"],
    [class*="comment"] {
      background-color: ${colors.bgCard} !important;
      border-color: ${colors.border} !important;
    }

    /*========================================
      Smooth transitions for dynamic content
    ========================================*/

    * {
      transition: background-color 0.1s ease, border-color 0.1s ease;
    }

    /* Disable transitions for specific elements */
    a, button, input, img, video {
      transition: none;
    }

    a {
      transition: color 0.15s ease !important;
    }
  `;

  // Check for dark mode preference
  function isDarkMode() {
    return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
  }

  // Apply or remove dark mode styles
  let styleElement = null;

  function applyDarkMode() {
    if (!styleElement) {
      styleElement = document.createElement('style');
      styleElement.id = 'wsj-dark-mode-styles';
      styleElement.textContent = darkModeCSS;
    }
    if (!document.getElementById('wsj-dark-mode-styles')) {
      (document.head || document.documentElement).appendChild(styleElement);
    }
  }

  function removeDarkMode() {
    const existing = document.getElementById('wsj-dark-mode-styles');
    if (existing) {
      existing.remove();
    }
  }

  function updateTheme() {
    if (isDarkMode()) {
      applyDarkMode();
    } else {
      removeDarkMode();
    }
  }

  // Initial application
  updateTheme();

  // Listen for system theme changes
  if (window.matchMedia) {
    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateTheme);
  }

  // Re-apply styles when DOM is ready (for dynamic content)
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', updateTheme);
  }

  // Observe for dynamic content changes
  const observer = new MutationObserver((mutations) => {
    if (isDarkMode() && !document.getElementById('wsj-dark-mode-styles')) {
      applyDarkMode();
    }
  });

  // Start observing when body is available
  function startObserver() {
    if (document.body) {
      observer.observe(document.body, {
        childList: true,
        subtree: true,
      });
    } else {
      requestAnimationFrame(startObserver);
    }
  }

  startObserver();

  console.log('🌙 WSJ Dark Mode loaded - respects system preference');
})();