// ==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); }); }; // decide OFF at document-start, then inject or skip no-blink (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} #SDM_body, 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} `; } } 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