X Dim Mode

Restores the Dim (dark blue) background option to X/Twitter display settings.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name         X Dim Mode
// @namespace    https://greasyfork.org/zh-CN/users/1495808-flartiny
// @version      1.2.0
// @description  Restores the Dim (dark blue) background option to X/Twitter display settings.
// @author       flartiny
// @license      MIT
// @match        https://x.com/*
// @match        https://twitter.com/*
// @match        https://pro.x.com/*
// @run-at       document-start
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addValueChangeListener
// @grant        GM_registerMenuCommand
// ==/UserScript==

(function initXDimBrowserApi(globalScope) {
  const LOCAL_PREFIX = "xdm:";
  const fallbackListeners = new Set();
  let gmRemoteListenerReady = false;
  let localStorageListenerReady = false;

  function getExtApi() {
    if (typeof browser !== "undefined" && browser.storage) {
      return browser;
    }
    if (typeof chrome !== "undefined" && chrome.storage) {
      return chrome;
    }
    return null;
  }

  function hasGmStorage() {
    return typeof GM_getValue === "function" && typeof GM_setValue === "function";
  }

  function parseStoredValue(rawValue) {
    if (rawValue === null || rawValue === undefined) return undefined;
    try {
      return JSON.parse(rawValue);
    } catch {
      return undefined;
    }
  }

  function readLocalValue(key) {
    try {
      return parseStoredValue(globalScope.localStorage.getItem(LOCAL_PREFIX + key));
    } catch {
      return undefined;
    }
  }

  function writeLocalValue(key, value) {
    try {
      if (value === undefined) {
        globalScope.localStorage.removeItem(LOCAL_PREFIX + key);
      } else {
        globalScope.localStorage.setItem(LOCAL_PREFIX + key, JSON.stringify(value));
      }
    } catch {
      // Ignore write failures in restricted storage contexts
    }
  }

  function emitFallbackChange(changes) {
    for (const listener of fallbackListeners) {
      listener(changes);
    }
  }

  function createGetResult(key, reader) {
    if (typeof key === "string") {
      return { [key]: reader(key) };
    }

    if (Array.isArray(key)) {
      const result = {};
      for (const item of key) result[item] = reader(item);
      return result;
    }

    if (key && typeof key === "object") {
      const result = {};
      for (const [item, defaultValue] of Object.entries(key)) {
        const value = reader(item);
        result[item] = value === undefined ? defaultValue : value;
      }
      return result;
    }

    return {};
  }

  function setupGmRemoteListener() {
    if (gmRemoteListenerReady) return;
    if (typeof GM_addValueChangeListener !== "function") return;

    gmRemoteListenerReady = true;
    GM_addValueChangeListener("enabled", (key, oldValue, newValue, remote) => {
      if (!remote) return;
      emitFallbackChange({
        [key]: { oldValue, newValue },
      });
    });
  }

  function setupLocalStorageListener() {
    if (localStorageListenerReady) return;
    if (typeof globalScope.addEventListener !== "function") return;

    localStorageListenerReady = true;
    globalScope.addEventListener("storage", (event) => {
      if (!event.key || !event.key.startsWith(LOCAL_PREFIX)) return;
      const key = event.key.slice(LOCAL_PREFIX.length);
      emitFallbackChange({
        [key]: {
          oldValue: parseStoredValue(event.oldValue),
          newValue: parseStoredValue(event.newValue),
        },
      });
    });
  }

  function storageGet(key, cb) {
    const api = getExtApi();
    if (api && typeof browser !== "undefined" && api === browser) {
      api.storage.local
        .get(key)
        .then((result) => cb(result || {}))
        .catch(() => cb({}));
      return;
    }

    if (api) {
      api.storage.local.get(key, (result) => {
        cb(result || {});
      });
      return;
    }

    if (hasGmStorage()) {
      cb(createGetResult(key, (item) => GM_getValue(item)));
      return;
    }

    cb(createGetResult(key, (item) => readLocalValue(item)));
  }

  function storageSet(value, cb) {
    const api = getExtApi();
    if (api && typeof browser !== "undefined" && api === browser) {
      api.storage.local
        .set(value)
        .then(() => {
          if (cb) cb();
        })
        .catch(() => {
          if (cb) cb();
        });
      return;
    }

    if (api) {
      api.storage.local.set(value, () => {
        if (cb) cb();
      });
      return;
    }

    const changes = {};
    for (const [key, newValue] of Object.entries(value || {})) {
      const oldValue = hasGmStorage() ? GM_getValue(key) : readLocalValue(key);
      if (hasGmStorage()) {
        GM_setValue(key, newValue);
      } else {
        writeLocalValue(key, newValue);
      }
      changes[key] = { oldValue, newValue };
    }

    if (Object.keys(changes).length) {
      emitFallbackChange(changes);
    }

    if (cb) cb();
  }

  function onStorageChanged(handler) {
    const api = getExtApi();
    if (api) {
      api.storage.onChanged.addListener((changes, areaName) => {
        if (areaName && areaName !== "local") return;
        handler(changes);
      });
      return;
    }

    fallbackListeners.add(handler);

    if (hasGmStorage()) {
      setupGmRemoteListener();
      return;
    }

    setupLocalStorageListener();
  }

  function registerToggleMenuCommands() {
    if (typeof GM_registerMenuCommand !== "function") return;

    GM_registerMenuCommand("X Dim Mode: Enable", () => {
      storageSet({ enabled: true });
    });

    GM_registerMenuCommand("X Dim Mode: Disable", () => {
      storageSet({ enabled: false });
    });
  }

  globalScope.XDimBrowserApi = {
    getExtApi,
    storageGet,
    storageSet,
    onStorageChanged,
    registerToggleMenuCommands,
  };
})(globalThis);

const DIM_BASE_ID = "x-dim-base-ext";
const DIM_BTN_ID = "x-dim-option-btn";
const DIM_CLASS = "x-dim-active";
const { storageGet, storageSet, onStorageChanged } = globalThis.XDimBrowserApi;

// ── Dim Theme CSS ──────────────────────────────────────────────────

const BASE_CSS = `
  /* Dim theme variables */
  html.${DIM_CLASS} {
    --xdm-bg: rgb(21, 32, 43);
    --xdm-bg-hover: rgb(30, 39, 50);
    --xdm-bg-elevated: rgb(39, 51, 64);
    --xdm-backdrop: rgba(21, 32, 43, .85);
    --xdm-text: rgb(139, 152, 165);
    --xdm-border: rgb(56, 68, 77);
  }

  /* Override X's own Lights Out theme variables */
  html.${DIM_CLASS} body.LightsOut {
    --color: var(--xdm-text);
    --border: 206 16% 26%;
    --input: 206 16% 26%;
    --border-color: var(--xdm-border);
  }

  /* Chat / DM interface (Tailwind + shadcn/Radix) */
  html.${DIM_CLASS}[data-theme="dark"],
  html.${DIM_CLASS} [data-theme="dark"] {
    --background: 215 29% 13%;
    --border: 206 16% 26%;
    --input: 206 16% 26%;
    --muted-foreground: 206 16% 55%;
    --color-background: 215 29% 13%;
    --color-gray-0: 215 29% 13%;
    --color-gray-50: 206 16% 26%;
    --color-gray-100: 206 16% 26%;
    --color-gray-700: 206 16% 60%;
    --color-gray-800: 206 16% 50%;
  }

  /* ── Black background overrides ── */

  /* Body — catches class-based black bg (e.g. Creator Studio) */
  html.${DIM_CLASS} body {
    background-color: var(--xdm-bg) !important;
  }

  /* Inline styles (covers body, divs, modals, dropdowns, etc.) */
  html.${DIM_CLASS} [style*="background-color: rgb(0, 0, 0)"],
  html.${DIM_CLASS} [style*="background-color: rgba(0, 0, 0, 1)"] {
    background-color: var(--xdm-bg) !important;
  }
  /* Elevated section cards (rgb(24,24,27) in dark mode → slightly lighter in dim) */
  html.${DIM_CLASS} [style*="background-color: rgb(24, 24, 27)"] {
    background-color: var(--xdm-bg-hover) !important;
  }
  /* Icon containers in menu rows (Premium, etc.) */
  html.${DIM_CLASS} [role="link"] > div > div:first-child div:has(> svg:only-child) {
    background-color: var(--xdm-bg-elevated) !important;
  }

  /* X utility classes for black backgrounds */
  html.${DIM_CLASS} .r-kemksi,
  html.${DIM_CLASS} .r-1niwhzg,
  html.${DIM_CLASS} .r-yfoy6g,
  html.${DIM_CLASS} .r-14lw9ot {
    background-color: var(--xdm-bg) !important;
  }
  /* Action-button hover circles — make transparent so they match any parent bg */
  html.${DIM_CLASS} .r-1niwhzg.r-sdzlij {
    background-color: transparent !important;
  }
  /* Timeline top bar */
  html.${DIM_CLASS} .r-5zmot {
    background-color: var(--xdm-backdrop) !important;
  }
  /* Tweet character counter separator */
  html.${DIM_CLASS} .r-1shrkeu {
    background-color: var(--xdm-border) !important;
  }
  /* Sidebar button hover */
  html.${DIM_CLASS} .r-1hdo0pc {
    background-color: var(--xdm-bg-hover) !important;
  }
  /* Secondary background (section cards on Premium, etc.) */
  html.${DIM_CLASS} .r-g2wdr4 {
    background-color: var(--xdm-bg-hover) !important;
  }
  html.${DIM_CLASS} .r-g2wdr4 [role="link"]:hover {
    background-color: var(--xdm-bg-elevated) !important;
  }

  /* Borders */
  html.${DIM_CLASS} .r-1kqtdi0,
  html.${DIM_CLASS} .r-1roi411 {
    border-color: var(--xdm-border) !important;
  }
  html.${DIM_CLASS} .r-2sztyj {
    border-top-color: var(--xdm-border) !important;
  }
  html.${DIM_CLASS} .r-1igl3o0,
  html.${DIM_CLASS} .r-rull8r {
    border-bottom-color: var(--xdm-border) !important;
  }
  /* Separators / dividers */
  html.${DIM_CLASS} .r-gu4em3,
  html.${DIM_CLASS} .r-1bnu78o {
    background-color: var(--xdm-border) !important;
  }

  /* Search bar icon, tweet character counter */
  html.${DIM_CLASS} .r-1bwzh9t {
    color: var(--xdm-text) !important;
  }
  /* "What's happening" text */
  html.${DIM_CLASS} .draftjs-styles_0 .public-DraftEditorPlaceholder-root,
  html.${DIM_CLASS} .public-DraftEditorPlaceholder-inner {
    color: var(--xdm-text) !important;
  }
  /* Secondary text */
  html.${DIM_CLASS} [style*="color: rgb(113, 118, 123)"],
  html.${DIM_CLASS} [style*="-webkit-line-clamp: 3; color: rgb(113, 118, 123)"],
  html.${DIM_CLASS} [style*="-webkit-line-clamp: 2; color: rgb(113, 118, 123)"] {
    color: var(--xdm-text) !important;
  }
  /* Placeholders */
  html.${DIM_CLASS} ::placeholder {
    color: var(--xdm-text) !important;
  }

  /* Tailwind classes used in chat/DM interface */
  html.${DIM_CLASS} .bg-gray-0 {
    background-color: var(--xdm-bg) !important;
  }
  html.${DIM_CLASS} .border-gray-50,
  html.${DIM_CLASS} .border-gray-100 {
    border-color: var(--xdm-border) !important;
  }

  /* Grok buttons (active) */
  html.${DIM_CLASS} [style*="border-color: rgb(47, 51, 54)"].r-1che71a {
    background-color: var(--xdm-bg-hover) !important;
  }

  /* Scanner-discovered black backgrounds */
  html.${DIM_CLASS} .xdm-dimmed {
    background-color: var(--xdm-bg) !important;
  }
  /* Scanner-discovered elevated backgrounds (e.g. section cards) */
  html.${DIM_CLASS} .xdm-dimmed-elevated {
    background-color: var(--xdm-bg-hover) !important;
  }
  /* Creator Studio icon containers (jf-element framework) */
  html.${DIM_CLASS} .jf-element:has(> span:only-child > svg:only-child) {
    background-color: var(--xdm-bg-elevated) !important;
  }
  /* Creator Studio dividers inside elevated section cards */
  html.${DIM_CLASS} .xdm-dimmed-elevated .jf-element:empty {
    background-color: var(--xdm-border) !important;
    border-color: var(--xdm-border) !important;
  }

  /* Scrollbar */
  html.${DIM_CLASS} {
    scrollbar-color: var(--xdm-border) var(--xdm-bg);
  }
`;

// Always update the style element — prevents stale CSS after extension reload
function ensureBaseCSS() {
  let style = document.getElementById(DIM_BASE_ID);
  if (!style) {
    style = document.createElement("style");
    style.id = DIM_BASE_ID;
    (document.head || document.documentElement).appendChild(style);
  }
  if (style.textContent !== BASE_CSS) style.textContent = BASE_CSS;
}

// Inject CSS immediately at document_start — don't wait for async storage read.
// Rules are gated by html.x-dim-active so they're inert until the class is added.
ensureBaseCSS();

function applyDim() {
  ensureBaseCSS();
  document.documentElement.classList.add(DIM_CLASS);
  if (document.body) queueScan([document.body]);
}

function removeDim() {
  document.documentElement.classList.remove(DIM_CLASS);
  // Cancel any pending scan
  if (_scanFrame) {
    cancelAnimationFrame(_scanFrame);
    _scanFrame = 0;
    _pending.clear();
  }
  // Remove scanner-applied classes (non-destructive — doesn't touch original styles)
  for (const el of document.querySelectorAll(".xdm-dimmed, .xdm-dimmed-elevated")) {
    el.classList.remove("xdm-dimmed", "xdm-dimmed-elevated");
  }
}

// ── System Theme Sync ─────────────────────────────────────────────
// Follows OS preference: dark → Dim, light → Default.
// Watches body.LightsOut (X's dark mode class) to detect theme state.

let _bodyObserver;
let _suspendedForLight = false;

function syncDimWithTheme() {
  if (!_enabled || !document.body) return;
  const hasLightsOut = document.body.classList.contains("LightsOut");
  const dimActive = document.documentElement.classList.contains(DIM_CLASS);
  if (hasLightsOut) {
    // X is in dark mode → activate dim
    _suspendedForLight = false;
    if (!dimActive) {
      applyDim();
      for (const ms of [500, 1500, 3000, 5000]) setTimeout(fullRescan, ms);
    }
  } else if (dimActive && _seenLightsOut) {
    // X switched to light mode (LightsOut was present, now removed) → suspend dim
    _suspendedForLight = true;
    removeDim();
  }
}

// Track whether X has ever been in dark mode this session.
// Prevents removing dim before X has finished initializing.
let _seenLightsOut = false;

function startBodyObserver() {
  if (_bodyObserver || !document.body) return;
  if (document.body.classList.contains("LightsOut")) _seenLightsOut = true;
  _bodyObserver = new MutationObserver(() => {
    if (document.body.classList.contains("LightsOut")) _seenLightsOut = true;
    syncDimWithTheme();
  });
  _bodyObserver.observe(document.body, {
    attributes: true,
    attributeFilter: ["class"],
  });
}

function stopBodyObserver() {
  if (_bodyObserver) {
    _bodyObserver.disconnect();
    _bodyObserver = null;
  }
}

// ── Black Background Scanner ─────────────────────────────────────
// Catches inline black backgrounds not covered by known CSS selectors.
// Uses a CSS class (not inline styles) so toggling is instant and non-destructive.

let _scanFrame = 0;
const _pending = new Set();

function queueScan(nodes) {
  for (const n of nodes) {
    if (n && n.nodeType === 1) _pending.add(n);
  }
  if (_pending.size && !_scanFrame) {
    _scanFrame = requestAnimationFrame(flushScan);
  }
}

function flushScan() {
  _scanFrame = 0;
  if (!document.documentElement.classList.contains(DIM_CLASS)) {
    _pending.clear();
    return;
  }
  const batch = [..._pending];
  _pending.clear();
  for (const node of batch) dimSubtree(node);
}

function dimSubtree(root) {
  dimElement(root);
  for (const el of root.querySelectorAll("div,main,aside,header,nav,section,article,footer,button")) {
    dimElement(el);
  }
}

function dimElement(el) {
  if (!el || el.nodeType !== 1 || el.classList.contains("xdm-dimmed") || el.classList.contains("xdm-dimmed-elevated")) return;
  const bg = el.classList.contains("jf-element")
    ? (() => { try { return getComputedStyle(el).backgroundColor; } catch { return ""; } })()
    : el.style.backgroundColor;
  if (bg === "rgb(0, 0, 0)" || bg === "rgba(0, 0, 0, 1)") {
    el.classList.add("xdm-dimmed");
  } else if (bg === "rgb(24, 24, 27)") {
    el.classList.add("xdm-dimmed-elevated");
  }
}

// ── Display Settings Injection ─────────────────────────────────────

const CHECKMARK_SVG = `<svg viewBox="0 0 24 24" aria-hidden="true" class="r-4qtqp9 r-yyyyoo r-dnmrzs r-bnwqim r-lrvibr r-m6rgpd r-jwli3a r-1hjwoze r-12ym1je"><g><path d="M9.64 18.952l-5.55-4.861 1.317-1.504 3.951 3.459 8.459-10.948L19.4 6.32 9.64 18.952z"></path></g></svg>`;

function setSelected(btnEl) {
  btnEl.style.borderColor = "rgb(29, 155, 240)";
  btnEl.style.borderWidth = "2px";
  const circle = btnEl.querySelector('[role="radio"] > div');
  if (circle) {
    circle.style.backgroundColor = "rgb(29, 155, 240)";
    circle.style.borderColor = "rgb(29, 155, 240)";
    circle.innerHTML = CHECKMARK_SVG;
  }
  const input = btnEl.querySelector('input[type="radio"]');
  if (input) input.checked = true;
}

function setUnselected(btnEl) {
  btnEl.style.borderColor = "rgb(51, 54, 57)";
  btnEl.style.borderWidth = "1px";
  const circle = btnEl.querySelector('[role="radio"] > div');
  if (circle) {
    circle.style.backgroundColor = "rgba(0, 0, 0, 0)";
    circle.style.borderColor = "rgb(185, 202, 211)";
    circle.innerHTML = "";
  }
  const input = btnEl.querySelector('input[type="radio"]');
  if (input) input.checked = false;
}

function tryInjectDimOption() {
  if (document.getElementById(DIM_BTN_ID)) return;

  // Find the background picker by its radio inputs (language-independent)
  const bgRadio = document.querySelector('input[name="background-picker"]');
  if (!bgRadio) return;
  const radiogroup = bgRadio.closest('[role="radiogroup"]');
  if (!radiogroup) return;

  const buttons = radiogroup.querySelectorAll(':scope > div');
  if (buttons.length < 2) return;

  const defaultBtn = buttons[0];
  const lightsOutBtn = buttons[1];

  // Clone the Lights Out button as our base
  const dimBtn = lightsOutBtn.cloneNode(true);
  dimBtn.id = DIM_BTN_ID;

  // Set dim background color
  dimBtn.style.backgroundColor = "rgb(21, 32, 43)";

  // Change label to "Dim"
  const label = dimBtn.querySelector("span");
  if (label) label.textContent = "Dim";

  // Update radio input
  const input = dimBtn.querySelector('input[type="radio"]');
  if (input) {
    input.setAttribute("aria-label", "Dim");
    input.checked = false;
  }

  // Insert between Default and Lights Out
  radiogroup.insertBefore(dimBtn, lightsOutBtn);

  // Set initial visual state based on whether dim is enabled
  storageGet("enabled", ({ enabled }) => {
    syncSettingsButtons(!!enabled);
  });

  // ── Click handlers ──

  dimBtn.addEventListener("click", () => {
    storageSet({ enabled: true });
    syncSettingsButtons(true);
    activateLightsOut();
  });

  // When Default or Lights Out is clicked directly, disable Dim
  for (const nativeBtn of [defaultBtn, lightsOutBtn]) {
    nativeBtn.addEventListener("click", () => {
      if (_switchingToDim) return; // Ignore clicks triggered by dim switch
      storageSet({ enabled: false });
      setUnselected(dimBtn);
    });
  }
}

// ── Lights Out Helper ──────────────────────────────────────────────
// Clicks X's Lights Out radio (if the Display settings page is open) to ensure
// the correct base theme. Used by both the Dim button and the popup toggle.

let _switchingToDim = false;

function activateLightsOut() {
  const dimBtn = document.getElementById(DIM_BTN_ID);
  if (!dimBtn) return; // Settings page not open
  const radiogroup = dimBtn.closest('[role="radiogroup"]');
  if (!radiogroup) return;
  const allBtns = radiogroup.querySelectorAll(":scope > div");
  const lightsOutBtn = allBtns[allBtns.length - 1];
  if (!lightsOutBtn) return;
  const loInput = lightsOutBtn.querySelector('input[type="radio"]');
  if (loInput && !loInput.checked) {
    _switchingToDim = true;
    loInput.click();
    loInput.dispatchEvent(new Event("input", { bubbles: true }));
    loInput.dispatchEvent(new Event("change", { bubbles: true }));
    setTimeout(() => { _switchingToDim = false; }, 300);
  }
}

// ── Observer & Init ────────────────────────────────────────────────

let _enabled = false;
let observer;

function startObserver() {
  if (observer) return;
  observer = new MutationObserver((mutations) => {
    try {
      // Re-apply dim if class was removed by X (unless suspended for light mode)
      if (_enabled && !_suspendedForLight && !document.documentElement.classList.contains(DIM_CLASS)) {
        applyDim();
      }
      // Scan newly added nodes for black backgrounds
      if (_enabled && document.documentElement.classList.contains(DIM_CLASS)) {
        for (const m of mutations) {
          if (m.addedNodes.length) queueScan(m.addedNodes);
        }
      }
      // Try to inject the Dim button on the display settings page
      tryInjectDimOption();
      // Start body observer once body is available
      if (_enabled && document.body && !_bodyObserver) {
        startBodyObserver();
      }
    } catch {
      // Extension context invalidated after reload — clean up
      observer.disconnect();
    }
  });
  observer.observe(document.documentElement, {
    childList: true,
    subtree: true,
  });
}

// Re-scan the entire body to catch elements the initial scan or observer missed
function fullRescan() {
  if (_enabled && document.body) queueScan([document.body]);
}

// Init — single storage read, then use cached state
storageGet("enabled", ({ enabled }) => {
  if (enabled === undefined) {
    _enabled = true;
    storageSet({ enabled: true });
  } else {
    _enabled = !!enabled;
  }

  if (_enabled) {
    // Apply dim immediately if system is dark (avoids flash of black).
    // If system is light, body observer will handle it once X sets its theme.
    const systemDark = !window.matchMedia || window.matchMedia("(prefers-color-scheme: dark)").matches;
    if (systemDark) {
      applyDim();
      for (const ms of [500, 1500, 3000, 5000]) setTimeout(fullRescan, ms);
    }
  }

  startObserver();
  tryInjectDimOption();

  // Start body observer if body is already available
  if (_enabled && document.body) {
    startBodyObserver();
  }
});

// Sync the radio buttons on the Display settings page with the current state
function syncSettingsButtons(enabled) {
  const dimBtn = document.getElementById(DIM_BTN_ID);
  if (!dimBtn) return;
  const radiogroup = dimBtn.closest('[role="radiogroup"]');
  if (!radiogroup) return;
  const allBtns = radiogroup.querySelectorAll(":scope > div");
  const lightsOutBtn = allBtns[allBtns.length - 1];

  if (enabled) {
    setSelected(dimBtn);
    for (const btn of allBtns) {
      if (btn !== dimBtn) setUnselected(btn);
    }
  } else {
    setUnselected(dimBtn);
    if (lightsOutBtn) setSelected(lightsOutBtn);
  }
}

// Listen for toggle — updates cached state synchronously
onStorageChanged((changes) => {
  if (changes.enabled) {
    _enabled = !!changes.enabled.newValue;
    if (_enabled) {
      _suspendedForLight = false;
      startBodyObserver();
      applyDim();
      activateLightsOut();
    } else {
      stopBodyObserver();
      removeDim();
    }
    syncSettingsButtons(_enabled);
  }
});

(function initTampermonkeyMenu() {
  if (!globalThis.XDimBrowserApi) return;
  globalThis.XDimBrowserApi.registerToggleMenuCommands();
})();