// ==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/ // @downloadURL none // ==/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`, 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 :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;iwindow.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 = ` `; 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); }