WSJ Dark Mode

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

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         WSJ Dark Mode
// @namespace    https://www.wsj.com
// @version      0.0.4
// @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 - elegant dark theme
  const colors = {
    bg: '#000000',
    bgElevated: '#000000',
    bgCard: '#000000',
    bgHover: '#0a0a0a',
    text: '#e8e6e3',
    textMuted: '#a8a5a0',
    textSubtle: '#888888',
    accent: '#d4a853',
    accentHover: '#e6bc6a',
    link: '#7eb8da',
    linkHover: '#a3d0eb',
    border: '#1a1a1a',
    borderSubtle: '#111111',
  };
 
  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*="Tooltip"],
    [class*="tooltip"] {
      background-color: #000000 !important;
      border-color: ${colors.border} !important;
      box-shadow: 0 8px 32px rgba(0, 0, 0, 0.8) !important;
    }
 
    /* Menu-specific styles - high specificity */
    [class*="Menu"],
    [class*="menu"],
    div[class*="Menu"],
    div[class*="menu"],
    ul[class*="Menu"],
    ul[class*="menu"] {
      background-color: #000000 !important;
      border: 1px solid ${colors.border} !important;
    }
 
    /* Menu items */
    [class*="Menu"] > *,
    [class*="menu"] > *,
    [class*="Menu"] li,
    [class*="menu"] li,
    [class*="Menu"] a,
    [class*="menu"] a {
      background-color: #000000 !important;
    }
 
    [class*="Menu"] a:hover,
    [class*="menu"] a:hover,
    [class*="Menu"] li:hover,
    [class*="menu"] li:hover {
      background-color: ${colors.bgHover} !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;
    }
 
    /* Navigation dropdown menus */
    [class*="Dropdown"],
    [class*="dropdown"],
    [class*="Flyout"],
    [class*="flyout"],
    [class*="Submenu"],
    [class*="submenu"],
    [class*="SubNav"],
    [class*="subnav"],
    [class*="MenuPanel"],
    [class*="menu-panel"],
    [class*="NavPanel"],
    [class*="nav-panel"],
    [class*="DropdownMenu"],
    [class*="dropdown-menu"],
    nav ul ul,
    nav > div > div,
    [role="menu"],
    [aria-expanded="true"] + div,
    [aria-expanded="true"] + ul,
    header [class*="expanded"],
    header [class*="open"],
    header [class*="active"] > div,
    header [class*="hover"] > div {
      background-color: #000000 !important;
      border: 1px solid ${colors.border} !important;
      box-shadow: 0 4px 20px rgba(0, 0, 0, 0.9) !important;
    }
 
    /* Nested navigation links in dropdowns */
    nav a,
    [class*="Nav"] a,
    [class*="nav"] a,
    header a {
      background-color: transparent !important;
    }
 
    nav li,
    [class*="Nav"] li,
    header li {
      background-color: transparent !important;
    }
 
    /* Dropdown container backgrounds */
    nav [class*="container"],
    nav [class*="wrapper"],
    nav [class*="content"],
    header [class*="container"],
    header [class*="wrapper"] {
      background-color: #000000 !important;
    }
 
    /* WSJ Logo - invert images for dark mode */
    [class*="Logo"] img,
    [class*="logo"] img,
    .wsj-logo img {
      filter: brightness(0) invert(1) !important;
    }

    /* WSJ Logo - SVG logos */
    [class*="Logo"] svg,
    [class*="logo"] svg,
    [class*="WSJLogo"] svg,
    [class*="wsj-logo"] svg,
    [class*="Masthead"] svg,
    [class*="masthead"] svg,
    header svg[class*="logo" i],
    nav svg[class*="logo" i] {
      filter: brightness(0) invert(1) !important;
    }

    /* WSJ specific logo classes */
    [class*="WSJLogo"],
    [class*="wsj-logo"],
    [class*="wsjLogo"],
    [class*="WSJTheme--logo"],
    [class*="SiteLogo"],
    [class*="site-logo"],
    [class*="BrandLogo"],
    [class*="brand-logo"] {
      filter: brightness(0) invert(1) !important;
    }

    /* Header logo links - target the logo container */
    header [class*="Logo"],
    header [class*="logo"],
    nav [class*="Logo"],
    nav [class*="logo"],
    [class*="Masthead"] [class*="Logo"],
    [class*="masthead"] [class*="logo"] {
      filter: brightness(0) invert(1) !important;
    }

    /* Ensure child elements of logo also get inverted */
    [class*="Logo"] *,
    [class*="logo"][class*="wsj" i] *,
    [class*="WSJLogo"] *,
    [class*="wsj-logo"] * {
      filter: inherit !important;
    }

    /* Reset filter for non-logo header elements */
    header a:not([class*="logo" i]):not([class*="Logo"]),
    nav a:not([class*="logo" i]):not([class*="Logo"]) {
      filter: none !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.bgHover} !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.border};
      border-radius: 5px;
    }
 
    ::-webkit-scrollbar-thumb:hover {
      background: ${colors.textSubtle};
    }
 
    /* Firefox scrollbar */
    * {
      scrollbar-width: thin;
      scrollbar-color: ${colors.border} ${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');
})();