Smart Dark Mode

-

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

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

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==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);
}