Smart Dark Mode

-

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Smart Dark Mode
// @description  -
// @version      2025.10.17
// @match        *://*/*
// @grant        GM.getValue
// @grant        GM.setValue
// @grant        GM_registerMenuCommand
// @run-at       document-start
// @icon         data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23232323' class='bi bi-moon-stars-fill' viewBox='0 0 16 16'%3e%3cpath d='M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z'/%3e%3cpath d='M10.794 3.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387a1.734 1.734 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.734 1.734 0 0 0 9.31 6.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387a1.734 1.734 0 0 0 1.097-1.097l.387-1.162zM13.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732l-.774-.258a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L13.863.1z'/%3e%3c/svg%3e
// @namespace https://ndaesik.tistory.com/
// ==/UserScript==

const normHost = h => String(h||'').toLowerCase().replace(/^www\./,'');
const HOST = normHost(location.hostname);
const hostMatch = (h, e) => h===e || h.endsWith('.'+e);

let state = {
  settings: ['hotKeySetOn','Ctrl + D','setTimeOff','18:00','07:00'],
  alwaysOnList: '',
  alwaysOffList: `youtube.com,
m.youtube.com,
music.youtube.com,
studio.youtube.com,
docs.google.com,
keep.google.com`,
  uiReady: false
};

let drkMo;
let EARLY_OFF = false;

const earlyParseList = s => String(s||'')
  .split(/[\r\n,]+/)
  .map(v => v.trim().toLowerCase().replace(/^www\./,''))
  .filter(Boolean);

const earlyUrlMatch = list => {
  const paths = earlyParseList(list);
  return paths.some(entry=>{
    if (entry.includes('/')) {
      const [eHost,...rest]=entry.split('/');
      const ePath='/'+rest.join('/');
      return hostMatch(HOST,eHost) && location.pathname.startsWith(ePath);
    }
    return hostMatch(HOST,entry);
  });
};

(async () => {
  try {
    const offList = await GM.getValue('alwaysOffList','');
    EARLY_OFF = earlyUrlMatch(offList);
  } catch(_) { EARLY_OFF = false; }
  if (!EARLY_OFF && self === top) {
    const s = document.createElement('style');
    s.className = 'preventBlinkCSS';
    s.textContent = `*{background:#202124!important;border-color:#3c4043!important;color-scheme:dark!important;color:#e3e3e3!important;transition:none!important}`;
    document.documentElement.appendChild(s);
  }
})();

GM_registerMenuCommand('On/Off', () => window.postMessage({__SDM__: 'toggle'}, '*'));
GM_registerMenuCommand('Panel',  () => window.postMessage({__SDM__: 'panel' }, '*'));

window.addEventListener('message', e => {
  if (!e || !e.data || e.data.__SDM__==null) return;
  const cmd = e.data.__SDM__;
  (async () => {
    await ensureInit();
    if (cmd === 'toggle') safeToggle();
    if (cmd === 'panel')  togglePanel();
  })();
});

window.addEventListener('load', () => { ensureInit().then(postLoad); });

async function ensureInit() {
  if (state.uiReady) return;
  try {
    state.settings      = await GM.getValue('settings', state.settings);
    state.alwaysOnList  = await GM.getValue('alwaysOnList', state.alwaysOnList);
    state.alwaysOffList = await GM.getValue('alwaysOffList', state.alwaysOffList);
  } catch(_) {}
  buildUI();
  wireUI();
  state.uiReady = true;
}

function postLoad() {
  try { initialApplyLogic(); watchSpa(); } catch(_) {}
}

const parseList = s => String(s||'')
  .split(/[\r\n,]+/)
  .map(v => v.trim().toLowerCase().replace(/^www\./,''))
  .filter(Boolean);

const urlMatch = list => {
  const paths = parseList(list);
  return paths.some(entry=>{
    if (entry.includes('/')) {
      const [eHost,...rest]=entry.split('/');
      const ePath='/'+rest.join('/');
      return hostMatch(HOST,eHost) && location.pathname.startsWith(ePath);
    }
    return hostMatch(HOST,entry);
  });
};

function ensureDrkStyle() {
  if (!drkMo) {
    drkMo = document.createElement('style');
    drkMo.className = 'drkMo';
    drkMo.textContent = `
html{color-scheme:dark!important;background:#fff;color:#000}
html *{color-scheme:light!important;text-shadow:0 0 .1px}
html body{background:none!important}

html,
html :is(img,image,embed,video,canvas,option,object,:fullscreen:not(iframe),iframe:not(:fullscreen)),
html body>* [style*="url("]:not([style*="cursor:"]):not([type="text"]) {
  filter: invert(1) hue-rotate(180deg)!important;
}

html body>* [style*="url("]:not([style*="cursor:"]) :not(#_),
html:not(#_) :is(canvas,option,object) :is(img,image,embed,video),
html:not(#_) :is(video:fullscreen,img[src*="/svg/"],img[src*=".svg."],img[src*="fonts.gstatic.com/s/i/"]) {
  filter: unset!important;
}

#SDM_body { filter: invert(1) hue-rotate(180deg)!important; }
#SDM_body *{ color-scheme:dark!important; }
`;
  }
}

function hardOffNow() { return EARLY_OFF || urlMatch(state.alwaysOffList); }

function applyFilter() {
  if (hardOffNow()) return;
  ensureDrkStyle();
  if (!document.querySelector('style.drkMo')) document.head.appendChild(drkMo);
}

function removeFilterAll() {
  document.querySelectorAll('style.drkMo').forEach(e=>e.remove());
  document.querySelector('.preventBlinkCSS')?.remove();
}

const isOn = () => !!document.querySelector('style.drkMo');

function checkTimeSet() {
  const timeChk = document.querySelector('#SDM_timeSet')?.checked ?? (state.settings[2]==='setTimeOn');
  if (!timeChk) return true;
  const from = (document.querySelector('#SDM_timeSet_input_from')?.value || state.settings[3] || '18:00');
  const to   = (document.querySelector('#SDM_timeSet_input_to')?.value   || state.settings[4] || '07:00');
  const [fh,fm] = from.split(':').map(n=>+n); const [th,tm] = to.split(':').map(n=>+n);
  const now = new Date(); const ch=now.getHours(), cm=now.getMinutes();
  const afterStart = ch>fh || (ch===fh && cm>=fm);
  const beforeEnd  = ch<th || (ch===th && cm<tm);
  return (fh<=th) ? (afterStart && beforeEnd) : (afterStart || beforeEnd);
}

function initialApplyLogic() {
  if (!hardOffNow()) document.querySelector('.preventBlinkCSS')?.remove();
  const offNow = hardOffNow();
  const onNow  = urlMatch(state.alwaysOnList);
  const inTime = checkTimeSet();
  const shouldApply = !offNow && inTime && ( onNow || autoDetectBright() );
  if (offNow) { removeFilterAll(); setToggle(false); }
  else if (shouldApply) { applyFilter(); setToggle(true); }
  else { setToggle(isOn()); }
}

function autoDetectBright() {
  try {
    const frame = self !== top;
    const bodyZero = document.body ? document.body.offsetHeight===0 : false;
    const elems = document.querySelectorAll('body > :not(script)');
    const rgb = el => {
      const m = getComputedStyle(el).getPropertyValue('background-color').match(/\d+/g)||[0,0,0,1];
      return m.map(x=>+x);
    };
    const bright = el => { const [r,g,b,a]=rgb(el); return a===0 || (r*.299+g*.587+b*.114)>186; };
    if ((!frame && !bodyZero || frame) && bright(document.documentElement) && bright(document.body)) return true;
    if (!frame && bodyZero) {
      for (let i=0;i<elems.length;i++){
        if (elems[i].scrollHeight>window.innerHeight && bright(elems[i])) return true;
      }
    }
  } catch(_) {}
  return false;
}

function watchSpa() {
  let lastHref = location.href;
  new MutationObserver(() => {
    if (lastHref !== location.href) {
      lastHref = location.href;
      EARLY_OFF = urlMatch(state.alwaysOffList);
      if (hardOffNow()) { removeFilterAll(); setToggle(false); }
      else if (!isOn() && (urlMatch(state.alwaysOnList) && checkTimeSet())) { applyFilter(); setToggle(true); }
    }
  }).observe(document.body || document.documentElement, {subtree:true, childList:true});
}

function buildUI() {
  if (document.getElementById('SDM_body')) return;
  const ui = `
<div id="SDM_body" class="SDM_root" style="display:none">
  <div class="SDM_wrap">
    <div class="SDM_bar">
      <div class="SDM_title">Smart Dark Mode</div>
      <div class="SDM_barBtns">
        <button class="SDM_btn" id="SDM_add_page" title="Add current domain">+</button>
        <label class="SDM_switch" title="Toggle filter">
          <input id="SDM_toggle" type="checkbox" class="SDM_tabInput">
          <span class="SDM_slider"></span>
        </label>
        <button class="SDM_btn" id="SDM_close">✕</button>
      </div>
    </div>
    <div class="SDM_tabs">
      <input id="tab_on" class="SDM_tabInput" type="radio" name="sdm_tab" checked><label class="SDM_tabLabel" for="tab_on">Always On</label>
      <input id="tab_off" class="SDM_tabInput" type="radio" name="sdm_tab"><label class="SDM_tabLabel" for="tab_off">Always Off</label>
      <input id="tab_settings" class="SDM_tabInput" type="radio" name="sdm_tab"><label class="SDM_tabLabel" for="tab_settings">Settings</label>
    </div>
    <div class="SDM_main">
      <div class="SDM_tabc" data-tab="on"><textarea id="SDM_on_textarea" class="SDM_textarea" spellcheck="false" placeholder="example.com, mysite.com">${state.alwaysOnList}</textarea></div>
      <div class="SDM_tabc" data-tab="off" style="display:none"><textarea id="SDM_off_textarea" class="SDM_textarea" spellcheck="false" placeholder="example.com, mysite.com">${state.alwaysOffList}</textarea></div>
      <div class="SDM_tabc" data-tab="settings" style="display:none">
        <div class="SDM_row">
          <label class="SDM_tgl"><input id="SDM_hotkey" type="checkbox" class="SDM_tabInput" ${state.settings[0]==='hotKeySetOn'?'checked':''}><span>Hotkey</span></label>
          <input id="SDM_hotkey_input" class="SDM_text" value="${state.settings[1]}" placeholder="Ctrl + D">
        </div>
        <div class="SDM_row">
          <label class="SDM_tgl"><input id="SDM_timeSet" type="checkbox" class="SDM_tabInput" ${state.settings[2]==='setTimeOn'?'checked':''}><span>Time Window</span></label>
          <input id="SDM_timeSet_input_from" class="SDM_time" maxlength="5" value="${state.settings[3]}"><span class="SDM_sep">~</span><input id="SDM_timeSet_input_to" class="SDM_time" maxlength="5" value="${state.settings[4]}">
        </div>
      </div>
    </div>
  </div>
</div>
<style>
#SDM_body.SDM_root{position:fixed;top:24px;right:24px;z-index:2147483647;font-family:system-ui,Segoe UI,Roboto,Apple SD Gothic Neo,Arial}
#SDM_body .SDM_wrap{width:340px;border-radius:14px;overflow:hidden;box-shadow:0 10px 24px rgba(0,0,0,.45);background:#0f1115;border:1px solid #262a33}
#SDM_body .SDM_bar{height:44px;display:flex;align-items:center;justify-content:space-between;padding:0 10px;background:linear-gradient(180deg,#12151b,#0f1115);border-bottom:1px solid #1b1f27}
#SDM_body .SDM_title{font-weight:600;font-size:14px;line-height:1.2;color:#e3e3ea;letter-spacing:.2px}
#SDM_body .SDM_barBtns{display:flex;gap:8px;align-items:center}
#SDM_body .SDM_btn{width:28px;height:28px;border-radius:8px;border:1px solid #2a2f3a;background:#151922;color:#cfd2d8;cursor:pointer}
#SDM_body .SDM_btn:hover{border-color:#3a4050}
#SDM_body .SDM_switch{position:relative;display:inline-block;width:46px;height:26px}
#SDM_body .SDM_switch input{opacity:0;width:0;height:0}
#SDM_body .SDM_slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:#2a2f3a;border-radius:16px;transition:.2s}
#SDM_body .SDM_slider:before{position:absolute;content:"";height:20px;width:20px;left:3px;top:3px;background:#cfd2d8;border-radius:50%;transition:.2s}
#SDM_body .SDM_switch input:checked + .SDM_slider{background:#ffb100}
#SDM_body .SDM_switch input:checked + .SDM_slider:before{transform:translateX(20px);background:#1a1a1a}
#SDM_body .SDM_tabs{display:grid;grid-template-columns:1fr 1fr 1fr;background:#0f1115}
#SDM_body .SDM_tabInput{all:unset}
#SDM_body .SDM_tabInput[type="radio"]{position:absolute;opacity:0;pointer-events:none}
#SDM_body .SDM_tabLabel{padding:10px 0;text-align:center;font-weight:600;font-size:12px;line-height:1;color:#9aa1ad;border-bottom:2px solid transparent;cursor:pointer;user-select:none}
#SDM_body #tab_on:checked   + .SDM_tabLabel{color:#ffb100;border-color:#ffb100}
#SDM_body #tab_off:checked  + .SDM_tabLabel{color:#ffb100;border-color:#ffb100}
#SDM_body #tab_settings:checked + .SDM_tabLabel{color:#ffb100;border-color:#ffb100}
#SDM_body .SDM_main{padding:10px;background:#0f1115}
#SDM_body .SDM_tabc{height:300px}
#SDM_body .SDM_textarea{width:100%;height:100%;resize:none;box-sizing:border-box;border:1px solid #2a2f3a;background:#0b0d11;color:#dfe3ea;border-radius:10px;padding:10px;font:13px/1.4 ui-monospace,Consolas,Monaco}
#SDM_body .SDM_text{height:34px;border:1px solid #2a2f3a;background:#0b0d11;color:#e3e6ec;border-radius:8px;padding:0 10px;min-width:160px;font:13px/1.2 system-ui}
#SDM_body .SDM_time{height:34px;border:1px solid #2a2f3a;background:#0b0d11;color:#e3e6ec;border-radius:8px;padding:0 10px;width:70px;font:13px/1.2 system-ui;text-align:center}
#SDM_body .SDM_sep{color:#8f96a3;margin:0 6px}
#SDM_body .SDM_row{display:flex;align-items:center;gap:10px;margin:10px 0}
#SDM_body .SDM_tgl{display:flex;align-items:center;gap:8px;color:#c9ced9;font:500 13px/1.2 system-ui}
#SDM_body .SDM_root *{color:#e3e6ec}
#SDM_body #SDM_hotkey_input:focus{outline:2px solid #ffb100; box-shadow:0 0 0 3px rgba(255,177,0,.15)}
</style>
`;
  document.body.insertAdjacentHTML('beforeend', ui);
}

function wireUI() {
  document.getElementById('SDM_toggle').addEventListener('click', safeToggle);
  document.getElementById('SDM_close').addEventListener('click', togglePanel);

  [['tab_on','on'],['tab_off','off'],['tab_settings','settings']].forEach(([id,name])=>{
    document.getElementById(id).addEventListener('change',()=>{
      document.querySelectorAll('#SDM_body .SDM_tabc').forEach(v=>v.style.display='none');
      document.querySelector(`#SDM_body .SDM_tabc[data-tab="${name}"]`).style.display='block';
    });
  });

  document.getElementById('SDM_add_page').addEventListener('click', () => {
    const domain = HOST;
    if (document.getElementById('tab_on').checked) {
      const t = document.querySelector('#SDM_on_textarea');
      t.value = (t.value ? t.value.trim()+', ' : '') + domain;
    } else if (document.getElementById('tab_off').checked) {
      const t = document.querySelector('#SDM_off_textarea');
      t.value = (t.value ? t.value.trim()+', ' : '') + domain;
    }
    saveSettings();
  });

  document.querySelector('#SDM_body').addEventListener('input', saveSettings);
  document.querySelector('#SDM_body').addEventListener('change', saveSettings);

  const hkInput = document.getElementById('SDM_hotkey_input');
  hkInput.addEventListener('focus', ()=> hkInput.select());
  hkInput.addEventListener('keydown', e => {
    if (e.key === 'Tab') return;
    if (e.key === 'Escape') { hkInput.blur(); e.preventDefault(); return; }
    if (e.key === 'Backspace' || e.key === 'Delete') {
      hkInput.value = ''; saveSettings(); e.preventDefault(); return;
    }
    let combo = '';
    if (e.ctrlKey && e.key!=='Control') combo+='Ctrl + ';
    if (e.altKey && e.key!=='Alt') combo+='Alt + ';
    if (e.shiftKey && e.key!=='Shift') combo+='Shift + ';
    let key = e.key;
    if (/^.$/u.test(key)) key = key.toUpperCase();
    const ignore = ['Control','Alt','Shift','Meta','OS','Dead','Unidentified'];
    if (!ignore.includes(key)) {
      hkInput.value = combo + key;
      saveSettings();
    }
    e.preventDefault();
  });

  document.addEventListener('keydown', e => {
    const a = document.activeElement;
    if (a && (/^(input|textarea)$/i.test(a.tagName) || a.isContentEditable)) return;
    const seq = (document.querySelector('#SDM_hotkey_input')?.value || state.settings[1])
      .split(' + ').map(s=>s.trim()).filter(Boolean);
    const needCtrl = seq.includes('Ctrl'), needAlt = seq.includes('Alt'), needShift = seq.includes('Shift');
    const mainKey = seq.find(k=>!['Ctrl','Alt','Shift'].includes(k)) || '';
    const match =
      (!needCtrl || e.ctrlKey) &&
      (!needAlt || e.altKey) &&
      (!needShift || e.shiftKey) &&
      (!!mainKey && mainKey.toUpperCase() === (e.key || '').toUpperCase());
    const hotOn = (document.querySelector('#SDM_hotkey')?.checked ?? (state.settings[0]==='hotKeySetOn'));
    if (hotOn && match) { e.preventDefault(); safeToggle(); }
  });
}

function togglePanel() {
  const el = document.getElementById('SDM_body');
  if (!el) return;
  el.style.display = (el.style.display==='none' || !el.style.display)?'block':'none';
}

function setToggle(v) {
  const t = document.querySelector('#SDM_toggle');
  if (t) t.checked = !!v;
}

function safeToggle() {
  if (hardOffNow()) { removeFilterAll(); setToggle(false); return; }
  if (isOn()) { removeFilterAll(); setToggle(false); }
  else { applyFilter(); setToggle(true); }
}

function saveSettings() {
  state.alwaysOnList  = document.querySelector('#SDM_on_textarea').value.replace(/^, ?/,'');
  state.alwaysOffList = document.querySelector('#SDM_off_textarea').value.replace(/^, ?/,'');
  state.settings = [
    document.querySelector('#SDM_hotkey').checked ? 'hotKeySetOn' : 'hotKeySetOff',
    document.querySelector('#SDM_hotkey_input').value,
    document.querySelector('#SDM_timeSet').checked ? 'setTimeOn' : 'setTimeOff',
    document.querySelector('#SDM_timeSet_input_from').value,
    document.querySelector('#SDM_timeSet_input_to').value
  ];
  GM.setValue('alwaysOnList',  state.alwaysOnList);
  GM.setValue('alwaysOffList', state.alwaysOffList);
  GM.setValue('settings',      state.settings);
}